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
|
@@ -17,26 +17,33 @@ import { logExtension } from "../extension-log.js";
|
|
|
17
17
|
import * as fs from "node:fs";
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { recordRunner } from "../widget-state.js";
|
|
20
|
+
import { incrementDegradationCount, recordDegradationOnce, } from "../degradation-ledger.js";
|
|
20
21
|
import { detectFileKind } from "../file-kinds.js";
|
|
21
22
|
import { detectFileRole } from "../file-role.js";
|
|
23
|
+
import { classifyGeneratedOrArtifactDetailed, } from "../generated-artifacts.js";
|
|
22
24
|
import { isTestFile } from "../file-utils.js";
|
|
23
25
|
import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
24
26
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
25
27
|
import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
|
|
26
28
|
import { isSpawnableCommand } from "../installer/index.js";
|
|
27
|
-
import { normalizeMapKey } from "../path-utils.js";
|
|
29
|
+
import { isAbsoluteFilePath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
|
|
28
30
|
import { loadPiLensProjectConfig } from "../project-lens-config.js";
|
|
29
31
|
import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
|
|
30
32
|
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
31
33
|
import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
|
|
32
34
|
import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./runners/utils/availability-policy.js";
|
|
35
|
+
import { recordAvailabilityProbeOverrun, getDispatchAvailabilityGeneration, } from "./runners/utils/runner-helpers.js";
|
|
36
|
+
import { createAvailabilityProbeFlight } from "../availability-probe-flight.js";
|
|
33
37
|
import { applyDispositions } from "../diagnostic-dispositions.js";
|
|
34
38
|
import { applyInlineSuppressions } from "./inline-suppressions.js";
|
|
35
39
|
import { getToolPlan } from "./plan.js";
|
|
36
40
|
import { resolveRunnerPath } from "./runner-context.js";
|
|
41
|
+
import { classifyObservedRunner, COLLECT_LATER_THRESHOLD_MS, observeRunnerLatency, } from "./collect-later-tier.js";
|
|
42
|
+
import { deferRunnerFindings } from "./pending-runner-findings.js";
|
|
37
43
|
import { applyRulePolicy, rulePolicyMapFromConfig } from "./rule-policy.js";
|
|
38
44
|
import { getToolProfile } from "./tool-profile.js";
|
|
39
45
|
import { isRunnerSkipReason } from "./types.js";
|
|
46
|
+
const dispatcherProbeFlights = createAvailabilityProbeFlight({ generation: () => getDispatchAvailabilityGeneration() });
|
|
40
47
|
import { formatDiagnostics } from "./utils/format-utils.js";
|
|
41
48
|
// --- Runner Registry ---
|
|
42
49
|
export class RunnerRegistry {
|
|
@@ -55,7 +62,7 @@ export class RunnerRegistry {
|
|
|
55
62
|
for (const runner of this.runners.values()) {
|
|
56
63
|
if (isTest && runner.skipTestFiles)
|
|
57
64
|
continue;
|
|
58
|
-
if (
|
|
65
|
+
if (runnerAppliesToKind(runner, kind)) {
|
|
59
66
|
matching.push(runner);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
@@ -68,6 +75,10 @@ export class RunnerRegistry {
|
|
|
68
75
|
this.runners.clear();
|
|
69
76
|
}
|
|
70
77
|
}
|
|
78
|
+
function runnerAppliesToKind(runner, kind) {
|
|
79
|
+
return (runner.appliesTo.length === 0 ||
|
|
80
|
+
(kind !== undefined && runner.appliesTo.includes(kind)));
|
|
81
|
+
}
|
|
71
82
|
// --- Tool Availability Cache ---
|
|
72
83
|
/**
|
|
73
84
|
* Normalize a command name to a FactStore session key.
|
|
@@ -134,15 +145,19 @@ export async function checkToolAvailability(command, facts) {
|
|
|
134
145
|
const startedAt = Date.now();
|
|
135
146
|
let result;
|
|
136
147
|
let hostStallMs;
|
|
148
|
+
let probeJoined = false;
|
|
137
149
|
try {
|
|
138
|
-
|
|
150
|
+
const shared = dispatcherProbeFlights.run(`dispatcher:${key}`, () => safeSpawnAsync(command, ["--version"], {
|
|
139
151
|
timeout: TOOL_PROBE_TIMEOUT_MS,
|
|
140
|
-
});
|
|
152
|
+
}));
|
|
153
|
+
probeJoined = shared.joined;
|
|
154
|
+
result = await shared.promise;
|
|
141
155
|
}
|
|
142
156
|
finally {
|
|
143
157
|
hostStallMs = sampler.stop();
|
|
144
158
|
}
|
|
145
159
|
const elapsedMs = Date.now() - startedAt;
|
|
160
|
+
recordAvailabilityProbeOverrun(command, key, elapsedMs, TOOL_PROBE_TIMEOUT_MS, result.failure);
|
|
146
161
|
if (result.status === 0) {
|
|
147
162
|
facts.setSessionFact(key, true);
|
|
148
163
|
// The tool answered: retire the cooldown facts rather than leaving a
|
|
@@ -156,6 +171,7 @@ export async function checkToolAvailability(command, facts) {
|
|
|
156
171
|
cause: "ok",
|
|
157
172
|
elapsedMs,
|
|
158
173
|
latched: true,
|
|
174
|
+
classifiedBy: probeJoined ? "joined" : "probe",
|
|
159
175
|
hostStallMs,
|
|
160
176
|
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
161
177
|
});
|
|
@@ -188,7 +204,7 @@ export async function checkToolAvailability(command, facts) {
|
|
|
188
204
|
hostStallMs,
|
|
189
205
|
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
190
206
|
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
191
|
-
classifiedBy: "probe",
|
|
207
|
+
classifiedBy: probeJoined ? "joined" : "probe",
|
|
192
208
|
evidence,
|
|
193
209
|
});
|
|
194
210
|
return false;
|
|
@@ -234,7 +250,19 @@ telemetryModel, telemetryProvider) {
|
|
|
234
250
|
const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
|
|
235
251
|
const normalizedFilePath = normalizeMapKey(absoluteFilePath);
|
|
236
252
|
const kind = detectFileKind(normalizedFilePath);
|
|
237
|
-
const
|
|
253
|
+
const contentPrefix = readFilePrefix(normalizedFilePath);
|
|
254
|
+
const fileRole = detectFileRole(normalizedFilePath, contentPrefix);
|
|
255
|
+
// Captured once here so the generated short-circuit below can emit a
|
|
256
|
+
// `dispatch_skipped_generated` record carrying the deciding evidence tier
|
|
257
|
+
// and the measured line-shape statistic — without re-reading the file
|
|
258
|
+
// (refs #2346). The content passed is the same 4096-byte prefix already
|
|
259
|
+
// read for role detection, so this classification costs no extra I/O.
|
|
260
|
+
const generatedDetail = fileRole === "generated"
|
|
261
|
+
? classifyGeneratedOrArtifactDetailed(normalizedFilePath, {
|
|
262
|
+
content: contentPrefix,
|
|
263
|
+
includeDeclarations: false,
|
|
264
|
+
})
|
|
265
|
+
: undefined;
|
|
238
266
|
const projectConfig = loadPiLensProjectConfig(normalizedCwd);
|
|
239
267
|
return {
|
|
240
268
|
filePath: normalizedFilePath,
|
|
@@ -242,6 +270,8 @@ telemetryModel, telemetryProvider) {
|
|
|
242
270
|
cwd: normalizedCwd,
|
|
243
271
|
kind,
|
|
244
272
|
fileRole,
|
|
273
|
+
generatedEvidence: generatedDetail?.evidence,
|
|
274
|
+
generatedLineShapeMean: generatedDetail?.lineShapeMean,
|
|
245
275
|
pi,
|
|
246
276
|
autofix: false,
|
|
247
277
|
deltaMode: !pi.getFlag("no-delta"),
|
|
@@ -419,13 +449,19 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
419
449
|
// The marker describes this exact silent-scanner set. A scanner can
|
|
420
450
|
// recover while another goes dark on the same file, so the set belongs
|
|
421
451
|
// in the session dedupe identity rather than only kind and path.
|
|
452
|
+
// #2016: these are SCANNER IDS, not filesystem paths. `normalizeMapKey`
|
|
453
|
+
// would realpath each one; on Windows that fails, falls through to
|
|
454
|
+
// `resolveNonExisting`, and resolves the id against the CURRENT process
|
|
455
|
+
// cwd, so the dedupe key differed by platform and by cwd (the #2219
|
|
456
|
+
// non-path-sentinel class). The cheap syntactic fold is what this
|
|
457
|
+
// session-scoped dedupe key actually needs.
|
|
422
458
|
const silentScannerSet = [...new Set(unconfirmedServerIds)]
|
|
423
|
-
.map(
|
|
459
|
+
.map(normalizeEphemeralMapKey)
|
|
424
460
|
// Code-unit comparator: the sorted set is a dedupe KEY, so ordering
|
|
425
461
|
// must be deterministic across locales — localeCompare is not.
|
|
426
462
|
.sort((a, b) => Number(a > b) - Number(a < b))
|
|
427
463
|
.join(",");
|
|
428
|
-
const onceKey = `${ctx.kind}:${
|
|
464
|
+
const onceKey = `${ctx.kind}:${ctx.filePath}:${silentScannerSet}`;
|
|
429
465
|
if (coverageNoticeSeen.has(onceKey))
|
|
430
466
|
return undefined;
|
|
431
467
|
coverageNoticeSeen.add(onceKey);
|
|
@@ -445,7 +481,9 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
445
481
|
const primaryHasCoverage = relevant.some((r) => r.status === "succeeded" || r.status === "failed");
|
|
446
482
|
if (primaryHasCoverage)
|
|
447
483
|
return undefined;
|
|
448
|
-
const allPrimarySkipped = relevant.every((r) => r.status === "skipped" ||
|
|
484
|
+
const allPrimarySkipped = relevant.every((r) => r.status === "skipped" ||
|
|
485
|
+
r.status === "when_skipped" ||
|
|
486
|
+
r.status === "test_file_skipped");
|
|
449
487
|
if (!allPrimarySkipped)
|
|
450
488
|
return undefined;
|
|
451
489
|
const plan = getToolPlan(ctx.kind);
|
|
@@ -467,7 +505,7 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
467
505
|
(r.status === "succeeded" || r.status === "failed"));
|
|
468
506
|
if (anyLinterHasCoverage)
|
|
469
507
|
return undefined;
|
|
470
|
-
const onceKey = `${ctx.kind}:${
|
|
508
|
+
const onceKey = `${ctx.kind}:${ctx.filePath}`;
|
|
471
509
|
if (coverageNoticeSeen.has(onceKey))
|
|
472
510
|
return undefined;
|
|
473
511
|
coverageNoticeSeen.add(onceKey);
|
|
@@ -482,6 +520,11 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
482
520
|
}
|
|
483
521
|
const latencyReports = [];
|
|
484
522
|
const coverageNoticeSeen = new Set();
|
|
523
|
+
// One `dispatch_skipped_generated` phase record per file per process (refs
|
|
524
|
+
// #2346): a generated file dispatched repeatedly must not spam latency.log,
|
|
525
|
+
// so only the first skip of each file emits the row; the degradation ledger
|
|
526
|
+
// below still tallies every repetition with a bounded per-subject count.
|
|
527
|
+
const generatedSkipRecorded = new Set();
|
|
485
528
|
export function getLatencyReports() {
|
|
486
529
|
return [...latencyReports];
|
|
487
530
|
}
|
|
@@ -490,6 +533,7 @@ export function clearLatencyReports() {
|
|
|
490
533
|
}
|
|
491
534
|
export function clearCoverageNoticeState() {
|
|
492
535
|
coverageNoticeSeen.clear();
|
|
536
|
+
generatedSkipRecorded.clear();
|
|
493
537
|
}
|
|
494
538
|
export function formatLatencyReport(report) {
|
|
495
539
|
const lines = [];
|
|
@@ -571,6 +615,59 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
571
615
|
});
|
|
572
616
|
continue;
|
|
573
617
|
}
|
|
618
|
+
// Same skipTestFiles gate RunnerRegistry.getForKind applies for the
|
|
619
|
+
// per-edit path (#2337): the plan/group path resolves runners by id via
|
|
620
|
+
// registry.get() instead, which bypasses getForKind entirely, so a
|
|
621
|
+
// runner declaring skipTestFiles never had it enforced here.
|
|
622
|
+
if (runner.skipTestFiles && isTestFile(ctx.filePath)) {
|
|
623
|
+
latencies.push({
|
|
624
|
+
runnerId,
|
|
625
|
+
startTime: runnerStart,
|
|
626
|
+
endTime: Date.now(),
|
|
627
|
+
durationMs: Date.now() - runnerStart,
|
|
628
|
+
status: "test_file_skipped",
|
|
629
|
+
diagnosticCount: 0,
|
|
630
|
+
semantic: "none",
|
|
631
|
+
});
|
|
632
|
+
logLatency({
|
|
633
|
+
type: "runner",
|
|
634
|
+
filePath: ctx.filePath,
|
|
635
|
+
runnerId,
|
|
636
|
+
durationMs: 0,
|
|
637
|
+
status: "test_file_skipped",
|
|
638
|
+
diagnosticCount: 0,
|
|
639
|
+
semantic: "none",
|
|
640
|
+
});
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
// Keep explicit groups aligned with RunnerRegistry.getForKind(): an empty
|
|
644
|
+
// appliesTo list means every kind, while a populated list must contain the
|
|
645
|
+
// current kind. A filtered runner remains visible as skipped telemetry so
|
|
646
|
+
// language mismatches cannot be mistaken for runner failures.
|
|
647
|
+
const appliesToCurrentKind = runnerAppliesToKind(runner, ctx.kind);
|
|
648
|
+
if (!appliesToCurrentKind) {
|
|
649
|
+
const runnerEnd = Date.now();
|
|
650
|
+
latencies.push({
|
|
651
|
+
runnerId,
|
|
652
|
+
startTime: runnerStart,
|
|
653
|
+
endTime: runnerEnd,
|
|
654
|
+
durationMs: 0,
|
|
655
|
+
status: "skipped",
|
|
656
|
+
diagnosticCount: 0,
|
|
657
|
+
semantic: "unknown",
|
|
658
|
+
});
|
|
659
|
+
logLatency({
|
|
660
|
+
type: "runner",
|
|
661
|
+
filePath: ctx.filePath,
|
|
662
|
+
runnerId,
|
|
663
|
+
durationMs: 0,
|
|
664
|
+
status: "skipped",
|
|
665
|
+
diagnosticCount: 0,
|
|
666
|
+
semantic: "unknown",
|
|
667
|
+
metadata: { reason: "applies_to", kind: ctx.kind },
|
|
668
|
+
});
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
574
671
|
// Check preconditions
|
|
575
672
|
let shouldRun = true;
|
|
576
673
|
if (runner.when) {
|
|
@@ -603,10 +700,123 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
603
700
|
});
|
|
604
701
|
continue;
|
|
605
702
|
}
|
|
703
|
+
const projectRoot = ctx.projectRoot ?? ctx.cwd;
|
|
704
|
+
// Only post-write dispatches participate. Project scans and direct API
|
|
705
|
+
// callers must retain their existing synchronous semantics.
|
|
706
|
+
const observedTier = ctx.writeIndex === undefined
|
|
707
|
+
? "inline"
|
|
708
|
+
: classifyObservedRunner(projectRoot, runner.id);
|
|
709
|
+
if (observedTier === "collect-later") {
|
|
710
|
+
const markedAtMs = Date.now();
|
|
711
|
+
const deferred = runRunner(ctx, runner, semantic).then((result) => {
|
|
712
|
+
const durationMs = Date.now() - markedAtMs;
|
|
713
|
+
const tier = observeRunnerLatency({
|
|
714
|
+
projectRoot,
|
|
715
|
+
runnerId: runner.id,
|
|
716
|
+
durationMs,
|
|
717
|
+
timedOut: result.failureKind === "timeout",
|
|
718
|
+
});
|
|
719
|
+
if (tier !== observedTier) {
|
|
720
|
+
logLatency({
|
|
721
|
+
type: "phase",
|
|
722
|
+
filePath: ctx.filePath,
|
|
723
|
+
phase: "runner_collect_later_tier_flip",
|
|
724
|
+
durationMs: 0,
|
|
725
|
+
metadata: {
|
|
726
|
+
runnerId: runner.id,
|
|
727
|
+
from: observedTier,
|
|
728
|
+
to: tier,
|
|
729
|
+
projectRoot,
|
|
730
|
+
},
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (tier === "collect-later") {
|
|
734
|
+
incrementDegradationCount({
|
|
735
|
+
kind: "runner-collect-later",
|
|
736
|
+
subject: `${projectRoot}:${runner.id}`,
|
|
737
|
+
reason: `observed ${durationMs}ms, threshold ${COLLECT_LATER_THRESHOLD_MS}ms`,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
logLatency({
|
|
741
|
+
type: "runner",
|
|
742
|
+
filePath: ctx.filePath,
|
|
743
|
+
runnerId: runner.id,
|
|
744
|
+
startedAt: new Date(markedAtMs).toISOString(),
|
|
745
|
+
durationMs,
|
|
746
|
+
status: result.status,
|
|
747
|
+
diagnosticCount: result.diagnostics.length,
|
|
748
|
+
semantic: result.semantic ?? semantic,
|
|
749
|
+
metadata: { tier: "collect-later", delivered: "turn_end" },
|
|
750
|
+
});
|
|
751
|
+
return result;
|
|
752
|
+
});
|
|
753
|
+
deferRunnerFindings({
|
|
754
|
+
filePath: ctx.filePath,
|
|
755
|
+
cwd: ctx.cwd,
|
|
756
|
+
projectRoot,
|
|
757
|
+
runnerId: runner.id,
|
|
758
|
+
markedAtMs,
|
|
759
|
+
writeIndex: ctx.writeIndex,
|
|
760
|
+
promise: deferred,
|
|
761
|
+
});
|
|
762
|
+
// A deferred runner is still an observed runner. Keep it visible in
|
|
763
|
+
// both the edit latency report and the widget until its turn-end result
|
|
764
|
+
// replaces this pending state (#2122 F1).
|
|
765
|
+
latencies.push({
|
|
766
|
+
runnerId: runner.id,
|
|
767
|
+
startTime: runnerStart,
|
|
768
|
+
endTime: runnerStart,
|
|
769
|
+
durationMs: 0,
|
|
770
|
+
status: "pending",
|
|
771
|
+
diagnosticCount: 0,
|
|
772
|
+
semantic: semantic,
|
|
773
|
+
});
|
|
774
|
+
recordRunner(ctx.filePath, runner.id, "pending", 0, 0, ctx.writeIndex);
|
|
775
|
+
logLatency({
|
|
776
|
+
type: "runner",
|
|
777
|
+
filePath: ctx.filePath,
|
|
778
|
+
runnerId: runner.id,
|
|
779
|
+
durationMs: 0,
|
|
780
|
+
status: "pending",
|
|
781
|
+
diagnosticCount: 0,
|
|
782
|
+
semantic,
|
|
783
|
+
metadata: { tier: "collect-later", delivered: "turn_end" },
|
|
784
|
+
});
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
606
787
|
const result = await runRunner(ctx, runner, semantic);
|
|
607
788
|
onRunnerResult?.(runnerId, result);
|
|
608
789
|
const runnerEnd = Date.now();
|
|
609
790
|
const duration = runnerEnd - runnerStart;
|
|
791
|
+
const tier = ctx.writeIndex === undefined
|
|
792
|
+
? "inline"
|
|
793
|
+
: observeRunnerLatency({
|
|
794
|
+
projectRoot,
|
|
795
|
+
runnerId: runner.id,
|
|
796
|
+
durationMs: duration,
|
|
797
|
+
timedOut: result.failureKind === "timeout",
|
|
798
|
+
});
|
|
799
|
+
if (tier !== observedTier) {
|
|
800
|
+
logLatency({
|
|
801
|
+
type: "phase",
|
|
802
|
+
filePath: ctx.filePath,
|
|
803
|
+
phase: "runner_collect_later_tier_flip",
|
|
804
|
+
durationMs: 0,
|
|
805
|
+
metadata: {
|
|
806
|
+
runnerId: runner.id,
|
|
807
|
+
from: observedTier,
|
|
808
|
+
to: tier,
|
|
809
|
+
projectRoot,
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
if (tier === "collect-later") {
|
|
814
|
+
incrementDegradationCount({
|
|
815
|
+
kind: "runner-collect-later",
|
|
816
|
+
subject: `${projectRoot}:${runner.id}`,
|
|
817
|
+
reason: `observed ${duration}ms, threshold ${COLLECT_LATER_THRESHOLD_MS}ms`,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
610
820
|
// Runner definitions are a typed API, but embedders/plugins can still
|
|
611
821
|
// return untyped objects at runtime. Admit only the closed taxonomy and
|
|
612
822
|
// only on actual skips so free text cannot enter durable latency metadata.
|
|
@@ -672,6 +882,31 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
672
882
|
export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
673
883
|
const _overallStart = Date.now();
|
|
674
884
|
if (ctx.fileRole === "generated") {
|
|
885
|
+
// The generated short-circuit (refs #2346): never ran before this fix
|
|
886
|
+
// for name-less machine-emitted files (a scraped/minified page has no
|
|
887
|
+
// `.min.js` pattern to catch it), so the classification now also has a
|
|
888
|
+
// content-shape tier. The skip is observable — one `dispatch_skipped_generated`
|
|
889
|
+
// phase record per file per process carrying the deciding evidence tier
|
|
890
|
+
// and the measured line-shape statistic. Deliberately NOT a degradation
|
|
891
|
+
// ledger entry: skipping a generated file is healthy behavior working
|
|
892
|
+
// as designed, and the ledger's bounded kind slots are reserved for
|
|
893
|
+
// genuine degradations (#2348 review F3 — the ledger precedent at the
|
|
894
|
+
// collect-later tier flip below records an actual capability loss).
|
|
895
|
+
const evidence = ctx.generatedEvidence;
|
|
896
|
+
const lineShapeMean = ctx.generatedLineShapeMean;
|
|
897
|
+
if (!generatedSkipRecorded.has(ctx.filePath)) {
|
|
898
|
+
generatedSkipRecorded.add(ctx.filePath);
|
|
899
|
+
logLatency({
|
|
900
|
+
type: "phase",
|
|
901
|
+
filePath: ctx.filePath,
|
|
902
|
+
phase: "dispatch_skipped_generated",
|
|
903
|
+
durationMs: 0,
|
|
904
|
+
metadata: {
|
|
905
|
+
evidence: evidence ?? "unknown",
|
|
906
|
+
...(lineShapeMean !== undefined && { lineShapeMean }),
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
}
|
|
675
910
|
return {
|
|
676
911
|
diagnostics: [],
|
|
677
912
|
blockers: [],
|
|
@@ -706,12 +941,46 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
706
941
|
// group order so output is deterministic.
|
|
707
942
|
const groupResults = await Promise.all(groups.map((group) => runGroup(ctx, group, registry, onRunnerResult)));
|
|
708
943
|
// Count baseline warnings before filtering (for delta count display)
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
944
|
+
//
|
|
945
|
+
// #2489: `ctx.filePath` is `createDispatchContext`'s `normalizedFilePath`
|
|
946
|
+
// (`resolveRunnerPath` -> `path.resolve` -> `normalizeMapKey`), which is
|
|
947
|
+
// ALWAYS absolute — the #2016 invariant this seam already relies on. A
|
|
948
|
+
// prior relative-path fallback key (`session.baseline.<path relative to
|
|
949
|
+
// ctx.cwd>`) existed here for self-consistency across the abs/rel read
|
|
950
|
+
// and write, but on `ctx.facts` singletons that outlive one project root
|
|
951
|
+
// (`dispatch/integration.ts`'s module-scope `sessionFacts`, reached by
|
|
952
|
+
// the warm `pilens_analyze` MCP route across any number of cwds) two
|
|
953
|
+
// different projects dispatching files that share a relative path (e.g.
|
|
954
|
+
// both have `src/index.ts`) collided on that fallback key, so project
|
|
955
|
+
// B's first-ever delta baseline read could silently return project A's
|
|
956
|
+
// stored diagnostics. Since the absolute key can never collide across
|
|
957
|
+
// projects (an absolute path is unique on the filesystem) and both
|
|
958
|
+
// writes always land together, the relative fallback is both unneeded
|
|
959
|
+
// and the sole source of the collision — removed rather than cwd-scoped
|
|
960
|
+
// (`cwd` is not a stable identity across this seam's callers, #2494).
|
|
961
|
+
//
|
|
962
|
+
// #2489 round 2: the #2016 invariant above is enforced by
|
|
963
|
+
// `createDispatchContext`, the sole real constructor, but nothing enforced
|
|
964
|
+
// it AT THIS SEAM — a hand-built `DispatchContext` (test scaffolding, a
|
|
965
|
+
// future caller) carrying a relative `filePath` would silently key the
|
|
966
|
+
// baseline under a value the constructor could never produce, and every
|
|
967
|
+
// subsequent same-session dispatch of the real (absolute-keyed) file would
|
|
968
|
+
// read back `undefined`, never the collision itself. Mirrors the sibling
|
|
969
|
+
// guard `recordEntitySnapshotDiff` already enforces for the entity-snapshot
|
|
970
|
+
// seam (`clients/review-graph/service.ts`, #2477): reject visibly and skip
|
|
971
|
+
// the baseline read/write for this dispatch rather than compute one under
|
|
972
|
+
// an unreachable key.
|
|
973
|
+
const baselinePathSafe = isAbsoluteFilePath(ctx.filePath);
|
|
974
|
+
if (ctx.deltaMode && !baselinePathSafe) {
|
|
975
|
+
recordDegradationOnce({
|
|
976
|
+
kind: "dispatch-non-absolute-baseline-path",
|
|
977
|
+
subject: ctx.filePath,
|
|
978
|
+
reason: "dispatchForFile requires an absolute ctx.filePath to key the delta baseline (refs #2489)",
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
const baselineAbsKey = `session.baseline.${ctx.filePath}`;
|
|
982
|
+
const previousBaseline = ctx.deltaMode && baselinePathSafe
|
|
983
|
+
? ctx.facts.getBoundedSessionFact(baselineAbsKey)
|
|
715
984
|
: undefined;
|
|
716
985
|
const baselineWarnings = previousBaseline?.filter((d) => d.semantic === "warning" || d.semantic === "none");
|
|
717
986
|
const baselineWarningCount = baselineWarnings?.length ?? 0;
|
|
@@ -778,9 +1047,12 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
778
1047
|
resolvedCount = filtered.fixed.length;
|
|
779
1048
|
}
|
|
780
1049
|
// Persist full current snapshot for next run (not delta-filtered subset).
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1050
|
+
// Gated on `baselinePathSafe` too: a non-absolute `ctx.filePath` already
|
|
1051
|
+
// skipped the read above (the degradation record fired there), and writing
|
|
1052
|
+
// under that same unreachable key here would just seed a baseline no real
|
|
1053
|
+
// reader (always absolute) could ever read back.
|
|
1054
|
+
if (ctx.deltaMode && baselinePathSafe) {
|
|
1055
|
+
ctx.facts.setBoundedSessionFact(baselineAbsKey, [...dedupedDiagnostics]);
|
|
784
1056
|
}
|
|
785
1057
|
// Categorize results
|
|
786
1058
|
const blockers = visibleDiagnostics.filter((d) => d.semantic === "blocking");
|
|
@@ -819,6 +1091,12 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
819
1091
|
output += formatDiagnostics([coverageNotice], "warning", 1);
|
|
820
1092
|
warnings.push(coverageNotice);
|
|
821
1093
|
}
|
|
1094
|
+
const pendingRunners = runnerLatencies
|
|
1095
|
+
.filter((runner) => runner.status === "pending")
|
|
1096
|
+
.map((runner) => runner.runnerId);
|
|
1097
|
+
if (pendingRunners.length > 0) {
|
|
1098
|
+
output += `\n⏳ Pending runners (reported at turn end): ${pendingRunners.join(", ")}\n`;
|
|
1099
|
+
}
|
|
822
1100
|
// Generate and store latency report
|
|
823
1101
|
const overallEnd = Date.now();
|
|
824
1102
|
const latencyReport = {
|
|
@@ -862,6 +1140,13 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
862
1140
|
})),
|
|
863
1141
|
totalDiagnostics: visibleDiagnostics.length,
|
|
864
1142
|
blockers: blockers.length,
|
|
1143
|
+
// #2489 round 2: distinguishes a delta baseline HIT (a prior snapshot
|
|
1144
|
+
// was read for this file this session) from a MISS (first dispatch, or
|
|
1145
|
+
// the non-absolute-path guard above skipped the read) — `dispatch_start`
|
|
1146
|
+
// (above, `metadata: { groupCount, kind, runners }`) fires before the
|
|
1147
|
+
// baseline read ever runs and cannot carry either value.
|
|
1148
|
+
baselineHit: previousBaseline !== undefined,
|
|
1149
|
+
baselineWarningCount,
|
|
865
1150
|
},
|
|
866
1151
|
});
|
|
867
1152
|
return {
|