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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic test-runner delivery for the post-agent idle window (#2366).
|
|
3
|
+
*
|
|
4
|
+
* Test results remain in `test-runner-findings` for pull diagnostics and the
|
|
5
|
+
* commit guard. This module only stages an owner-qualified pointer and
|
|
6
|
+
* appends a non-context custom entry after the host proves that the agent is
|
|
7
|
+
* idle. Provenance validation stays in `peekTestFindings`, the one shared
|
|
8
|
+
* delivery gate for this cache.
|
|
9
|
+
*/
|
|
10
|
+
import { emitBounded } from "./bounded-telemetry.js";
|
|
11
|
+
import { peekTestFindings } from "./runtime-context.js";
|
|
12
|
+
import { fitLines } from "./tui-fit.js";
|
|
13
|
+
export const TEST_RUNNER_ENTRY_TYPE = "pilens:test-runner-findings";
|
|
14
|
+
const MAX_PENDING_DELIVERIES = 32;
|
|
15
|
+
const MAX_ENTRY_CONTENT = 12_000;
|
|
16
|
+
const pending = new Map();
|
|
17
|
+
function key(cwd, sessionId, ownerId) {
|
|
18
|
+
// `cwd` comes from the same host session field at turn_end and settle. Keep
|
|
19
|
+
// that canonical value unchanged so cache reads use the identical workspace
|
|
20
|
+
// identity; dispatch contexts already normalize their own path fields.
|
|
21
|
+
return `${ownerId ?? "direct"}\u0000${cwd}\u0000${sessionId}`;
|
|
22
|
+
}
|
|
23
|
+
function boundedContent(content) {
|
|
24
|
+
if (content.length <= MAX_ENTRY_CONTENT)
|
|
25
|
+
return { content, droppedDetailCount: 0 };
|
|
26
|
+
return {
|
|
27
|
+
content: `${content.slice(0, MAX_ENTRY_CONTENT)}\n[…details truncated]`,
|
|
28
|
+
droppedDetailCount: content.length - MAX_ENTRY_CONTENT,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function record(identity, outcome, delivery, metadata = {}) {
|
|
32
|
+
emitBounded("test_runner_delivery", identity, {
|
|
33
|
+
filePath: delivery.cwd,
|
|
34
|
+
durationMs: 0,
|
|
35
|
+
metadata: {
|
|
36
|
+
outcome,
|
|
37
|
+
sessionId: delivery.sessionId,
|
|
38
|
+
generation: delivery.generation,
|
|
39
|
+
targetCount: delivery.targetCount,
|
|
40
|
+
deliveryBoundary: "agent_settled",
|
|
41
|
+
ownership: "pi",
|
|
42
|
+
droppedDetailCount: 0,
|
|
43
|
+
...metadata,
|
|
44
|
+
},
|
|
45
|
+
}, {
|
|
46
|
+
capPerTurn: { limit: 8, turnIndex: delivery.generation },
|
|
47
|
+
ledgerKind: outcome === "delivery-failed" || outcome === "capability-unavailable"
|
|
48
|
+
? "test-runner-delivery"
|
|
49
|
+
: undefined,
|
|
50
|
+
reason: `test runner delivery ${outcome}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function prunePending() {
|
|
54
|
+
while (pending.size > MAX_PENDING_DELIVERIES) {
|
|
55
|
+
const oldest = pending.keys().next();
|
|
56
|
+
if (oldest.done)
|
|
57
|
+
return;
|
|
58
|
+
pending.delete(oldest.value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Stage the newest result for this session. Clean results supersede failures. */
|
|
62
|
+
export function stageTestRunnerDelivery(args) {
|
|
63
|
+
const deliveryKey = key(args.cwd, args.sessionId, args.owner?.ownerId);
|
|
64
|
+
const prior = pending.get(deliveryKey);
|
|
65
|
+
if (prior && prior.generation > args.generation) {
|
|
66
|
+
const superseded = {
|
|
67
|
+
cwd: args.cwd,
|
|
68
|
+
sessionId: args.sessionId,
|
|
69
|
+
ownerId: args.owner?.ownerId,
|
|
70
|
+
generation: args.generation,
|
|
71
|
+
targetCount: args.targetCount,
|
|
72
|
+
createdAt: Date.now(),
|
|
73
|
+
owner: args.owner,
|
|
74
|
+
};
|
|
75
|
+
record(deliveryKey, "superseded", superseded, {
|
|
76
|
+
currentGeneration: prior.generation,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!args.hasFindings) {
|
|
81
|
+
if (prior) {
|
|
82
|
+
pending.delete(deliveryKey);
|
|
83
|
+
record(deliveryKey, "superseded", {
|
|
84
|
+
...prior,
|
|
85
|
+
targetCount: args.targetCount,
|
|
86
|
+
generation: args.generation,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const delivery = {
|
|
92
|
+
cwd: args.cwd,
|
|
93
|
+
sessionId: args.sessionId,
|
|
94
|
+
ownerId: args.owner?.ownerId,
|
|
95
|
+
generation: args.generation,
|
|
96
|
+
targetCount: args.targetCount,
|
|
97
|
+
createdAt: Date.now(),
|
|
98
|
+
owner: args.owner,
|
|
99
|
+
};
|
|
100
|
+
pending.set(deliveryKey, delivery);
|
|
101
|
+
prunePending();
|
|
102
|
+
record(deliveryKey, "staged", delivery);
|
|
103
|
+
}
|
|
104
|
+
/** Deliver the latest staged result during a host-confirmed idle window. */
|
|
105
|
+
export function deliverTestRunnerFindings(args) {
|
|
106
|
+
const cwd = args.ctx.cwd ?? process.cwd();
|
|
107
|
+
const deliveryKey = key(cwd, args.sessionId, args.ownerId);
|
|
108
|
+
const delivery = pending.get(deliveryKey);
|
|
109
|
+
if (!delivery)
|
|
110
|
+
return;
|
|
111
|
+
const currentGeneration = args.cacheManager.readCache("test-runner-findings", cwd)?.data?.testRunGeneration;
|
|
112
|
+
if (currentGeneration !== undefined &&
|
|
113
|
+
currentGeneration > delivery.generation) {
|
|
114
|
+
pending.delete(deliveryKey);
|
|
115
|
+
record(deliveryKey, "superseded", delivery, {
|
|
116
|
+
currentGeneration,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (typeof args.ctx.isIdle !== "function") {
|
|
121
|
+
pending.delete(deliveryKey);
|
|
122
|
+
record(deliveryKey, "capability-unavailable", delivery, {
|
|
123
|
+
reason: "host does not expose ctx.isIdle",
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
if (!args.ctx.isIdle()) {
|
|
129
|
+
record(deliveryKey, "carried", delivery);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
record(deliveryKey, "delivery-failed", delivery, {
|
|
135
|
+
error: String(error).slice(0, 500),
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const findings = peekTestFindings(args.cacheManager, cwd, args.runtime, true);
|
|
140
|
+
if (!findings) {
|
|
141
|
+
pending.delete(deliveryKey);
|
|
142
|
+
record(deliveryKey, "superseded", delivery);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Recheck immediately before append. The host may accept a prompt between
|
|
146
|
+
// the first check and this synchronous call.
|
|
147
|
+
try {
|
|
148
|
+
if (!args.ctx.isIdle()) {
|
|
149
|
+
record(deliveryKey, "carried", delivery);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
record(deliveryKey, "delivery-failed", delivery, {
|
|
155
|
+
error: String(error).slice(0, 500),
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// SAFETY: appendEntry is an optional host capability absent from older Pi SDKs.
|
|
160
|
+
const appendEntry = args.pi.appendEntry;
|
|
161
|
+
if (typeof appendEntry !== "function") {
|
|
162
|
+
pending.delete(deliveryKey);
|
|
163
|
+
record(deliveryKey, "capability-unavailable", delivery);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const bounded = boundedContent(findings.messages[0]?.content ?? "");
|
|
167
|
+
try {
|
|
168
|
+
appendEntry.call(args.pi, TEST_RUNNER_ENTRY_TYPE, {
|
|
169
|
+
...bounded,
|
|
170
|
+
sessionId: delivery.sessionId,
|
|
171
|
+
generation: delivery.generation,
|
|
172
|
+
targetCount: delivery.targetCount,
|
|
173
|
+
});
|
|
174
|
+
pending.delete(deliveryKey);
|
|
175
|
+
record(deliveryKey, "delivered", delivery, {
|
|
176
|
+
droppedDetailCount: bounded.droppedDetailCount,
|
|
177
|
+
ageMs: Math.max(0, Date.now() - delivery.createdAt),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
record(deliveryKey, "delivery-failed", delivery, {
|
|
182
|
+
error: String(error).slice(0, 500),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Deliver only the result staged by this activation and settled session. */
|
|
187
|
+
export function deliverStagedTestRunnerFindings(args) {
|
|
188
|
+
if (!args?.cwd || !args.ownerId)
|
|
189
|
+
return;
|
|
190
|
+
const delivery = args.sessionId
|
|
191
|
+
? pending.get(key(args.cwd, args.sessionId, args.ownerId))
|
|
192
|
+
: [...pending.values()]
|
|
193
|
+
.reverse()
|
|
194
|
+
.find((candidate) => candidate.cwd === args.cwd && candidate.ownerId === args.ownerId);
|
|
195
|
+
if (!delivery?.owner)
|
|
196
|
+
return;
|
|
197
|
+
deliverTestRunnerFindings({
|
|
198
|
+
pi: delivery.owner.pi,
|
|
199
|
+
ctx: delivery.owner.getCtx(),
|
|
200
|
+
cacheManager: delivery.owner.cacheManager,
|
|
201
|
+
runtime: delivery.owner.runtime,
|
|
202
|
+
sessionId: delivery.sessionId,
|
|
203
|
+
ownerId: delivery.ownerId,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
export function registerTestRunnerEntryRenderer(pi) {
|
|
207
|
+
// SAFETY: registerEntryRenderer is an optional host capability absent from older Pi SDKs.
|
|
208
|
+
const register = pi.registerEntryRenderer;
|
|
209
|
+
if (typeof register !== "function")
|
|
210
|
+
return false;
|
|
211
|
+
try {
|
|
212
|
+
register.call(pi, TEST_RUNNER_ENTRY_TYPE, renderTestRunnerEntry);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function renderTestRunnerEntry(entry, _options, theme) {
|
|
220
|
+
const content = entry.data?.content;
|
|
221
|
+
if (typeof content !== "string")
|
|
222
|
+
return undefined;
|
|
223
|
+
const lines = [
|
|
224
|
+
theme.fg("error", "pi-lens test failures"),
|
|
225
|
+
...content.split("\n"),
|
|
226
|
+
];
|
|
227
|
+
return {
|
|
228
|
+
render: (width) => fitLines(lines, width),
|
|
229
|
+
invalidate: () => { },
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Test-only reset; session ownership prevents cross-session consumption. */
|
|
233
|
+
export function _resetTestRunnerDeliveryForTests() {
|
|
234
|
+
pending.clear();
|
|
235
|
+
}
|
|
236
|
+
/** Clear staged results when a primary session replaces the owning runtime. */
|
|
237
|
+
export function resetTestRunnerDelivery() {
|
|
238
|
+
pending.clear();
|
|
239
|
+
}
|
|
@@ -1,7 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE final registration boundary for a host-facing tool: everything that
|
|
3
|
+
* must be true of a tool definition the instant before `pi.registerTool`
|
|
4
|
+
* sees it. `index.ts` runs every tool it registers — always-active,
|
|
5
|
+
* situational, and the compact-line-wrapped variants — through this one
|
|
6
|
+
* function, so a property added here reaches every tool without a second
|
|
7
|
+
* call site to keep in sync.
|
|
8
|
+
*
|
|
9
|
+
* Two jobs today:
|
|
10
|
+
* 1. metadata the host requires is present (a non-empty description);
|
|
11
|
+
* 2. an in-flight tool call HOLDS the event loop (#2507). pi-lens unrefs the
|
|
12
|
+
* LSP child, its stdio pipes and several waiting timers so an idle
|
|
13
|
+
* one-shot process can exit; in a headless child with no other referenced
|
|
14
|
+
* handle that let Node exit 0 in the middle of `lsp_diagnostics`. Every
|
|
15
|
+
* tool call therefore takes a counted hold for its own duration — see
|
|
16
|
+
* `clients/event-loop-hold.ts` for why the hold lives there and not in
|
|
17
|
+
* the unref itself.
|
|
18
|
+
*/
|
|
19
|
+
import { acquireEventLoopHold } from "./event-loop-hold.js";
|
|
1
20
|
export function normalizeToolDefinition(tool) {
|
|
2
21
|
const name = typeof tool.name === "string" ? tool.name.trim() : "tool";
|
|
3
22
|
const description = typeof tool.description === "string" && tool.description.trim().length > 0
|
|
4
23
|
? tool.description
|
|
5
24
|
: `Use the ${name} tool.`;
|
|
6
|
-
|
|
25
|
+
const execute = tool.execute;
|
|
26
|
+
if (typeof execute !== "function") {
|
|
27
|
+
return { ...tool, description };
|
|
28
|
+
}
|
|
29
|
+
const inner = execute;
|
|
30
|
+
return {
|
|
31
|
+
...tool,
|
|
32
|
+
description,
|
|
33
|
+
// A plain function, not an arrow: `this` stays whatever the host called
|
|
34
|
+
// the tool with, so a definition whose `execute` reads `this` keeps
|
|
35
|
+
// working. The hold is released in `finally`, so a rejected or aborted
|
|
36
|
+
// call releases exactly like a resolved one.
|
|
37
|
+
execute: async function (...args) {
|
|
38
|
+
const release = acquireEventLoopHold(name);
|
|
39
|
+
try {
|
|
40
|
+
return await inner.apply(this, args);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
release();
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
7
47
|
}
|
|
@@ -176,7 +176,10 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
|
|
|
176
176
|
{
|
|
177
177
|
formatterNames: ["prettier"],
|
|
178
178
|
defaultFormatter: "prettier",
|
|
179
|
-
|
|
179
|
+
// HTML commonly carries template markers ({{JS}} embeds, Handlebars,
|
|
180
|
+
// Helm-adjacent Go templates). Prettier reinterprets them as code and
|
|
181
|
+
// corrupts them. Opt in via project prettier config; do not run by default.
|
|
182
|
+
defaultWhenUnconfigured: false,
|
|
180
183
|
gate: "smart-default",
|
|
181
184
|
},
|
|
182
185
|
],
|
|
@@ -185,7 +188,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
|
|
|
185
188
|
{
|
|
186
189
|
formatterNames: ["prettier"],
|
|
187
190
|
defaultFormatter: "prettier",
|
|
188
|
-
defaultWhenUnconfigured:
|
|
191
|
+
defaultWhenUnconfigured: false,
|
|
189
192
|
gate: "smart-default",
|
|
190
193
|
},
|
|
191
194
|
],
|
|
@@ -194,7 +197,10 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
|
|
|
194
197
|
{
|
|
195
198
|
formatterNames: ["prettier"],
|
|
196
199
|
defaultFormatter: "prettier",
|
|
197
|
-
|
|
200
|
+
// YAML commonly carries template markers (Helm `{{ .Values.x }}`,
|
|
201
|
+
// Ansible `{{ var }}`). Prettier splits `{{` into `{ {` and reindents
|
|
202
|
+
// embedded templates. Opt in via project prettier config.
|
|
203
|
+
defaultWhenUnconfigured: false,
|
|
198
204
|
gate: "smart-default",
|
|
199
205
|
},
|
|
200
206
|
],
|
|
@@ -203,7 +209,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
|
|
|
203
209
|
{
|
|
204
210
|
formatterNames: ["prettier"],
|
|
205
211
|
defaultFormatter: "prettier",
|
|
206
|
-
defaultWhenUnconfigured:
|
|
212
|
+
defaultWhenUnconfigured: false,
|
|
207
213
|
gate: "smart-default",
|
|
208
214
|
},
|
|
209
215
|
],
|
|
@@ -984,7 +990,7 @@ const TOOL_COMMAND_SPECS = new Map([
|
|
|
984
990
|
{
|
|
985
991
|
command: "markdownlint-cli2",
|
|
986
992
|
windowsExt: ".cmd",
|
|
987
|
-
versionArgs: ["--
|
|
993
|
+
versionArgs: ["--no-globs", "-"],
|
|
988
994
|
managedToolId: "markdownlint",
|
|
989
995
|
},
|
|
990
996
|
],
|
|
@@ -2236,8 +2242,14 @@ export function _getSpotlessGradleReadCountForTests() {
|
|
|
2236
2242
|
* executable Gradle source. This is deliberately a small lexical pre-pass,
|
|
2237
2243
|
* not a Groovy/Kotlin parser. In particular, statically disabled constructs
|
|
2238
2244
|
* such as `if (false) { ktlint() }` remain a documented false-positive.
|
|
2245
|
+
*
|
|
2246
|
+
* Exported (with `gradleBlockRanges` below) so `clients/gradle-ktfmt-
|
|
2247
|
+
* style.ts` (#2468) reuses this exact pre-pass for the `ktfmt { }` extension
|
|
2248
|
+
* block instead of a second hand-rolled Gradle lexer — the same
|
|
2249
|
+
* single-source-of-truth reuse `clients/cargo-manifest.ts` did for TOML
|
|
2250
|
+
* parsing (#2466).
|
|
2239
2251
|
*/
|
|
2240
|
-
function stripGradleCommentsAndStrings(source) {
|
|
2252
|
+
export function stripGradleCommentsAndStrings(source) {
|
|
2241
2253
|
let result = "";
|
|
2242
2254
|
let state = "code";
|
|
2243
2255
|
let quote = "";
|
|
@@ -2303,24 +2315,82 @@ function stripGradleCommentsAndStrings(source) {
|
|
|
2303
2315
|
}
|
|
2304
2316
|
return result;
|
|
2305
2317
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2318
|
+
const GRADLE_IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
|
|
2319
|
+
/**
|
|
2320
|
+
* The identifier immediately preceding a `{`, or `""`. Mirrors the
|
|
2321
|
+
* `\b<name>\s*\{` shape the named lookup used before it became a filter over
|
|
2322
|
+
* this scanner: whitespace (including newlines) between the name and the
|
|
2323
|
+
* brace is skipped, and `.` ends the identifier so `com.ncorti.ktfmt { }`
|
|
2324
|
+
* still reads as `ktfmt` while `something_ktfmt { }` does not.
|
|
2325
|
+
*/
|
|
2326
|
+
function gradleBlockNameBefore(source, open) {
|
|
2327
|
+
let index = open - 1;
|
|
2328
|
+
while (index >= 0 && /\s/.test(source[index]))
|
|
2329
|
+
index -= 1;
|
|
2330
|
+
const end = index + 1;
|
|
2331
|
+
while (index >= 0 && GRADLE_IDENTIFIER_CHAR.test(source[index]))
|
|
2332
|
+
index -= 1;
|
|
2333
|
+
return source.slice(index + 1, end);
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Body ranges of EVERY brace pair in `source`, in source order, each labelled
|
|
2337
|
+
* with the identifier that introduced it.
|
|
2338
|
+
*
|
|
2339
|
+
* This is the repo's only Gradle brace scanner: `namedGradleBlockRanges` and
|
|
2340
|
+
* `namedGradleBlockBodies` are filters over it. Scanning every pair — rather
|
|
2341
|
+
* than only the pairs whose name a caller thought to ask about — is what lets
|
|
2342
|
+
* `clients/gradle-ktfmt-style.ts` (#2468 review round 3) answer "what block
|
|
2343
|
+
* ENCLOSES this one?" instead of only "is it inside the one name I checked?".
|
|
2344
|
+
* The distinction is load-bearing there: `configure(subprojects.filter { … })
|
|
2345
|
+
* { ktfmt { } }`, `project(":app") { ktfmt { } }` and a convention-plugin
|
|
2346
|
+
* wrapper all have to be recognized as *some* enclosing scope the resolver
|
|
2347
|
+
* cannot evaluate, so it can fail closed rather than treat the block as
|
|
2348
|
+
* top-level.
|
|
2349
|
+
*
|
|
2350
|
+
* Unbalanced braces are best-effort: an unmatched `}` is ignored and an
|
|
2351
|
+
* unclosed `{` yields no range, exactly as the previous per-name depth scan
|
|
2352
|
+
* behaved.
|
|
2353
|
+
*/
|
|
2354
|
+
export function gradleBlockRanges(source) {
|
|
2355
|
+
const ranges = [];
|
|
2356
|
+
const opens = [];
|
|
2357
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
2358
|
+
const char = source[index];
|
|
2359
|
+
if (char === "{") {
|
|
2360
|
+
opens.push(index);
|
|
2361
|
+
continue;
|
|
2321
2362
|
}
|
|
2363
|
+
if (char !== "}")
|
|
2364
|
+
continue;
|
|
2365
|
+
const open = opens.pop();
|
|
2366
|
+
if (open === undefined)
|
|
2367
|
+
continue;
|
|
2368
|
+
ranges.push({
|
|
2369
|
+
name: gradleBlockNameBefore(source, open),
|
|
2370
|
+
start: open + 1,
|
|
2371
|
+
end: index,
|
|
2372
|
+
});
|
|
2322
2373
|
}
|
|
2323
|
-
|
|
2374
|
+
// Pairs are completed innermost-first; callers want source order.
|
|
2375
|
+
return ranges.sort((a, b) => a.start - b.start);
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Body ranges of every `<name> { ... }` block, in source order.
|
|
2379
|
+
*
|
|
2380
|
+
* The offsets exist so a caller can ask where a block sits RELATIVE to
|
|
2381
|
+
* another one — `clients/gradle-ktfmt-style.ts` (#2468) needs to know which
|
|
2382
|
+
* block encloses a `ktfmt { }` one, because Gradle's `subprojects { }`
|
|
2383
|
+
* applies it to the children and NOT to the project declaring it, while
|
|
2384
|
+
* `allprojects { }` applies to both. Containment of two ranges is the only
|
|
2385
|
+
* reliable way to answer that; matching body TEXT would collide whenever two
|
|
2386
|
+
* blocks happen to have identical bodies. `namedGradleBlockBodies` stays the
|
|
2387
|
+
* ergonomic form for callers that only need the text.
|
|
2388
|
+
*/
|
|
2389
|
+
function namedGradleBlockRanges(source, name) {
|
|
2390
|
+
return gradleBlockRanges(source).filter((range) => range.name === name);
|
|
2391
|
+
}
|
|
2392
|
+
function namedGradleBlockBodies(source, name) {
|
|
2393
|
+
return namedGradleBlockRanges(source, name).map((range) => source.slice(range.start, range.end));
|
|
2324
2394
|
}
|
|
2325
2395
|
/**
|
|
2326
2396
|
* Resolve the formatter elected by a Spotless `kotlin { ... }` block.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
|
|
15
15
|
import * as crypto from "node:crypto";
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
|
+
import { BoundedFifoMap } from "./bounded-cache.js";
|
|
17
18
|
import { normalizeFilePath } from "./path-utils.js";
|
|
18
19
|
const TREE_RETIREMENT_GRACE_MICROTASKS = 4;
|
|
19
20
|
export const TREE_CACHE_COUNTER_KEYS = [
|
|
@@ -82,17 +83,23 @@ export function deriveScanTreeCacheCapacity(fileCount, currentMaxSize) {
|
|
|
82
83
|
return Math.max(currentMaxSize, target);
|
|
83
84
|
}
|
|
84
85
|
export class TreeCache {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// BoundedFifoMap, not BoundedLruCache, even though eviction here IS true
|
|
87
|
+
// LRU: recency is refreshed by this class's own explicit `delete`+`set`
|
|
88
|
+
// touch on a VALIDATED hit (see `get`), and only there. A `get` that
|
|
89
|
+
// promoted on every read would also promote entries `get` goes on to
|
|
90
|
+
// reject (content-hash mismatch returns null without removing), silently
|
|
91
|
+
// changing which entry eviction targets (#2442 review F7).
|
|
92
|
+
cache = new BoundedFifoMap(TREE_CACHE_DEFAULT_MAX_SIZE);
|
|
93
|
+
recentlyEvicted;
|
|
88
94
|
evictionHistoryMax;
|
|
89
95
|
debug;
|
|
90
96
|
counters = createTreeCacheCounters();
|
|
91
97
|
counterObserver;
|
|
92
98
|
treeErrorObserver;
|
|
93
99
|
constructor(maxSize = TREE_CACHE_DEFAULT_MAX_SIZE, debug = false, evictionHistoryMax = 4096, counterObserver, treeErrorObserver) {
|
|
94
|
-
this.
|
|
100
|
+
this.cache.setMaxEntries(Math.max(1, Math.floor(maxSize)));
|
|
95
101
|
this.evictionHistoryMax = Math.max(1, Math.floor(evictionHistoryMax));
|
|
102
|
+
this.recentlyEvicted = new BoundedFifoMap(this.evictionHistoryMax);
|
|
96
103
|
this.counterObserver = counterObserver;
|
|
97
104
|
this.treeErrorObserver = treeErrorObserver;
|
|
98
105
|
this.debug = debug
|
|
@@ -109,7 +116,7 @@ export class TreeCache {
|
|
|
109
116
|
}
|
|
110
117
|
/** Current capacity ceiling (entry count, not bytes — see class docstring). */
|
|
111
118
|
getMaxSize() {
|
|
112
|
-
return this.
|
|
119
|
+
return this.cache.getMaxEntries();
|
|
113
120
|
}
|
|
114
121
|
/**
|
|
115
122
|
* Resize the capacity ceiling (#1715). Growing only changes the bound —
|
|
@@ -120,17 +127,24 @@ export class TreeCache {
|
|
|
120
127
|
* entries can never record a hit.
|
|
121
128
|
*/
|
|
122
129
|
setMaxSize(n) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
this.retireEvicted(this.cache.setMaxEntries(Math.max(1, Math.floor(n))));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Book-keep entries the bounded map dropped: ghost history, the eviction
|
|
134
|
+
* counter, and the WASM-heap tree retirement (#417/#890) that made this
|
|
135
|
+
* cache's eviction side-effect-coupled in the first place. One
|
|
136
|
+
* implementation, shared by the two paths that can overflow
|
|
137
|
+
* ({@link setMaxSize} and {@link set}) — the shape `BoundedFifoMap.set`'s
|
|
138
|
+
* `[key, value]` return exists to enable (#2442 review F7).
|
|
139
|
+
*/
|
|
140
|
+
retireEvicted(evicted) {
|
|
141
|
+
for (const [key, cached] of evicted) {
|
|
142
|
+
this.rememberEviction(key, cached);
|
|
132
143
|
this.recordCounter("evictions");
|
|
133
|
-
|
|
144
|
+
// The bounded map already removed the entry, so retire the tree
|
|
145
|
+
// directly rather than through removeEntry's re-lookup.
|
|
146
|
+
this.retireTree(cached.tree);
|
|
147
|
+
this.debug(`Evicted: ${key}`);
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
/**
|
|
@@ -175,14 +189,9 @@ export class TreeCache {
|
|
|
175
189
|
}
|
|
176
190
|
rememberEviction(key, cached) {
|
|
177
191
|
this.recentlyEvicted.delete(key);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.recentlyEvicted.delete(oldestKey);
|
|
182
|
-
this.recordCounter("ghostHistoryDrops");
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
this.recentlyEvicted.set(key, cached.contentHash);
|
|
192
|
+
const dropped = this.recentlyEvicted.set(key, cached.contentHash);
|
|
193
|
+
if (dropped.length > 0)
|
|
194
|
+
this.recordCounter("ghostHistoryDrops");
|
|
186
195
|
}
|
|
187
196
|
/**
|
|
188
197
|
* Generate hash for file content
|
|
@@ -298,20 +307,11 @@ export class TreeCache {
|
|
|
298
307
|
this.recordCounter("replacements");
|
|
299
308
|
this.removeEntry(key);
|
|
300
309
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (firstKey) {
|
|
307
|
-
const evicted = this.cache.get(firstKey);
|
|
308
|
-
if (evicted)
|
|
309
|
-
this.rememberEviction(firstKey, evicted);
|
|
310
|
-
this.recordCounter("evictions");
|
|
311
|
-
this.removeEntry(firstKey);
|
|
312
|
-
this.debug(`Evicted: ${firstKey}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
310
|
+
// Overflow eviction itself now happens on the `this.cache.set` below —
|
|
311
|
+
// the bounded map evicts the least-recently-used entry (this class's
|
|
312
|
+
// explicit delete+set touch on a validated hit makes insertion order
|
|
313
|
+
// recency order, #890) and hands back the dropped [key, tree] pair for
|
|
314
|
+
// `retireEvicted` to free.
|
|
315
315
|
let mtime = 0;
|
|
316
316
|
try {
|
|
317
317
|
mtime = fs.statSync(filePath).mtimeMs;
|
|
@@ -320,14 +320,14 @@ export class TreeCache {
|
|
|
320
320
|
// File deleted between parse and cache — cache with mtime=0;
|
|
321
321
|
// next get() will miss on mtime check and re-parse
|
|
322
322
|
}
|
|
323
|
-
this.cache.set(key, {
|
|
323
|
+
this.retireEvicted(this.cache.set(key, {
|
|
324
324
|
tree,
|
|
325
325
|
contentHash,
|
|
326
326
|
languageId,
|
|
327
327
|
fileSize: Buffer.byteLength(content, "utf8"),
|
|
328
328
|
lineCount: content.split("\n").length,
|
|
329
329
|
lastModified: mtime,
|
|
330
|
-
});
|
|
330
|
+
}));
|
|
331
331
|
this.debug(`Cached: ${filePath} (${content.split("\n").length} lines)`);
|
|
332
332
|
}
|
|
333
333
|
/**
|
|
@@ -355,7 +355,7 @@ export class TreeCache {
|
|
|
355
355
|
return {
|
|
356
356
|
...this.counters,
|
|
357
357
|
size: this.cache.size,
|
|
358
|
-
maxSize: this.
|
|
358
|
+
maxSize: this.cache.getMaxEntries(),
|
|
359
359
|
totalLines,
|
|
360
360
|
totalBytes,
|
|
361
361
|
misses: this.counters.lookups - this.counters.hits,
|