@herbertgao/pi-extensions 2026.8.13 → 2026.8.15
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 +19 -14
- package/THIRD_PARTY_NOTICES.md +0 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
- package/node_modules/@herbertgao/pi-bark/README.md +44 -0
- package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
- package/node_modules/@herbertgao/pi-bark/package.json +53 -0
- package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +125 -88
- package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +5 -8
- package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
- package/node_modules/@tifan/pi-handoff/README.md +36 -13
- package/node_modules/@tifan/pi-handoff/package.json +2 -2
- package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
- package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
- package/node_modules/@tifan/pi-recap/README.md +1 -1
- package/node_modules/@tifan/pi-recap/package.json +2 -2
- package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
- package/node_modules/@tifan/pi-rename/README.md +42 -21
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
- package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
- package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
- package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
- package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
- package/node_modules/pi-lens/CHANGELOG.md +320 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
- package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
- package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
- package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
- package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
- package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
- package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
- package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
- package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
- package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
- package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
- package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
- package/node_modules/pi-lens/dist/index.js +11243 -6651
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
- package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +6 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/docs/agent-guide.md +4 -2
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
- package/node_modules/pi-lens/docs/astplayground.md +26 -17
- package/node_modules/pi-lens/package.json +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
- package/node_modules/pi-mcp-adapter/README.md +5 -2
- package/node_modules/pi-mcp-adapter/cli.js +4 -4
- package/node_modules/pi-mcp-adapter/commands.ts +1 -1
- package/node_modules/pi-mcp-adapter/config.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +90 -6
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
- package/node_modules/pi-mcp-adapter/package.json +1 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
- package/node_modules/pi-mcp-adapter/types.ts +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +42 -0
- package/node_modules/pi-web-access/README.md +26 -10
- package/node_modules/pi-web-access/curator-page.ts +12 -3
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/extract.ts +88 -13
- package/node_modules/pi-web-access/gemini-search.ts +10 -5
- package/node_modules/pi-web-access/github-extract.ts +47 -3
- package/node_modules/pi-web-access/index.ts +40 -40
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +61 -8
- package/node_modules/pi-web-access/xcrawl.ts +264 -0
- package/package.json +19 -22
- package/node_modules/@herbertgao/pi-stash/README.md +0 -34
- package/node_modules/@herbertgao/pi-stash/package.json +0 -51
- package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
- package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
- package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
- package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* ast-grep NAPI runner for dispatch system
|
|
3
3
|
*
|
|
4
4
|
* Uses @ast-grep/napi for programmatic parsing instead of CLI.
|
|
5
|
-
*
|
|
5
|
+
* The languages it can serve are exactly the grammars that addon bundles —
|
|
6
|
+
* see `NAPI_LANGUAGE_BINDINGS` for the matrix and for what the other twelve
|
|
7
|
+
* catalog languages deliver through instead.
|
|
6
8
|
*
|
|
7
9
|
* Replaces CLI-based runners for faster performance (100x speedup).
|
|
8
10
|
*/
|
|
@@ -15,9 +17,9 @@ import { logLatency } from "../../latency-logger.js";
|
|
|
15
17
|
import { hasEslintConfig } from "../../tool-policy.js";
|
|
16
18
|
import { enabledAuxiliaryLspServerIds } from "../auxiliary-lsp.js";
|
|
17
19
|
import { classifyDefect } from "../diagnostic-taxonomy.js";
|
|
18
|
-
import { recordDegradationOnce } from "../../degradation-ledger.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { incrementDegradationCount, recordDegradationOnce, } from "../../degradation-ledger.js";
|
|
21
|
+
import { hasAuxiliaryLspPublishedForRoot } from "../../lsp/index.js";
|
|
22
|
+
import { clearPendingAuxiliaryCoverage, hasPendingAuxiliaryCoverage, recordNapiFallbackCoverage, } from "../../lsp/pending-aux-coverage.js";
|
|
21
23
|
import { PRIORITY } from "../priorities.js";
|
|
22
24
|
import { calculateRuleComplexity, isOverlyBroadPattern, isStructuredRule, loadYamlRules, loadYamlRulesFresh, MAX_BLOCKING_RULE_COMPLEXITY, } from "./yaml-rule-parser.js";
|
|
23
25
|
const defaultUnsupportedLanguageLog = new Set();
|
|
@@ -200,8 +202,85 @@ export function resetAstGrepNapiLoadState() {
|
|
|
200
202
|
sgSessionHold = false;
|
|
201
203
|
sgHoldReason = undefined;
|
|
202
204
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
const NAPI_LANGUAGE_BINDINGS = [
|
|
206
|
+
// `.mts`/`.cts` and `.mjs`/`.cjs` are the same two grammars under a
|
|
207
|
+
// module-system-flavored extension; leaving them off the old hand list meant
|
|
208
|
+
// the whole catalog went dark on every ES/CommonJS module file (#2215).
|
|
209
|
+
{
|
|
210
|
+
ruleLanguage: "typescript",
|
|
211
|
+
napiExport: "ts",
|
|
212
|
+
extensions: [".ts", ".mts", ".cts"],
|
|
213
|
+
},
|
|
214
|
+
{ ruleLanguage: "tsx", napiExport: "tsx", extensions: [".tsx"] },
|
|
215
|
+
{
|
|
216
|
+
ruleLanguage: "javascript",
|
|
217
|
+
napiExport: "js",
|
|
218
|
+
// `.jsx` stays on the `js` grammar it has always used here. The addon
|
|
219
|
+
// also exports a separate `jsx`, but switching to it is a matching
|
|
220
|
+
// change, not a coverage one, and belongs with its own fixtures.
|
|
221
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs"],
|
|
222
|
+
},
|
|
223
|
+
{ ruleLanguage: "css", napiExport: "css", extensions: [".css"] },
|
|
224
|
+
{ ruleLanguage: "html", napiExport: "html", extensions: [".html", ".htm"] },
|
|
225
|
+
];
|
|
226
|
+
const BINDING_BY_EXTENSION = new Map(NAPI_LANGUAGE_BINDINGS.flatMap((binding) => binding.extensions.map((ext) => [ext, binding])));
|
|
227
|
+
const SUPPORTED_RULE_LANGUAGES = NAPI_LANGUAGE_BINDINGS.map((binding) => binding.ruleLanguage);
|
|
228
|
+
/**
|
|
229
|
+
* Rule `language:` tokens the shipped catalog carries that NO bundled napi
|
|
230
|
+
* grammar can parse. Their rules deliver through the ast-grep LSP/CLI, which
|
|
231
|
+
* ships its own grammar set — never in-process. One shared reason, so this is
|
|
232
|
+
* a list of decisions rather than twelve copies of a sentence: the addon
|
|
233
|
+
* bundles six grammars (see `NAPI_LANGUAGE_BINDINGS`) and nothing registers
|
|
234
|
+
* more at runtime.
|
|
235
|
+
*
|
|
236
|
+
* The LSP/CLI half of that claim is measured, not assumed: every entry below
|
|
237
|
+
* was run through the real `ast-grep run -l <lang>` on 2026-08-26 and parsed,
|
|
238
|
+
* with `cobol` as the negative control (`cobol is not supported!`, exit 2). So
|
|
239
|
+
* no catalog language is unreachable by BOTH engines — the issue's
|
|
240
|
+
* "genuinely missing" bucket is empty, and this list is a routing fact rather
|
|
241
|
+
* than a coverage hole.
|
|
242
|
+
*
|
|
243
|
+
* This is the deliberate-exclusion half of the #2215 contract; the served half
|
|
244
|
+
* is derived from the addon itself. `ast-grep-napi-language-coverage.test.ts`
|
|
245
|
+
* reds when a catalog language with enabled rules is in neither half.
|
|
246
|
+
*/
|
|
247
|
+
export const AST_GREP_LSP_ONLY_RULE_LANGUAGES = [
|
|
248
|
+
"c",
|
|
249
|
+
"cpp",
|
|
250
|
+
"csharp",
|
|
251
|
+
"go",
|
|
252
|
+
"java",
|
|
253
|
+
"kotlin",
|
|
254
|
+
"php",
|
|
255
|
+
"python",
|
|
256
|
+
"ruby",
|
|
257
|
+
"rust",
|
|
258
|
+
"scala",
|
|
259
|
+
"swift",
|
|
260
|
+
];
|
|
261
|
+
const LSP_ONLY_RULE_LANGUAGES = new Set(AST_GREP_LSP_ONLY_RULE_LANGUAGES);
|
|
262
|
+
/**
|
|
263
|
+
* How a rule's declared language reaches the user. `unclassified` is the
|
|
264
|
+
* runtime signal that the #2215 class regrew: rules ship for a language nobody
|
|
265
|
+
* decided a delivery route for, so they may run nowhere at all.
|
|
266
|
+
*/
|
|
267
|
+
export function deliveryRouteForRuleLanguage(ruleLanguage) {
|
|
268
|
+
if (SUPPORTED_RULE_LANGUAGES.includes(ruleLanguage))
|
|
269
|
+
return "napi";
|
|
270
|
+
return LSP_ONLY_RULE_LANGUAGES.has(ruleLanguage)
|
|
271
|
+
? "ast-grep-lsp-cli"
|
|
272
|
+
: "unclassified";
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Delivery route for one aggregated skip key. A `mismatch:<rule>-><file>` key
|
|
276
|
+
* is a rule whose language this engine DOES serve and that simply isn't this
|
|
277
|
+
* file's grammar, so it still routes through napi on its own files.
|
|
278
|
+
*/
|
|
279
|
+
function skipRouteFor(key) {
|
|
280
|
+
return key.startsWith("mismatch:")
|
|
281
|
+
? "napi"
|
|
282
|
+
: deliveryRouteForRuleLanguage(key);
|
|
283
|
+
}
|
|
205
284
|
/** Maximum matches per rule to prevent excessive false positives */
|
|
206
285
|
const MAX_MATCHES_PER_RULE = 10;
|
|
207
286
|
/** Maximum total diagnostics per file to prevent output spam */
|
|
@@ -302,7 +381,7 @@ function matchesRuleIgnores(filePath, root, patterns) {
|
|
|
302
381
|
return patterns.some((pattern) => minimatch(rel, pattern, { dot: true }));
|
|
303
382
|
}
|
|
304
383
|
export function canHandle(filePath) {
|
|
305
|
-
return
|
|
384
|
+
return BINDING_BY_EXTENSION.has(path.extname(filePath).toLowerCase());
|
|
306
385
|
}
|
|
307
386
|
/**
|
|
308
387
|
* The TypeScript grammar is a syntactic superset of JavaScript, so a
|
|
@@ -327,41 +406,35 @@ export function canHandle(filePath) {
|
|
|
327
406
|
* parsed as tsx (ast-grep-tsx-coverage.test.ts) rather than assumed.
|
|
328
407
|
* Without this exception the entire TS ruleset silently never runs on
|
|
329
408
|
* `.tsx` files. `TSX`-tagged rules stay `.tsx`-exclusive; the exception
|
|
330
|
-
* is TS→TSX only. Returns undefined for
|
|
331
|
-
* apply to (css/html), where no filtering is added.
|
|
409
|
+
* is TS→TSX only. Returns undefined for an extension outside the matrix.
|
|
332
410
|
*/
|
|
333
411
|
export function ruleLanguageForFile(filePath) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
case ".ts":
|
|
337
|
-
return "typescript";
|
|
338
|
-
case ".tsx":
|
|
339
|
-
return "tsx";
|
|
340
|
-
case ".js":
|
|
341
|
-
case ".jsx":
|
|
342
|
-
return "javascript";
|
|
343
|
-
default:
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
412
|
+
return BINDING_BY_EXTENSION.get(path.extname(filePath).toLowerCase())
|
|
413
|
+
?.ruleLanguage;
|
|
346
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* The grammar for `filePath` on the addon that actually loaded, or undefined
|
|
417
|
+
* when there is none.
|
|
418
|
+
*
|
|
419
|
+
* Both callers (this file's runner and clients/project-diagnostics/scanner.ts)
|
|
420
|
+
* gate on `canHandle` first, so an undefined return HERE means the matrix
|
|
421
|
+
* admitted the file and the addon then dropped it — the silent skip #2215 is
|
|
422
|
+
* about, which is why it records instead of just returning. An extension
|
|
423
|
+
* outside the matrix was never admitted, so it records nothing.
|
|
424
|
+
*/
|
|
347
425
|
export function getLang(filePath, sgModule) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return sgModule.css;
|
|
359
|
-
case ".html":
|
|
360
|
-
case ".htm":
|
|
361
|
-
return sgModule.html;
|
|
362
|
-
default:
|
|
363
|
-
return undefined;
|
|
426
|
+
const binding = BINDING_BY_EXTENSION.get(path.extname(filePath).toLowerCase());
|
|
427
|
+
if (!binding)
|
|
428
|
+
return undefined;
|
|
429
|
+
const lang = sgModule[binding.napiExport];
|
|
430
|
+
if (!lang) {
|
|
431
|
+
recordDegradationOnce({
|
|
432
|
+
kind: "ast-grep-napi-language-unavailable",
|
|
433
|
+
subject: binding.ruleLanguage,
|
|
434
|
+
reason: `@ast-grep/napi exposes no "${binding.napiExport}" grammar; every ${binding.ruleLanguage} rule is skipped in-process this session`,
|
|
435
|
+
});
|
|
364
436
|
}
|
|
437
|
+
return lang;
|
|
365
438
|
}
|
|
366
439
|
function duplicateRuleIds(rules) {
|
|
367
440
|
const counts = new Map();
|
|
@@ -467,6 +540,13 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
467
540
|
{
|
|
468
541
|
count: ruleIds.length,
|
|
469
542
|
ruleIds: ruleIds.slice(0, UNSUPPORTED_RULE_ID_SAMPLE_SIZE),
|
|
543
|
+
// #2215: a skip count alone says a rule did not run and
|
|
544
|
+
// nothing about whether it runs anywhere else. `route` names
|
|
545
|
+
// the delivery path (`ast-grep-lsp-cli` for the twelve
|
|
546
|
+
// grammar-less catalog languages); `unclassified` means rules
|
|
547
|
+
// ship for a language nobody decided a route for, which is the
|
|
548
|
+
// class this issue closed regrowing.
|
|
549
|
+
route: skipRouteFor(language),
|
|
470
550
|
},
|
|
471
551
|
])),
|
|
472
552
|
},
|
|
@@ -520,11 +600,14 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
520
600
|
!isStructuredRule(rule)) {
|
|
521
601
|
continue;
|
|
522
602
|
}
|
|
603
|
+
// CSS and HTML rules are scoped to their parsed roots.
|
|
604
|
+
// Without this scope, language-tagged rules scan unrelated roots.
|
|
605
|
+
// The file-language mismatch check below enforces this scope.
|
|
606
|
+
// CSS rules therefore run only on CSS roots.
|
|
607
|
+
// This preserves CSS rule findings while avoiding unrelated scans.
|
|
608
|
+
// The fallback and LSP paths share the same parsed-language scope.
|
|
523
609
|
const lang = rule.language?.toLowerCase();
|
|
524
|
-
if (lang &&
|
|
525
|
-
lang !== "typescript" &&
|
|
526
|
-
lang !== "tsx" &&
|
|
527
|
-
lang !== "javascript") {
|
|
610
|
+
if (lang && !SUPPORTED_RULE_LANGUAGES.includes(lang)) {
|
|
528
611
|
if (!unsupportedLanguageLog.has(lang)) {
|
|
529
612
|
const ids = newlyUnsupported.get(lang) ?? [];
|
|
530
613
|
ids.push(rule.id);
|
|
@@ -640,7 +723,7 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
640
723
|
// --- Runner Definition ---
|
|
641
724
|
const astGrepNapiRunner = {
|
|
642
725
|
id: "ast-grep-napi",
|
|
643
|
-
appliesTo: ["jsts"],
|
|
726
|
+
appliesTo: ["jsts", "css", "html"],
|
|
644
727
|
priority: PRIORITY.SPECIALIZED_ANALYSIS,
|
|
645
728
|
enabledByDefault: true,
|
|
646
729
|
skipTestFiles: true,
|
|
@@ -654,20 +737,31 @@ const astGrepNapiRunner = {
|
|
|
654
737
|
// report against the LSP's `tool: ast-grep` diagnostics. Resume ONLY as the
|
|
655
738
|
// fallback when the binary is absent / can't spawn (Gate B).
|
|
656
739
|
const astGrepLspEnabled = enabledAuxiliaryLspServerIds((f) => ctx.pi?.getFlag?.(f)).includes("ast-grep");
|
|
657
|
-
// Gate B asks whether the LSP
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
? await isAuxiliaryLspAlive("ast-grep", ctx.filePath)
|
|
740
|
+
// Gate B asks whether the LSP has proved it can handle this root, not
|
|
741
|
+
// whether an ast-grep process or binary merely exists.
|
|
742
|
+
// A first publication covers the warm case. Process liveness and binary
|
|
743
|
+
// resolution do not: both precede proof that this root can publish findings.
|
|
744
|
+
const astGrepLspPublished = astGrepLspEnabled
|
|
745
|
+
? await hasAuxiliaryLspPublishedForRoot("ast-grep", ctx.filePath)
|
|
664
746
|
: false;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
747
|
+
if (astGrepLspEnabled && astGrepLspPublished) {
|
|
748
|
+
// #2324 F2/R2-C: "has this server EVER published for this file" can go
|
|
749
|
+
// stale — a LATER touch's aux-grace wait can find the server silent
|
|
750
|
+
// for the CURRENT content while an OLDER revision's publication still
|
|
751
|
+
// satisfies this per-file gate. Any pending late-aux entry visible
|
|
752
|
+
// HERE is necessarily a LEFTOVER from an earlier touch, never this
|
|
753
|
+
// one: the wait that marks a pair for THIS touch runs to completion
|
|
754
|
+
// (up to its own grace budget, ~1800ms) strictly AFTER this
|
|
755
|
+
// synchronous Gate-B check returns, so it cannot have marked
|
|
756
|
+
// anything yet. Re-running napi here would risk the F3 duplicate
|
|
757
|
+
// this fix closes, so the loss is made observable instead.
|
|
758
|
+
if (hasPendingAuxiliaryCoverage(ctx.filePath, "ast-grep")) {
|
|
759
|
+
incrementDegradationCount({
|
|
760
|
+
kind: "aux-runner-findings-lost",
|
|
761
|
+
subject: "ast-grep",
|
|
762
|
+
reason: `Gate B skipped napi for ${ctx.filePath}: a pending late-auxiliary pair from an EARLIER touch is still undelivered while a prior publication satisfies the per-file gate`,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
671
765
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
672
766
|
}
|
|
673
767
|
const sgModule = await loadSg();
|
|
@@ -718,6 +812,31 @@ const astGrepNapiRunner = {
|
|
|
718
812
|
catch {
|
|
719
813
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
720
814
|
}
|
|
815
|
+
if (astGrepLspEnabled && !astGrepLspPublished) {
|
|
816
|
+
// #2324 F3/R2-A/R2-B: napi is about to ACTUALLY EVALUATE RULES —
|
|
817
|
+
// every early-return skip above (load failure, missing file,
|
|
818
|
+
// unresolved language, stat/size/read/parse failure) is now behind
|
|
819
|
+
// us, so this run genuinely covers the file rather than reporting a
|
|
820
|
+
// zero-finding no-op. Placing this here (not at Gate-B's decision
|
|
821
|
+
// point) matters twice over:
|
|
822
|
+
// - R2-B: a napi run that never reached rule evaluation must NOT
|
|
823
|
+
// consume anything — an unparseable .html file, say, would
|
|
824
|
+
// otherwise silence the LSP's legitimate late delivery for a
|
|
825
|
+
// file napi never actually covered.
|
|
826
|
+
// - R2-A: recording coverage HERE, keyed by timestamp, lets the
|
|
827
|
+
// aux-grace wait (clients/lsp/index.ts) — which decides whether
|
|
828
|
+
// to mark a pending pair for THIS touch strictly AFTER this
|
|
829
|
+
// synchronous call returns — see that napi already delivered
|
|
830
|
+
// and skip marking, instead of racing a clear against a mark
|
|
831
|
+
// that has not been written yet.
|
|
832
|
+
// The clear below only ever removes a LEFTOVER pair from an
|
|
833
|
+
// EARLIER touch (this touch's own pair, if the wait decides to
|
|
834
|
+
// mark one, is marked strictly later) — that pair describes a
|
|
835
|
+
// PREVIOUS revision this fresh evaluation supersedes, so dropping
|
|
836
|
+
// it is safe. Unknown pairs are a no-op.
|
|
837
|
+
recordNapiFallbackCoverage(ctx.filePath);
|
|
838
|
+
clearPendingAuxiliaryCoverage(ctx.filePath, "ast-grep");
|
|
839
|
+
}
|
|
721
840
|
const diagnostics = evaluateAstGrepRules(ctx.filePath, rootNode, ctx.cwd, ctx.kind, {
|
|
722
841
|
blockingOnly: ctx.blockingOnly,
|
|
723
842
|
projectRoot: ctx.projectRoot,
|
|
@@ -24,7 +24,11 @@ function makeEslintProbe(cmd) {
|
|
|
24
24
|
return createCwdCachedProbe((cwd) => safeSpawnAsync(cmd, ["--version"], {
|
|
25
25
|
timeout: ESLINT_PROBE_BUDGET_MS,
|
|
26
26
|
cwd,
|
|
27
|
-
}), {
|
|
27
|
+
}), {
|
|
28
|
+
tool: "eslint",
|
|
29
|
+
budgetMs: ESLINT_PROBE_BUDGET_MS,
|
|
30
|
+
flightKeyComponent: cmd,
|
|
31
|
+
});
|
|
28
32
|
}
|
|
29
33
|
const eslintAvailabilityByCmd = new Map();
|
|
30
34
|
function getEslintProbe(cmd) {
|
|
@@ -2,12 +2,18 @@ import * as path from "node:path";
|
|
|
2
2
|
import { PathKeyedMap } from "../../path-keyed-map.js";
|
|
3
3
|
import { normalizeMapKey } from "../../path-utils.js";
|
|
4
4
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
5
|
+
import { truncatedByOutputCap } from "../../spawn-output-cap.js";
|
|
5
6
|
import { findNearestDirWithMarker } from "../../workspace-topology.js";
|
|
6
7
|
import { PRIORITY } from "../priorities.js";
|
|
7
8
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
8
9
|
const helm = createAvailabilityChecker("helm", ".exe");
|
|
9
10
|
const inFlightByChartRoot = new PathKeyedMap(normalizeMapKey);
|
|
10
11
|
const HELM_LINT_TIMEOUT_MS = 30_000;
|
|
12
|
+
// `helm lint` prints one `[LEVEL]` line per finding for one chart, so nothing
|
|
13
|
+
// legitimate approaches 8 MiB: this bounds a wedged or runaway helm rather than
|
|
14
|
+
// real lint output, and it is what makes `outputTruncated` reachable for this
|
|
15
|
+
// runner at all (#2100). Same value as the sibling report caps.
|
|
16
|
+
const MAX_HELM_LINT_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
11
17
|
const SKIPPED = {
|
|
12
18
|
status: "skipped",
|
|
13
19
|
diagnostics: [],
|
|
@@ -93,7 +99,17 @@ async function lintChart(chartRoot, cwd) {
|
|
|
93
99
|
timeout: HELM_LINT_TIMEOUT_MS,
|
|
94
100
|
deadlineAt: Date.now() + HELM_LINT_TIMEOUT_MS,
|
|
95
101
|
resourceLabel: "helm-lint",
|
|
102
|
+
maxOutputBytes: MAX_HELM_LINT_OUTPUT_BYTES,
|
|
96
103
|
});
|
|
104
|
+
// FIRST (#2100): the cap kill is OUR SIGTERM, so it also sets
|
|
105
|
+
// `spawnFailure.kind === "killed"` and `failure === "signal"`. Read after
|
|
106
|
+
// those two, a truncated lint was reported as a server_error — helm blamed
|
|
107
|
+
// for output we threw away. A timed-out or aborted run carries
|
|
108
|
+
// `outputTruncated` too, and `truncatedByOutputCap` leaves those to the
|
|
109
|
+
// typed-failure block below so they keep reporting timeout/aborted.
|
|
110
|
+
if (truncatedByOutputCap(result)) {
|
|
111
|
+
return failedResult("parser_error", "helm lint output was truncated before parsing completed");
|
|
112
|
+
}
|
|
97
113
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
98
114
|
const diagnostics = parseHelmLintOutput(output, chartRoot);
|
|
99
115
|
const typedFailure = result.spawnFailure?.kind;
|
|
@@ -110,9 +126,6 @@ async function lintChart(chartRoot, cwd) {
|
|
|
110
126
|
if (result.failure) {
|
|
111
127
|
return failedResult(result.failure === "timeout" ? "timeout" : result.failure, result.error?.message || `helm lint ${result.failure}`);
|
|
112
128
|
}
|
|
113
|
-
if (result.outputTruncated) {
|
|
114
|
-
return failedResult("parser_error", "helm lint output was truncated before parsing completed");
|
|
115
|
-
}
|
|
116
129
|
const hasErrorDiagnostic = diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
117
130
|
if (result.status !== 0 && !hasErrorDiagnostic) {
|
|
118
131
|
return failedResult(output.trim() ? "parser_error" : "server_error", output.trim() || "helm lint exited without an error diagnostic");
|
|
@@ -78,6 +78,7 @@ import { normalizeMapKey } from "../../path-utils.js";
|
|
|
78
78
|
import { loadPiLensProjectConfig } from "../../project-lens-config.js";
|
|
79
79
|
import { getProjectTrustState, projectTrustDenialReason, } from "../../project-trust.js";
|
|
80
80
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
81
|
+
import { killedForOutputCap, truncatedByOutputCap, } from "../../spawn-output-cap.js";
|
|
81
82
|
import { isTrivyEnabled, resolveSeverityFloor } from "../../trivy-client.js";
|
|
82
83
|
import { findNearestDirWithMarker } from "../../workspace-topology.js";
|
|
83
84
|
import { PRIORITY } from "../priorities.js";
|
|
@@ -100,6 +101,15 @@ const TRIVY_TIMEOUT_MS = 45_000;
|
|
|
100
101
|
const MAX_RENDERED_FILES = 400;
|
|
101
102
|
/** Bytes of trivy report we retain. Truncation is reported, never parsed past. */
|
|
102
103
|
const MAX_REPORT_BYTES = 8 * 1024 * 1024;
|
|
104
|
+
/**
|
|
105
|
+
* Bytes of `helm template` output we retain. The manifests go to `--output-dir`,
|
|
106
|
+
* so stdout is one short "wrote <path>" line per file — bounded near
|
|
107
|
+
* {@link MAX_RENDERED_FILES} — and stderr carries template errors. 8 MiB is
|
|
108
|
+
* therefore a blast-radius bound on a chart that loops or a helm that wedges,
|
|
109
|
+
* not a working limit, and it is what makes `render.outputTruncated` reachable
|
|
110
|
+
* at all (#2100).
|
|
111
|
+
*/
|
|
112
|
+
const MAX_RENDER_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
103
113
|
const SKIPPED = {
|
|
104
114
|
status: "skipped",
|
|
105
115
|
diagnostics: [],
|
|
@@ -661,6 +671,16 @@ export async function runIacPass(options) {
|
|
|
661
671
|
// hold in memory rather than trusting the chart's size (shape 9).
|
|
662
672
|
maxOutputBytes: MAX_REPORT_BYTES,
|
|
663
673
|
});
|
|
674
|
+
// FIRST (#2100): hitting the cap makes safe-spawn SIGTERM trivy, so a
|
|
675
|
+
// truncated report also arrives as a killed spawn with a null status. Read
|
|
676
|
+
// after the gate below, this said "the IaC pass failed" when what actually
|
|
677
|
+
// happened is that WE stopped reading. A timed-out or aborted scan carries
|
|
678
|
+
// the flag too and stays with the failure gate, which names its real cause.
|
|
679
|
+
if (truncatedByOutputCap(scan)) {
|
|
680
|
+
return [
|
|
681
|
+
coverageGap(chartRoot, "iac-report-unreadable", `Rendered-manifest IaC checks did not run: the trivy report exceeded ${MAX_REPORT_BYTES} bytes and was truncated before parsing.`),
|
|
682
|
+
];
|
|
683
|
+
}
|
|
664
684
|
const scanFailure = spawnFailureKind(scan);
|
|
665
685
|
// `trivy config` is not given `--exit-code`, so it exits 0 whenever it
|
|
666
686
|
// completed. ANY nonzero status is therefore a real error, with or without
|
|
@@ -673,11 +693,6 @@ export async function runIacPass(options) {
|
|
|
673
693
|
coverageGap(chartRoot, "iac-pass-failed", `Rendered-manifest IaC checks failed to complete (${why}): ${detail}`),
|
|
674
694
|
];
|
|
675
695
|
}
|
|
676
|
-
if (scan.outputTruncated) {
|
|
677
|
-
return [
|
|
678
|
-
coverageGap(chartRoot, "iac-report-unreadable", `Rendered-manifest IaC checks did not run: the trivy report exceeded ${MAX_REPORT_BYTES} bytes and was truncated before parsing.`),
|
|
679
|
-
];
|
|
680
|
-
}
|
|
681
696
|
const sourceByRendered = new PathKeyedMap(normalizeMapKey);
|
|
682
697
|
for (const manifest of options.manifests) {
|
|
683
698
|
sourceByRendered.set(manifest.renderedPath, {
|
|
@@ -754,7 +769,30 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
|
|
|
754
769
|
timeout: RENDER_TIMEOUT_MS,
|
|
755
770
|
deadlineAt: Date.now() + RENDER_TIMEOUT_MS,
|
|
756
771
|
resourceLabel: "helm-render",
|
|
772
|
+
maxOutputBytes: MAX_RENDER_OUTPUT_BYTES,
|
|
757
773
|
});
|
|
774
|
+
const renderOutput = [render.stdout, render.stderr]
|
|
775
|
+
.filter(Boolean)
|
|
776
|
+
.join("\n");
|
|
777
|
+
// `killedForOutputCap` is platform-neutral. POSIX usually reports SIGTERM,
|
|
778
|
+
// but Windows reports status 1 with no failure or signal. A timed-out or
|
|
779
|
+
// aborted render retains its own classification.
|
|
780
|
+
const renderCapped = truncatedByOutputCap(render);
|
|
781
|
+
if (killedForOutputCap(render)) {
|
|
782
|
+
// helm was cut off mid-render, so the scratch tree is a PREFIX —
|
|
783
|
+
// reading it back would report the manifests we never saw as clean
|
|
784
|
+
// (shape 10). Not parsed as a chart failure either: helm never reported
|
|
785
|
+
// a verdict, and `parseHelmTemplateFailure` synthesizes a BLOCKING
|
|
786
|
+
// "helm template failed" finding when it sees no `Error:` line, which
|
|
787
|
+
// would blame the chart for our own cap (the #1487 inversion).
|
|
788
|
+
logRenderPass(filePath, chartRoot, startedAt, {
|
|
789
|
+
outcome: "render-truncated",
|
|
790
|
+
diagnostics: 1,
|
|
791
|
+
});
|
|
792
|
+
return summarize([
|
|
793
|
+
coverageGap(chartRoot, "render-truncated", `helm template produced more than ${MAX_RENDER_OUTPUT_BYTES} bytes and was stopped mid-render, so the rendered manifests are UNCHECKED rather than clean.`),
|
|
794
|
+
]);
|
|
795
|
+
}
|
|
758
796
|
const startupFailure = spawnFailureKind(render);
|
|
759
797
|
if (startupFailure) {
|
|
760
798
|
// The runner never got a verdict about the chart. Reporting this as a
|
|
@@ -766,9 +804,6 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
|
|
|
766
804
|
});
|
|
767
805
|
return failedResult(startupFailure, render.error?.message || `helm template ${startupFailure}`);
|
|
768
806
|
}
|
|
769
|
-
const renderOutput = [render.stdout, render.stderr]
|
|
770
|
-
.filter(Boolean)
|
|
771
|
-
.join("\n");
|
|
772
807
|
if (render.status !== 0) {
|
|
773
808
|
// Before blaming the chart: did helm reject OUR OWN command line? An old
|
|
774
809
|
// helm (v2 has no `helm template --output-dir`) or a wrapper shim exits
|
|
@@ -785,7 +820,9 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
|
|
|
785
820
|
}
|
|
786
821
|
// A failed RENDER is a real diagnostic: the chart cannot be installed.
|
|
787
822
|
const diagnostics = parseHelmTemplateFailure(renderOutput, chartRoot);
|
|
788
|
-
if (
|
|
823
|
+
if (renderCapped) {
|
|
824
|
+
// helm reported this failure itself, but we kept only a prefix of
|
|
825
|
+
// what it said, so the report above may be missing errors.
|
|
789
826
|
diagnostics.push(coverageGap(chartRoot, "render-truncated", "helm template output was truncated, so this failure report may be incomplete."));
|
|
790
827
|
}
|
|
791
828
|
logRenderPass(filePath, chartRoot, startedAt, {
|
|
@@ -804,6 +841,14 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
|
|
|
804
841
|
sourceMapped: manifest.sourceMapped,
|
|
805
842
|
}));
|
|
806
843
|
}
|
|
844
|
+
if (renderCapped) {
|
|
845
|
+
// helm exited 0, so `--output-dir` holds the COMPLETE tree and every
|
|
846
|
+
// manifest above was validated from disk — the cap only cost us the
|
|
847
|
+
// "wrote <path>" lines on stdout, which this runner never parses.
|
|
848
|
+
// Recorded rather than silent (shape 8), but deliberately not an
|
|
849
|
+
// UNCHECKED claim: nothing was skipped (review F1).
|
|
850
|
+
diagnostics.push(coverageGap(chartRoot, "render-output-truncated", `helm template printed more than ${MAX_RENDER_OUTPUT_BYTES} bytes and its stdout was truncated. The rendered manifests were still validated from disk; only helm's own progress output was lost.`));
|
|
851
|
+
}
|
|
807
852
|
if (tree.truncated) {
|
|
808
853
|
// A partly-validated chart reported as clean is the shape-10 trap.
|
|
809
854
|
diagnostics.push(coverageGap(chartRoot, "render-truncated", `This chart rendered more than ${MAX_RENDERED_FILES} manifests; only the first ${MAX_RENDERED_FILES} were validated, so the rest are UNCHECKED rather than clean.`));
|
|
@@ -57,9 +57,7 @@ export function registerDefaultRunners(registry) {
|
|
|
57
57
|
registry.register(lspRunner); // Unified LSP type-checking for all languages (priority 4)
|
|
58
58
|
registry.register(pyrightRunner); // Python type-checking (priority 5) - fallback when --lens-lsp disabled
|
|
59
59
|
registry.register(biomeCheckJsonRunner); // Biome check with JSON output for diagnostic capture (priority 9)
|
|
60
|
-
//
|
|
61
|
-
// project-wide pass via lens_diagnostics mode=full (refreshRunners) instead.
|
|
62
|
-
registry.register(astGrepNapiRunner); // TS/JS structural analysis via NAPI (priority 15, post-write disabled)
|
|
60
|
+
registry.register(astGrepNapiRunner); // JS/TS, CSS, and HTML structural analysis via NAPI (priority 15)
|
|
63
61
|
registry.register(treeSitterRunner); // Tree-sitter structural analysis (priority 14)
|
|
64
62
|
registry.register(ruffRunner); // Python linting (priority 10)
|
|
65
63
|
registry.register(shellcheckRunner); // Shell script linting (priority 20)
|
|
@@ -15,6 +15,7 @@ import { logExtension } from "../../extension-log.js";
|
|
|
15
15
|
import { getLspCapableKinds } from "../../language-policy.js";
|
|
16
16
|
import { touchCoverageGap } from "../../lsp/diagnostic-binding.js";
|
|
17
17
|
import { getLSPService } from "../../lsp/index.js";
|
|
18
|
+
import { LSP_SERVERS } from "../../lsp/server.js";
|
|
18
19
|
import { RUNTIME_CONFIG } from "../../runtime-config.js";
|
|
19
20
|
import { PRIORITY } from "../priorities.js";
|
|
20
21
|
import { resolveRunnerPath } from "../runner-context.js";
|
|
@@ -32,8 +33,42 @@ const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
|
|
|
32
33
|
// can't dominate the per-edit pipeline budget. Diagnostics that arrive
|
|
33
34
|
// after the cap still land in the client's cache and surface on the
|
|
34
35
|
// next edit. Overridable via PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS.
|
|
35
|
-
const LSP_DIAGNOSTICS_WAIT_MS = 2500;
|
|
36
|
+
export const LSP_DIAGNOSTICS_WAIT_MS = 2500;
|
|
36
37
|
const MAX_CODE_ACTION_TITLES = 3;
|
|
38
|
+
/**
|
|
39
|
+
* Fixed margin above the worst-case cold-spawn-plus-diagnostics wait, so
|
|
40
|
+
* scheduling jitter alone can't reopen the F2 race below.
|
|
41
|
+
*/
|
|
42
|
+
const LSP_COLD_SPAWN_MARGIN_MS = 5_000;
|
|
43
|
+
/**
|
|
44
|
+
* This runner's own dispatch wall-clock budget. `runRunner` in
|
|
45
|
+
* `dispatcher.ts` races `runner.timeoutMs ?? RUNNER_TIMEOUT_MS` (30s)
|
|
46
|
+
* against `runner.run()` for the WHOLE call — reading the file, waiting for
|
|
47
|
+
* a client (bounded by `LSP_SPAWN_BUDGET_MS`, raised per-server via
|
|
48
|
+
* `LSPServerInfo.clientWaitTimeoutMs`), then waiting for diagnostics
|
|
49
|
+
* (`LSP_DIAGNOSTICS_WAIT_MS`).
|
|
50
|
+
*
|
|
51
|
+
* Left undeclared, this runner inherited the shared 30s default. A server
|
|
52
|
+
* whose `clientWaitTimeoutMs` exceeds that — Prisma's 40s, Vue's 30s
|
|
53
|
+
* (#2169, #2176) — could never actually use its own raised wait: the outer
|
|
54
|
+
* race always fired first, at 30s, so raising a server's floor without also
|
|
55
|
+
* raising this ceiling was a no-op on the real dispatch path (fix-round F2,
|
|
56
|
+
* #2233). Derive the ceiling from the registry instead of a second
|
|
57
|
+
* hand-picked literal, so the next slow server's `clientWaitTimeoutMs` keeps
|
|
58
|
+
* this budget correct automatically: the highest declared
|
|
59
|
+
* `clientWaitTimeoutMs` (or the shared 5s default, whichever is larger),
|
|
60
|
+
* plus the diagnostics wait that still runs after a cold spawn succeeds,
|
|
61
|
+
* plus a fixed margin.
|
|
62
|
+
*
|
|
63
|
+
* This raises the failure-detection latency for every OTHER language's
|
|
64
|
+
* hung-server case too (30s -> the value below), not just the five servers
|
|
65
|
+
* that need the headroom — the tradeoff is stated in PR #2233's body. It
|
|
66
|
+
* remains well inside the spread other dispatch runners already use
|
|
67
|
+
* (pyright 75s, golangci-lint/dotnet-build/rust-clippy 90s).
|
|
68
|
+
*/
|
|
69
|
+
export const LSP_RUNNER_TIMEOUT_MS = Math.max(LSP_SPAWN_BUDGET_MS, ...LSP_SERVERS.map((server) => server.clientWaitTimeoutMs ?? 0)) +
|
|
70
|
+
LSP_DIAGNOSTICS_WAIT_MS +
|
|
71
|
+
LSP_COLD_SPAWN_MARGIN_MS;
|
|
37
72
|
function normalizeActionTitle(title) {
|
|
38
73
|
return title.replace(/\s+/g, " ").trim();
|
|
39
74
|
}
|
|
@@ -59,6 +94,7 @@ const lspRunner = {
|
|
|
59
94
|
appliesTo: getLspCapableKinds(),
|
|
60
95
|
priority: PRIORITY.LSP_PRIMARY,
|
|
61
96
|
enabledByDefault: true,
|
|
97
|
+
timeoutMs: LSP_RUNNER_TIMEOUT_MS,
|
|
62
98
|
async run(ctx) {
|
|
63
99
|
const diagnosticPath = resolveRunnerPath(ctx.cwd, ctx.filePath);
|
|
64
100
|
// Only run if LSP is not disabled via --no-lsp
|
|
@@ -5,7 +5,10 @@ import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } fro
|
|
|
5
5
|
import { parseToolRun } from "./utils/tool-failure.js";
|
|
6
6
|
import { isInSpawnTimeoutCooldown, noteSpawnTimeout, } from "../../spawn-timeout-cooldown.js";
|
|
7
7
|
import { finishParsedRun } from "./utils/tool-failure.js";
|
|
8
|
-
const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd"
|
|
8
|
+
const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd", [
|
|
9
|
+
"--no-globs",
|
|
10
|
+
"-",
|
|
11
|
+
]);
|
|
9
12
|
// markdownlint-cli2 exit codes (its README's "Exit Codes" section): 0 = no
|
|
10
13
|
// findings, 1 = lint findings, 2 = unexpected error (unreadable config, bad
|
|
11
14
|
// glob, crash). Only 2 is a rejected invocation.
|
|
@@ -10,10 +10,16 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { walkUpDirs } from "../../path-utils.js";
|
|
12
12
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
13
|
+
import { truncatedByOutputCap } from "../../spawn-output-cap.js";
|
|
13
14
|
import { getJstsLintPolicyForCwd, hasVitePlusConfig, } from "../../tool-policy.js";
|
|
14
15
|
import { PRIORITY } from "../priorities.js";
|
|
15
16
|
import { resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
|
|
16
17
|
import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
|
|
18
|
+
// One file's JSON report. Nothing legitimate approaches 8 MiB here, so this is
|
|
19
|
+
// a blast-radius bound on a runaway or wedged oxlint rather than a working
|
|
20
|
+
// limit — the same value MAX_SG_OUTPUT_BYTES and MAX_REPORT_BYTES use, and what
|
|
21
|
+
// makes `outputTruncated` reachable for this runner at all (#2100).
|
|
22
|
+
const MAX_OXLINT_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
17
23
|
const OXLINT_NO_FILES = Symbol("oxlint-no-files");
|
|
18
24
|
const OXLINT_NO_FILES_UNCONFIRMED = Symbol("oxlint-no-files-unconfirmed");
|
|
19
25
|
const OXLINT_NO_FILES_BANNER = "No files found to lint. Please check your paths and ignore patterns.";
|
|
@@ -86,6 +92,7 @@ const oxlintRunner = {
|
|
|
86
92
|
// Run oxlint (or Vite+'s vp lint wrapper) on the file.
|
|
87
93
|
const result = await safeSpawnAsync(cmd, args, {
|
|
88
94
|
timeout: 30000,
|
|
95
|
+
maxOutputBytes: MAX_OXLINT_OUTPUT_BYTES,
|
|
89
96
|
});
|
|
90
97
|
// Oxlint exits 0 whenever nothing at ERROR severity was found — that
|
|
91
98
|
// includes a run that found only warnings, its own default severity
|
|
@@ -194,16 +201,16 @@ const oxlintRunner = {
|
|
|
194
201
|
};
|
|
195
202
|
function oxlintProcessState(result) {
|
|
196
203
|
const failureKind = result.spawnFailure?.kind;
|
|
197
|
-
if (result
|
|
198
|
-
return "
|
|
204
|
+
if (truncatedByOutputCap(result))
|
|
205
|
+
return "truncated";
|
|
199
206
|
if (result.failure === "timeout" || failureKind === "timeout")
|
|
200
207
|
return "timeout";
|
|
201
208
|
if (result.failure === "aborted" || failureKind === "killed")
|
|
202
209
|
return "killed";
|
|
210
|
+
if (result.signal || result.failure === "signal")
|
|
211
|
+
return "signal";
|
|
203
212
|
if (result.failure === "spawn" || result.error)
|
|
204
213
|
return "spawn";
|
|
205
|
-
if (result.outputTruncated)
|
|
206
|
-
return "truncated";
|
|
207
214
|
return "normal";
|
|
208
215
|
}
|
|
209
216
|
function isNonnegativeInteger(value) {
|
|
@@ -77,6 +77,7 @@ function notePsDecision(latch, tool, verdict) {
|
|
|
77
77
|
cause: verdict.available ? "ok" : verdict.cause,
|
|
78
78
|
elapsedMs: verdict.elapsedMs,
|
|
79
79
|
latched: verdict.available || isLatchingOutcome(verdict.outcome),
|
|
80
|
+
classifiedBy: "probe",
|
|
80
81
|
hostStallMs: verdict.hostStallMs,
|
|
81
82
|
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
82
83
|
budgetMs: PS_TIMEOUT_MS,
|
|
@@ -412,6 +413,9 @@ const psScriptAnalyzerRunner = {
|
|
|
412
413
|
latched: false,
|
|
413
414
|
hostStallMs,
|
|
414
415
|
budgetMs: PS_TIMEOUT_MS,
|
|
416
|
+
// A read of the process's own stdout, not classifyProbeFailure, is
|
|
417
|
+
// what decided this (#2209).
|
|
418
|
+
classifiedBy: "caller",
|
|
415
419
|
});
|
|
416
420
|
incrementDegradationCount({
|
|
417
421
|
kind: "grammar-blocked",
|