@herbertgao/pi-extensions 2026.9.0 → 2026.9.1
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/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
- package/node_modules/@czottmann/pi-automode/README.md +20 -0
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/pi-lens/CHANGELOG.md +988 -0
- package/node_modules/pi-lens/README.md +3 -0
- package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
- package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
- package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
- package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
- package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
- package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
- package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
- package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
- package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
- package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
- package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
- package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
- package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
- package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
- package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
- package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
- package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
- package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
- package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
- package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
- package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
- package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
- package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
- package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
- package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
- package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
- package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
- package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
- package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
- package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
- package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
- package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
- package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
- package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
- package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
- package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
- package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
- package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
- package/node_modules/pi-lens/dist/index.js +64335 -54300
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +127 -5
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
- package/node_modules/pi-lens/dist/tools/shared.js +0 -1
- package/node_modules/pi-lens/docs/agent-guide.md +64 -1
- package/node_modules/pi-lens/docs/configuration.md +222 -0
- package/node_modules/pi-lens/docs/dependencies.md +3 -3
- package/node_modules/pi-lens/docs/features.md +44 -5
- package/node_modules/pi-lens/docs/language-coverage.md +2 -2
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
- package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
- package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
- package/node_modules/pi-lens/package.json +15 -23
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
- package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +62 -15
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +242 -1
- package/node_modules/pi-web-access/index.ts +118 -64
- package/node_modules/pi-web-access/mistral-search.ts +281 -0
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/perplexity.ts +14 -1
- package/node_modules/pi-web-access/utils.ts +23 -6
- package/node_modules/pi-web-access/xai-search.ts +96 -33
- package/package.json +5 -5
|
@@ -1,68 +1,345 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
4
|
+
import { createFileTime } from "./file-time.js";
|
|
2
5
|
import { detectLineEnding, normalizeToLF, restoreLineEndings, } from "./host-edit-normalize.js";
|
|
6
|
+
import { PathKeyedMap } from "./path-keyed-map.js";
|
|
7
|
+
import { normalizeMapKey } from "./path-utils.js";
|
|
3
8
|
import { boundedEditIndexes, createReadGuardEditBatchSummary, formatBoundedEditIndexes, logReadGuardEvent, } from "./read-guard-logger.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const MAX_APPLIED_RECORDS_PER_FILE = 8;
|
|
10
|
+
/** Exported for #2442's bounded-eviction test; not a public API. */
|
|
11
|
+
export const MAX_APPLIED_RECORD_FILES = 64;
|
|
12
|
+
/**
|
|
13
|
+
* Canonical key for an applied edit: LF-normalized, per-line trailing
|
|
14
|
+
* whitespace stripped, trailing empty lines dropped. Retry payloads go through
|
|
15
|
+
* the same normalization, so an identical resubmission matches its record even
|
|
16
|
+
* when the two passes' autopatch results diverged on trailing whitespace
|
|
17
|
+
* (deterministic given identical payloads; the strip removes the only
|
|
18
|
+
* payload-dependent axis that survives to the retry).
|
|
19
|
+
*/
|
|
20
|
+
export function stripOldTextTrailingWhitespace(value) {
|
|
21
|
+
const lines = value
|
|
22
|
+
.replace(/\r\n/g, "\n")
|
|
23
|
+
.split("\n")
|
|
24
|
+
.map((l) => l.trimEnd());
|
|
25
|
+
while (lines.length > 1 && lines[lines.length - 1] === "")
|
|
26
|
+
lines.pop();
|
|
27
|
+
return lines.join("\n");
|
|
28
|
+
}
|
|
29
|
+
function editPairDigest(oldText, newText) {
|
|
30
|
+
return createHash("sha256")
|
|
31
|
+
.update(JSON.stringify([oldText, newText ?? ""]), "utf8")
|
|
32
|
+
.digest("hex");
|
|
33
|
+
}
|
|
34
|
+
function countLfOccurrences(content, needle) {
|
|
35
|
+
if (!needle)
|
|
36
|
+
return 0;
|
|
37
|
+
let count = 0;
|
|
38
|
+
let pos = 0;
|
|
39
|
+
while (pos < content.length) {
|
|
40
|
+
const idx = content.indexOf(needle, pos);
|
|
41
|
+
if (idx === -1)
|
|
42
|
+
break;
|
|
43
|
+
count += 1;
|
|
44
|
+
pos = idx + needle.length;
|
|
45
|
+
}
|
|
46
|
+
return count;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Evidence check for an exact-retry recognition (#2402): the recorded pair was
|
|
50
|
+
* applied by this process; the content evidence answers whether that state is
|
|
51
|
+
* still the file's state. Deliberately NOT a global heuristic — it only runs
|
|
52
|
+
* for an edit whose exact (oldText, newText) pair is on record.
|
|
53
|
+
*
|
|
54
|
+
* - oldText not contained in newText: the applied state holds when oldText is
|
|
55
|
+
* gone and newText is present.
|
|
56
|
+
* - oldText contained in newText (e.g. an extended import line): the applied
|
|
57
|
+
* state holds when every remaining oldText occurrence lies inside a newText
|
|
58
|
+
* occurrence. Re-applying would duplicate newText — the exact #2402 loop.
|
|
59
|
+
*/
|
|
60
|
+
export function isExactAppliedRetry(args) {
|
|
61
|
+
const { contentLf, oldKey, newKey, contentHash, expectedContentHash, expectedAfterWriteHash, } = args;
|
|
62
|
+
// Hash gate (fail-safe): when the caller recorded any file-state hash, the
|
|
63
|
+
// current file must match one of the states THIS process produced — the
|
|
64
|
+
// post-commit bytes or the post-afterWrite (formatter) bytes. A file in any
|
|
65
|
+
// other state is an unrecognized third-party change; we refuse rather than
|
|
66
|
+
// re-resolve against it. The gate is skipped only when no hash was recorded.
|
|
67
|
+
const haveExpectedHash = expectedContentHash !== undefined || expectedAfterWriteHash !== undefined;
|
|
68
|
+
if (haveExpectedHash) {
|
|
69
|
+
const matchesKnownState = contentHash !== undefined &&
|
|
70
|
+
(contentHash === expectedContentHash ||
|
|
71
|
+
contentHash === expectedAfterWriteHash);
|
|
72
|
+
if (!matchesKnownState)
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (!oldKey)
|
|
76
|
+
return false;
|
|
77
|
+
if (newKey === "")
|
|
78
|
+
return countLfOccurrences(contentLf, oldKey) === 0;
|
|
79
|
+
if (!newKey.includes(oldKey)) {
|
|
80
|
+
return (countLfOccurrences(contentLf, oldKey) === 0 &&
|
|
81
|
+
countLfOccurrences(contentLf, newKey) >= 1);
|
|
82
|
+
}
|
|
83
|
+
const newOccurrences = [];
|
|
84
|
+
let pos = 0;
|
|
85
|
+
while (pos < contentLf.length) {
|
|
86
|
+
const idx = contentLf.indexOf(newKey, pos);
|
|
87
|
+
if (idx === -1)
|
|
88
|
+
break;
|
|
89
|
+
newOccurrences.push([idx, idx + newKey.length]);
|
|
90
|
+
pos = idx + newKey.length;
|
|
91
|
+
}
|
|
92
|
+
if (newOccurrences.length === 0)
|
|
93
|
+
return false;
|
|
94
|
+
pos = 0;
|
|
95
|
+
while (pos < contentLf.length) {
|
|
96
|
+
const idx = contentLf.indexOf(oldKey, pos);
|
|
97
|
+
if (idx === -1)
|
|
98
|
+
break;
|
|
99
|
+
const end = idx + oldKey.length;
|
|
100
|
+
if (!newOccurrences.some(([s, e]) => s <= idx && e >= end))
|
|
101
|
+
return false;
|
|
102
|
+
pos = end;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Session-scoped store of applied partial/full edits, keyed by normalized file
|
|
108
|
+
* path. `RuntimeCoordinator.resetForSession` clears it, so an applied record
|
|
109
|
+
* never outlives the session whose edit produced it. Records are bounded on
|
|
110
|
+
* both axes (per-file entries, and files) with oldest-entry eviction.
|
|
111
|
+
*/
|
|
112
|
+
export class PartialApplyRecordStore {
|
|
113
|
+
// Bounded on the PathKeyedMap itself (#2442): the file axis used to
|
|
114
|
+
// hand-roll `keys().next().value` here, which also evicted an unrelated
|
|
115
|
+
// file when a path ALREADY in the map was re-recorded at capacity.
|
|
116
|
+
files = new PathKeyedMap(normalizeMapKey, MAX_APPLIED_RECORD_FILES);
|
|
117
|
+
find(filePath, oldText, newText) {
|
|
118
|
+
if (!oldText)
|
|
119
|
+
return undefined;
|
|
120
|
+
const pairDigest = editPairDigest(oldText, newText);
|
|
121
|
+
const records = this.files.get(filePath) ?? [];
|
|
122
|
+
return records.find((r) => r.pairDigest === pairDigest);
|
|
123
|
+
}
|
|
124
|
+
record(filePath, oldText, newText, contentHash) {
|
|
125
|
+
if (!oldText)
|
|
126
|
+
return;
|
|
127
|
+
const pairDigest = editPairDigest(oldText, newText);
|
|
128
|
+
const records = this.files.get(filePath) ?? [];
|
|
129
|
+
const existing = records.findIndex((r) => r.pairDigest === pairDigest);
|
|
130
|
+
if (existing >= 0)
|
|
131
|
+
records.splice(existing, 1);
|
|
132
|
+
const entry = {
|
|
133
|
+
pairDigest,
|
|
134
|
+
contentHash: contentHash ?? hashFile(filePath),
|
|
135
|
+
appliedAtMs: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
if (records.length >= MAX_APPLIED_RECORDS_PER_FILE)
|
|
138
|
+
records.shift();
|
|
139
|
+
records.push(entry);
|
|
140
|
+
this.files.set(filePath, records); // the map bounds its own file axis
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stamps the post-afterWrite file hash onto an existing record so a later
|
|
144
|
+
* identical retry against the formatter-rewritten file is still recognized as
|
|
145
|
+
* already-applied (#2402). No-op when the pair was never recorded.
|
|
146
|
+
*/
|
|
147
|
+
noteAfterWriteHash(filePath, oldText, newText, afterWriteContentHash) {
|
|
148
|
+
if (!oldText)
|
|
149
|
+
return;
|
|
150
|
+
const pairDigest = editPairDigest(oldText, newText);
|
|
151
|
+
const record = this.files
|
|
152
|
+
.get(filePath)
|
|
153
|
+
?.find((r) => r.pairDigest === pairDigest);
|
|
154
|
+
if (record)
|
|
155
|
+
record.afterWriteContentHash = afterWriteContentHash;
|
|
156
|
+
}
|
|
157
|
+
clear() {
|
|
158
|
+
this.files.clear();
|
|
159
|
+
}
|
|
160
|
+
get fileCount() {
|
|
161
|
+
return this.files.size;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Locks are process-global in FileTime (module-global map keyed by resolved
|
|
165
|
+
// path), so one instance here serializes against every other FileTime user.
|
|
166
|
+
const fileLock = createFileTime("partial-edit-apply");
|
|
167
|
+
function hashBytes(content) {
|
|
168
|
+
return createHash("sha256").update(content).digest("hex");
|
|
169
|
+
}
|
|
170
|
+
function hashFile(filePath) {
|
|
171
|
+
try {
|
|
172
|
+
return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Internal rejection signal: raised only BEFORE any bytes are written, so a
|
|
180
|
+
* rejection can never be confused with a commit failure.
|
|
181
|
+
*/
|
|
182
|
+
class PartialApplyRejectionError extends Error {
|
|
183
|
+
reason;
|
|
184
|
+
detail;
|
|
185
|
+
constructor(reason, detail) {
|
|
186
|
+
super(detail);
|
|
187
|
+
this.reason = reason;
|
|
188
|
+
this.detail = detail;
|
|
189
|
+
this.name = "PartialApplyRejectionError";
|
|
190
|
+
}
|
|
12
191
|
}
|
|
13
192
|
/**
|
|
14
|
-
* Applies
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
193
|
+
* Applies preflight-approved spans from the original snapshot, then invokes the
|
|
194
|
+
* caller's normal post-edit bookkeeping/pipeline hook (#1053, #2402).
|
|
195
|
+
*
|
|
196
|
+
* Contract:
|
|
197
|
+
* - Spans are consumed as resolved; oldText is never re-searched against a
|
|
198
|
+
* changed buffer.
|
|
199
|
+
* - The whole batch is validated (snapshot identity, span bounds, span text,
|
|
200
|
+
* pairwise non-overlap) BEFORE any write; a rejection writes nothing and
|
|
201
|
+
* dispatches no post-edit pipeline.
|
|
202
|
+
* - The commit goes through the shared atomic writer under the file lock.
|
|
203
|
+
* - A committed write and a post-edit analysis failure are separate outcomes;
|
|
204
|
+
* the caller decides the message, and committed bytes are never reported as
|
|
205
|
+
* a retryable oldText miss.
|
|
18
206
|
*/
|
|
19
207
|
export async function applyPartiallyApplicableEdits(args) {
|
|
20
208
|
const startedAt = Date.now();
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
209
|
+
const summaryRef = args.summary;
|
|
210
|
+
const correlationId = args.correlationId;
|
|
211
|
+
const logBatchEvent = (entry) => logReadGuardEvent({ ...entry, correlationId });
|
|
212
|
+
const reject = (reason, detail) => {
|
|
213
|
+
logBatchEvent({
|
|
214
|
+
event: "edit_partial_apply_rejected",
|
|
215
|
+
filePath: args.filePath,
|
|
216
|
+
metadata: {
|
|
217
|
+
tool: "edit",
|
|
218
|
+
rejectionReason: reason,
|
|
219
|
+
detail,
|
|
220
|
+
editCount: args.edits.length,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
appliedCount: 0,
|
|
225
|
+
appliedTotal: 0,
|
|
226
|
+
appliedIndices: "",
|
|
227
|
+
postEditStatus: "not_run",
|
|
228
|
+
rejected: { reason, detail },
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
if (args.edits.length === 0) {
|
|
232
|
+
return reject("invalid_batch", "no preflight-approved edits were carried into the apply step");
|
|
233
|
+
}
|
|
234
|
+
const snapshot = args.edits[0].snapshot;
|
|
30
235
|
for (const edit of args.edits) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const replaced = replaceOnce(content, oldText, newText);
|
|
34
|
-
if (!replaced.changed) {
|
|
35
|
-
skipped.push(edit.originalIndex);
|
|
36
|
-
continue;
|
|
236
|
+
if (edit.snapshot.hash !== snapshot.hash) {
|
|
237
|
+
return reject("invalid_batch", "carried edits reference different snapshot identities; the batch must be rebuilt from one preflight");
|
|
37
238
|
}
|
|
38
|
-
content = replaced.content;
|
|
39
|
-
applied.push(edit.originalIndex);
|
|
40
239
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
240
|
+
// Validate + commit inside the file lock so a concurrent same-file writer
|
|
241
|
+
// (formatter, LSP edit, another session's partial apply) cannot interleave
|
|
242
|
+
// between the identity re-check and the rename.
|
|
243
|
+
const commit = () => {
|
|
244
|
+
const rawBytes = fs.readFileSync(args.filePath);
|
|
245
|
+
if (hashBytes(rawBytes) !== snapshot.hash) {
|
|
246
|
+
throw new PartialApplyRejectionError("stale_snapshot", "the file changed since the preflight resolved these edits; rebuild the edit from the current content");
|
|
247
|
+
}
|
|
248
|
+
const raw = rawBytes.toString("utf8");
|
|
249
|
+
const ending = detectLineEnding(raw);
|
|
250
|
+
const lf = normalizeToLF(raw);
|
|
251
|
+
// The span-text equality is the single span guard: `slice` clamps to the
|
|
252
|
+
// buffer, so an out-of-bounds, inverted, or drifted span cannot produce
|
|
253
|
+
// the approved text and is rejected here as one rejection class. A
|
|
254
|
+
// separate bounds check would only re-label the same failure.
|
|
255
|
+
const ordered = [...args.edits].sort((a, b) => a.spanStart - b.spanStart);
|
|
256
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
257
|
+
const edit = ordered[i];
|
|
258
|
+
if (lf.slice(edit.spanStart, edit.spanEnd) !== edit.appliedSpanText) {
|
|
259
|
+
throw new PartialApplyRejectionError("span_changed", `edits[${edit.originalIndex}] span no longer holds the text the preflight approved; the batch must be rebuilt from a fresh preflight`);
|
|
260
|
+
}
|
|
261
|
+
if (i > 0) {
|
|
262
|
+
const prev = ordered[i - 1];
|
|
263
|
+
if (edit.spanStart < prev.spanEnd) {
|
|
264
|
+
throw new PartialApplyRejectionError("span_overlap", `edits[${prev.originalIndex}] and edits[${edit.originalIndex}] resolved to overlapping spans; extend one oldText to disambiguate the location`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
let committed = lf;
|
|
269
|
+
for (let i = ordered.length - 1; i >= 0; i -= 1) {
|
|
270
|
+
const edit = ordered[i];
|
|
271
|
+
const newText = normalizeToLF(edit.newText ?? "");
|
|
272
|
+
committed =
|
|
273
|
+
committed.slice(0, edit.spanStart) +
|
|
274
|
+
newText +
|
|
275
|
+
committed.slice(edit.spanEnd);
|
|
45
276
|
}
|
|
277
|
+
const output = restoreLineEndings(committed, ending);
|
|
278
|
+
const targetPath = fs.lstatSync(args.filePath).isSymbolicLink()
|
|
279
|
+
? fs.realpathSync(args.filePath)
|
|
280
|
+
: args.filePath;
|
|
281
|
+
const mode = fs.statSync(args.filePath).mode & 0o7777;
|
|
282
|
+
writeFileAtomic(targetPath, output, {
|
|
283
|
+
bestEffort: false,
|
|
284
|
+
mode,
|
|
285
|
+
});
|
|
286
|
+
return hashBytes(Buffer.from(output, "utf8"));
|
|
287
|
+
};
|
|
288
|
+
let commitStatus = "committed";
|
|
289
|
+
let committedContentHash;
|
|
290
|
+
try {
|
|
291
|
+
await fileLock.withLock(args.filePath, async () => {
|
|
292
|
+
committedContentHash = commit();
|
|
293
|
+
for (const edit of args.edits) {
|
|
294
|
+
args.recordStore?.record(args.filePath, edit.oldText, edit.newText, committedContentHash);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
46
297
|
}
|
|
47
298
|
catch (error) {
|
|
299
|
+
if (error instanceof PartialApplyRejectionError) {
|
|
300
|
+
commitStatus = "not_attempted";
|
|
301
|
+
const baseSummary = summaryRef ??
|
|
302
|
+
createReadGuardEditBatchSummary({
|
|
303
|
+
requestedIndexes: boundedEditIndexes(args.edits.map((edit) => edit.originalIndex)),
|
|
304
|
+
requestedTotal: args.edits.length,
|
|
305
|
+
});
|
|
306
|
+
const summary = createReadGuardEditBatchSummary({
|
|
307
|
+
...baseSummary,
|
|
308
|
+
participantIds: correlationId
|
|
309
|
+
? [...baseSummary.participantIds, correlationId]
|
|
310
|
+
: baseSummary.participantIds,
|
|
311
|
+
participantTotal: correlationId
|
|
312
|
+
? baseSummary.participantTotal + 1
|
|
313
|
+
: baseSummary.participantTotal,
|
|
314
|
+
commitStatus,
|
|
315
|
+
postEditStatus: "not_run",
|
|
316
|
+
terminalStatus: "blocked",
|
|
317
|
+
durationMs: Date.now() - startedAt,
|
|
318
|
+
});
|
|
319
|
+
logBatchEvent({
|
|
320
|
+
event: "edit_batch_summary",
|
|
321
|
+
filePath: args.filePath,
|
|
322
|
+
metadata: { tool: "edit", editBatchSummary: summary },
|
|
323
|
+
});
|
|
324
|
+
return reject(error.reason, error.detail);
|
|
325
|
+
}
|
|
48
326
|
commitStatus = "failed";
|
|
49
|
-
if (
|
|
327
|
+
if (summaryRef || correlationId) {
|
|
50
328
|
const summary = createReadGuardEditBatchSummary({
|
|
51
|
-
...(
|
|
52
|
-
requestedIndexes: boundedEditIndexes(args.edits.
|
|
329
|
+
...(summaryRef ?? {
|
|
330
|
+
requestedIndexes: boundedEditIndexes(args.edits.map((edit) => edit.originalIndex)),
|
|
53
331
|
requestedTotal: args.edits.length,
|
|
54
332
|
}),
|
|
55
333
|
appliedIndexes: [],
|
|
56
334
|
appliedTotal: 0,
|
|
57
|
-
participantIds:
|
|
58
|
-
participantTotal:
|
|
335
|
+
participantIds: correlationId ? [correlationId] : undefined,
|
|
336
|
+
participantTotal: correlationId ? 1 : undefined,
|
|
59
337
|
commitStatus,
|
|
60
338
|
terminalStatus: "failed",
|
|
61
339
|
durationMs: Date.now() - startedAt,
|
|
62
340
|
});
|
|
63
|
-
|
|
341
|
+
logBatchEvent({
|
|
64
342
|
event: "edit_batch_summary",
|
|
65
|
-
correlationId: args.correlationId,
|
|
66
343
|
filePath: args.filePath,
|
|
67
344
|
metadata: { tool: "edit", editBatchSummary: summary },
|
|
68
345
|
});
|
|
@@ -71,89 +348,91 @@ export async function applyPartiallyApplicableEdits(args) {
|
|
|
71
348
|
}
|
|
72
349
|
let postEditOutput;
|
|
73
350
|
let postEditStatus = "not_run";
|
|
74
|
-
if (
|
|
351
|
+
if (args.afterWrite) {
|
|
75
352
|
try {
|
|
76
353
|
postEditOutput = await args.afterWrite();
|
|
77
354
|
postEditStatus = "succeeded";
|
|
355
|
+
// #2402: the afterWrite pipeline (a formatter) may have rewritten the
|
|
356
|
+
// file. Record the new state so an identical retry against the
|
|
357
|
+
// formatted bytes is still recognized as already-applied instead of
|
|
358
|
+
// falling back to oldText resolution and re-applying. Only stamped when
|
|
359
|
+
// the pipeline actually changed the bytes; the post-commit hash already
|
|
360
|
+
// covers a no-op pass.
|
|
361
|
+
if (args.recordStore) {
|
|
362
|
+
const afterWriteHash = hashFile(args.filePath);
|
|
363
|
+
if (afterWriteHash !== undefined &&
|
|
364
|
+
afterWriteHash !== committedContentHash) {
|
|
365
|
+
for (const edit of args.edits) {
|
|
366
|
+
args.recordStore.noteAfterWriteHash(args.filePath, edit.oldText, edit.newText, afterWriteHash);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
78
370
|
}
|
|
79
371
|
catch (error) {
|
|
80
372
|
// The write is committed. Preserve the existing caller behavior for
|
|
81
373
|
// uninstrumented callers; the read-guard path records and handles it.
|
|
82
374
|
postEditStatus = "failed";
|
|
83
|
-
if (!
|
|
375
|
+
if (!summaryRef && !correlationId)
|
|
84
376
|
throw error;
|
|
85
377
|
}
|
|
86
378
|
}
|
|
87
|
-
|
|
379
|
+
const appliedIndexes = boundedEditIndexes(args.edits.map((edit) => edit.originalIndex));
|
|
380
|
+
if (!summaryRef && !correlationId) {
|
|
88
381
|
return {
|
|
89
|
-
appliedCount:
|
|
90
|
-
|
|
382
|
+
appliedCount: args.edits.length,
|
|
383
|
+
appliedTotal: args.edits.length,
|
|
384
|
+
appliedIndices: formatBoundedEditIndexes(appliedIndexes),
|
|
91
385
|
postEditOutput,
|
|
386
|
+
postEditStatus,
|
|
92
387
|
};
|
|
93
388
|
}
|
|
94
|
-
const baseSummary =
|
|
389
|
+
const baseSummary = summaryRef ??
|
|
95
390
|
createReadGuardEditBatchSummary({
|
|
96
|
-
requestedIndexes:
|
|
391
|
+
requestedIndexes: appliedIndexes,
|
|
97
392
|
requestedTotal: args.edits.length,
|
|
98
|
-
resolvedIndexes:
|
|
393
|
+
resolvedIndexes: appliedIndexes,
|
|
99
394
|
resolvedTotal: args.edits.length,
|
|
100
395
|
});
|
|
101
396
|
const summary = createReadGuardEditBatchSummary({
|
|
102
397
|
...baseSummary,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
code: "replace_once_skipped",
|
|
108
|
-
})),
|
|
109
|
-
],
|
|
110
|
-
rejectedTotal: baseSummary.rejectedTotal + skipped.length,
|
|
111
|
-
appliedIndexes: applied,
|
|
112
|
-
appliedTotal: applied.length,
|
|
113
|
-
participantIds: args.correlationId
|
|
114
|
-
? [...baseSummary.participantIds, args.correlationId]
|
|
398
|
+
appliedIndexes,
|
|
399
|
+
appliedTotal: args.edits.length,
|
|
400
|
+
participantIds: correlationId
|
|
401
|
+
? [...baseSummary.participantIds, correlationId]
|
|
115
402
|
: baseSummary.participantIds,
|
|
116
|
-
participantTotal:
|
|
403
|
+
participantTotal: correlationId
|
|
117
404
|
? baseSummary.participantTotal + 1
|
|
118
405
|
: baseSummary.participantTotal,
|
|
119
406
|
commitStatus,
|
|
120
407
|
postEditStatus,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
? "skipped"
|
|
125
|
-
: "success",
|
|
408
|
+
// Commit failures rethrow above, so reaching here means the commit
|
|
409
|
+
// landed; only the post-edit analysis can fail from this point on.
|
|
410
|
+
terminalStatus: postEditStatus === "failed" ? "failed" : "success",
|
|
126
411
|
durationMs: Date.now() - startedAt,
|
|
127
412
|
});
|
|
128
413
|
if (postEditStatus === "failed") {
|
|
129
|
-
|
|
414
|
+
logBatchEvent({
|
|
130
415
|
event: "edit_post_edit_pipeline_failed",
|
|
131
|
-
correlationId: args.correlationId,
|
|
132
416
|
filePath: args.filePath,
|
|
133
417
|
metadata: {
|
|
134
418
|
tool: "edit",
|
|
135
419
|
commitStatus,
|
|
136
|
-
appliedCount:
|
|
137
|
-
appliedIndexes
|
|
138
|
-
appliedTotal:
|
|
420
|
+
appliedCount: args.edits.length,
|
|
421
|
+
appliedIndexes,
|
|
422
|
+
appliedTotal: args.edits.length,
|
|
139
423
|
},
|
|
140
424
|
});
|
|
141
425
|
}
|
|
142
|
-
|
|
426
|
+
logBatchEvent({
|
|
143
427
|
event: "edit_batch_summary",
|
|
144
|
-
correlationId: args.correlationId,
|
|
145
428
|
filePath: args.filePath,
|
|
146
429
|
metadata: { tool: "edit", editBatchSummary: summary },
|
|
147
430
|
});
|
|
148
431
|
return {
|
|
149
|
-
appliedCount:
|
|
150
|
-
appliedTotal:
|
|
151
|
-
appliedIndices: formatBoundedEditIndexes(
|
|
432
|
+
appliedCount: args.edits.length,
|
|
433
|
+
appliedTotal: args.edits.length,
|
|
434
|
+
appliedIndices: formatBoundedEditIndexes(appliedIndexes),
|
|
152
435
|
postEditOutput,
|
|
153
|
-
skippedCount: skipped.length,
|
|
154
|
-
skippedTotal: skipped.length,
|
|
155
|
-
skippedIndices: formatBoundedEditIndexes(skipped),
|
|
156
|
-
indexesTruncated: applied.length > 100 || skipped.length > 100 || summary.indexesTruncated,
|
|
157
436
|
postEditStatus,
|
|
158
437
|
summary,
|
|
159
438
|
};
|
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
import { logLatency } from "./latency-logger.js";
|
|
2
|
+
import { getProcessSingleton } from "./process-singletons.js";
|
|
2
3
|
// Verified guesses are expected host behavior, not degradations. Keep their
|
|
3
4
|
// exact session count in memory and publish one bounded row at session end.
|
|
4
5
|
// This tally is intentionally memory-only: a process crash loses its count.
|
|
5
6
|
// The rollup is best-effort session telemetry, not durable accounting.
|
|
6
|
-
|
|
7
|
+
//
|
|
8
|
+
// Process-singleton backed (AGENTS.md catalog shape 25), NOT a module-scope
|
|
9
|
+
// `let` like `bus-events-logger.ts`'s rollup: a `let` here exists once per
|
|
10
|
+
// module EVALUATION, not once per process, and #2146 measured one pid
|
|
11
|
+
// evaluating this graph nine times — a bare `let` would only ever count the
|
|
12
|
+
// guesses routed through its own copy, undercounting every rollup that is not
|
|
13
|
+
// reached through the first evaluation. This is the same decision its sibling
|
|
14
|
+
// `session-start-observability.ts`'s bind rollup made in #2249.
|
|
15
|
+
const VERIFIED_GUESS_FAMILY = "path-attribution-verified-guess-count";
|
|
16
|
+
const VERIFIED_GUESS_VERSION = 1;
|
|
17
|
+
function verifiedGuessCount() {
|
|
18
|
+
return getProcessSingleton(VERIFIED_GUESS_FAMILY, VERIFIED_GUESS_VERSION, () => ({
|
|
19
|
+
count: 0,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
7
22
|
export function recordVerifiedPathAttributionGuess() {
|
|
8
|
-
verifiedGuessCount += 1;
|
|
23
|
+
verifiedGuessCount().count += 1;
|
|
9
24
|
}
|
|
10
25
|
export function getVerifiedPathAttributionGuessCount() {
|
|
11
|
-
return verifiedGuessCount;
|
|
26
|
+
return verifiedGuessCount().count;
|
|
12
27
|
}
|
|
13
28
|
export function emitVerifiedPathAttributionRollup(filePath) {
|
|
14
|
-
|
|
29
|
+
const cell = verifiedGuessCount();
|
|
30
|
+
if (cell.count === 0)
|
|
15
31
|
return;
|
|
16
32
|
logLatency({
|
|
17
33
|
type: "phase",
|
|
18
34
|
phase: "path_attribution_verified_rollup",
|
|
19
35
|
filePath,
|
|
20
36
|
durationMs: 0,
|
|
21
|
-
metadata: { count:
|
|
37
|
+
metadata: { count: cell.count },
|
|
22
38
|
});
|
|
23
|
-
|
|
39
|
+
cell.count = 0;
|
|
24
40
|
}
|
|
25
41
|
export function resetVerifiedPathAttributionGuessCount() {
|
|
26
|
-
verifiedGuessCount = 0;
|
|
42
|
+
verifiedGuessCount().count = 0;
|
|
27
43
|
}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* write (last-writer-wins), matching a plain `Map`'s "the key you set is the
|
|
22
22
|
* key you see" intuition while still de-duplicating by normalized identity.
|
|
23
23
|
*
|
|
24
|
+
* Optionally BOUNDED: pass `maxEntries` and the backing store becomes a
|
|
25
|
+
* `BoundedFifoMap` (see the constructor). Unbounded by default.
|
|
26
|
+
*
|
|
24
27
|
* Choose the normalizer to match how the keyed state is used:
|
|
25
28
|
* - `normalizeEphemeralMapKey` (path-utils.ts) — cheap slash-fold +
|
|
26
29
|
* win32-lowercase, NO filesystem I/O — for hot, single-process, in-memory
|
|
@@ -28,11 +31,27 @@
|
|
|
28
31
|
* - `normalizeMapKey` — realpath-canonicalizing — for long-lived state shared
|
|
29
32
|
* across call sites where symlink/real-casing resolution matters.
|
|
30
33
|
*/
|
|
34
|
+
import { BoundedFifoMap } from "./bounded-cache.js";
|
|
31
35
|
export class PathKeyedMap {
|
|
32
36
|
normalize;
|
|
33
|
-
store
|
|
34
|
-
|
|
37
|
+
store;
|
|
38
|
+
/**
|
|
39
|
+
* `maxEntries` bounds the map with insertion-order (FIFO) eviction, on
|
|
40
|
+
* `BoundedFifoMap` — the repo's one eviction implementation (#2442). Omit
|
|
41
|
+
* it for an unbounded map. A path-keyed map that needs a bound gets it
|
|
42
|
+
* here rather than hand-rolling `keys().next().value` at the call site,
|
|
43
|
+
* which is what `PartialApplyRecordStore` did before #2442's review round.
|
|
44
|
+
*
|
|
45
|
+
* FIFO, not LRU, and evicting AFTER the write, not before: a `set` of an
|
|
46
|
+
* already-resident path is an update, and must not drop an unrelated file
|
|
47
|
+
* to make room for a key that needs none.
|
|
48
|
+
*/
|
|
49
|
+
constructor(normalize, maxEntries) {
|
|
35
50
|
this.normalize = normalize;
|
|
51
|
+
this.store =
|
|
52
|
+
maxEntries === undefined
|
|
53
|
+
? new Map()
|
|
54
|
+
: new BoundedFifoMap(maxEntries);
|
|
36
55
|
}
|
|
37
56
|
get size() {
|
|
38
57
|
return this.store.size;
|