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,11 +1,25 @@
|
|
|
1
1
|
/** Bounded, process-local telemetry for behavior degraded during one session. */
|
|
2
2
|
import { logExtension } from "./extension-log.js";
|
|
3
|
-
import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
|
|
3
|
+
import { DEGRADATION_ENTRIES_PER_KIND, LEDGER_FIELD_MAX, truncateForLedger, } from "./ledger-bounds.js";
|
|
4
4
|
import { logLatency } from "./latency-logger.js";
|
|
5
|
-
import { getSinkWriteFailures, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
5
|
+
import { getSinkRotations, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
6
|
+
// #2146: pulled at READ time, never pushed. `process-singletons.ts` is a
|
|
7
|
+
// dependency leaf on purpose — it cannot import this module without closing a
|
|
8
|
+
// no-client-cycles cycle through instance-registry/instance-reaper — so the
|
|
9
|
+
// ledger reaches IN for its reset log, the same inversion `getSinkWriteFailures`
|
|
10
|
+
// above uses.
|
|
11
|
+
import { getProcessSingletonResets, PROCESS_SINGLETON_RESET_KIND, } from "./process-singletons.js";
|
|
12
|
+
// #2506: same inversion, same reason — the log-dir resolver cannot import this
|
|
13
|
+
// module (directly OR dynamically). THIS module is on a no-client-cycles cycle
|
|
14
|
+
// via extension-log.ts; the resolver's whole correctness argument is that it
|
|
15
|
+
// sits off every cycle, so an edge in that direction would put it back on one.
|
|
16
|
+
// `getGlobalPiLensLogDir()`'s probe-home-redirect event is therefore written
|
|
17
|
+
// into `probe-home-state.ts` and read back here instead. See that module's doc
|
|
18
|
+
// comment for the full account.
|
|
19
|
+
import { getProbeHomeRedirectEvent } from "./probe-home-state.js";
|
|
6
20
|
// Re-exported so existing importers keep one name for the ledger's bound.
|
|
7
21
|
export { LEDGER_FIELD_MAX, truncateForLedger };
|
|
8
|
-
const ENTRIES_PER_KIND =
|
|
22
|
+
const ENTRIES_PER_KIND = DEGRADATION_ENTRIES_PER_KIND;
|
|
9
23
|
const MAX_DISTINCT_KINDS = 32;
|
|
10
24
|
const OVERFLOW_KIND = "other";
|
|
11
25
|
const groups = new Map();
|
|
@@ -59,7 +73,7 @@ export function recordDegradationOnce(record) {
|
|
|
59
73
|
return;
|
|
60
74
|
onceKeys.add(key);
|
|
61
75
|
if (recordDegradation({ kind, subject, reason: record.reason })) {
|
|
62
|
-
logDurableDegradation(kind, subject, 1);
|
|
76
|
+
logDurableDegradation(kind, subject, 1, record.metadata, record.code);
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
catch (error) {
|
|
@@ -108,7 +122,7 @@ export function incrementDegradationCount(record) {
|
|
|
108
122
|
// Durable rows use the summary's admission and emit the first event and
|
|
109
123
|
// power-of-two milestones only, keeping the sink bounded.
|
|
110
124
|
if (admitted && isPowerOfTwo(count)) {
|
|
111
|
-
logDurableDegradation(kind, subject, count);
|
|
125
|
+
logDurableDegradation(kind, subject, count, record.metadata, record.code);
|
|
112
126
|
}
|
|
113
127
|
return count === 1;
|
|
114
128
|
}
|
|
@@ -124,15 +138,37 @@ export function incrementDegradationCount(record) {
|
|
|
124
138
|
* redaction, and write queue. The subject and kind were already bounded by the
|
|
125
139
|
* ledger before reaching this seam.
|
|
126
140
|
*/
|
|
127
|
-
function logDurableDegradation(kind, subject, count) {
|
|
141
|
+
function logDurableDegradation(kind, subject, count, metadata, code) {
|
|
142
|
+
const boundedMetadata = boundLedgerMetadata(metadata);
|
|
128
143
|
logLatency({
|
|
129
144
|
type: "phase",
|
|
130
145
|
phase: "degradation_ledger",
|
|
131
146
|
filePath: subject,
|
|
132
147
|
durationMs: 0,
|
|
133
|
-
metadata: {
|
|
148
|
+
metadata: {
|
|
149
|
+
...boundedMetadata,
|
|
150
|
+
kind,
|
|
151
|
+
subject,
|
|
152
|
+
count,
|
|
153
|
+
ledgerGeneration,
|
|
154
|
+
// Reserved row field, written AFTER the bounded caller metadata so the
|
|
155
|
+
// `MAX_METADATA_KEYS` cap cannot evict the stable code (#2418).
|
|
156
|
+
...(code ? { code } : {}),
|
|
157
|
+
},
|
|
134
158
|
});
|
|
135
159
|
}
|
|
160
|
+
const MAX_METADATA_KEYS = 8;
|
|
161
|
+
function boundLedgerMetadata(metadata) {
|
|
162
|
+
if (!metadata)
|
|
163
|
+
return {};
|
|
164
|
+
const entries = Object.entries(metadata);
|
|
165
|
+
const kept = entries.slice(0, MAX_METADATA_KEYS);
|
|
166
|
+
const bounded = Object.fromEntries(kept.map(([key, value]) => [key, truncateForLedger(value)]));
|
|
167
|
+
const dropped = entries.length - kept.length;
|
|
168
|
+
if (dropped > 0)
|
|
169
|
+
bounded.metadataDropped = dropped;
|
|
170
|
+
return bounded;
|
|
171
|
+
}
|
|
136
172
|
function isPowerOfTwo(value) {
|
|
137
173
|
return value > 0 && (value & (value - 1)) === 0;
|
|
138
174
|
}
|
|
@@ -165,6 +201,70 @@ export function getDegradationSummary() {
|
|
|
165
201
|
})),
|
|
166
202
|
});
|
|
167
203
|
}
|
|
204
|
+
// #2505, same read-time fold: a mid-session rotation is visible without
|
|
205
|
+
// this module writing about it through the very sink family it is
|
|
206
|
+
// reporting on — see the `log-sink-rotated` doc comment on
|
|
207
|
+
// `DegradationKind`.
|
|
208
|
+
const sinkRotations = getSinkRotations();
|
|
209
|
+
const rotated = sinkRotations.filter((sink) => sink.rotationCount > 0);
|
|
210
|
+
if (rotated.length > 0) {
|
|
211
|
+
summary.push({
|
|
212
|
+
kind: "log-sink-rotated",
|
|
213
|
+
count: rotated.reduce((total, sink) => total + sink.rotationCount, 0),
|
|
214
|
+
droppedCount: 0,
|
|
215
|
+
latestReasons: rotated.map((sink) => ({
|
|
216
|
+
subject: truncateForLedger(sink.file),
|
|
217
|
+
reason: truncateForLedger(`${sink.rotationCount} rotation(s) at the configured byte bound`),
|
|
218
|
+
})),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// A rotation the writer ATTEMPTED and could not complete is a different
|
|
222
|
+
// fact from a rotation that happened, and the only signal that a sink is
|
|
223
|
+
// growing past its bound right now (#2505 review F2).
|
|
224
|
+
const rotateFailed = sinkRotations.filter((sink) => sink.failureCount > 0);
|
|
225
|
+
if (rotateFailed.length > 0) {
|
|
226
|
+
summary.push({
|
|
227
|
+
kind: "log-sink-rotate-failed",
|
|
228
|
+
count: rotateFailed.reduce((total, sink) => total + sink.failureCount, 0),
|
|
229
|
+
droppedCount: 0,
|
|
230
|
+
latestReasons: rotateFailed.map((sink) => ({
|
|
231
|
+
subject: truncateForLedger(sink.file),
|
|
232
|
+
reason: truncateForLedger(`${sink.failureCount} failed rotation attempt(s); this sink is growing past its byte bound`),
|
|
233
|
+
})),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// #2146, same read-time fold: process-singleton resets live in the leaf
|
|
237
|
+
// module's own bounded log. One entry per family, so this group's count is
|
|
238
|
+
// the number of families this build could not adopt, never an event tally.
|
|
239
|
+
const singletonResets = getProcessSingletonResets();
|
|
240
|
+
if (singletonResets.length > 0) {
|
|
241
|
+
summary.push({
|
|
242
|
+
kind: PROCESS_SINGLETON_RESET_KIND,
|
|
243
|
+
count: singletonResets.length,
|
|
244
|
+
droppedCount: 0,
|
|
245
|
+
latestReasons: singletonResets.map((reset) => ({
|
|
246
|
+
subject: truncateForLedger(reset.family),
|
|
247
|
+
reason: truncateForLedger(reset.reason),
|
|
248
|
+
})),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// #2506, same read-time fold: `getGlobalPiLensLogDir()`'s probe-home redirect
|
|
252
|
+
// fires at most once per process (see `probe-home-state.ts`), so this is a
|
|
253
|
+
// presence check, not a tally.
|
|
254
|
+
const probeHomeRedirect = getProbeHomeRedirectEvent();
|
|
255
|
+
if (probeHomeRedirect) {
|
|
256
|
+
summary.push({
|
|
257
|
+
kind: "global-dir-probe-redirect",
|
|
258
|
+
count: 1,
|
|
259
|
+
droppedCount: 0,
|
|
260
|
+
latestReasons: [
|
|
261
|
+
{
|
|
262
|
+
subject: truncateForLedger(probeHomeRedirect.probeHome),
|
|
263
|
+
reason: truncateForLedger(`PI_LENS_HOME unset with cwd in an agent worktree/tmp probe context (${probeHomeRedirect.cwd}), or PILENS_PROBE=1 forced it; LOGS redirected away from the real home directory (tools, bin and instances.json are unaffected)`),
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
});
|
|
267
|
+
}
|
|
168
268
|
return summary;
|
|
169
269
|
}
|
|
170
270
|
function isRenderableSummary(value) {
|
|
@@ -183,6 +283,34 @@ function isRenderableSummary(value) {
|
|
|
183
283
|
typeof entry.reason === "string"));
|
|
184
284
|
});
|
|
185
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Kinds that record something the system did ON PURPOSE, correctly, and
|
|
288
|
+
* that a reader only needs a tally of — never a call to action (#2505
|
|
289
|
+
* review). A routine log rotation at the configured bound is the writer
|
|
290
|
+
* working as designed; giving it the same warning marker a real
|
|
291
|
+
* degradation gets trains the reader to ignore the marker. The FAILED
|
|
292
|
+
* rotation is the line that has to stand out, so it is deliberately NOT
|
|
293
|
+
* in this set.
|
|
294
|
+
*/
|
|
295
|
+
const INFORMATIONAL_DEGRADATION_KINDS = new Set([
|
|
296
|
+
"log-sink-rotated",
|
|
297
|
+
// #2504 review round 8 (S1): both fire on the ordinary re-edit cadence
|
|
298
|
+
// (a carry-forward window closing, or a session boundary) and are
|
|
299
|
+
// self-healing -- the next turn's analysis or deferral re-observes the
|
|
300
|
+
// file -- so a `⚠` overstates them the same way a routine log rotation
|
|
301
|
+
// would.
|
|
302
|
+
"actionable-warnings-inband-superseded",
|
|
303
|
+
"actionable-warnings-deferred-superseded",
|
|
304
|
+
// #2523: a hook await abandoned because the session is tearing down. The
|
|
305
|
+
// teardown signal firing is the design working, so it is a tally; the
|
|
306
|
+
// BUDGET blow-out (`hook-await-exceeded`) is the line that has to stand
|
|
307
|
+
// out, and a caller's own Escape records nothing at all.
|
|
308
|
+
"hook-await-abandoned",
|
|
309
|
+
// #2524: can fire on a query that ultimately settles `ok` (see the kind's
|
|
310
|
+
// doc comment above) and is frequent/self-healing by design — a `⚠` would
|
|
311
|
+
// cry wolf on the sampler's ordinary best-effort data loss.
|
|
312
|
+
"resource-sampler-scanner-escalated",
|
|
313
|
+
]);
|
|
186
314
|
export function renderDegradationLines(summary = getDegradationSummary()) {
|
|
187
315
|
if (!isRenderableSummary(summary))
|
|
188
316
|
return [];
|
|
@@ -191,6 +319,9 @@ export function renderDegradationLines(summary = getDegradationSummary()) {
|
|
|
191
319
|
return [
|
|
192
320
|
"Degradations:",
|
|
193
321
|
...summary.map((group) => {
|
|
322
|
+
if (INFORMATIONAL_DEGRADATION_KINDS.has(group.kind)) {
|
|
323
|
+
return ` ${group.kind}: ${group.count}`;
|
|
324
|
+
}
|
|
194
325
|
const latest = group.latestReasons.at(-1);
|
|
195
326
|
return ` ⚠ ${group.kind}: ${group.count}${latest ? ` — ${latest.subject}: ${latest.reason}` : ""}`;
|
|
196
327
|
}),
|
|
@@ -218,6 +349,33 @@ export function resetDegradationLedger() {
|
|
|
218
349
|
// process-lifetime latch too — it re-arms alongside the rest of the
|
|
219
350
|
// ledger rather than surviving past the session that observed it.
|
|
220
351
|
resetSinkWriteFailures();
|
|
352
|
+
// #2505, same catalog shape 17 re-arm: a rotation tally recurs (new writes
|
|
353
|
+
// keep crossing the bound), so clearing it costs nothing and a later
|
|
354
|
+
// session re-observes the fact fresh.
|
|
355
|
+
resetSinkRotations();
|
|
356
|
+
// #2146 review F3: the OTHER pulled source, `getProcessSingletonResets()`,
|
|
357
|
+
// deliberately does NOT re-arm here, and the difference from its neighbour
|
|
358
|
+
// above is the point. A sink write failure recurs — new writes fail, so
|
|
359
|
+
// clearing the tally costs nothing and a later session re-observes the
|
|
360
|
+
// problem. A process-singleton reset happens once, at module-evaluation
|
|
361
|
+
// time, and cannot recur: after it, the container holds only compatible
|
|
362
|
+
// cells. Clearing it would show the fact in the first session's
|
|
363
|
+
// `pilens_health` and hide it from every session after, which is exactly
|
|
364
|
+
// when someone reads that line. The row is bounded independently of the
|
|
365
|
+
// session (one entry per family, capped at 16), so leaving it costs a fixed
|
|
366
|
+
// handful of lines and keeps a process-scope fact visible for the process's
|
|
367
|
+
// life. Deliberate exception to catalog shape 17, not an oversight.
|
|
368
|
+
//
|
|
369
|
+
// `getProbeHomeRedirectEvent()` (#2506) is the same shape as the
|
|
370
|
+
// process-singleton case above and for the same reason: the redirect is
|
|
371
|
+
// resolved at most once per PROCESS (memoized in the `globalThis` slot
|
|
372
|
+
// `probe-home-state.ts` owns, not a session-scoped one), so it cannot
|
|
373
|
+
// recur within this process's life either — clearing it here would hide
|
|
374
|
+
// the fact from every session after the first in the same probe process.
|
|
221
375
|
}
|
|
222
|
-
|
|
376
|
+
// Re-exported so every existing importer keeps its specifier. The value now
|
|
377
|
+
// lives in the `ledger-bounds.js` leaf (#2426), so a producer that only needs
|
|
378
|
+
// the BOUND does not have to import the ledger — and cannot be broken by a test
|
|
379
|
+
// that mocks it wholesale.
|
|
380
|
+
export { DEGRADATION_ENTRIES_PER_KIND };
|
|
223
381
|
export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
|
|
@@ -153,3 +153,17 @@ export function formatRetirementNote(deadLines) {
|
|
|
153
153
|
return (`Retired after this delivery${cited}: the file no longer has these lines, ` +
|
|
154
154
|
"so this finding cannot be re-confirmed and will not be served again.");
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The one-line note a surface appends when it RETIRES a dependency-drift
|
|
158
|
+
* demotion at its delivery cap (#1950). Deliberately worded differently from
|
|
159
|
+
* {@link formatRetirementNote}: that one means the finding is provably
|
|
160
|
+
* unrecoverable (the cited lines are gone), while THIS retirement is a
|
|
161
|
+
* bounded-noise cap on a record that is still recoverable — a fresh dispatch
|
|
162
|
+
* of the file could still confirm or clear it. Saying "cannot be re-confirmed"
|
|
163
|
+
* here would be false.
|
|
164
|
+
*/
|
|
165
|
+
export function formatDeliveryCapNote(deliveryCount) {
|
|
166
|
+
return (`Not shown again after ${deliveryCount} deliveries with no re-run: ` +
|
|
167
|
+
"this finding may still be accurate, but re-serving it further adds no " +
|
|
168
|
+
"new information. A fresh dispatch of this file will re-confirm or clear it.");
|
|
169
|
+
}
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
import { createSubsystemLogger } from "./extension-log.js";
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
+
import { mapWithConcurrency } from "./map-with-concurrency.js";
|
|
14
15
|
import { findNodeToolBinary } from "./package-manager.js";
|
|
15
16
|
import { isFullyQualified } from "./path-utils.js";
|
|
16
17
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
18
|
+
import { compareOrdinal } from "./string-utils.js";
|
|
17
19
|
import { createAvailabilityChecker, discoverManagedTool, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
18
20
|
import { createAvailabilityLatch, } from "./dispatch/runners/utils/availability-policy.js";
|
|
19
21
|
/**
|
|
@@ -137,28 +139,16 @@ async function resolvedCommandIsStale(resolved, spawnableCache) {
|
|
|
137
139
|
}
|
|
138
140
|
/**
|
|
139
141
|
* Run `mapper` over `items` with at most `concurrency` in flight at once.
|
|
142
|
+
*
|
|
140
143
|
* Exported (#1810 review F6) so `clients/dispatch/runners/biome-check.ts`'s
|
|
141
144
|
* `resolveBiomeFixKinds` can reuse this exact worker-pool shape for its
|
|
142
|
-
* `biome explain` fan-out instead of carrying a second copy
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
+
* `biome explain` fan-out instead of carrying a second copy. #2504 moved the
|
|
146
|
+
* VALUE to the zero-dependency `map-with-concurrency.js` leaf — a third caller
|
|
147
|
+
* (`runtime-turn.ts`) needs the pool without pulling this analyzer client into
|
|
148
|
+
* the eager startup graph — and re-exports it here so every existing importer
|
|
149
|
+
* keeps its specifier. This stays the helper's documented home.
|
|
145
150
|
*/
|
|
146
|
-
export
|
|
147
|
-
if (items.length === 0)
|
|
148
|
-
return;
|
|
149
|
-
let nextIndex = 0;
|
|
150
|
-
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
151
|
-
const worker = async () => {
|
|
152
|
-
while (true) {
|
|
153
|
-
const index = nextIndex++;
|
|
154
|
-
if (index >= items.length)
|
|
155
|
-
return;
|
|
156
|
-
await mapper(items[index]);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
const workers = Array.from({ length: workerCount }, () => worker());
|
|
160
|
-
await Promise.all(workers);
|
|
161
|
-
}
|
|
151
|
+
export { mapWithConcurrency };
|
|
162
152
|
// --- Client ---
|
|
163
153
|
export class DependencyChecker {
|
|
164
154
|
madgeAvailability = createAvailabilityChecker("madge", ".cmd", ["--version"], {
|
|
@@ -501,8 +491,14 @@ export class DependencyChecker {
|
|
|
501
491
|
const existing = this.checkInFlight.get(key);
|
|
502
492
|
if (existing)
|
|
503
493
|
return existing;
|
|
494
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
495
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
496
|
+
// evict a live successor a second writer registered under the same key
|
|
497
|
+
// mid-flight, after which the next caller starts a duplicate check.
|
|
504
498
|
const promise = this.runCheckFile(normalized, projectRoot, gen).finally(() => {
|
|
505
|
-
this.checkInFlight.
|
|
499
|
+
if (this.checkInFlight.get(key) === promise) {
|
|
500
|
+
this.checkInFlight.delete(key);
|
|
501
|
+
}
|
|
506
502
|
});
|
|
507
503
|
this.checkInFlight.set(key, promise);
|
|
508
504
|
return promise;
|
|
@@ -781,8 +777,14 @@ export class DependencyChecker {
|
|
|
781
777
|
// does, and the two overlap (runtime-session, fresh-fetch), so it takes a
|
|
782
778
|
// generation too — claimed before the spawn, as late as a scan can.
|
|
783
779
|
const gen = ++this.opGeneration;
|
|
780
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
781
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
782
|
+
// evict a live successor a second writer registered under the same key
|
|
783
|
+
// mid-flight, after which the next caller starts a duplicate scan.
|
|
784
784
|
const promise = this.runScanProject(projectRoot, gen).finally(() => {
|
|
785
|
-
this.scanInFlight.
|
|
785
|
+
if (this.scanInFlight.get(projectRoot) === promise) {
|
|
786
|
+
this.scanInFlight.delete(projectRoot);
|
|
787
|
+
}
|
|
786
788
|
});
|
|
787
789
|
this.scanInFlight.set(projectRoot, promise);
|
|
788
790
|
return promise;
|
|
@@ -830,7 +832,11 @@ export class DependencyChecker {
|
|
|
830
832
|
const seen = new Set();
|
|
831
833
|
let output = `[Circular Deps] ${circular.length} cycle(s) found:\n`;
|
|
832
834
|
for (const dep of circular) {
|
|
833
|
-
|
|
835
|
+
// Copy before sorting: `dep.path` is rendered verbatim below (and by
|
|
836
|
+
// other CircularDep consumers, e.g. madge.ts's diagnostic renderer),
|
|
837
|
+
// so the dedupe key must not reorder — let alone mutate in place —
|
|
838
|
+
// the path a user reads.
|
|
839
|
+
const cycleKey = [...dep.path].sort(compareOrdinal).join("→");
|
|
834
840
|
if (seen.has(cycleKey))
|
|
835
841
|
continue;
|
|
836
842
|
seen.add(cycleKey);
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
* healing direction never re-trigger a resync — only the rising edge does.
|
|
63
63
|
*/
|
|
64
64
|
import * as fs from "node:fs";
|
|
65
|
+
import { BoundedFifoMap } from "./bounded-cache.js";
|
|
65
66
|
import { logLatency } from "./latency-logger.js";
|
|
66
67
|
import { toProjectRelativePath } from "./path-utils.js";
|
|
67
68
|
import { STALE_LINE_MARKER } from "./stale-marker.js";
|
|
@@ -90,16 +91,12 @@ export function createLineCountCache() {
|
|
|
90
91
|
// mismatched mtime always recomputes (see the module doc comment). Bounded
|
|
91
92
|
// via FIFO eviction (mirrors `READ_GUARD_MAX_FILES`'s precedent) so a long
|
|
92
93
|
// session's file count cannot grow this without limit.
|
|
93
|
-
const MAX_SHARED_CACHE_ENTRIES = 512;
|
|
94
|
-
const sharedLineCountCache = new
|
|
94
|
+
export const MAX_SHARED_CACHE_ENTRIES = 512;
|
|
95
|
+
const sharedLineCountCache = new BoundedFifoMap(MAX_SHARED_CACHE_ENTRIES);
|
|
95
96
|
function rememberInSharedCache(cache, filePath, entry) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const oldest = cache.keys().next().value;
|
|
100
|
-
if (oldest !== undefined)
|
|
101
|
-
cache.delete(oldest);
|
|
102
|
-
}
|
|
97
|
+
// `cache` is either the shared BoundedFifoMap (bounds itself on `set`) or a
|
|
98
|
+
// test-isolated plain `Map` (unbounded by design) — either way, this is
|
|
99
|
+
// just a write.
|
|
103
100
|
cache.set(filePath, entry);
|
|
104
101
|
}
|
|
105
102
|
/** Test-only: drop the shared memo between test files/cases. */
|
|
@@ -117,6 +114,12 @@ export function _resetSharedLineCountCacheForTests() {
|
|
|
117
114
|
export function _seedSharedLineCountCacheForTests(filePath, entry) {
|
|
118
115
|
sharedLineCountCache.set(filePath, entry);
|
|
119
116
|
}
|
|
117
|
+
/** #2442 test-only: membership check that bypasses the real-stat requirement
|
|
118
|
+
* `getCachedLineCount` imposes on every call — proves capacity eviction
|
|
119
|
+
* directly, without needing 513 real files on disk. */
|
|
120
|
+
export function _sharedLineCountCacheHasForTests(filePath) {
|
|
121
|
+
return sharedLineCountCache.has(filePath);
|
|
122
|
+
}
|
|
120
123
|
/**
|
|
121
124
|
* Count newline bytes in `filePath` via bounded raw reads — never a full
|
|
122
125
|
* `readFileSync`/UTF-8 decode of the whole file. `0x0A` (LF) never appears as
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { isTestMode } from "./env-utils.js";
|
|
8
|
-
import {
|
|
8
|
+
import { getGlobalPiLensLogDir } from "./probe-home-state.js";
|
|
9
|
+
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
9
10
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
10
11
|
function getLogDir() {
|
|
11
|
-
return path.join(
|
|
12
|
+
return path.join(getGlobalPiLensLogDir(), "logs");
|
|
12
13
|
}
|
|
13
14
|
function getLogFile() {
|
|
14
15
|
const date = new Date().toISOString().split("T")[0];
|
|
@@ -24,8 +25,17 @@ export function getDiagnosticLogger() {
|
|
|
24
25
|
}
|
|
25
26
|
export function createDiagnosticLogger() {
|
|
26
27
|
// Lazy filePath: the log file is keyed on the current date, resolved per
|
|
27
|
-
// drain so a long-lived logger rolls over at midnight.
|
|
28
|
-
|
|
28
|
+
// drain so a long-lived logger rolls over at midnight. A daily rollover
|
|
29
|
+
// is NOT a size bound (#2505): one very busy day, or a long-lived
|
|
30
|
+
// process that never re-runs the session-start sweep, grows a single
|
|
31
|
+
// day file without limit. The backup keeps the .jsonl extension so the
|
|
32
|
+
// retention sweep in log-cleanup.ts (which matches /\.jsonl$/ under
|
|
33
|
+
// logs/) reaps it like any other daily file.
|
|
34
|
+
const writer = createNdjsonLogger({
|
|
35
|
+
filePath: () => getLogFile(),
|
|
36
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
37
|
+
backupPath: () => getLogFile().replace(/\.jsonl$/, ".rotated.jsonl"),
|
|
38
|
+
});
|
|
29
39
|
return {
|
|
30
40
|
log(entry) {
|
|
31
41
|
if (isTestMode()) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Observed-latency classification shared by dispatch and LSP tier seams. */
|
|
2
|
+
import { normalizeMapKey } from "../path-utils.js";
|
|
3
|
+
/** A runner gets this much time before the next run moves off the write path. */
|
|
4
|
+
export const COLLECT_LATER_THRESHOLD_MS = 5_000;
|
|
5
|
+
const slowRunners = new Map();
|
|
6
|
+
function key(projectRoot, runnerId) {
|
|
7
|
+
return `${normalizeMapKey(projectRoot)}\u0000${runnerId}`;
|
|
8
|
+
}
|
|
9
|
+
/** Read the current observed tier. Missing observations stay inline. */
|
|
10
|
+
export function classifyObservedRunner(projectRoot, runnerId) {
|
|
11
|
+
return slowRunners.has(key(projectRoot, runnerId))
|
|
12
|
+
? "collect-later"
|
|
13
|
+
: "inline";
|
|
14
|
+
}
|
|
15
|
+
/** Record one completed latency observation and return the resulting tier. */
|
|
16
|
+
export function observeRunnerLatency(options) {
|
|
17
|
+
const normalizedProjectRoot = normalizeMapKey(options.projectRoot);
|
|
18
|
+
const runnerKey = key(normalizedProjectRoot, options.runnerId);
|
|
19
|
+
if (options.timedOut === true ||
|
|
20
|
+
options.durationMs > COLLECT_LATER_THRESHOLD_MS) {
|
|
21
|
+
slowRunners.set(runnerKey, {
|
|
22
|
+
projectRoot: normalizedProjectRoot,
|
|
23
|
+
runnerId: options.runnerId,
|
|
24
|
+
});
|
|
25
|
+
return "collect-later";
|
|
26
|
+
}
|
|
27
|
+
// A fast completed run is affirmative recovery, not merely absence of a
|
|
28
|
+
// failure. This deliberately prevents a process-lifetime latch.
|
|
29
|
+
slowRunners.delete(runnerKey);
|
|
30
|
+
return "inline";
|
|
31
|
+
}
|
|
32
|
+
/** Session boundary reset. */
|
|
33
|
+
export function resetObservedRunnerLatency() {
|
|
34
|
+
slowRunners.clear();
|
|
35
|
+
}
|
|
36
|
+
/** Test-only inspection without exposing mutable state. */
|
|
37
|
+
export function observedRunnerTierForTests(projectRoot, runnerId) {
|
|
38
|
+
return classifyObservedRunner(projectRoot, runnerId);
|
|
39
|
+
}
|