pi-lens 4.1.2 → 4.1.4
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/CHANGELOG.md +1308 -0
- package/README.md +3 -0
- package/config/biome/core.jsonc +11 -2
- package/config/dependency-cruiser-eager-allowlist.json +91 -0
- package/dist/clients/actionable-warnings-logger.js +18 -3
- package/dist/clients/actionable-warnings.js +927 -80
- package/dist/clients/agent-behavior-client.js +13 -4
- package/dist/clients/ast-grep-client.js +136 -9
- package/dist/clients/ast-grep-rule-manager.js +60 -5
- package/dist/clients/ast-grep-tool-logger.js +2 -2
- package/dist/clients/availability-probe-flight.js +12 -0
- package/dist/clients/bash-file-access.js +1 -3
- package/dist/clients/biome-client.js +16 -2
- package/dist/clients/blocker-freshness.js +34 -0
- package/dist/clients/bootstrap.js +509 -73
- package/dist/clients/bounded-cache.js +152 -12
- package/dist/clients/bounded-telemetry.js +115 -23
- package/dist/clients/build-identity.js +95 -0
- package/dist/clients/bundled-resource-health.js +113 -0
- package/dist/clients/bus-events-logger.js +2 -2
- package/dist/clients/bus-publish.js +3 -0
- package/dist/clients/cache/rule-cache.js +33 -5
- package/dist/clients/cache-manager.js +105 -6
- package/dist/clients/cache-observability.js +46 -19
- package/dist/clients/cargo-manifest.js +422 -0
- package/dist/clients/cascade-logger.js +17 -3
- package/dist/clients/code-quality-warnings.js +13 -3
- package/dist/clients/complexity-client.js +16 -5
- package/dist/clients/config-core/deny.js +221 -0
- package/dist/clients/config-core/index.js +47 -0
- package/dist/clients/config-core/merge.js +357 -0
- package/dist/clients/config-core/normalize.js +340 -0
- package/dist/clients/config-core/process-spec.js +248 -0
- package/dist/clients/config-core/provenance.js +171 -0
- package/dist/clients/config-core/records.js +218 -0
- package/dist/clients/config-core/resolve.js +125 -0
- package/dist/clients/config-core/safe-object.js +78 -0
- package/dist/clients/config-core/schema.js +167 -0
- package/dist/clients/config-diagnostic-codes.js +281 -0
- package/dist/clients/config-locations.js +160 -0
- package/dist/clients/config-resolve.js +789 -0
- package/dist/clients/config-schema.js +197 -0
- package/dist/clients/config-warn.js +407 -0
- package/dist/clients/dead-code-client.js +33 -25
- package/dist/clients/dead-code-logger.js +2 -2
- package/dist/clients/deadline-utils.js +178 -0
- package/dist/clients/debug-handles.js +2 -2
- package/dist/clients/debug-heap.js +3 -3
- package/dist/clients/deferred-lsp-work.js +106 -0
- package/dist/clients/degradation-ledger.js +166 -8
- package/dist/clients/demoted-finding-render.js +14 -0
- package/dist/clients/dependency-checker.js +28 -22
- package/dist/clients/deps/minimatch.js +1 -1
- package/dist/clients/diagnostic-line-freshness.js +12 -9
- package/dist/clients/diagnostic-logger.js +14 -4
- package/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/dist/clients/dispatch/dispatcher.js +304 -19
- package/dist/clients/dispatch/fact-store.js +328 -9
- package/dist/clients/dispatch/integration.js +214 -107
- package/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/dist/clients/dispatch/plan.js +21 -5
- package/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
- package/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/dist/clients/dispatch/runners/cpp-check.js +0 -1
- package/dist/clients/dispatch/runners/credo.js +0 -1
- package/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/dist/clients/dispatch/runners/detekt.js +0 -1
- package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/dist/clients/dispatch/runners/eslint.js +5 -2
- package/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/dist/clients/dispatch/runners/helm-lint.js +16 -4
- package/dist/clients/dispatch/runners/helm-render.js +54 -10
- package/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/dist/clients/dispatch/runners/index.js +1 -3
- package/dist/clients/dispatch/runners/javac.js +0 -1
- package/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/dist/clients/dispatch/runners/lsp.js +37 -2
- package/dist/clients/dispatch/runners/markdownlint.js +4 -2
- package/dist/clients/dispatch/runners/mypy.js +0 -1
- package/dist/clients/dispatch/runners/oxlint.js +24 -22
- package/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
- package/dist/clients/dispatch/runners/pyright.js +7 -4
- package/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/dist/clients/dispatch/runners/ruff.js +0 -1
- package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
- package/dist/clients/dispatch/runners/shellcheck.js +0 -1
- package/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/dist/clients/dispatch/runners/spellcheck.js +0 -1
- package/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/dist/clients/dispatch/runners/taplo.js +0 -1
- package/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/dist/clients/dispatch/runners/tflint.js +0 -1
- package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
- package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
- package/dist/clients/dispatch/runners/vale.js +0 -1
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/dist/clients/dispatch/runners/yamllint.js +0 -1
- package/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/dist/clients/disposition-logger.js +3 -3
- package/dist/clients/effective-config.js +403 -0
- package/dist/clients/error-class.js +23 -0
- package/dist/clients/event-loop-hold.js +274 -0
- package/dist/clients/event-loop-monitor.js +66 -1
- package/dist/clients/extension-log.js +2 -2
- package/dist/clients/feature-hints.js +2 -1
- package/dist/clients/file-time.js +6 -1
- package/dist/clients/file-utils.js +266 -13
- package/dist/clients/finding-delivery-gate.js +55 -13
- package/dist/clients/format-service.js +6 -1
- package/dist/clients/formatters.js +303 -104
- package/dist/clients/freshness-cadence.js +17 -0
- package/dist/clients/generated-artifacts.js +114 -19
- package/dist/clients/generation-guard.js +4 -9
- package/dist/clients/git-guard.js +198 -48
- package/dist/clients/git-tracked-ignore.js +32 -1
- package/dist/clients/go-client.js +39 -0
- package/dist/clients/govulncheck-client.js +5 -1
- package/dist/clients/gradle-ktfmt-style.js +252 -0
- package/dist/clients/gzip-stage-write.js +7 -0
- package/dist/clients/hashline-anchor.js +424 -0
- package/dist/clients/hook-budgets.js +76 -0
- package/dist/clients/host-edit-normalize.js +5 -2
- package/dist/clients/host-ports.js +1 -1
- package/dist/clients/installer/index.js +200 -84
- package/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/dist/clients/instance-reaper.js +108 -243
- package/dist/clients/instance-registry-lock.js +145 -0
- package/dist/clients/instance-registry.js +435 -122
- package/dist/clients/jscpd-client.js +6 -1
- package/dist/clients/language-profile.js +4 -2
- package/dist/clients/language-registry.js +595 -0
- package/dist/clients/latency-logger.js +179 -3
- package/dist/clients/ledger-bounds.js +13 -0
- package/dist/clients/lens-config.js +141 -30
- package/dist/clients/lens-engine.js +30 -37
- package/dist/clients/lens-flag-registry.js +67 -6
- package/dist/clients/lens-map.js +3 -1
- package/dist/clients/log-cleanup.js +2 -2
- package/dist/clients/lsp/aggregation.js +3 -1
- package/dist/clients/lsp/cascade-tier.js +4 -0
- package/dist/clients/lsp/client.js +672 -49
- package/dist/clients/lsp/config.js +504 -156
- package/dist/clients/lsp/diagnostic-binding.js +10 -5
- package/dist/clients/lsp/edits.js +15 -2
- package/dist/clients/lsp/index.js +1179 -247
- package/dist/clients/lsp/jvm-runtime.js +5 -0
- package/dist/clients/lsp/language.js +18 -169
- package/dist/clients/lsp/launch.js +38 -9
- package/dist/clients/lsp/pending-aux-coverage.js +113 -12
- package/dist/clients/lsp/server.js +426 -160
- package/dist/clients/lsp/session-roots.js +192 -0
- package/dist/clients/lsp/spawn-history.js +2 -7
- package/dist/clients/lsp/tsserver-sync.js +9 -2
- package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
- package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/dist/clients/lsp-mutation.js +179 -23
- package/dist/clients/map-with-concurrency.js +36 -0
- package/dist/clients/mcp/analyze.js +45 -10
- package/dist/clients/mcp/session.js +19 -17
- package/dist/clients/memory-sampler.js +28 -1
- package/dist/clients/message-end-attribution.js +30 -0
- package/dist/clients/metrics-history.js +21 -4
- package/dist/clients/middle-man-analysis.js +2 -4
- package/dist/clients/module-report.js +20 -45
- package/dist/clients/mutating-tool.js +651 -0
- package/dist/clients/mutation-attribution.js +368 -0
- package/dist/clients/mutation-bridge.js +240 -0
- package/dist/clients/ndjson-logger.js +247 -20
- package/dist/clients/observed-mutation-sources.js +101 -0
- package/dist/clients/observed-mutation.js +1215 -0
- package/dist/clients/opaque-mutation-scan.js +300 -40
- package/dist/clients/opengrep-config.js +4 -0
- package/dist/clients/package-manager.js +201 -44
- package/dist/clients/partial-edit-apply.js +359 -80
- package/dist/clients/path-attribution-telemetry.js +23 -7
- package/dist/clients/path-keyed-map.js +35 -3
- package/dist/clients/path-utils.js +460 -13
- package/dist/clients/persist-debounce.js +8 -1
- package/dist/clients/php-cs-fixer-config.js +114 -0
- package/dist/clients/pipeline.js +77 -24
- package/dist/clients/probe-home-state.js +228 -0
- package/dist/clients/process-bridge.js +66 -0
- package/dist/clients/process-singletons.js +210 -0
- package/dist/clients/process-snapshot.js +68 -0
- package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
- package/dist/clients/project-diagnostics/scanner.js +61 -24
- package/dist/clients/project-lens-config.js +393 -122
- package/dist/clients/project-snapshot.js +85 -35
- package/dist/clients/python-environment.js +217 -0
- package/dist/clients/python-provenance.js +639 -0
- package/dist/clients/quiet-window.js +6 -1
- package/dist/clients/read-bridge.js +13 -21
- package/dist/clients/read-expansion.js +9 -46
- package/dist/clients/read-guard-logger.js +39 -6
- package/dist/clients/read-guard-tool-lines.js +364 -212
- package/dist/clients/read-guard.js +95 -7
- package/dist/clients/recent-touches.js +17 -5
- package/dist/clients/resource-sampler.js +195 -96
- package/dist/clients/review-graph/builder.js +752 -132
- package/dist/clients/review-graph/service.js +55 -3
- package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/dist/clients/review-graph/workspace-modules.js +10 -74
- package/dist/clients/review-graph-logger.js +30 -4
- package/dist/clients/runtime-agent-end.js +18 -2
- package/dist/clients/runtime-context.js +49 -16
- package/dist/clients/runtime-coordinator.js +93 -6
- package/dist/clients/runtime-session.js +169 -34
- package/dist/clients/runtime-tool-call.js +183 -35
- package/dist/clients/runtime-tool-result.js +894 -241
- package/dist/clients/runtime-turn.js +1264 -74
- package/dist/clients/rust-client.js +31 -0
- package/dist/clients/safe-spawn.js +191 -28
- package/dist/clients/sanitize.js +7 -1
- package/dist/clients/scratch-tree-policy.js +1 -3
- package/dist/clients/security-scan-client.js +51 -17
- package/dist/clients/session-lifecycle.js +238 -42
- package/dist/clients/session-start-observability.js +79 -0
- package/dist/clients/sessionstart-logger.js +12 -3
- package/dist/clients/sg-runner.js +27 -15
- package/dist/clients/sgconfig.js +6 -1
- package/dist/clients/shared-checkout-guard.js +338 -0
- package/dist/clients/skills-resolver.js +105 -0
- package/dist/clients/smells-rollup.js +2 -2
- package/dist/clients/spawn-output-cap.js +37 -0
- package/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/dist/clients/startup-scan.js +6 -7
- package/dist/clients/startup-timing.js +69 -4
- package/dist/clients/string-utils.js +33 -0
- package/dist/clients/subagent-mode.js +17 -4
- package/dist/clients/test-runner-client.js +586 -118
- package/dist/clients/test-runner-delivery.js +239 -0
- package/dist/clients/tool-definition.js +41 -1
- package/dist/clients/tool-policy.js +92 -22
- package/dist/clients/tree-sitter-cache.js +40 -40
- package/dist/clients/tree-sitter-client.js +97 -53
- package/dist/clients/tree-sitter-logger.js +19 -3
- package/dist/clients/tree-sitter-query-loader.js +81 -1
- package/dist/clients/tree-sitter-shared.js +51 -46
- package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/dist/clients/typos-config.js +5 -0
- package/dist/clients/user-notify.js +6 -2
- package/dist/clients/warm-attach.js +8 -10
- package/dist/clients/widget-state.js +191 -26
- package/dist/clients/word-index-logger.js +15 -3
- package/dist/clients/word-index-store.js +463 -0
- package/dist/clients/word-index.js +815 -125
- package/dist/clients/workspace-topology.js +25 -1
- package/dist/clients/zizmor-config.js +29 -6
- package/dist/index.js +68577 -53950
- package/dist/mcp/analyze-cli.js +3 -1
- package/dist/mcp/build-staleness.js +8 -7
- package/dist/mcp/cli.js +3 -1
- package/dist/mcp/server.js +133 -6
- package/dist/scripts/lib/process-scan.mjs +583 -0
- package/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/dist/tools/effective-config.js +89 -0
- package/dist/tools/lens-diagnostics.js +99 -24
- package/dist/tools/lsp-diagnostics.js +81 -85
- package/dist/tools/lsp-navigation.js +40 -17
- package/dist/tools/shared.js +0 -1
- package/docs/agent-guide.md +68 -3
- package/docs/ast-grep_rules_catalog.md +41 -10
- package/docs/astplayground.md +26 -17
- package/docs/configuration.md +222 -0
- package/docs/dependencies.md +3 -3
- package/docs/features.md +44 -5
- package/docs/language-coverage.md +2 -2
- package/docs/pi-lens-fixer.md +25 -0
- package/docs/pi-lens-investigator.md +25 -0
- package/docs/pi-lens-reviewer.md +27 -0
- package/docs/pi-lens-subagent.md +38 -0
- package/docs/pi-lens-warden.md +55 -0
- package/docs/public-api-stability.md +359 -0
- package/docs/release-qa-baseline.md +182 -0
- package/docs/subagent-compat.md +110 -30
- package/docs/tree-sitter_rules_catalog.md +2 -2
- package/package.json +17 -21
- package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/scripts/install-selftest.mjs +58 -2
- package/scripts/lib/skills-predicate.mjs +129 -0
- package/scripts/rpc-load-check.mjs +3 -1
|
@@ -20,17 +20,20 @@ import * as fs from "node:fs";
|
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
21
|
import * as path from "node:path";
|
|
22
22
|
import { getDegradationLedgerGeneration, incrementDegradationCount, recordDegradation, recordDegradationOnce, } from "./degradation-ledger.js";
|
|
23
|
+
import { BoundedFifoMap } from "./bounded-cache.js";
|
|
23
24
|
import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
|
|
24
25
|
import { transientRetryDelayMs } from "./dispatch/runners/utils/availability-policy.js";
|
|
25
26
|
import { getProjectIgnoreMatcher, isExcludedDirName } from "./file-utils.js";
|
|
26
27
|
import { downloadGrammarDetailed, fileHasWasmMagic, grammarBlockReason, grammarFileSha256, isVendoredGrammar, LANGUAGE_TO_GRAMMAR, pinnedGrammarHash, vendoredGrammarRefusal, vendoredGrammarsDir, } from "./grammar-source.js";
|
|
27
28
|
import { resolvePackagePath } from "./package-root.js";
|
|
28
29
|
import { assertInstallAllowed, getProjectTrustGeneration, } from "./project-trust.js";
|
|
30
|
+
import { escapeRegExp } from "./string-utils.js";
|
|
29
31
|
import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
|
|
30
32
|
import { notifyUserDegradation } from "./user-notify.js";
|
|
31
33
|
const _require = createRequire(import.meta.url);
|
|
32
34
|
import { createTreeCacheCounters, deriveScanTreeCacheCapacity, TreeCache, } from "./tree-sitter-cache.js";
|
|
33
35
|
import { TreeSitterNavigator } from "./tree-sitter-navigator.js";
|
|
36
|
+
import { isProvenSqlAlchemySessionReceiver, isSafePsycopgIdentifierComposition, isSqlAlchemyEntityQueryArgument, isSqlAlchemyStatementArgument, PYTHON_SQLALCHEMY_RECEIVER_NAMES, PYTHON_SQLALCHEMY_STATEMENT_BUILDERS, } from "./python-provenance.js";
|
|
34
37
|
import { TreeSitterQueryLoader, } from "./tree-sitter-query-loader.js";
|
|
35
38
|
// Hard cap on a single structural-search file walk. Bounds a misrooted scan so
|
|
36
39
|
// it can't enumerate an unbounded tree synchronously before result collection
|
|
@@ -46,6 +49,14 @@ const QUERY_BATCH_MAX_LOAD_FAILURES = 3;
|
|
|
46
49
|
// must not stall the batched query walk; the filter fails open when this cap
|
|
47
50
|
// is reached so unrelated diagnostics and the current match are preserved.
|
|
48
51
|
const NO_NESTED_ANCHOR_VISIT_CAP = 10_000;
|
|
52
|
+
// The execute-style Python DB-API/ORM methods this rule treats as SQL sinks.
|
|
53
|
+
// Hoisted so the post-filter does not rebuild the set per match.
|
|
54
|
+
const PYTHON_SQL_SINK_METHODS = new Set([
|
|
55
|
+
"execute",
|
|
56
|
+
"executemany",
|
|
57
|
+
"query",
|
|
58
|
+
"raw",
|
|
59
|
+
]);
|
|
49
60
|
const NOT_PARSED = { parsed: false };
|
|
50
61
|
function createParserCounters() {
|
|
51
62
|
return {
|
|
@@ -269,12 +280,19 @@ export class TreeSitterClient {
|
|
|
269
280
|
ParserClass = null;
|
|
270
281
|
// biome-ignore lint/suspicious/noExplicitAny: Language loader from module
|
|
271
282
|
LanguageLoader = null;
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
/** Combined multi-rule queries by language + rule-set identity (null = don't retry). */
|
|
275
|
-
queryBatchCache = new Map();
|
|
283
|
+
// Declared BEFORE the two caches below: a static read by an instance field
|
|
284
|
+
// initializer must already be initialized (TS2729).
|
|
276
285
|
static QUERY_CACHE_MAX_ENTRIES = 256;
|
|
277
286
|
static QUERY_BATCH_CACHE_MAX_ENTRIES = 256;
|
|
287
|
+
// biome-ignore lint/suspicious/noExplicitAny: Compiled query cache by language+pattern hash
|
|
288
|
+
// BoundedFifoMap, not BoundedLruCache: recency here is refreshed by this
|
|
289
|
+
// class's own explicit delete+set on both the read and the write path (see
|
|
290
|
+
// cacheQuery and the two lookup sites) — exactly the raw-`Map` discipline
|
|
291
|
+
// the FIFO map documents. A get() that promoted on its own would change
|
|
292
|
+
// which entry eviction targets (#2442 review F5/F7).
|
|
293
|
+
queryCache = new BoundedFifoMap(TreeSitterClient.QUERY_CACHE_MAX_ENTRIES);
|
|
294
|
+
/** Combined multi-rule queries by language + rule-set identity (null = don't retry). */
|
|
295
|
+
queryBatchCache = new BoundedFifoMap(TreeSitterClient.QUERY_BATCH_CACHE_MAX_ENTRIES);
|
|
278
296
|
queryCacheCap() {
|
|
279
297
|
const value = Number.parseInt(process.env.PI_LENS_TREE_SITTER_QUERY_CACHE_CAP ?? "", 10);
|
|
280
298
|
return Number.isSafeInteger(value) && value > 0
|
|
@@ -287,27 +305,25 @@ export class TreeSitterClient {
|
|
|
287
305
|
? value
|
|
288
306
|
: TreeSitterClient.QUERY_BATCH_CACHE_MAX_ENTRIES;
|
|
289
307
|
}
|
|
308
|
+
// biome-ignore lint/suspicious/noExplicitAny: compiled query objects
|
|
290
309
|
cacheQuery(key, value) {
|
|
291
|
-
this.queryCache.delete(key);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
310
|
+
this.queryCache.delete(key); // write-refresh: this write is the newest
|
|
311
|
+
// The cap is env-readable, so re-apply it before every write — a raised
|
|
312
|
+
// or lowered ceiling has to take effect on the next insert, not on the
|
|
313
|
+
// next restart. Both calls hand back the dropped [key, value] pairs so
|
|
314
|
+
// the evicted compiled query's native handle is still freed (#2442
|
|
315
|
+
// review F5/F7 — this is why set() returns the VALUE, not just the key).
|
|
316
|
+
const evicted = this.queryCache.setMaxEntries(this.queryCacheCap());
|
|
317
|
+
evicted.push(...this.queryCache.set(key, value));
|
|
318
|
+
for (const [, dropped] of evicted)
|
|
319
|
+
dropped?.query?.delete?.();
|
|
300
320
|
}
|
|
301
321
|
cacheQueryBatch(key, value) {
|
|
302
322
|
this.queryBatchCache.delete(key);
|
|
303
|
-
this.queryBatchCache.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
break;
|
|
308
|
-
this.queryBatchCache.delete(oldest[0]);
|
|
309
|
-
oldest[1]?.query?.delete?.();
|
|
310
|
-
}
|
|
323
|
+
const evicted = this.queryBatchCache.setMaxEntries(this.queryBatchCacheCap());
|
|
324
|
+
evicted.push(...this.queryBatchCache.set(key, value));
|
|
325
|
+
for (const [, dropped] of evicted)
|
|
326
|
+
dropped?.query?.delete?.();
|
|
311
327
|
}
|
|
312
328
|
/** Consecutive grammar-load failures per batch key — bounds load retries (#889). */
|
|
313
329
|
queryBatchLoadFailures = new Map();
|
|
@@ -318,7 +334,13 @@ export class TreeSitterClient {
|
|
|
318
334
|
activeMeasurements = 0;
|
|
319
335
|
onWasmAbort;
|
|
320
336
|
wasmAborted = false;
|
|
321
|
-
|
|
337
|
+
grammarDirResolutionDeps;
|
|
338
|
+
constructor(verbose = false, onWasmAbort, grammarDirResolutionDeps) {
|
|
339
|
+
this.grammarDirResolutionDeps = grammarDirResolutionDeps ?? {
|
|
340
|
+
resolveAsset: (asset) => this.resolveWebTreeSitterAsset(asset),
|
|
341
|
+
resolvePackage: (specifier) => _require.resolve(specifier),
|
|
342
|
+
cwd: () => process.cwd(),
|
|
343
|
+
};
|
|
322
344
|
this.grammarsDir = this.findGrammarsDir();
|
|
323
345
|
this.verbose = verbose;
|
|
324
346
|
this.onWasmAbort = onWasmAbort;
|
|
@@ -708,8 +730,8 @@ export class TreeSitterClient {
|
|
|
708
730
|
});
|
|
709
731
|
}
|
|
710
732
|
/** Find tree-sitter grammar directory */
|
|
711
|
-
findGrammarsDir() {
|
|
712
|
-
const grammarsDir =
|
|
733
|
+
findGrammarsDir(deps = this.grammarDirResolutionDeps) {
|
|
734
|
+
const grammarsDir = deps.resolveAsset("grammars");
|
|
713
735
|
if (grammarsDir &&
|
|
714
736
|
fs.existsSync(path.join(grammarsDir, "tree-sitter-typescript.wasm"))) {
|
|
715
737
|
return grammarsDir;
|
|
@@ -717,14 +739,14 @@ export class TreeSitterClient {
|
|
|
717
739
|
// Fallback: a real `tree-sitter-wasms` package, if the user installed one
|
|
718
740
|
// (it is not a pi-lens dependency — grammars ship bundled / lazy-fetched).
|
|
719
741
|
try {
|
|
720
|
-
const wasmsOut = path.join(path.dirname(
|
|
742
|
+
const wasmsOut = path.join(path.dirname(deps.resolvePackage("tree-sitter-wasms/package.json")), "out");
|
|
721
743
|
if (fs.existsSync(wasmsOut))
|
|
722
744
|
return wasmsOut;
|
|
723
745
|
}
|
|
724
746
|
catch {
|
|
725
747
|
/* fall through */
|
|
726
748
|
}
|
|
727
|
-
const cwdWasms = path.join(
|
|
749
|
+
const cwdWasms = path.join(deps.cwd(), "node_modules", "tree-sitter-wasms", "out");
|
|
728
750
|
if (fs.existsSync(cwdWasms))
|
|
729
751
|
return cwdWasms;
|
|
730
752
|
return "";
|
|
@@ -1442,7 +1464,8 @@ export class TreeSitterClient {
|
|
|
1442
1464
|
const perQuery = new Map();
|
|
1443
1465
|
await this.parseFileAndUse(filePath, languageId, contentOverride, (tree) => {
|
|
1444
1466
|
try {
|
|
1445
|
-
|
|
1467
|
+
const rootNode = tree.rootNode;
|
|
1468
|
+
for (const match of batch.query.matches(rootNode)) {
|
|
1446
1469
|
const owner = batch.ownerOfPattern[match.patternIndex];
|
|
1447
1470
|
if (owner === undefined)
|
|
1448
1471
|
continue;
|
|
@@ -1459,7 +1482,7 @@ export class TreeSitterClient {
|
|
|
1459
1482
|
if (!this.evaluatePredicates(batch.query, match))
|
|
1460
1483
|
continue;
|
|
1461
1484
|
if (entry.postFilter &&
|
|
1462
|
-
!this.applyPostFilter(entry.postFilter, entry.postFilterParams, captures,
|
|
1485
|
+
!this.applyPostFilter(entry.postFilter, entry.postFilterParams, captures, rootNode)) {
|
|
1463
1486
|
continue;
|
|
1464
1487
|
}
|
|
1465
1488
|
if (match.captures.length === 0)
|
|
@@ -1830,14 +1853,16 @@ export class TreeSitterClient {
|
|
|
1830
1853
|
}
|
|
1831
1854
|
return false;
|
|
1832
1855
|
}
|
|
1856
|
+
/**
|
|
1857
|
+
* SQLAlchemy sessions are conventionally bound to one of a handful of
|
|
1858
|
+
* receiver names. Structural provenance (`python-provenance.ts`) proves the
|
|
1859
|
+
* annotated case; this name check keeps the far more common unannotated
|
|
1860
|
+
* `session.execute(stmt)` quiet, as it has been since the exemption was
|
|
1861
|
+
* added. Removing it regresses every codebase that never annotates.
|
|
1862
|
+
*/
|
|
1833
1863
|
isLikelySqlAlchemyReceiver(text) {
|
|
1834
1864
|
const tail = text.split(".").pop() ?? text;
|
|
1835
|
-
return
|
|
1836
|
-
"session",
|
|
1837
|
-
"db_session",
|
|
1838
|
-
"async_session",
|
|
1839
|
-
"sync_session",
|
|
1840
|
-
]).has(tail.toLowerCase());
|
|
1865
|
+
return PYTHON_SQLALCHEMY_RECEIVER_NAMES.has(tail.toLowerCase());
|
|
1841
1866
|
}
|
|
1842
1867
|
/**
|
|
1843
1868
|
* The body statements of a `switch_case`, in order. A switch_case's named
|
|
@@ -2391,12 +2416,20 @@ export class TreeSitterClient {
|
|
|
2391
2416
|
return null;
|
|
2392
2417
|
return object.text;
|
|
2393
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* `session.execute(select(...))` and friends pass a statement OBJECT, not a
|
|
2421
|
+
* SQL string: parameterized by construction, and far too noisy as blockers.
|
|
2422
|
+
*/
|
|
2394
2423
|
isSafeSqlAlchemyExpressionCall(node) {
|
|
2395
2424
|
if (node.type !== "call")
|
|
2396
2425
|
return false;
|
|
2397
2426
|
const callee = node.children?.[0]?.text ?? "";
|
|
2398
2427
|
const expression = node.text;
|
|
2399
|
-
|
|
2428
|
+
for (const name of PYTHON_SQLALCHEMY_STATEMENT_BUILDERS) {
|
|
2429
|
+
if (callee === name || expression.startsWith(`${name}(`))
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2432
|
+
return false;
|
|
2400
2433
|
}
|
|
2401
2434
|
/**
|
|
2402
2435
|
* Post-filter predicate: returns true if the match should be kept, false to skip.
|
|
@@ -2463,15 +2496,6 @@ export class TreeSitterClient {
|
|
|
2463
2496
|
}
|
|
2464
2497
|
return slots;
|
|
2465
2498
|
}
|
|
2466
|
-
/**
|
|
2467
|
-
* Escape a string for safe interpolation into a `RegExp` source —
|
|
2468
|
-
* needed anywhere an identifier's raw text is combined with regex
|
|
2469
|
-
* metacharacters like `\b` word boundaries (see
|
|
2470
|
-
* "not_closed_or_try_with_resources" below; #1089 P2).
|
|
2471
|
-
*/
|
|
2472
|
-
function escapeRegExp(s) {
|
|
2473
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2474
|
-
}
|
|
2475
2499
|
switch (postFilter) {
|
|
2476
2500
|
case "no_nested_anchor_chain": {
|
|
2477
2501
|
// Tree-sitter queries can express the opening-tag shape, but they
|
|
@@ -3540,20 +3564,39 @@ export class TreeSitterClient {
|
|
|
3540
3564
|
/^(Open|OpenFile|ReadFile|WriteFile|Create|Remove|RemoveAll)$/.test(captures.FN?.text ?? ""));
|
|
3541
3565
|
case "py_sql_injection_sink": {
|
|
3542
3566
|
const fn = captures.FN?.text ?? "";
|
|
3543
|
-
if (!
|
|
3567
|
+
if (!PYTHON_SQL_SINK_METHODS.has(fn))
|
|
3544
3568
|
return false;
|
|
3545
|
-
}
|
|
3546
3569
|
const sqlNode = captures.SQL;
|
|
3547
|
-
const receiver = captures.OBJ
|
|
3548
|
-
//
|
|
3549
|
-
//
|
|
3550
|
-
//
|
|
3551
|
-
if (fn === "execute" &&
|
|
3570
|
+
const receiver = captures.OBJ;
|
|
3571
|
+
// Pre-existing exemptions — kept verbatim so no current user
|
|
3572
|
+
// regresses (#2577 review): an unannotated session receiver, and a
|
|
3573
|
+
// statement-builder call in argument position.
|
|
3574
|
+
if (fn === "execute" &&
|
|
3575
|
+
this.isLikelySqlAlchemyReceiver(receiver?.text ?? "")) {
|
|
3552
3576
|
return false;
|
|
3553
3577
|
}
|
|
3554
3578
|
if (sqlNode && this.isSafeSqlAlchemyExpressionCall(sqlNode)) {
|
|
3555
3579
|
return false;
|
|
3556
3580
|
}
|
|
3581
|
+
// #2576: a receiver PROVEN to be a sqlalchemy Session/AsyncSession.
|
|
3582
|
+
// `Session.query` takes entity classes and `Session.execute` a
|
|
3583
|
+
// statement object (a builder call, or a name bound to one —
|
|
3584
|
+
// `stmt = select(User)`, which the check above cannot see). Neither
|
|
3585
|
+
// suppression may swallow composed SQL text (#2577 review F1/F2).
|
|
3586
|
+
if (isProvenSqlAlchemySessionReceiver(receiver, rootNode)) {
|
|
3587
|
+
if (fn === "query" &&
|
|
3588
|
+
isSqlAlchemyEntityQueryArgument(sqlNode, rootNode)) {
|
|
3589
|
+
return false;
|
|
3590
|
+
}
|
|
3591
|
+
if (fn === "execute" &&
|
|
3592
|
+
isSqlAlchemyStatementArgument(sqlNode, rootNode)) {
|
|
3593
|
+
return false;
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
// #2576: psycopg `sql.SQL("...").format(sql.Identifier(...))`.
|
|
3597
|
+
if (isSafePsycopgIdentifierComposition(sqlNode, rootNode)) {
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3557
3600
|
return true;
|
|
3558
3601
|
}
|
|
3559
3602
|
case "go_sql_injection_sink":
|
|
@@ -3862,7 +3905,8 @@ export class TreeSitterClient {
|
|
|
3862
3905
|
const matches = [];
|
|
3863
3906
|
await this.parseFileAndUse(filePath, languageId, contentOverride, (tree) => {
|
|
3864
3907
|
try {
|
|
3865
|
-
const
|
|
3908
|
+
const rootNode = tree.rootNode;
|
|
3909
|
+
const queryMatches = query.matches(rootNode);
|
|
3866
3910
|
for (const match of queryMatches) {
|
|
3867
3911
|
const captures = {};
|
|
3868
3912
|
for (const capture of match.captures) {
|
|
@@ -3877,7 +3921,7 @@ export class TreeSitterClient {
|
|
|
3877
3921
|
continue;
|
|
3878
3922
|
}
|
|
3879
3923
|
if (postFilter &&
|
|
3880
|
-
!this.applyPostFilter(postFilter, postFilterParams, captures,
|
|
3924
|
+
!this.applyPostFilter(postFilter, postFilterParams, captures, rootNode)) {
|
|
3881
3925
|
continue;
|
|
3882
3926
|
}
|
|
3883
3927
|
if (match.captures.length > 0) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { isTestMode } from "./env-utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
4
4
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
5
|
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
6
|
-
|
|
6
|
+
import { normalizeLoggedPath } from "./path-utils.js";
|
|
7
|
+
const TREE_SITTER_LOG_DIR = getGlobalPiLensLogDir();
|
|
7
8
|
const TREE_SITTER_LOG_FILE = path.join(TREE_SITTER_LOG_DIR, "tree-sitter.log");
|
|
8
9
|
const writer = createNdjsonLogger({
|
|
9
10
|
filePath: TREE_SITTER_LOG_FILE,
|
|
@@ -27,11 +28,26 @@ const CACHE_COUNTER_KEYS = [
|
|
|
27
28
|
"parserDurationMs",
|
|
28
29
|
"parserFailures",
|
|
29
30
|
];
|
|
31
|
+
/**
|
|
32
|
+
* #2219 (the #2141 class): `filePath` mixes raw `path.resolve()`/`cwd`
|
|
33
|
+
* values (`project-diagnostics/scanner.ts`, `review-graph/builder.ts`,
|
|
34
|
+
* `tree-sitter-shared.ts`) with already-normalized `ctx.filePath` (the
|
|
35
|
+
* `dispatch/runners/tree-sitter.ts` call sites) and the `"<tree-sitter>"`
|
|
36
|
+
* sentinel `logTreeSitterDiagnostic` falls back to below. This is the single
|
|
37
|
+
* seam every one of those paths funnels through (`logTreeSitterCacheStats`
|
|
38
|
+
* and `logTreeSitterDiagnostic` both call this function), so normalize once
|
|
39
|
+
* here — guarded via `normalizeLoggedPath` so the sentinel passes through
|
|
40
|
+
* unchanged instead of being resolved against the process cwd.
|
|
41
|
+
*/
|
|
30
42
|
export function logTreeSitter(entry) {
|
|
31
43
|
if (isTestMode()) {
|
|
32
44
|
return;
|
|
33
45
|
}
|
|
34
|
-
writer.log({
|
|
46
|
+
writer.log({
|
|
47
|
+
ts: new Date().toISOString(),
|
|
48
|
+
...entry,
|
|
49
|
+
filePath: normalizeLoggedPath(entry.filePath),
|
|
50
|
+
});
|
|
35
51
|
}
|
|
36
52
|
export function logTreeSitterCacheStats(options) {
|
|
37
53
|
const delta = Object.fromEntries(CACHE_COUNTER_KEYS.map((key) => [key, options.stats[key]]));
|
|
@@ -7,7 +7,51 @@
|
|
|
7
7
|
import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { classifyBundledResourceDir, reportBundledResourceDirHealth, } from "./bundled-resource-health.js";
|
|
11
|
+
import { getDegradationLedgerGeneration } from "./degradation-ledger.js";
|
|
10
12
|
import { resolvePackagePath } from "./package-root.js";
|
|
13
|
+
/**
|
|
14
|
+
* The bundled `rules/tree-sitter-queries` root — the ONE spelling of this
|
|
15
|
+
* path, imported by `clients/cache/rule-cache.ts` (re-exported there as
|
|
16
|
+
* `BUNDLED_RULES_ROOT`, #2636 review F4) rather than computed a second time,
|
|
17
|
+
* so the "one ledger row, not two" claim for `tree-sitter-queries-dir-missing`
|
|
18
|
+
* rests on reference equality, not merely two independently-resolved strings
|
|
19
|
+
* that happen to match.
|
|
20
|
+
*/
|
|
21
|
+
export const BUNDLED_QUERIES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
|
|
22
|
+
let cachedBundledQueriesRootHealth;
|
|
23
|
+
let cachedBundledQueriesRootHealthGeneration;
|
|
24
|
+
/**
|
|
25
|
+
* #2636 review F6/round-2 F3: memoized, but keyed on the degradation
|
|
26
|
+
* ledger's OWN generation counter (bumped by `resetDegradationLedger`, wired
|
|
27
|
+
* into `handleSessionStart`) rather than "compute once, forever" — round 1's
|
|
28
|
+
* version overclaimed that the underlying fact "cannot change mid-process
|
|
29
|
+
* any more than the package's own install location can", but a managed
|
|
30
|
+
* extension cache RELOCATING a live install mid-process is exactly the
|
|
31
|
+
* failure #2587/#2626 investigated: a permanently-cached "absent" verdict
|
|
32
|
+
* from the FIRST probe would never notice the directory coming back (or
|
|
33
|
+
* disappearing later), the same silent-zero shape this issue exists to end.
|
|
34
|
+
* Re-probing once per SESSION (not once per call) is the right middle
|
|
35
|
+
* ground: a real, measured `readdirSync` cost (4.2 µs) is paid once per
|
|
36
|
+
* session rather than on every dispatched file — same generation-keyed
|
|
37
|
+
* pattern `clients/ast-grep-client.ts`'s `ensureRulesHealthReported` uses
|
|
38
|
+
* for its own per-instance re-check.
|
|
39
|
+
*/
|
|
40
|
+
export function getBundledQueriesRootHealth() {
|
|
41
|
+
const generation = getDegradationLedgerGeneration();
|
|
42
|
+
if (cachedBundledQueriesRootHealth === undefined ||
|
|
43
|
+
cachedBundledQueriesRootHealthGeneration !== generation) {
|
|
44
|
+
cachedBundledQueriesRootHealthGeneration = generation;
|
|
45
|
+
cachedBundledQueriesRootHealth =
|
|
46
|
+
classifyBundledResourceDir(BUNDLED_QUERIES_ROOT);
|
|
47
|
+
}
|
|
48
|
+
return cachedBundledQueriesRootHealth;
|
|
49
|
+
}
|
|
50
|
+
/** Test-only: clear the memo so a scenario can simulate a different install layout. */
|
|
51
|
+
export function _resetBundledQueriesRootHealthForTests() {
|
|
52
|
+
cachedBundledQueriesRootHealth = undefined;
|
|
53
|
+
cachedBundledQueriesRootHealthGeneration = undefined;
|
|
54
|
+
}
|
|
11
55
|
export function isDisabledQueryDirectoryName(name) {
|
|
12
56
|
return name.endsWith("-disabled");
|
|
13
57
|
}
|
|
@@ -60,7 +104,7 @@ export function ruleFilesForLanguage(languageId, rootDir = process.cwd()) {
|
|
|
60
104
|
for (const lang of ruleSourceLanguages(languageId)) {
|
|
61
105
|
for (const dir of [
|
|
62
106
|
path.join(resolvedRoot, "rules", "tree-sitter-queries", lang),
|
|
63
|
-
|
|
107
|
+
path.join(BUNDLED_QUERIES_ROOT, lang),
|
|
64
108
|
]) {
|
|
65
109
|
if (!fs.existsSync(dir))
|
|
66
110
|
continue;
|
|
@@ -70,6 +114,42 @@ export function ruleFilesForLanguage(languageId, rootDir = process.cwd()) {
|
|
|
70
114
|
}
|
|
71
115
|
}
|
|
72
116
|
}
|
|
117
|
+
// #2636 (review F2): a language resolving zero files here is NORMAL when
|
|
118
|
+
// nobody has authored bundled/project queries for it — seven REACHABLE
|
|
119
|
+
// grammars have none by design: bash, dart, elixir, lua, ocaml, swift, zig
|
|
120
|
+
// (`.sh`/`.bash`, `.dart`, `.ex`/`.exs`, `.lua`, `.ml`/`.mli`, `.swift`,
|
|
121
|
+
// `.zig` — see `language-registry.ts`'s `EXTENSION_TO_GRAMMAR`). cobol and
|
|
122
|
+
// plsql are NOT in that registry at all — only their `-disabled` query
|
|
123
|
+
// directories exist — so `ruleFilesForLanguage` never actually resolves
|
|
124
|
+
// those two languageIds in production; they are not examples of this
|
|
125
|
+
// case. A BUG looks identical from an empty `files` set alone: the
|
|
126
|
+
// bundled root itself relocated out from under the package (same
|
|
127
|
+
// managed-cache-relocation shape #2626 fixed for skills/). Only pay for
|
|
128
|
+
// the extra classification in this COLD branch (never on the common,
|
|
129
|
+
// non-empty path), key it on the shared ROOT rather than this call's
|
|
130
|
+
// `languageId` (every language hitting an actually-missing root collapses
|
|
131
|
+
// into the SAME ledger row instead of one per language), and only RECORD
|
|
132
|
+
// when the root is actually unhealthy — one of the seven by-design-empty
|
|
133
|
+
// languages is touched routinely (any `.sh`/`.lua` edit).
|
|
134
|
+
//
|
|
135
|
+
// #2636 review round 2, F2: NO separate phase/latency row here (unlike
|
|
136
|
+
// the ast-grep/skills sibling sites, which log one PER CONSTRUCTION or
|
|
137
|
+
// PER REQUEST — a bounded cardinality). This branch runs on EVERY
|
|
138
|
+
// dispatched file while the root stays broken (AGENTS.md's "no raw
|
|
139
|
+
// per-occurrence log for repeats"): 200 touches of a by-design-empty
|
|
140
|
+
// language against a broken root would otherwise write 200 raw
|
|
141
|
+
// `latency.log` rows. `reportBundledResourceDirHealth`'s
|
|
142
|
+
// `incrementDegradationCount` already answers "did this run" AND "how
|
|
143
|
+
// many times" in a BOUNDED way — one row per session at count 1, then
|
|
144
|
+
// only at power-of-two milestones (1, 2, 4, 8, … so 200 occurrences write
|
|
145
|
+
// exactly 8 durable rows) — so a second, unbounded record here would add
|
|
146
|
+
// nothing the ledger row lacks.
|
|
147
|
+
if (files.size === 0) {
|
|
148
|
+
const health = getBundledQueriesRootHealth();
|
|
149
|
+
if (health.status !== "healthy") {
|
|
150
|
+
reportBundledResourceDirHealth("tree-sitter-queries-dir-missing", BUNDLED_QUERIES_ROOT, health, "bundled tree-sitter query rules");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
73
153
|
return [...files];
|
|
74
154
|
}
|
|
75
155
|
/**
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { notifyUserDegradation } from "./user-notify.js";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import { TreeSitterClient, } from "./tree-sitter-client.js";
|
|
19
|
+
import { EXTENSION_TO_GRAMMAR } from "./language-registry.js";
|
|
19
20
|
import { logTreeSitter } from "./tree-sitter-logger.js";
|
|
20
21
|
let _shared = null;
|
|
21
22
|
let _wasmAborted = false;
|
|
@@ -80,56 +81,60 @@ export function _resetSharedTreeSitterClientForTests() {
|
|
|
80
81
|
_wasmAborted = false;
|
|
81
82
|
_wasmAbortedAt = undefined;
|
|
82
83
|
}
|
|
83
|
-
// Grammar selection by extension —
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// scanner
|
|
90
|
-
//
|
|
91
|
-
// `
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
".mts": "typescript",
|
|
96
|
-
".cts": "typescript",
|
|
97
|
-
".tsx": "tsx",
|
|
98
|
-
".js": "javascript",
|
|
99
|
-
".mjs": "javascript",
|
|
100
|
-
".cjs": "javascript",
|
|
101
|
-
".jsx": "javascript",
|
|
102
|
-
".py": "python",
|
|
103
|
-
".go": "go",
|
|
104
|
-
".rs": "rust",
|
|
105
|
-
".rb": "ruby",
|
|
106
|
-
".c": "c",
|
|
107
|
-
".h": "c",
|
|
108
|
-
".cc": "cpp",
|
|
109
|
-
".cpp": "cpp",
|
|
110
|
-
".cxx": "cpp",
|
|
111
|
-
".c++": "cpp",
|
|
112
|
-
".hh": "cpp",
|
|
113
|
-
".hpp": "cpp",
|
|
114
|
-
".hxx": "cpp",
|
|
115
|
-
".inl": "cpp",
|
|
116
|
-
".ipp": "cpp",
|
|
117
|
-
".tpp": "cpp",
|
|
118
|
-
".txx": "cpp",
|
|
119
|
-
".cu": "cpp",
|
|
120
|
-
".hip": "cpp",
|
|
121
|
-
".cs": "csharp",
|
|
122
|
-
".php": "php",
|
|
123
|
-
".phtml": "php",
|
|
124
|
-
".php3": "php",
|
|
125
|
-
".php4": "php",
|
|
126
|
-
".php5": "php",
|
|
127
|
-
".css": "css",
|
|
128
|
-
};
|
|
84
|
+
// Grammar selection by extension — projected from the canonical language
|
|
85
|
+
// registry (language-registry.ts, #2424), which is now THE ext→grammar
|
|
86
|
+
// authority. `.tsx` → the tsx grammar (parses JSX); `.jsx` → the javascript
|
|
87
|
+
// grammar. The project scanner (project-diagnostics/scanner.ts), read expansion
|
|
88
|
+
// (read-expansion.ts) and module-report (`tsLangForFile`, #887) all project the
|
|
89
|
+
// same registry column, so none of them can drift from this one: before #2424
|
|
90
|
+
// the scanner spread this map and layered java/kotlin on top while read
|
|
91
|
+
// expansion kept a hand-copied near-copy of it.
|
|
92
|
+
// `Readonly` because the projection is `Object.freeze`d at the source, and the
|
|
93
|
+
// mutable `Record` type let a caller type-check a write that would throw at
|
|
94
|
+
// runtime (#2424 review, S4).
|
|
95
|
+
export const EXT_TO_LANG = EXTENSION_TO_GRAMMAR;
|
|
129
96
|
/** Resolve a tree-sitter grammar/language id from a file path's extension. */
|
|
130
97
|
export function resolveTreeSitterLanguage(filePath) {
|
|
131
98
|
return EXT_TO_LANG[path.extname(filePath).toLowerCase()];
|
|
132
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The grammars `clients/complexity-client.ts` has a node mapping for.
|
|
102
|
+
*
|
|
103
|
+
* Lives HERE, beside the ext→grammar resolver, because the question "can this
|
|
104
|
+
* path produce a complexity baseline?" has to be answerable WITHOUT loading
|
|
105
|
+
* the analyzer graph (#2467): `clients/runtime-tool-call.ts` asks it on every
|
|
106
|
+
* tool call to decide whether the seventeen-module load is worth starting, and
|
|
107
|
+
* the client that owns the answer is behind that very load.
|
|
108
|
+
*
|
|
109
|
+
* It is not a second extension table — the extension half still comes from the
|
|
110
|
+
* one canonical registry through {@link resolveTreeSitterLanguage}. It is the
|
|
111
|
+
* language-id half, and `LANGUAGE_NODES` is typed
|
|
112
|
+
* `Record<ComplexityLanguageId, …>`, so adding a grammar there without adding
|
|
113
|
+
* it here (or the reverse) is a COMPILE error rather than a predicate that
|
|
114
|
+
* quietly drifts from the client it is supposed to mirror.
|
|
115
|
+
*/
|
|
116
|
+
export const COMPLEXITY_LANGUAGE_IDS = [
|
|
117
|
+
"typescript",
|
|
118
|
+
"tsx",
|
|
119
|
+
"javascript",
|
|
120
|
+
"python",
|
|
121
|
+
"go",
|
|
122
|
+
"rust",
|
|
123
|
+
];
|
|
124
|
+
/** Narrow a resolved grammar id to one complexity can analyze. */
|
|
125
|
+
export function isComplexityLanguageId(languageId) {
|
|
126
|
+
return COMPLEXITY_LANGUAGE_IDS.includes(languageId);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Can a complexity baseline be produced for this path? Pure, synchronous, and
|
|
130
|
+
* free of every analyzer module — the guard `handleToolCallImpl` needs above
|
|
131
|
+
* its demand on the bootstrap seam. `ComplexityClient.isSupportedFile`
|
|
132
|
+
* delegates to it, so there is exactly one answer.
|
|
133
|
+
*/
|
|
134
|
+
export function isComplexitySupportedFile(filePath) {
|
|
135
|
+
const languageId = resolveTreeSitterLanguage(filePath);
|
|
136
|
+
return languageId !== undefined && isComplexityLanguageId(languageId);
|
|
137
|
+
}
|
|
133
138
|
export async function withTreeSitterRoot(filePath, content, consume) {
|
|
134
139
|
const languageId = resolveTreeSitterLanguage(filePath);
|
|
135
140
|
const client = getSharedTreeSitterClient();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Extracts definitions and references from source files
|
|
4
4
|
*/
|
|
5
5
|
import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
|
|
6
|
+
import { EXTENSION_TO_GRAMMAR, KIND_TO_GRAMMAR } from "./language-registry.js";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
|
|
8
9
|
// Tree-sitter query patterns for symbol extraction
|
|
@@ -563,6 +564,33 @@ SYMBOL_QUERIES.javascript = {
|
|
|
563
564
|
constructor: (identifier) @newIdent) @newRef
|
|
564
565
|
`,
|
|
565
566
|
};
|
|
567
|
+
/**
|
|
568
|
+
* The grammar a KIND-keyed consumer (review-graph builder, module-report
|
|
569
|
+
* outline) should extract `filePath` with, or undefined when this file is not
|
|
570
|
+
* something the symbol extractor can read.
|
|
571
|
+
*
|
|
572
|
+
* Three registry-derived steps, no hand-kept table (#2424 replaced one switch
|
|
573
|
+
* in review-graph/builder.ts and one map in module-report.ts with this):
|
|
574
|
+
* 1. the kind must have a single grammar-bearing language, or declare a
|
|
575
|
+
* `kindFallback` one — so extension-split `jsts` is excluded and its
|
|
576
|
+
* callers keep resolving it by path;
|
|
577
|
+
* 2. the file's own extension wins where it has grammar wiring, which is what
|
|
578
|
+
* splits `cxx` into `.c`/`.h` -> c and the rest -> cpp;
|
|
579
|
+
* 3. the grammar must actually have symbol queries here, which is what keeps
|
|
580
|
+
* css/json/yaml/html/toml (grammars that ship, queries that do not) out.
|
|
581
|
+
*/
|
|
582
|
+
export function symbolExtractionGrammar(kind, filePath) {
|
|
583
|
+
if (!kind)
|
|
584
|
+
return undefined;
|
|
585
|
+
const kindGrammar = KIND_TO_GRAMMAR[kind];
|
|
586
|
+
if (!kindGrammar)
|
|
587
|
+
return undefined;
|
|
588
|
+
const byExtension = filePath
|
|
589
|
+
? EXTENSION_TO_GRAMMAR[path.extname(filePath).toLowerCase()]
|
|
590
|
+
: undefined;
|
|
591
|
+
const grammar = byExtension ?? kindGrammar;
|
|
592
|
+
return grammar in SYMBOL_QUERIES ? grammar : undefined;
|
|
593
|
+
}
|
|
566
594
|
/** Language keys with symbol queries; used by fixture drift guards. */
|
|
567
595
|
export function getSymbolQueryLanguages() {
|
|
568
596
|
return Object.keys(SYMBOL_QUERIES);
|
|
@@ -19,6 +19,11 @@ export const LOCAL_TYPOS_CONFIG_NAMES = [
|
|
|
19
19
|
"_typos.toml",
|
|
20
20
|
".typos.toml",
|
|
21
21
|
];
|
|
22
|
+
// Deliberately UNCEILINGED at $HOME (refs #2472 review round 3, F1): typos
|
|
23
|
+
// discovers `~/typos.toml` as the user's legitimate global config, and
|
|
24
|
+
// `typos-lsp` merges an injected config with its own discovery (injected
|
|
25
|
+
// wins) — a ceilinged search here silently hid the user's config from that
|
|
26
|
+
// merge and let pi-lens's own shipped `_typos.toml` clobber it instead.
|
|
22
27
|
export function findLocalTyposConfig(startDir) {
|
|
23
28
|
return findLocalToolConfig(startDir, LOCAL_TYPOS_CONFIG_NAMES);
|
|
24
29
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* is a no-op and the ndjson sink remains the sole destination — the message is
|
|
20
20
|
* never lost, only un-rendered.
|
|
21
21
|
*/
|
|
22
|
+
import { withConfigDiagnosticCode, } from "./config-diagnostic-codes.js";
|
|
22
23
|
let notifierGetter;
|
|
23
24
|
/** Called once from the extension entry with a getter over the live `ctx.ui.notify`. */
|
|
24
25
|
export function wireUserNotifier(portsOrGetter) {
|
|
@@ -40,10 +41,13 @@ export function hasUserNotifier() {
|
|
|
40
41
|
* `ctx` after a session replacement must degrade to a no-op, not break the
|
|
41
42
|
* diagnostic path that called it.
|
|
42
43
|
*/
|
|
43
|
-
export function notifyUserDegradation(message, level = "warning") {
|
|
44
|
+
export function notifyUserDegradation(message, level = "warning", options) {
|
|
44
45
|
try {
|
|
45
46
|
const notify = notifierGetter?.();
|
|
46
|
-
|
|
47
|
+
const coded = options?.code
|
|
48
|
+
? withConfigDiagnosticCode(message, options.code)
|
|
49
|
+
: message;
|
|
50
|
+
notify?.(coded, level);
|
|
47
51
|
}
|
|
48
52
|
catch {
|
|
49
53
|
// The ndjson sink already holds this message; a dead host is not an error.
|