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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { recordDegradationOnce } from "../degradation-ledger.js";
|
|
2
|
+
import { isAbsoluteFilePath, normalizeMapKey } from "../path-utils.js";
|
|
1
3
|
import { computeImpactCascade as computeImpactCascadeImpl, computeTransitiveImpact as computeTransitiveImpactImpl, } from "./query.js";
|
|
2
4
|
import { buildOrUpdateGraph as buildOrUpdateGraphImpl, } from "./builder.js";
|
|
3
5
|
import { formatImpactCascade as formatImpactCascadeImpl } from "./format.js";
|
|
@@ -19,7 +21,57 @@ export function computeTransitiveImpact(graph, seedFile, options) {
|
|
|
19
21
|
return computeTransitiveImpactImpl(graph, seedFile, options);
|
|
20
22
|
}
|
|
21
23
|
export function recordEntitySnapshotDiff(facts, filePath, nextSnapshot) {
|
|
22
|
-
|
|
24
|
+
// #2477 round 2 (reviewer F1/F2): folding `cwd` into this key was REVERTED.
|
|
25
|
+
// The writer (`clients/dispatch/runners/tree-sitter.ts`) passes `ctx.cwd`,
|
|
26
|
+
// which `createDispatchContext` (`clients/dispatch/dispatcher.ts:324-326`)
|
|
27
|
+
// sets to the file's NEAREST LANGUAGE-ROOT (`resolveLanguageRootForFile`),
|
|
28
|
+
// while the reader (`upsertChangedSymbols`, `clients/review-graph/builder.ts`,
|
|
29
|
+
// reached via `computeCascadeForFile` → `buildOrUpdateGraph`) keys off the
|
|
30
|
+
// WORKSPACE cwd. In a monorepo/cargo-workspace/go-module layout those two
|
|
31
|
+
// cwd values diverge, so a folded `(cwd, path)` key the builder reader can
|
|
32
|
+
// never hit zeroed `graphChangedSymbolCount` on every build — reproduced:
|
|
33
|
+
// master returns `["alpha","beta"]`, the folded-key version returns `[]`
|
|
34
|
+
// — and made the cascade over-fan to every symbol (`query.ts:230-238`
|
|
35
|
+
// treats an empty `changedSymbols` as "no narrowing").
|
|
36
|
+
//
|
|
37
|
+
// The #2477 cross-project collision this was meant to close cannot reach
|
|
38
|
+
// this function in production: the sole writer always passes `ctx.filePath`,
|
|
39
|
+
// which `createDispatchContext` sets via `resolveRunnerPath` to an
|
|
40
|
+
// ALWAYS-ABSOLUTE path (the #2016 invariant, `dispatcher.ts:320,327,346`).
|
|
41
|
+
// A relative `filePath` here can only happen through a caller regression.
|
|
42
|
+
// Enforce that instead of re-keying: reject a non-absolute `filePath` with
|
|
43
|
+
// a visible, bounded ledger record (never silently, per AGENTS.md) and
|
|
44
|
+
// skip the diff rather than compute one under a key the real reader could
|
|
45
|
+
// never reach anyway. `recordDegradationOnce` (not a thrown error) is used
|
|
46
|
+
// because the sole call site's surrounding try/catch
|
|
47
|
+
// (`tree-sitter.ts` — "entity snapshot / blast-radius enrichment is
|
|
48
|
+
// best-effort") is a BARE, UNLOGGED catch: a thrown error there vanishes
|
|
49
|
+
// with no trace, which is strictly worse than a ledger record.
|
|
50
|
+
if (!isAbsoluteFilePath(filePath)) {
|
|
51
|
+
recordDegradationOnce({
|
|
52
|
+
kind: "review-graph-non-absolute-entity-path",
|
|
53
|
+
subject: filePath,
|
|
54
|
+
reason: "recordEntitySnapshotDiff requires an absolute filePath (refs #2477)",
|
|
55
|
+
});
|
|
56
|
+
return { added: [], removed: [], modified: [] };
|
|
57
|
+
}
|
|
58
|
+
// Normalize once at this boundary, then reuse the folded path for both
|
|
59
|
+
// per-file facts. An unnormalized write is a key the builder reader can never
|
|
60
|
+
// hit, and an unnormalized snapshot forks a second empty diff (#2355).
|
|
61
|
+
const normalizedFilePath = normalizeMapKey(filePath);
|
|
62
|
+
const snapshotKey = `${ENTITY_SNAPSHOT_PREFIX}${normalizedFilePath}`;
|
|
63
|
+
const changedSymbolsKey = `${CHANGED_SYMBOLS_PREFIX}${normalizedFilePath}`;
|
|
64
|
+
const stored = facts.getBoundedSessionFact(snapshotKey);
|
|
65
|
+
// An evicted snapshot is unknown, not empty. Diffing against an empty Map
|
|
66
|
+
// puts every entity in `added`, which reads downstream as "the whole file
|
|
67
|
+
// changed" and schedules a blast-radius run for a file that did not change.
|
|
68
|
+
// Re-seed the snapshot and report no diff instead (#2282 review F1).
|
|
69
|
+
if (stored === undefined && facts.wasBoundedSessionFactEvicted(snapshotKey)) {
|
|
70
|
+
facts.setBoundedSessionFact(snapshotKey, new Map(nextSnapshot));
|
|
71
|
+
facts.setBoundedSessionFact(changedSymbolsKey, []);
|
|
72
|
+
return { added: [], removed: [], modified: [] };
|
|
73
|
+
}
|
|
74
|
+
const prev = stored ?? new Map();
|
|
23
75
|
const added = [];
|
|
24
76
|
const removed = [];
|
|
25
77
|
const modified = [];
|
|
@@ -38,7 +90,7 @@ export function recordEntitySnapshotDiff(facts, filePath, nextSnapshot) {
|
|
|
38
90
|
.map((key) => key.split(":")[1])
|
|
39
91
|
.filter(Boolean)),
|
|
40
92
|
];
|
|
41
|
-
facts.
|
|
42
|
-
facts.
|
|
93
|
+
facts.setBoundedSessionFact(snapshotKey, new Map(nextSnapshot));
|
|
94
|
+
facts.setBoundedSessionFact(changedSymbolsKey, changedSymbols);
|
|
43
95
|
return { added, removed, modified };
|
|
44
96
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { BoundedFifoMap } from "../bounded-cache.js";
|
|
2
3
|
import { normalizeMapKey } from "../path-utils.js";
|
|
3
4
|
/**
|
|
4
5
|
* THE producer/consumer hash contract, in one place (#955 review): scanner
|
|
@@ -17,7 +18,10 @@ export function reviewGraphIrContentHash(content) {
|
|
|
17
18
|
// registry's lifetime is the scan-to-build window, not the process lifetime.
|
|
18
19
|
const MAX_ROOTS = 8;
|
|
19
20
|
const MAX_FILES_PER_ROOT = 20_000;
|
|
20
|
-
|
|
21
|
+
// Both levels are FIFO, matching the hand-rolled blocks these replaced: no
|
|
22
|
+
// read on either map refreshed recency before #2442, and the consume-once
|
|
23
|
+
// contract above means a read is normally a DELETE anyway.
|
|
24
|
+
const roots = new BoundedFifoMap(MAX_ROOTS);
|
|
21
25
|
let accepted = 0;
|
|
22
26
|
let rejected = 0;
|
|
23
27
|
function rootKey(cwd) {
|
|
@@ -33,23 +37,11 @@ export function publishReviewGraphFileIr(cwd, entry) {
|
|
|
33
37
|
const key = rootKey(cwd);
|
|
34
38
|
let files = roots.get(key);
|
|
35
39
|
if (!files) {
|
|
36
|
-
files = new
|
|
40
|
+
files = new BoundedFifoMap(MAX_FILES_PER_ROOT);
|
|
37
41
|
roots.set(key, files);
|
|
38
|
-
while (roots.size > MAX_ROOTS) {
|
|
39
|
-
const oldest = roots.keys().next().value;
|
|
40
|
-
if (oldest === undefined)
|
|
41
|
-
break;
|
|
42
|
-
roots.delete(oldest);
|
|
43
|
-
}
|
|
44
42
|
}
|
|
45
43
|
const fileKey = normalizeMapKey(entry.filePath);
|
|
46
|
-
files.delete(fileKey);
|
|
47
|
-
while (files.size >= MAX_FILES_PER_ROOT) {
|
|
48
|
-
const oldest = files.keys().next().value;
|
|
49
|
-
if (oldest === undefined)
|
|
50
|
-
break;
|
|
51
|
-
files.delete(oldest);
|
|
52
|
-
}
|
|
44
|
+
files.delete(fileKey); // republish is a refresh: it becomes the newest
|
|
53
45
|
files.set(fileKey, { ...entry, filePath: fileKey });
|
|
54
46
|
}
|
|
55
47
|
/**
|
|
@@ -2,9 +2,13 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import { BoundedLruCache } from "../bounded-cache.js";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { findGoverningTsconfigDir, getDirectoryMarkers, } from "../workspace-topology.js";
|
|
5
|
+
import { findGoverningTsconfigDir, getDirectoryMarkers, registerWorkspaceTopologyReset, } from "../workspace-topology.js";
|
|
6
6
|
const cache = new BoundedLruCache(64);
|
|
7
7
|
const referencesCache = new BoundedLruCache(64);
|
|
8
|
+
registerWorkspaceTopologyReset(() => {
|
|
9
|
+
cache.clear();
|
|
10
|
+
referencesCache.clear();
|
|
11
|
+
});
|
|
8
12
|
/** Strip JSONC comments and trailing commas without touching string contents. */
|
|
9
13
|
function parseJsonc(content) {
|
|
10
14
|
let output = "";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { hasCargoWorkspaceTable, readCargoDependencyNames, readCargoPackageName, readCargoWorkspaceMembers, } from "../cargo-manifest.js";
|
|
9
10
|
import { getProjectIgnoreMatcher, isExcludedDirName, } from "../file-utils.js";
|
|
10
11
|
import { normalizeMapKey } from "../path-utils.js";
|
|
11
12
|
import { getWorkspaceManifestMarkers } from "../workspace-topology.js";
|
|
@@ -52,8 +53,12 @@ function detectWorkspaceType(cwd) {
|
|
|
52
53
|
if (markers.hasCargoToml) {
|
|
53
54
|
try {
|
|
54
55
|
const content = fs.readFileSync(path.join(cwd, "Cargo.toml"), "utf-8");
|
|
55
|
-
|
|
56
|
+
// The shared presence check (#2498) — see its own doc comment in
|
|
57
|
+
// cargo-manifest.ts for why a bare `.includes`/regex reimplementation
|
|
58
|
+
// of this test is wrong (comment/sentinel handling).
|
|
59
|
+
if (hasCargoWorkspaceTable(content)) {
|
|
56
60
|
return "cargo";
|
|
61
|
+
}
|
|
57
62
|
}
|
|
58
63
|
catch { }
|
|
59
64
|
}
|
|
@@ -146,71 +151,6 @@ function extractYamlList(content, key) {
|
|
|
146
151
|
}
|
|
147
152
|
return values;
|
|
148
153
|
}
|
|
149
|
-
function extractTomlArray(content, key) {
|
|
150
|
-
const prefix = `${key}`;
|
|
151
|
-
let collecting = false;
|
|
152
|
-
let buffer = "";
|
|
153
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
154
|
-
const line = rawLine.split("#", 1)[0].trim();
|
|
155
|
-
if (!line)
|
|
156
|
-
continue;
|
|
157
|
-
if (!collecting) {
|
|
158
|
-
if (!line.startsWith(prefix))
|
|
159
|
-
continue;
|
|
160
|
-
const equalsIndex = line.indexOf("=");
|
|
161
|
-
if (equalsIndex === -1 || line.slice(0, equalsIndex).trim() !== key)
|
|
162
|
-
continue;
|
|
163
|
-
const afterEquals = line.slice(equalsIndex + 1).trim();
|
|
164
|
-
if (!afterEquals.startsWith("["))
|
|
165
|
-
continue;
|
|
166
|
-
collecting = true;
|
|
167
|
-
buffer += afterEquals.slice(1);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
buffer += `,${line}`;
|
|
171
|
-
}
|
|
172
|
-
const closeIndex = buffer.indexOf("]");
|
|
173
|
-
if (closeIndex !== -1) {
|
|
174
|
-
buffer = buffer.slice(0, closeIndex);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return buffer
|
|
179
|
-
.split(",")
|
|
180
|
-
.map((s) => stripQuotes(s.trim()))
|
|
181
|
-
.filter(Boolean);
|
|
182
|
-
}
|
|
183
|
-
function extractTomlSection(content, section) {
|
|
184
|
-
const lines = [];
|
|
185
|
-
let inSection = false;
|
|
186
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
187
|
-
const trimmed = rawLine.trim();
|
|
188
|
-
if (trimmed === `[${section}]`) {
|
|
189
|
-
inSection = true;
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (inSection && trimmed.startsWith("[") && trimmed.endsWith("]"))
|
|
193
|
-
break;
|
|
194
|
-
if (inSection)
|
|
195
|
-
lines.push(rawLine);
|
|
196
|
-
}
|
|
197
|
-
return lines;
|
|
198
|
-
}
|
|
199
|
-
function extractTomlString(content, key) {
|
|
200
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
201
|
-
const line = rawLine.split("#", 1)[0].trim();
|
|
202
|
-
const equalsIndex = line.indexOf("=");
|
|
203
|
-
if (equalsIndex === -1 || line.slice(0, equalsIndex).trim() !== key)
|
|
204
|
-
continue;
|
|
205
|
-
const value = line.slice(equalsIndex + 1).trim();
|
|
206
|
-
const quote = value[0];
|
|
207
|
-
if ((quote !== '"' && quote !== "'") || value.length < 2)
|
|
208
|
-
return undefined;
|
|
209
|
-
const endIndex = value.indexOf(quote, 1);
|
|
210
|
-
return endIndex === -1 ? undefined : value.slice(1, endIndex);
|
|
211
|
-
}
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
154
|
function moduleFromPackageJson(cwd, pkgRoot) {
|
|
215
155
|
const pkgJson = readJsonSafe(path.join(pkgRoot, "package.json"));
|
|
216
156
|
if (!pkgJson?.name)
|
|
@@ -250,7 +190,7 @@ function scanCargoModules(cwd) {
|
|
|
250
190
|
let members = [];
|
|
251
191
|
try {
|
|
252
192
|
const content = fs.readFileSync(path.join(cwd, "Cargo.toml"), "utf-8");
|
|
253
|
-
members =
|
|
193
|
+
members = readCargoWorkspaceMembers(content);
|
|
254
194
|
}
|
|
255
195
|
catch {
|
|
256
196
|
return [];
|
|
@@ -260,15 +200,11 @@ function scanCargoModules(cwd) {
|
|
|
260
200
|
const pkgRoot = path.resolve(cwd, member);
|
|
261
201
|
const memberToml = path.join(pkgRoot, "Cargo.toml");
|
|
262
202
|
let name = "";
|
|
263
|
-
|
|
203
|
+
let deps = [];
|
|
264
204
|
try {
|
|
265
205
|
const content = fs.readFileSync(memberToml, "utf-8");
|
|
266
|
-
name =
|
|
267
|
-
|
|
268
|
-
const depName = line.trim().match(/^([A-Za-z0-9_-]+)\s*=/);
|
|
269
|
-
if (depName)
|
|
270
|
-
deps.push(depName[1]);
|
|
271
|
-
}
|
|
206
|
+
name = readCargoPackageName(content) ?? "";
|
|
207
|
+
deps = readCargoDependencyNames(content);
|
|
272
208
|
}
|
|
273
209
|
catch {
|
|
274
210
|
continue;
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { isTestMode } from "./env-utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
4
|
+
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
4
5
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
|
-
|
|
6
|
+
import { normalizeFilePath } from "./path-utils.js";
|
|
7
|
+
const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensLogDir();
|
|
6
8
|
const REVIEW_GRAPH_LOG_FILE = path.join(REVIEW_GRAPH_LOG_DIR, "review-graph.log");
|
|
7
|
-
|
|
9
|
+
// Bounded like every other global sink (#2505) — see sessionstart-logger.
|
|
10
|
+
const writer = createNdjsonLogger({
|
|
11
|
+
filePath: REVIEW_GRAPH_LOG_FILE,
|
|
12
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
13
|
+
backupPath: `${REVIEW_GRAPH_LOG_FILE}.1`,
|
|
14
|
+
});
|
|
8
15
|
/**
|
|
9
16
|
* Build the one canonical graph metadata shape used by build and persist logs.
|
|
10
17
|
* Counts come from the graph instance; source-file count may use the exact
|
|
@@ -27,6 +34,9 @@ export function makeReviewGraphBuildMetadata(graph, options = {}) {
|
|
|
27
34
|
graph.persistCoverage?.sourceFilesTruncated
|
|
28
35
|
? { sourceFilesTruncated: true }
|
|
29
36
|
: {}),
|
|
37
|
+
...(options.pathNormalizeCalls === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { pathNormalizeCalls: options.pathNormalizeCalls }),
|
|
30
40
|
nodes: graph.nodes.size,
|
|
31
41
|
edges: graph.edges.length,
|
|
32
42
|
...(graph.persistCoverage
|
|
@@ -34,11 +44,27 @@ export function makeReviewGraphBuildMetadata(graph, options = {}) {
|
|
|
34
44
|
: {}),
|
|
35
45
|
};
|
|
36
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* #2141: call sites feed this a mix of raw `cwd` params and already-normalized
|
|
49
|
+
* cache keys (`normalizeMapKey(path.resolve(cwd))`), so the same project root
|
|
50
|
+
* showed up in the log as both `C:\...\pi-free` and `C:/.../pi-free`. Rather
|
|
51
|
+
* than auditing every call site, normalize once here — the single emit
|
|
52
|
+
* seam — matching the form `normalizeFilePath` already produces for every
|
|
53
|
+
* other structured log's `cwd` field (bus-publish, disposition-publish,
|
|
54
|
+
* diagnostics-publish, format-events-publish all normalize at their publish
|
|
55
|
+
* call sites; this pulls the same conversion in-house so no future call site
|
|
56
|
+
* can regress it).
|
|
57
|
+
*/
|
|
37
58
|
export function logReviewGraph(entry) {
|
|
38
59
|
if (isTestMode()) {
|
|
39
60
|
return;
|
|
40
61
|
}
|
|
41
|
-
writer.log({
|
|
62
|
+
writer.log({
|
|
63
|
+
ts: new Date().toISOString(),
|
|
64
|
+
...entry,
|
|
65
|
+
cwd: normalizeFilePath(entry.cwd),
|
|
66
|
+
pid: process.pid,
|
|
67
|
+
});
|
|
42
68
|
}
|
|
43
69
|
export function getReviewGraphLogPath() {
|
|
44
70
|
return REVIEW_GRAPH_LOG_FILE;
|
|
@@ -190,6 +190,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
190
190
|
changed: [],
|
|
191
191
|
failed: [],
|
|
192
192
|
skipped: [],
|
|
193
|
+
unavailable: [],
|
|
193
194
|
};
|
|
194
195
|
// #502 fix provenance: per-path {tool, kind} entries accumulated across the
|
|
195
196
|
// deferred-format loop below, passed as `fixes` on the batch
|
|
@@ -348,7 +349,8 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
348
349
|
return !disabled;
|
|
349
350
|
});
|
|
350
351
|
if (formatRecords.length > 0) {
|
|
351
|
-
const work =
|
|
352
|
+
const work = [];
|
|
353
|
+
work.length = formatRecords.length;
|
|
352
354
|
const started = new Set();
|
|
353
355
|
let nextIndex = 0;
|
|
354
356
|
const worker = async () => {
|
|
@@ -430,6 +432,18 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
430
432
|
},
|
|
431
433
|
], "format-failed");
|
|
432
434
|
}
|
|
435
|
+
// #2413: an unavailable formatter is durable, not transient — record it
|
|
436
|
+
// once and DO NOT requeue. Requeuing here is exactly what made
|
|
437
|
+
// `spawn oxfmt ENOENT` re-fire on every agent_end for the same files.
|
|
438
|
+
if (result.formatUnavailable.length > 0) {
|
|
439
|
+
for (const u of result.formatUnavailable) {
|
|
440
|
+
summary.unavailable.push({
|
|
441
|
+
filePath,
|
|
442
|
+
formatter: u.formatter,
|
|
443
|
+
reason: u.reason,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
433
447
|
if (result.formatChanged) {
|
|
434
448
|
summary.changed.push(filePath);
|
|
435
449
|
for (const tool of result.formattersUsed) {
|
|
@@ -663,6 +677,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
663
677
|
changed: summary.changed.length,
|
|
664
678
|
failed: summary.failed.length,
|
|
665
679
|
skipped: summary.skipped.length,
|
|
680
|
+
unavailable: summary.unavailable.length,
|
|
666
681
|
},
|
|
667
682
|
});
|
|
668
683
|
logLatency({
|
|
@@ -676,9 +691,10 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
676
691
|
changed: summary.changed.length,
|
|
677
692
|
failed: summary.failed.length,
|
|
678
693
|
skipped: summary.skipped.length,
|
|
694
|
+
unavailable: summary.unavailable.length,
|
|
679
695
|
},
|
|
680
696
|
});
|
|
681
|
-
dbg(`agent_end deferred_format complete: formatted=${summary.formatted} changed=${summary.changed.length} failed=${summary.failed.length} skipped=${summary.skipped.length}`);
|
|
697
|
+
dbg(`agent_end deferred_format complete: formatted=${summary.formatted} changed=${summary.changed.length} failed=${summary.failed.length} skipped=${summary.skipped.length} unavailable=${summary.unavailable.length}`);
|
|
682
698
|
if (summary.failed.length > 0) {
|
|
683
699
|
notify(`pi-lens deferred format: ${summary.changed.length} changed, ${summary.failed.length} failed`, "warning");
|
|
684
700
|
}
|
|
@@ -103,12 +103,58 @@ export function peekTestFindings(cacheManager, cwd, runtime, logDelivery = false
|
|
|
103
103
|
{
|
|
104
104
|
role: "user",
|
|
105
105
|
content: current
|
|
106
|
-
? `${AUTOMATION_FRAMING}
|
|
106
|
+
? `${AUTOMATION_FRAMING}${testFindingsCurrentPrefix(findings.data.runnerErrorOnly)}\n\n${findings.data.content}`
|
|
107
107
|
: `${AUTOMATION_FRAMING}${historicalTestContent(findings.data.content, findings.data.provenance)}`,
|
|
108
108
|
},
|
|
109
109
|
],
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* #2522: a batch made entirely of RUNNER errors (timeout, missing
|
|
114
|
+
* provider/binary — see `TestRunnerFindingsCache.runnerErrorOnly`) is not a
|
|
115
|
+
* failure the agent introduced, so it must not read as a blocker the way a
|
|
116
|
+
* genuine failing test does.
|
|
117
|
+
*/
|
|
118
|
+
function testFindingsCurrentPrefix(runnerErrorOnly) {
|
|
119
|
+
return runnerErrorOnly
|
|
120
|
+
? "Test runner could not complete last turn (advisory — not a failure introduced this turn):"
|
|
121
|
+
: "Test failures detected last turn — fix before continuing:";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Retire a delivered test-findings record: blank the content, keep everything a
|
|
125
|
+
* delivery does not settle.
|
|
126
|
+
*
|
|
127
|
+
* ONE function, called by both `consumeTestFindings` (the in-process context
|
|
128
|
+
* hook) and `acknowledgeTestFindings` (the MCP Stop-hook commit), because
|
|
129
|
+
* "what survives a retire" is exactly the kind of rule that rots when it is
|
|
130
|
+
* stated twice. It already had: round 2 taught the consume copy to preserve
|
|
131
|
+
* `deferredTargets` and left the acknowledge copy dropping them, and the
|
|
132
|
+
* acknowledge copy is the LIVE Stop-hook path — including the branch where
|
|
133
|
+
* `handleTurnEnd` never runs at all — so on that path the deferral set was
|
|
134
|
+
* wiped before anything could ever dispatch it (#2522 review round 3, F2).
|
|
135
|
+
*
|
|
136
|
+
* What survives, and why:
|
|
137
|
+
* - `testRunGeneration` — nulling the slot would let a still-in-flight OLDER
|
|
138
|
+
* batch read `undefined`, pass the strictly-greater suppression check, and
|
|
139
|
+
* resurrect a consumed one-shot advisory with stale results.
|
|
140
|
+
* - `deferredTargets` — delivering the advisory says the agent has SEEN the
|
|
141
|
+
* deferral, not that those targets ran. Dropping the list silently un-defers
|
|
142
|
+
* them and the cut batch is never finished.
|
|
143
|
+
* - `retiredTargets` — the session-scoped deferral cap. Dropping it re-arms a
|
|
144
|
+
* suite already measured as too slow for the batch budget, which puts the
|
|
145
|
+
* livelock back.
|
|
146
|
+
*
|
|
147
|
+
* An empty-content record peeks as undelivered while keeping all three intact.
|
|
148
|
+
*/
|
|
149
|
+
function retireTestFindings(cacheManager, cwd) {
|
|
150
|
+
const prior = cacheManager.readCache("test-runner-findings", cwd)?.data;
|
|
151
|
+
cacheManager.writeCache("test-runner-findings", {
|
|
152
|
+
content: "",
|
|
153
|
+
testRunGeneration: prior?.testRunGeneration,
|
|
154
|
+
deferredTargets: prior?.deferredTargets,
|
|
155
|
+
retiredTargets: prior?.retiredTargets,
|
|
156
|
+
}, cwd);
|
|
157
|
+
}
|
|
112
158
|
export function consumeTestFindings(cacheManager, cwd, runtime) {
|
|
113
159
|
const record = cacheManager.readCache("test-runner-findings", cwd);
|
|
114
160
|
if (!record?.data?.content)
|
|
@@ -116,16 +162,7 @@ export function consumeTestFindings(cacheManager, cwd, runtime) {
|
|
|
116
162
|
const findings = peekTestFindings(cacheManager, cwd, runtime, true);
|
|
117
163
|
if (!findings)
|
|
118
164
|
return;
|
|
119
|
-
|
|
120
|
-
// the whole slot would let a still-in-flight OLDER batch see `undefined`,
|
|
121
|
-
// pass the strictly-greater suppression check, and resurrect a consumed
|
|
122
|
-
// one-shot advisory with stale results. An empty-content record peeks as
|
|
123
|
-
// undelivered while keeping late-generation ordering intact.
|
|
124
|
-
const priorGeneration = cacheManager.readCache("test-runner-findings", cwd)?.data?.testRunGeneration;
|
|
125
|
-
cacheManager.writeCache("test-runner-findings", {
|
|
126
|
-
content: "",
|
|
127
|
-
testRunGeneration: priorGeneration,
|
|
128
|
-
}, cwd);
|
|
165
|
+
retireTestFindings(cacheManager, cwd);
|
|
129
166
|
return findings;
|
|
130
167
|
}
|
|
131
168
|
/** Complete an acknowledged MCP delivery without re-validating or re-rendering it. */
|
|
@@ -145,11 +182,7 @@ export function acknowledgeTestFindings(cacheManager, cwd) {
|
|
|
145
182
|
const findings = cacheManager.readCache("test-runner-findings", cwd);
|
|
146
183
|
if (!findings?.data?.content)
|
|
147
184
|
return;
|
|
148
|
-
|
|
149
|
-
cacheManager.writeCache("test-runner-findings", {
|
|
150
|
-
content: "",
|
|
151
|
-
testRunGeneration: findings.data.testRunGeneration,
|
|
152
|
-
}, cwd);
|
|
185
|
+
retireTestFindings(cacheManager, cwd);
|
|
153
186
|
}
|
|
154
187
|
export function consumeSessionStartGuidance(cacheManager, cwd) {
|
|
155
188
|
const guidance = cacheManager.readCache("session-start-guidance", cwd);
|
|
@@ -6,6 +6,7 @@ import { appendProjectChange, } from "./project-changes.js";
|
|
|
6
6
|
import { normalizeMapKey, pathsEqual } from "./path-utils.js";
|
|
7
7
|
import { PathKeyedMap } from "./path-keyed-map.js";
|
|
8
8
|
import { BoundedLruCache } from "./bounded-cache.js";
|
|
9
|
+
import { PartialApplyRecordStore } from "./partial-edit-apply.js";
|
|
9
10
|
import { ReadGuard } from "./read-guard.js";
|
|
10
11
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
11
12
|
import { TurnSummaryCollector } from "./turn-summary.js";
|
|
@@ -111,6 +112,11 @@ export class RuntimeCoordinator {
|
|
|
111
112
|
// (cheap, empty Map) but callers gate recording behind the
|
|
112
113
|
// `lens-turn-summary` flag so it's a true no-op when the feature is off.
|
|
113
114
|
_turnSummary = new TurnSummaryCollector();
|
|
115
|
+
// #2402/#1053: session-scoped applied-edit records for exact-retry
|
|
116
|
+
// recognition. Both producers (partial-apply commit, full native-edit
|
|
117
|
+
// success) write here; the preflight consults it before declaring an
|
|
118
|
+
// oldText miss, so an identical retry never re-executes a committed write.
|
|
119
|
+
partialApplyRecords = new PartialApplyRecordStore();
|
|
114
120
|
resetForSession(startedAt = Date.now()) {
|
|
115
121
|
this._sessionGeneration += 1;
|
|
116
122
|
this._sessionStartedAt = startedAt;
|
|
@@ -159,6 +165,9 @@ export class RuntimeCoordinator {
|
|
|
159
165
|
this._actionableWarningsThisTurn.clear();
|
|
160
166
|
this._codeQualityWarningsThisTurn.clear();
|
|
161
167
|
this._turnSummary.clear();
|
|
168
|
+
// #2402: an applied-edit record is a fact about the session that applied
|
|
169
|
+
// it; a new session must re-resolve identical payloads from content.
|
|
170
|
+
this.partialApplyRecords.clear();
|
|
162
171
|
}
|
|
163
172
|
get sessionStartedAt() {
|
|
164
173
|
return this._sessionStartedAt;
|
|
@@ -317,9 +326,15 @@ export class RuntimeCoordinator {
|
|
|
317
326
|
get droppedMutationReceiptCount() {
|
|
318
327
|
return this._droppedMutationReceipts;
|
|
319
328
|
}
|
|
320
|
-
/**
|
|
321
|
-
|
|
322
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Atomically records write/edit ordering before debounce can coalesce it.
|
|
331
|
+
*
|
|
332
|
+
* Takes the classified {@link MutationKind}, not a raw tool name (#2423):
|
|
333
|
+
* an edit-shaped tool pi-lens does not name still demotes this file to the
|
|
334
|
+
* deferred pass, which is the safe timing.
|
|
335
|
+
*/
|
|
336
|
+
recordMutationToolReceipt(filePath, kind) {
|
|
337
|
+
if (kind === "write") {
|
|
323
338
|
this._writtenThisTurn.set(filePath, true);
|
|
324
339
|
}
|
|
325
340
|
else if (this._writtenThisTurn.has(filePath)) {
|
|
@@ -329,7 +344,7 @@ export class RuntimeCoordinator {
|
|
|
329
344
|
this._fixedThisTurn.delete(filePath);
|
|
330
345
|
}
|
|
331
346
|
return {
|
|
332
|
-
autofixMode:
|
|
347
|
+
autofixMode: kind === "edit" || this._autofixDemotedThisTurn.has(filePath)
|
|
333
348
|
? "deferred"
|
|
334
349
|
: "immediate",
|
|
335
350
|
};
|
|
@@ -734,6 +749,74 @@ export class RuntimeCoordinator {
|
|
|
734
749
|
this._pendingInlineBlockers.delete(key);
|
|
735
750
|
return true;
|
|
736
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* #1950 fix-round F1: read the current delivery count WITHOUT committing
|
|
754
|
+
* an increment. `runtime-turn.ts` needs the count a delivery would reach
|
|
755
|
+
* BEFORE it knows whether that delivery will actually reach the agent —
|
|
756
|
+
* the turn-end signature dedupe (`turn-end-findings-last`) can suppress a
|
|
757
|
+
* turn whose rendered content is identical to the last one, and a
|
|
758
|
+
* suppressed turn must not advance the counter (the agent never saw it).
|
|
759
|
+
* The caller peeks, renders the tentative body, and only calls
|
|
760
|
+
* {@link incrementInlineBlockerStaleDelivery} once it knows the turn's
|
|
761
|
+
* content is NOT being suppressed.
|
|
762
|
+
*/
|
|
763
|
+
peekInlineBlockerStaleDeliveryCount(filePath) {
|
|
764
|
+
const key = path.resolve(filePath);
|
|
765
|
+
return this._pendingInlineBlockers.get(key)?.staleDeliveryCount ?? 0;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* #1950: commit one more turn end that re-served this record while
|
|
769
|
+
* `stale`. The caller (`runtime-turn.ts`) only calls this for a
|
|
770
|
+
* `"dependency-drift"` demotion — a `"past-eof"` demotion retires after
|
|
771
|
+
* its own single delivery (#1944, `retireDemotedPastEofBlocker`) and
|
|
772
|
+
* never accumulates a count — and only AFTER confirming the turn's
|
|
773
|
+
* content will not be suppressed by the signature dedupe (see
|
|
774
|
+
* {@link peekInlineBlockerStaleDeliveryCount}), so this counts ACTUAL
|
|
775
|
+
* deliveries the agent saw, not turn-end loop iterations. Returns the new
|
|
776
|
+
* count, or 0 when there is no entry to count against (already retired,
|
|
777
|
+
* or never recorded).
|
|
778
|
+
*/
|
|
779
|
+
incrementInlineBlockerStaleDelivery(filePath) {
|
|
780
|
+
const key = path.resolve(filePath);
|
|
781
|
+
const existing = this._pendingInlineBlockers.get(key);
|
|
782
|
+
if (!existing)
|
|
783
|
+
return 0;
|
|
784
|
+
const next = (existing.staleDeliveryCount ?? 0) + 1;
|
|
785
|
+
this._pendingInlineBlockers.set(key, {
|
|
786
|
+
...existing,
|
|
787
|
+
staleDeliveryCount: next,
|
|
788
|
+
});
|
|
789
|
+
return next;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* #1950: retire a dependency-drift demotion once it has been delivered
|
|
793
|
+
* `DEPENDENCY_DRIFT_MAX_DELIVERIES` times with no re-run confirming or
|
|
794
|
+
* clearing it. Unlike {@link retireDemotedPastEofBlocker}, the record is
|
|
795
|
+
* NOT provably unrecoverable — its coordinates are still in bounds, and a
|
|
796
|
+
* fresh dispatch of the file could still confirm or clear it. This is a
|
|
797
|
+
* bounded-noise cap, not a "this can never resolve" verdict, and the
|
|
798
|
+
* caller's ledger reason must say so (`demoted-finding-retired`'s "capped,
|
|
799
|
+
* re-run can still confirm" vs. past-EOF's "retired, unrecoverable").
|
|
800
|
+
*
|
|
801
|
+
* Only ever touches this gate's OWN demotion (`staleReason ===
|
|
802
|
+
* "dependency-drift"`) — a past-EOF demotion on the same file is that
|
|
803
|
+
* gate's own record to retire, not this one's.
|
|
804
|
+
*
|
|
805
|
+
* @returns true when an entry was retired — the caller records it, so the
|
|
806
|
+
* suppression is never silent (#1432 Gap 1).
|
|
807
|
+
*/
|
|
808
|
+
retireDemotedDependencyDriftBlocker(filePath) {
|
|
809
|
+
const key = path.resolve(filePath);
|
|
810
|
+
const existing = this._pendingInlineBlockers.get(key);
|
|
811
|
+
if (!existing)
|
|
812
|
+
return false;
|
|
813
|
+
if (!existing.stale)
|
|
814
|
+
return false;
|
|
815
|
+
if (existing.staleReason !== "dependency-drift")
|
|
816
|
+
return false;
|
|
817
|
+
this._pendingInlineBlockers.delete(key);
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
737
820
|
/**
|
|
738
821
|
* Retire a file's inline blocker because a FRESH, content-bound diagnostic
|
|
739
822
|
* verdict proved it gone (#1561).
|
|
@@ -931,7 +1014,9 @@ export class RuntimeCoordinator {
|
|
|
931
1014
|
* for a same-kind re-touch. Callers publish each kind's first transition
|
|
932
1015
|
* without spamming repeated edits before `agent_end`.
|
|
933
1016
|
*/
|
|
934
|
-
deferMutation(filePath, cwd,
|
|
1017
|
+
deferMutation(filePath, cwd,
|
|
1018
|
+
/** Host tool name; any mutating tool, not just `write`/`edit` (#2423). */
|
|
1019
|
+
toolName, turnStateCwd, kind, ownerSessionId, originCwd) {
|
|
935
1020
|
const key = path.resolve(filePath);
|
|
936
1021
|
const now = Date.now();
|
|
937
1022
|
const resolvedOriginCwd = originCwd ?? turnStateCwd;
|
|
@@ -962,7 +1047,9 @@ export class RuntimeCoordinator {
|
|
|
962
1047
|
});
|
|
963
1048
|
return true;
|
|
964
1049
|
}
|
|
965
|
-
deferFormat(filePath, cwd,
|
|
1050
|
+
deferFormat(filePath, cwd,
|
|
1051
|
+
/** Host tool name; any mutating tool, not just `write`/`edit` (#2423). */
|
|
1052
|
+
toolName, turnStateCwd, ownerSessionId, originCwd) {
|
|
966
1053
|
return this.deferMutation(filePath, cwd, toolName, turnStateCwd, "format", ownerSessionId, originCwd);
|
|
967
1054
|
}
|
|
968
1055
|
get pendingDeferredFormatCount() {
|