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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process singletons — state that must exist ONCE PER PROCESS, not once per
|
|
3
|
+
* module evaluation (#2146).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS MODULE EXISTS. pi evaluates the pi-lens module graph more than once
|
|
6
|
+
* in a single process: dogfood pass 3 measured one pid emitting `host_boot`
|
|
7
|
+
* nine times, another four times. Source and compiled entries load through
|
|
8
|
+
* separate module graphs, in-process subagent sessions re-enter the extension
|
|
9
|
+
* loader, and vitest re-evaluates modules on `vi.resetModules()`. Every
|
|
10
|
+
* module-scope `let` therefore exists N times, and any state whose CORRECTNESS
|
|
11
|
+
* depends on being the process's only copy silently breaks:
|
|
12
|
+
*
|
|
13
|
+
* - `session-lifecycle.ts`'s registered primary: evaluation 2 starts with an
|
|
14
|
+
* empty registration, classifies a subagent temp root as `primary`, and runs
|
|
15
|
+
* the full session_start battery the #473/#2129/#2133 guard exists to
|
|
16
|
+
* decline. Measured cost: three identical word-index full rebuilds in one
|
|
17
|
+
* 90s burst, 240.8s of CPU for one index.
|
|
18
|
+
* - `instance-registry.ts`'s mutation tail: N tails are N concurrent
|
|
19
|
+
* read-modify-write cycles over one `instances.json`. Measured: three
|
|
20
|
+
* `instance-registry-corrupt` records in a 9s window, with two project roots
|
|
21
|
+
* and one live instance lost from the file.
|
|
22
|
+
*
|
|
23
|
+
* PRECEDENT, both in-repo and both cited rather than invented:
|
|
24
|
+
* - `clients/runtime-session.ts:1826` hangs `__piLensFirstSessionDone` /
|
|
25
|
+
* `__piLensWarmupScheduled` on `globalThis` for exactly this reason.
|
|
26
|
+
* - `clients/ndjson-logger.ts:125` does the versioned form: a
|
|
27
|
+
* `Symbol.for()` key, a schema string, a version number, and an explicit
|
|
28
|
+
* adopt-or-decline decision for a state written by a different build.
|
|
29
|
+
*
|
|
30
|
+
* This module generalizes the second one so each state family gets the same
|
|
31
|
+
* treatment without re-deriving the protocol per site.
|
|
32
|
+
*
|
|
33
|
+
* WHAT BELONGS HERE. Only state that is WRONG when duplicated: process-wide
|
|
34
|
+
* registrations, serialization points, and once-per-process latches. A memo or
|
|
35
|
+
* cache that re-derives the same answer from a stable source (an env read, a
|
|
36
|
+
* host probe) is merely wasteful when duplicated, not wrong, and stays at
|
|
37
|
+
* module scope — see the class-sweep table in the #2146 PR body for the
|
|
38
|
+
* per-family verdicts.
|
|
39
|
+
*
|
|
40
|
+
* VERSIONING AND THE OLDER-SHAPE FALLBACK. Two builds can meet in one process:
|
|
41
|
+
* a stale `dist/` graph and a fresh source graph, or an extension reload after
|
|
42
|
+
* an upgrade. Each family carries its own `version`. The rule is
|
|
43
|
+
* adopt-if-compatible, reset otherwise:
|
|
44
|
+
*
|
|
45
|
+
* - Same schema and same version -> ADOPT the existing value. This is the
|
|
46
|
+
* common case and the whole point of the module.
|
|
47
|
+
* - Anything else (missing schema, different schema, older version, NEWER
|
|
48
|
+
* version) -> do NOT adopt. A shape this build cannot read is not safely
|
|
49
|
+
* readable in either direction, so guessing is worse than starting clean.
|
|
50
|
+
* The cell is replaced with a fresh value from `create()` and ONE bounded
|
|
51
|
+
* `process-singleton-reset` entry is logged per family, so a nine-evaluation
|
|
52
|
+
* process still reports at most one row per family. Behavior after a reset is
|
|
53
|
+
* exactly today's module-scope behavior for that family, which is the
|
|
54
|
+
* fail-safe direction.
|
|
55
|
+
*
|
|
56
|
+
* The container key itself is versioned (`SINGLETON_HOST_KEY`). A future change
|
|
57
|
+
* to the CONTAINER shape bumps the key, so an old container is simply not
|
|
58
|
+
* found rather than mis-read.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* STATIC IMPORTS: none, deliberately. This module is a dependency leaf.
|
|
62
|
+
*
|
|
63
|
+
* It cannot import the degradation ledger. `instance-registry.ts` imports this
|
|
64
|
+
* module, and the ledger reaches `instance-reaper.ts` (through `extension-log`
|
|
65
|
+
* -> `file-utils` -> `git-tracked-ignore` -> `safe-spawn` -> `resource-sampler`),
|
|
66
|
+
* and `instance-reaper` imports the registry back. Any edge from here to the
|
|
67
|
+
* ledger closes a `no-client-cycles` cycle, which CI's dependency-boundaries
|
|
68
|
+
* lane rejects. A dynamic import does not help: the rule excludes
|
|
69
|
+
* `dynamic-import` only on the edge it evaluates, not on the intermediate edges
|
|
70
|
+
* of the cycle it walks.
|
|
71
|
+
*
|
|
72
|
+
* So the direction is inverted, exactly as `ndjson-logger.ts` already does for
|
|
73
|
+
* its own sink-write failures: this module keeps a bounded reset log, and
|
|
74
|
+
* `degradation-ledger.ts` PULLS it at read time through
|
|
75
|
+
* {@link getProcessSingletonResets} and folds it into `getDegradationSummary()`.
|
|
76
|
+
* The record still reaches `pilens_health`, with no import from here.
|
|
77
|
+
*/
|
|
78
|
+
/** Bump only when the CONTAINER shape changes, never for a family's shape. */
|
|
79
|
+
const SINGLETON_HOST_KEY = Symbol.for("pi-lens.process-singletons.v1");
|
|
80
|
+
const SINGLETON_SCHEMA = "pi-lens.process-singletons";
|
|
81
|
+
/** Degradation kind emitted when an incompatible cell is discarded. */
|
|
82
|
+
export const PROCESS_SINGLETON_RESET_KIND = "process-singleton-reset";
|
|
83
|
+
const singletonHost = globalThis;
|
|
84
|
+
/**
|
|
85
|
+
* The process's one container. Deliberately created on first read rather than
|
|
86
|
+
* at module scope of a single graph: whichever evaluation runs first wins, and
|
|
87
|
+
* every later evaluation adopts it.
|
|
88
|
+
*/
|
|
89
|
+
function container() {
|
|
90
|
+
const existing = singletonHost[SINGLETON_HOST_KEY];
|
|
91
|
+
if (existing instanceof Map)
|
|
92
|
+
return existing;
|
|
93
|
+
const fresh = new Map();
|
|
94
|
+
singletonHost[SINGLETON_HOST_KEY] = fresh;
|
|
95
|
+
return fresh;
|
|
96
|
+
}
|
|
97
|
+
function isAdoptable(cell, version) {
|
|
98
|
+
if (!cell || typeof cell !== "object")
|
|
99
|
+
return false;
|
|
100
|
+
const candidate = cell;
|
|
101
|
+
return (candidate.schema === SINGLETON_SCHEMA &&
|
|
102
|
+
candidate.version === version &&
|
|
103
|
+
candidate.value !== undefined);
|
|
104
|
+
}
|
|
105
|
+
/** One bounded reset log per process, capped so a pathological build pair
|
|
106
|
+
* cannot grow it without limit. */
|
|
107
|
+
export const PROCESS_SINGLETON_RESET_LOG_FAMILY = "process-singleton-reset-log";
|
|
108
|
+
const RESET_LOG_FAMILY = PROCESS_SINGLETON_RESET_LOG_FAMILY;
|
|
109
|
+
const RESET_LOG_VERSION = 1;
|
|
110
|
+
const RESET_LOG_CAP = 16;
|
|
111
|
+
function resetLog() {
|
|
112
|
+
return getProcessSingleton(RESET_LOG_FAMILY, RESET_LOG_VERSION, () => ({
|
|
113
|
+
entries: [],
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Record that an incompatible cell was discarded. Bounded twice: once per
|
|
118
|
+
* family (a nine-evaluation process logs at most one entry per family) and once
|
|
119
|
+
* overall by {@link RESET_LOG_CAP}.
|
|
120
|
+
*/
|
|
121
|
+
function recordIncompatibleCell(family, wantedVersion, found) {
|
|
122
|
+
// The reset log is itself a family, so recording ITS reset would recurse.
|
|
123
|
+
// Dropping that one record is correct: the log it would be written to is the
|
|
124
|
+
// thing being discarded.
|
|
125
|
+
if (family === RESET_LOG_FAMILY)
|
|
126
|
+
return;
|
|
127
|
+
const log = resetLog();
|
|
128
|
+
if (log.entries.some((entry) => entry.family === family))
|
|
129
|
+
return;
|
|
130
|
+
if (log.entries.length >= RESET_LOG_CAP)
|
|
131
|
+
return;
|
|
132
|
+
log.entries.push({
|
|
133
|
+
family,
|
|
134
|
+
reason: `incompatible process singleton discarded (found schema=${String(found.schema)} ` +
|
|
135
|
+
`version=${String(found.version)}, this build wants schema=${SINGLETON_SCHEMA} ` +
|
|
136
|
+
`version=${wantedVersion})`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Read-time view for `degradation-ledger.ts` (see the header). Never performs
|
|
141
|
+
* I/O and never throws, so folding it into a health summary is free.
|
|
142
|
+
*/
|
|
143
|
+
export function getProcessSingletonResets() {
|
|
144
|
+
return resetLog().entries.map((entry) => ({ ...entry }));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Return the process-wide value for `family`, creating it on the first
|
|
148
|
+
* evaluation that asks and adopting it on every later one.
|
|
149
|
+
*
|
|
150
|
+
* `version` describes the SHAPE of the value `create()` builds. Bump it when a
|
|
151
|
+
* build changes that shape, so a graph carrying the old shape is discarded
|
|
152
|
+
* instead of mis-read (see the module docstring's fallback rules).
|
|
153
|
+
*
|
|
154
|
+
* `create()` runs at most once per process per family, unless an incompatible
|
|
155
|
+
* cell forced a reset.
|
|
156
|
+
*
|
|
157
|
+
* `onIncompatible` is for values that own external resources. It runs before
|
|
158
|
+
* the cell is replaced, so the owner can tear those resources down safely.
|
|
159
|
+
*/
|
|
160
|
+
export function getProcessSingleton(family, version, create, onIncompatible) {
|
|
161
|
+
const cells = container();
|
|
162
|
+
const existing = cells.get(family);
|
|
163
|
+
if (isAdoptable(existing, version))
|
|
164
|
+
return existing.value;
|
|
165
|
+
if (existing !== undefined) {
|
|
166
|
+
// An incompatible cell from another build. One bounded row per family:
|
|
167
|
+
// nine evaluations must not write nine records (AGENTS.md's bounded-record
|
|
168
|
+
// rule; `recordDegradationOnce` keys on kind + subject).
|
|
169
|
+
recordIncompatibleCell(family, version, existing);
|
|
170
|
+
onIncompatible?.(existing.value);
|
|
171
|
+
}
|
|
172
|
+
const value = create();
|
|
173
|
+
cells.set(family, { schema: SINGLETON_SCHEMA, version, value });
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Test-only: drop the process-wide container so the next
|
|
178
|
+
* {@link getProcessSingleton} call rebuilds every family from `create()`.
|
|
179
|
+
*
|
|
180
|
+
* This clears the GLOBAL state, not a module-local copy — a reset that only
|
|
181
|
+
* cleared module scope would be the very defect this module fixes, and would
|
|
182
|
+
* make every suite that relies on isolation pass vacuously (catalog shape 7).
|
|
183
|
+
*/
|
|
184
|
+
export function _resetProcessSingletonsForTests() {
|
|
185
|
+
singletonHost[SINGLETON_HOST_KEY] = new Map();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Test-only: install a raw cell so a suite can exercise the older/newer-shape
|
|
189
|
+
* fallback without needing two real builds in one process.
|
|
190
|
+
*/
|
|
191
|
+
export function _seedProcessSingletonCellForTests(family, cell) {
|
|
192
|
+
container().set(family, cell);
|
|
193
|
+
}
|
|
194
|
+
// --- Module-evaluation ordinal (#2146 observability) ---
|
|
195
|
+
const EVALUATION_ORDINAL_FAMILY = "module-evaluation-ordinal";
|
|
196
|
+
const EVALUATION_ORDINAL_VERSION = 1;
|
|
197
|
+
/**
|
|
198
|
+
* How many times this process has evaluated the pi-lens module graph, 1-based.
|
|
199
|
+
*
|
|
200
|
+
* Called once, at module scope of `clients/startup-timing.ts`, so the number it
|
|
201
|
+
* returns is the evaluation count of the graph that contains the extension
|
|
202
|
+
* entry. It is carried on `host_boot.metadata.evaluationOrdinal` so a multi-eval
|
|
203
|
+
* process is greppable from `latency.log` instead of having to be inferred by
|
|
204
|
+
* counting `host_boot` lines per pid — the observability gap #2146 names.
|
|
205
|
+
*/
|
|
206
|
+
export function nextModuleEvaluationOrdinal() {
|
|
207
|
+
const state = getProcessSingleton(EVALUATION_ORDINAL_FAMILY, EVALUATION_ORDINAL_VERSION, () => ({ evaluations: 0 }));
|
|
208
|
+
state.evaluations += 1;
|
|
209
|
+
return state.evaluations;
|
|
210
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The extension runtime's door onto the ONE process-table seam (#2443).
|
|
3
|
+
*
|
|
4
|
+
* `scripts/lib/process-scan.mjs` owns what a process listing IS: the field →
|
|
5
|
+
* platform-column table, the Windows CIM/WQL and POSIX `ps` command
|
|
6
|
+
* composition, the WQL escaping, and the row parser. This module owns how
|
|
7
|
+
* clients/ RUNS one, which is genuinely different from how a script runs one
|
|
8
|
+
* and stays different on purpose:
|
|
9
|
+
*
|
|
10
|
+
* - the child and its stdout pipe are `unref`'d (#1155) so a one-shot
|
|
11
|
+
* `pi --print` can settle without waiting on a CIM query;
|
|
12
|
+
* - a child that blows its timeout is terminated through the caller's own
|
|
13
|
+
* tree-kill-and-verify machinery (#1864 F3), injected as `onTimeout` — a
|
|
14
|
+
* sweep that leaks its own scanner is the defect the sweep exists to fix;
|
|
15
|
+
* - the outcome carries a `SpawnCollectStatus`, so "the table is empty" and
|
|
16
|
+
* "the query never ran" stay distinguishable and the caller can record the
|
|
17
|
+
* degradation (#1857).
|
|
18
|
+
*
|
|
19
|
+
* WHY THE SEAM IS IMPORTED FROM scripts/ RATHER THAN LIVING HERE: the .mjs
|
|
20
|
+
* header argues it in full. Short version — `scripts/prune-agent-worktrees.mjs`
|
|
21
|
+
* is a SessionStart/SubagentStop hook that runs inside freshly created agent
|
|
22
|
+
* worktrees, where `clients/*.js` (gitignored build output) does not exist
|
|
23
|
+
* yet, so the shared half has to be the side that needs no build. This file
|
|
24
|
+
* is the single crossing point; nothing else in clients/ imports scripts/.
|
|
25
|
+
*/
|
|
26
|
+
import { spawnCollectStdoutResult, } from "./child-unref.js";
|
|
27
|
+
import { buildProcessQuery, parseProcessTable, } from "../scripts/lib/process-scan.mjs";
|
|
28
|
+
/**
|
|
29
|
+
* Re-exported, not redefined. `windowsExe` resolves an absolute System32
|
|
30
|
+
* interpreter path for a spawn this process is about to make (a bare
|
|
31
|
+
* `powershell.exe`/`taskkill.exe` is resolvable through a PATH a caller can
|
|
32
|
+
* shadow, and these spawns decide what gets killed) — one definition of that
|
|
33
|
+
* rule is the whole point of the seam.
|
|
34
|
+
*/
|
|
35
|
+
export { windowsExe } from "../scripts/lib/process-scan.mjs";
|
|
36
|
+
/**
|
|
37
|
+
* Run one process-table query on the extension's spawn rails.
|
|
38
|
+
*
|
|
39
|
+
* Never throws and never rejects: an unbuildable query (a Windows-only column
|
|
40
|
+
* asked for on POSIX, an empty filter) resolves as `spawn-error` with no rows,
|
|
41
|
+
* the same shape every other failure takes, because every caller here is
|
|
42
|
+
* best-effort instrumentation or a best-effort sweep.
|
|
43
|
+
*/
|
|
44
|
+
export async function queryProcessTable(request, options) {
|
|
45
|
+
let query;
|
|
46
|
+
try {
|
|
47
|
+
query = buildProcessQuery(request.fields, {
|
|
48
|
+
filter: request.filter,
|
|
49
|
+
excludeSelfPid: request.excludeSelfPid,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { rows: [], status: "spawn-error", serverSideFiltered: false };
|
|
54
|
+
}
|
|
55
|
+
const result = await spawnCollectStdoutResult(query.command, query.args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: options.timeoutMs, onTimeout: options.onTimeout });
|
|
56
|
+
return {
|
|
57
|
+
// Whatever the collector kept is parsed rather than discarded here: it
|
|
58
|
+
// drops stdout on a non-zero exit (that output is not evidence of what
|
|
59
|
+
// is NOT running) but KEEPS the partial table from a timed-out child,
|
|
60
|
+
// and the caller decides what a partial table is worth by reading
|
|
61
|
+
// `status`. Re-deciding that here would be a second policy.
|
|
62
|
+
rows: parseProcessTable(result.stdout, query.tabSeparated, query.fields),
|
|
63
|
+
status: result.status,
|
|
64
|
+
exitCode: result.exitCode,
|
|
65
|
+
timeoutKill: result.timeoutKill,
|
|
66
|
+
serverSideFiltered: query.serverSideFiltered,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -415,9 +415,10 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
|
|
|
415
415
|
// Deliberately never calls `writeCache`: there is nothing fresher to write
|
|
416
416
|
// back, only what turn_end already produced.
|
|
417
417
|
//
|
|
418
|
-
// No double-count / honesty gap (#533): `
|
|
419
|
-
// (`runtime-context.ts`)
|
|
420
|
-
//
|
|
418
|
+
// No double-count / honesty gap (#533): explicit `pilens_turn_end`
|
|
419
|
+
// delivery may call `consumeTestFindings` (`runtime-context.ts`) to
|
|
420
|
+
// read-and-clear this SAME cache key once. Automatic Pi delivery uses a
|
|
421
|
+
// non-context custom entry and never consumes this pull-diagnostics cache.
|
|
421
422
|
// This task only ever reads (never clears) it, so it can't race that
|
|
422
423
|
// consumption into re-delivering a message twice — at most it surfaces
|
|
423
424
|
// the same underlying failures a second time, through a different
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { compareOrdinal } from "../../string-utils.js";
|
|
2
3
|
function resolveCyclePath(cwd, file) {
|
|
3
4
|
return path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
4
5
|
}
|
|
@@ -36,7 +37,7 @@ export function circularDepsToProjectDiagnostics(cwd, circular) {
|
|
|
36
37
|
for (const dep of circular) {
|
|
37
38
|
const key = cycleMembers(dep)
|
|
38
39
|
.map((f) => resolveCyclePath(cwd, f))
|
|
39
|
-
.sort(
|
|
40
|
+
.sort(compareOrdinal)
|
|
40
41
|
.join("|");
|
|
41
42
|
if (seen.has(key))
|
|
42
43
|
continue;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRunnerErrorResult, } from "../../test-runner-client.js";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { advisoryPathKey, validateAdvisoryProvenance, } from "../../advisory-provenance.js";
|
|
@@ -59,6 +60,31 @@ export function testResultToProjectDiagnostics(result, stale = false) {
|
|
|
59
60
|
source: "project-scan",
|
|
60
61
|
}));
|
|
61
62
|
}
|
|
63
|
+
// #2532: a RUNNER error (timeout, missing provider/binary, a config
|
|
64
|
+
// failure — the suite itself never produced a verdict) is not a finding
|
|
65
|
+
// the agent introduced, exactly like the turn-end delivery framing
|
|
66
|
+
// (#2522) already treats it. `isRunnerErrorResult` is the single seam
|
|
67
|
+
// both surfaces read so the identical `TestResult` cannot classify
|
|
68
|
+
// differently here than it does in the turn-end message.
|
|
69
|
+
if (isRunnerErrorResult(result)) {
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
filePath: result.file,
|
|
73
|
+
severity: "info",
|
|
74
|
+
semantic: "none",
|
|
75
|
+
tool: "test-runner",
|
|
76
|
+
runner: result.runner,
|
|
77
|
+
rule: `test:${result.runner}`,
|
|
78
|
+
message: `${stalePrefix}Test run error: ${result.error}`,
|
|
79
|
+
source: "project-scan",
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
// #2532 review S3: a counted failure stays blocking even when `error` is
|
|
84
|
+
// ALSO set (pytest exit 2 "Interrupted" after `2 failed, 1 passed` — see
|
|
85
|
+
// `isRunnerErrorResult`'s doc). The pre-fix message mentioned `error`
|
|
86
|
+
// here via a ternary this PR's first version dropped; restored so the
|
|
87
|
+
// interruption is not silently lost from a still-blocking finding.
|
|
62
88
|
return [
|
|
63
89
|
{
|
|
64
90
|
filePath: result.file,
|
|
@@ -67,9 +93,7 @@ export function testResultToProjectDiagnostics(result, stale = false) {
|
|
|
67
93
|
tool: "test-runner",
|
|
68
94
|
runner: result.runner,
|
|
69
95
|
rule: `test:${result.runner}`,
|
|
70
|
-
message: `${stalePrefix}${result.error
|
|
71
|
-
? `Test run error: ${result.error}`
|
|
72
|
-
: `${result.failed} test(s) failed`}`,
|
|
96
|
+
message: `${stalePrefix}${result.failed} test(s) failed${result.error ? ` (runner also reported: ${result.error})` : ""}`,
|
|
73
97
|
source: "project-scan",
|
|
74
98
|
},
|
|
75
99
|
];
|
|
@@ -5,6 +5,7 @@ import { evaluateRules } from "../dispatch/fact-rule-runner.js";
|
|
|
5
5
|
import { runProviders } from "../dispatch/fact-runner.js";
|
|
6
6
|
import { FactStore } from "../dispatch/fact-store.js";
|
|
7
7
|
import { canHandle as astGrepCanHandle, getLang as astGrepGetLang, evaluateAstGrepRules, loadSg, } from "../dispatch/runners/ast-grep-napi.js";
|
|
8
|
+
import { detectFileKind } from "../file-kinds.js";
|
|
8
9
|
import { isTestFile } from "../file-utils.js";
|
|
9
10
|
import { isAtOrAboveHomeDir } from "../path-utils.js";
|
|
10
11
|
import { getProjectDiagnosticsScannerMaxFiles } from "../project-scale.js";
|
|
@@ -32,27 +33,30 @@ const FACT_RULE_EXTENSIONS = new Set([
|
|
|
32
33
|
".mjs",
|
|
33
34
|
".cjs",
|
|
34
35
|
]);
|
|
35
|
-
// Which languages the scan runs tree-sitter rules for
|
|
36
|
-
//
|
|
36
|
+
// Which languages the scan runs tree-sitter rules for. Identical to the shared
|
|
37
|
+
// per-edit resolver (EXT_TO_LANG), which since #2424 is itself a projection of
|
|
38
|
+
// the canonical language registry — so the scan and the per-edit path cannot
|
|
39
|
+
// disagree about `.tsx`→tsx / `.jsx`→javascript / the c-vs-cpp header split.
|
|
40
|
+
// (`.tsx` resolves to the tsx grammar, not typescript: the typescript grammar
|
|
41
|
+
// ERRORs on JSX; typescript RULES still apply because `queriesForLanguage(...)`
|
|
42
|
+
// below merges the typescript rule set onto tsx.)
|
|
37
43
|
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
+
// java + kotlin used to be layered on here because EXT_TO_LANG lacked them:
|
|
45
|
+
// they have loadable grammars and rule dirs (java: 24 rules, kotlin: 1) but no
|
|
46
|
+
// per-edit runner `appliesTo` entry, so only this broad project scan runs them.
|
|
47
|
+
// The registry now owns their extensions, so the spread is gone and this is a
|
|
48
|
+
// plain alias. scanner.test.ts asserts this map covers every non-disabled rule
|
|
49
|
+
// dir whose grammar is loadable, so adding a language dir fails a test until
|
|
50
|
+
// its extension is registered in the registry.
|
|
44
51
|
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
".kt": "kotlin",
|
|
54
|
-
".kts": "kotlin",
|
|
55
|
-
};
|
|
52
|
+
// This map is the scan's language RESOLVER, not its eligibility gate: it now
|
|
53
|
+
// answers for grammars with no rule dir too (bash/dart/elixir/lua/ocaml/swift/
|
|
54
|
+
// zig), and the pass gates each file on `hasTreeSitterRules` so a grammar
|
|
55
|
+
// without rules costs no read (#2424 review, F2).
|
|
56
|
+
// `Readonly` because the projection is `Object.freeze`d at the source (#2424
|
|
57
|
+
// review, S4) — the mutable `Record` type is what let the pre-#2424 spread
|
|
58
|
+
// layer java/kotlin on by hand in the first place.
|
|
59
|
+
export const TREE_SITTER_EXT_TO_LANG = EXT_TO_LANG;
|
|
56
60
|
function normalizeSeverity(severity) {
|
|
57
61
|
if (severity === "error" || severity === "warning" || severity === "hint") {
|
|
58
62
|
return severity;
|
|
@@ -100,7 +104,9 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
100
104
|
const queryMap = treeSitterReady
|
|
101
105
|
? await queryLoader.loadQueries(cwd)
|
|
102
106
|
: undefined;
|
|
103
|
-
|
|
107
|
+
// Subject labels this store's capacity-eviction telemetry distinctly from
|
|
108
|
+
// the other five production FactStore instances (#2243 review round 3, F1).
|
|
109
|
+
const facts = new FactStore("project-diagnostics-scanner");
|
|
104
110
|
const pi = { getFlag: () => undefined };
|
|
105
111
|
const treeSitter = [];
|
|
106
112
|
const factRules = [];
|
|
@@ -113,6 +119,29 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
113
119
|
// file out, so the caller can report a truncated filesScanned honestly.
|
|
114
120
|
let filesScanned = 0;
|
|
115
121
|
let wasmAborted = isTreeSitterWasmAborted();
|
|
122
|
+
/**
|
|
123
|
+
* Does this project actually have ENABLED tree-sitter rules for `langId`?
|
|
124
|
+
*
|
|
125
|
+
* The gate below used to read "does the extension have a grammar", which was
|
|
126
|
+
* the same question only because the ext -> grammar map hand-listed exactly
|
|
127
|
+
* the twelve grammars that also ship a `rules/tree-sitter-queries/<lang>/`
|
|
128
|
+
* dir. #2424 projected that map from the registry's full grammar column, so
|
|
129
|
+
* bash/dart/elixir/lua/ocaml/swift/zig arrived WITHOUT rules and every such
|
|
130
|
+
* file started being read, walked and counted for a rule set that is empty
|
|
131
|
+
* (#2424 review, F2). Memoized per language: `queriesForLanguage` rebuilds
|
|
132
|
+
* and filters its array on every call, and the scan asks once per file.
|
|
133
|
+
*/
|
|
134
|
+
const ruleCoverage = new Map();
|
|
135
|
+
const hasTreeSitterRules = (langId) => {
|
|
136
|
+
if (!queryMap || !client)
|
|
137
|
+
return false;
|
|
138
|
+
const memo = ruleCoverage.get(langId);
|
|
139
|
+
if (memo !== undefined)
|
|
140
|
+
return memo;
|
|
141
|
+
const covered = queriesForLanguage(queryMap, langId).length > 0;
|
|
142
|
+
ruleCoverage.set(langId, covered);
|
|
143
|
+
return covered;
|
|
144
|
+
};
|
|
116
145
|
const scanTreeSitterFile = async (filePath, langId, content) => {
|
|
117
146
|
if (!queryMap || !langId || !client || content === null)
|
|
118
147
|
return;
|
|
@@ -141,7 +170,7 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
141
170
|
}
|
|
142
171
|
};
|
|
143
172
|
const scanFactRulesFile = async (filePath, content) => {
|
|
144
|
-
facts.
|
|
173
|
+
facts.dropFileFacts(filePath);
|
|
145
174
|
// Seed the exact bytes already supplied to tree-sitter so the file-content
|
|
146
175
|
// provider is skipped by runProviders.
|
|
147
176
|
facts.setFileFact(filePath, "file.content", content);
|
|
@@ -160,9 +189,11 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
160
189
|
const startedAt = Date.now();
|
|
161
190
|
try {
|
|
162
191
|
const rootNode = lang.parse(content).root();
|
|
163
|
-
const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd,
|
|
192
|
+
const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd, detectFileKind(filePath), {
|
|
164
193
|
maxMatchesPerRule: AST_GREP_SCAN_MAX_MATCHES_PER_RULE,
|
|
165
194
|
maxTotalDiagnostics: AST_GREP_SCAN_MAX_DIAGNOSTICS_PER_FILE,
|
|
195
|
+
content,
|
|
196
|
+
sgModule,
|
|
166
197
|
});
|
|
167
198
|
for (const diagnostic of fileDiagnostics) {
|
|
168
199
|
astGrep.push(fromDispatchDiagnostic(diagnostic, "ast-grep-napi"));
|
|
@@ -187,7 +218,10 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
187
218
|
if (isTestFile(filePath))
|
|
188
219
|
continue;
|
|
189
220
|
const ext = path.extname(filePath);
|
|
190
|
-
|
|
221
|
+
// A grammar is not a consumer: only a language this project has
|
|
222
|
+
// ENABLED rules for can turn this file's bytes into a finding.
|
|
223
|
+
const grammarId = TREE_SITTER_EXT_TO_LANG[ext];
|
|
224
|
+
const langId = grammarId && hasTreeSitterRules(grammarId) ? grammarId : undefined;
|
|
191
225
|
const factEligible = FACT_RULE_EXTENSIONS.has(ext);
|
|
192
226
|
let astGrepLang;
|
|
193
227
|
if (sgModule && astGrepCanHandle(filePath)) {
|
|
@@ -264,7 +298,10 @@ async function scanFileMajorRules(cwd, files, signal) {
|
|
|
264
298
|
finally {
|
|
265
299
|
// This store belongs to the scan, and every consumer of this file's
|
|
266
300
|
// content and derived facts has completed by the end of the iteration.
|
|
267
|
-
|
|
301
|
+
// Drop without pinning: the scan is sequential and owns this store, so
|
|
302
|
+
// there is no concurrent walk to guard against, and pinning every
|
|
303
|
+
// scanned file would exempt it from the capacity cap (#2243).
|
|
304
|
+
facts.dropFileFacts(filePath);
|
|
268
305
|
}
|
|
269
306
|
}
|
|
270
307
|
if (wasmAborted) {
|