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
package/dist/clients/pipeline.js
CHANGED
|
@@ -25,7 +25,6 @@ import { classifyProbeFailure, createAvailabilityChecker, createAvailabilityLatc
|
|
|
25
25
|
import { findDetektConfig } from "./dispatch/runners/detekt.js";
|
|
26
26
|
import { detectFileKind, getFileKindLabel } from "./file-kinds.js";
|
|
27
27
|
import { detectFileChangedAfterCommand, getProjectIgnoreMatcher, isExcludedDirName, } from "./file-utils.js";
|
|
28
|
-
import { isInSpawnTimeoutCooldown } from "./spawn-timeout-cooldown.js";
|
|
29
28
|
import { logLatency } from "./latency-logger.js";
|
|
30
29
|
import { emitLensAnalysisComplete } from "./lens-events.js";
|
|
31
30
|
import { publishFilesTouched } from "./bus-publish.js";
|
|
@@ -35,7 +34,8 @@ import { clearGraphCache } from "./review-graph/builder.js";
|
|
|
35
34
|
import { BoundedLruCache } from "./bounded-cache.js";
|
|
36
35
|
import { RUNTIME_CONFIG } from "./runtime-config.js";
|
|
37
36
|
import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
|
|
38
|
-
import {
|
|
37
|
+
import { bounded } from "./deadline-utils.js";
|
|
38
|
+
import { enabledAuxiliaryLspServerIds } from "./dispatch/auxiliary-lsp.js";
|
|
39
39
|
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
40
40
|
import { dropFindingsForMissingPaths } from "./advisory-provenance.js";
|
|
41
41
|
import { getAutofixPolicyForFile, getPreferredAutofixTools, getRubocopCommand, hasBiomeConfig, hasDetektConfig, hasEslintConfig, hasGolangciConfig, hasKtfmtConfig, hasKtlintConfig, hasOxlintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, markdownlintConfigArgs, } from "./tool-policy.js";
|
|
@@ -338,17 +338,10 @@ async function tryDetektFix(filePath, cwd) {
|
|
|
338
338
|
const absPath = path.resolve(cwd, filePath);
|
|
339
339
|
return detectFileChangedAfterCommand(filePath, cmd, ["--auto-correct", "--input", absPath, "--config", configPath], cwd, [1, 2]);
|
|
340
340
|
}
|
|
341
|
-
// Exported for #1995 cooldown wiring tests: the guard on this lane is
|
|
342
|
-
// one of three mutation-proof surfaces.
|
|
343
341
|
export async function tryMarkdownlintFix(filePath, cwd) {
|
|
344
342
|
const cmd = await resolveToolCommandWithInstallFallback(cwd, "markdownlint");
|
|
345
343
|
if (!cmd)
|
|
346
344
|
return 0;
|
|
347
|
-
// #1995: skip the spawn entirely when the command is cooling down after a
|
|
348
|
-
// timeout — detectFileChangedAfterCommand also self-guards, but a wedged
|
|
349
|
-
// command should not even reach a second budget in the hot loop.
|
|
350
|
-
if (isInSpawnTimeoutCooldown(cmd))
|
|
351
|
-
return 0;
|
|
352
345
|
// Shared config-args seam (#1247): the lint runner consumes the same
|
|
353
346
|
// builder, so the bare --fix here can never fall back to markdownlint's
|
|
354
347
|
// default all-rules-on config again (the whole-file CHANGELOG/AGENTS
|
|
@@ -695,7 +688,6 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
695
688
|
const abort = getAmbientAbortSignal();
|
|
696
689
|
if (abort?.aborted)
|
|
697
690
|
return;
|
|
698
|
-
const bail = combineAbortSignals(abort, AbortSignal.timeout(budgetMs));
|
|
699
691
|
const startedAt = Date.now();
|
|
700
692
|
const touch = lspService
|
|
701
693
|
.touchFile(filePath, fileContent, {
|
|
@@ -709,13 +701,45 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
709
701
|
dbg(`LSP resync after autofix error: ${err}`);
|
|
710
702
|
return "done";
|
|
711
703
|
});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
704
|
+
// #2540: Kick off auxiliary server acquisition concurrently and unawaited
|
|
705
|
+
// with the ambient turn signal so auxiliary warmup overlaps with the primary
|
|
706
|
+
// server during resync. Leaves the single-occupancy deferred slot (#2509)
|
|
707
|
+
// free for actionable-warnings while Escape mid-turn abandons the wait.
|
|
708
|
+
//
|
|
709
|
+
// #2582 F3 — the #1766 F3 invariant re-opened for a different method.
|
|
710
|
+
// #2540 placed this kick-off BEFORE the primary touch, inside
|
|
711
|
+
// resyncLspFile's swallow-all catch, so anything that threw out of
|
|
712
|
+
// auxiliary acquisition abandoned the PRIMARY sync before it started:
|
|
713
|
+
// no touchFile, and no lsp_sync_abandoned record for the stall.
|
|
714
|
+
// Auxiliary warmup is a best-effort OVERLAP, never a precondition:
|
|
715
|
+
// the primary touch is already in flight above, and the async IIFE
|
|
716
|
+
// turns a synchronous throw out of the call into a rejection so
|
|
717
|
+
// `.catch` covers both failure directions. Order and containment are
|
|
718
|
+
// the invariant; do not hoist this back above the touch.
|
|
719
|
+
const auxServerIds = enabledAuxiliaryLspServerIds(getFlag);
|
|
720
|
+
if (auxServerIds.length > 0) {
|
|
721
|
+
void (async () => lspService.getAuxiliaryClientsForFile(filePath, new Set(auxServerIds), undefined, LSP_SPAWN_BUDGET_MS, abort, "tool_result_edit"))().catch(() => { });
|
|
722
|
+
}
|
|
723
|
+
// #2523 slice 2: this was `combineAbortSignals(abort,
|
|
724
|
+
// AbortSignal.timeout(budgetMs))` fed into a hand-rolled
|
|
725
|
+
// `Promise.race` — a private fifth copy of "deadline AND signal".
|
|
726
|
+
// `bounded()` is the same thing with the abandonment recorded, and
|
|
727
|
+
// without the composite signal that #2530 review F4 measured
|
|
728
|
+
// accumulating on the SOURCE.
|
|
729
|
+
//
|
|
730
|
+
// `touch` never rejects (it catches above) and never resolves
|
|
731
|
+
// `undefined`, so `undefined` here means exactly "a bound fired".
|
|
732
|
+
const outcome = await bounded(touch, {
|
|
733
|
+
ms: budgetMs,
|
|
734
|
+
// The ambient turn signal: set for the whole tool_result path and
|
|
735
|
+
// absent only in a bare unit harness, where the wall budget is
|
|
736
|
+
// still live (`bounded()` reads a missing signal as one that
|
|
737
|
+
// never aborts).
|
|
738
|
+
signal: abort,
|
|
739
|
+
hook: "tool_result_edit",
|
|
740
|
+
label: "resyncLspFile",
|
|
716
741
|
});
|
|
717
|
-
|
|
718
|
-
if (outcome === "bailed") {
|
|
742
|
+
if (outcome === undefined) {
|
|
719
743
|
// Abandon the still-pending write; the edit continues. Log it so this
|
|
720
744
|
// stall — previously an invisible hang — is queryable in latency.log.
|
|
721
745
|
//
|
|
@@ -725,12 +749,18 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
725
749
|
// old wording blamed as "slow/wedged" did not exist yet. Distinguish
|
|
726
750
|
// the two via a fresh, synchronous inFlight lookup so the record keeps
|
|
727
751
|
// the discriminating identity (which server, which lifecycle state).
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
752
|
+
// #2592: the `typeof lspService.isSpawnInFlight === "function"`
|
|
753
|
+
// hedge that used to wrap this call is GONE. It existed for one
|
|
754
|
+
// reason — 19 hand-rolled `getLSPService` doubles that lacked the
|
|
755
|
+
// method, whose TypeError would have landed in the catch below and
|
|
756
|
+
// suppressed this record entirely (#1766 F3) — and that population
|
|
757
|
+
// is now zero: every double is seeded from
|
|
758
|
+
// `makeLspServiceDouble`, `tests/support/lsp-double-baseline.json`
|
|
759
|
+
// is `{}`, and `tests/config/lsp-service-double-sweep.test.ts` reds
|
|
760
|
+
// on a fresh hand-rolled one. The real `LSPService` has always had
|
|
761
|
+
// the method (clients/lsp/index.ts), so the fallback was a test
|
|
762
|
+
// shape leaking into production — AGENTS.md shape 7.
|
|
763
|
+
const spawnInFlight = !abort?.aborted && lspService.isSpawnInFlight(filePath);
|
|
734
764
|
const reason = abort?.aborted
|
|
735
765
|
? "aborted"
|
|
736
766
|
: spawnInFlight
|
|
@@ -791,12 +821,29 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
|
|
|
791
821
|
let formatChanged = false;
|
|
792
822
|
let formattersUsed = [];
|
|
793
823
|
const formatFailures = [];
|
|
824
|
+
const formatUnavailable = [];
|
|
794
825
|
let fileContent;
|
|
795
826
|
const formatService = getFormatService();
|
|
796
827
|
try {
|
|
797
828
|
formatService.recordRead(filePath);
|
|
798
829
|
const result = await formatService.formatFile(filePath);
|
|
799
|
-
|
|
830
|
+
// An unavailable tool is NOT a formatter that ran (#2413): keep it out of
|
|
831
|
+
// `formattersUsed` (which drives change bookkeeping / turn summaries) and
|
|
832
|
+
// out of `formatFailures` (which requeues). Record it once, distinctly.
|
|
833
|
+
for (const f of result.formatters) {
|
|
834
|
+
if (f.outcome !== "unavailable")
|
|
835
|
+
continue;
|
|
836
|
+
const reason = f.error ?? "formatter executable not found";
|
|
837
|
+
formatUnavailable.push({ formatter: f.name, reason });
|
|
838
|
+
recordDegradationOnce({
|
|
839
|
+
kind: "formatter-unavailable",
|
|
840
|
+
subject: `${f.name}:${path.basename(filePath)}`,
|
|
841
|
+
reason,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
formattersUsed = result.formatters
|
|
845
|
+
.filter((f) => f.outcome !== "unavailable")
|
|
846
|
+
.map((f) => f.name);
|
|
800
847
|
if (result.anyChanged) {
|
|
801
848
|
formatChanged = true;
|
|
802
849
|
dbg("autoformat: " +
|
|
@@ -836,7 +883,13 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
|
|
|
836
883
|
catch {
|
|
837
884
|
fileContent = undefined;
|
|
838
885
|
}
|
|
839
|
-
return {
|
|
886
|
+
return {
|
|
887
|
+
formatChanged,
|
|
888
|
+
formattersUsed,
|
|
889
|
+
formatFailures,
|
|
890
|
+
formatUnavailable,
|
|
891
|
+
fileContent,
|
|
892
|
+
};
|
|
840
893
|
}
|
|
841
894
|
/**
|
|
842
895
|
* Build the 🔴 STOP blocker output with an inline code snippet for each
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The #2506 probe-home redirect, whole: the decision, the per-process memo it
|
|
3
|
+
* is cached in, and the degradation event that decision produced.
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT DECIDES. An ad-hoc probe against the BUILT `clients/*.js` — a bare
|
|
6
|
+
* `node -e`, a throwaway `.mjs`, a harness script — has no `PI_LENS_HOME` pin,
|
|
7
|
+
* so every logger and ledger it touches used to write into the maintainer's
|
|
8
|
+
* REAL `~/.pi-lens`. Confirmed live on 2026-09-02: two review probes left 42
|
|
9
|
+
* rows of `/p/.pi-lens.json` fixture garbage in real telemetry. When
|
|
10
|
+
* `PI_LENS_HOME` is unset and the process looks like a probe — `PILENS_PROBE=1`,
|
|
11
|
+
* a `cwd` inside a specific agent worktree, or a `cwd` under `os.tmpdir()` —
|
|
12
|
+
* `resolveProbeHomeDir()` answers with a per-probe directory instead.
|
|
13
|
+
*
|
|
14
|
+
* WHY IT ALL LIVES HERE and not beside its `getGlobalPiLensDir()` sibling in
|
|
15
|
+
* `file-utils.ts`, where round 2 of #2516 left it. `file-utils.ts` sits on an
|
|
16
|
+
* import cycle (it still does, via `safe-spawn.js` -> `resource-sampler.js` ->
|
|
17
|
+
* `instance-reaper.js` -> `file-utils.js`; the shorter route through
|
|
18
|
+
* `degradation-ledger.js` -> `extension-log.js` was this move's doing —
|
|
19
|
+
* `extension-log.ts` imported `file-utils.ts` for THIS function and nothing
|
|
20
|
+
* else, so the edge went with it, and the known-violations baseline dropped
|
|
21
|
+
* from 33 pinned cycles to 12 with none added). Fifteen log-family modules
|
|
22
|
+
* call `getGlobalPiLensLogDir()` at their OWN module
|
|
23
|
+
* top level. Under vitest's vite-SSR transform each import becomes a
|
|
24
|
+
* `__vite_ssr_import_N__` variable assigned in source order, so one of those
|
|
25
|
+
* top-level calls re-enters `file-utils.ts` while its body is only PARTWAY
|
|
26
|
+
* through those assignments — and a resolver sitting there then dereferences a
|
|
27
|
+
* binding that has not been assigned yet, throwing `ReferenceError: Cannot
|
|
28
|
+
* access '__vite_ssr_import_N__' before initialization`. Measured live: the
|
|
29
|
+
* re-entry arrives through `file-utils.ts`'s FIFTH import,
|
|
30
|
+
* `spawn-timeout-cooldown.js`, so every binding declared from the sixth import
|
|
31
|
+
* onward is unsafe on that path — which is most of the file.
|
|
32
|
+
*
|
|
33
|
+
* Round 2 of #2516 papered over that with an `isTestMode()` early return placed
|
|
34
|
+
* ahead of the first unsafe dereference. That was load-order luck, not a
|
|
35
|
+
* design: moving `file-utils.ts`'s own `isTestMode` import from fifth to last
|
|
36
|
+
* makes the GUARD LINE ITSELF throw the same error. It also DEFEATED the
|
|
37
|
+
* redirect for every process that has `VITEST` set but no `PI_LENS_HOME` pin —
|
|
38
|
+
* vitest's own globalSetup/child processes — sending their logs to the real
|
|
39
|
+
* home: the exact hermeticity hole the redirect exists to close.
|
|
40
|
+
*
|
|
41
|
+
* THIS module is off that cycle. It imports only node builtins and
|
|
42
|
+
* `path-utils.js`, itself a leaf (node builtins plus the vendored, zero-import
|
|
43
|
+
* `deps/minimatch.js`). A module off every cycle is fully evaluated before any
|
|
44
|
+
* importer's body runs — in plain ESM and under the vite-SSR transform alike —
|
|
45
|
+
* so none of its own bindings can ever be observed uninitialized, wherever an
|
|
46
|
+
* importer chooses to place the import. That is what makes the guard
|
|
47
|
+
* unnecessary rather than merely well-placed, and it is why module-scope
|
|
48
|
+
* `const`s (including the `Symbol.for` below) are ordinary here where they were
|
|
49
|
+
* a hazard in `file-utils.ts`.
|
|
50
|
+
*
|
|
51
|
+
* `tests/config/global-dir-probe-redirect.test.ts` pins the behaviour through
|
|
52
|
+
* real spawned `node` children, including the VITEST-set-but-unpinned case.
|
|
53
|
+
*/
|
|
54
|
+
import * as fs from "node:fs";
|
|
55
|
+
import * as os from "node:os";
|
|
56
|
+
import * as path from "node:path";
|
|
57
|
+
import { isUnderDir, normalizeFilePath } from "./path-utils.js";
|
|
58
|
+
/**
|
|
59
|
+
* The memo lives in a process-wide `globalThis` slot rather than a plain
|
|
60
|
+
* module-scope `let` so that a bundled `dist/index.js` and the loose
|
|
61
|
+
* `clients/*.js` loaded into the same process — the shape #335 ships — share
|
|
62
|
+
* ONE decision instead of memoizing two. A `Symbol.for` key is interned by
|
|
63
|
+
* string, so both copies of this module resolve to the same slot.
|
|
64
|
+
*/
|
|
65
|
+
const RESOLUTION_SLOT = Symbol.for("pi-lens.probe-home-state.resolution");
|
|
66
|
+
/** The memoized resolution for this process, or `undefined` before first use. */
|
|
67
|
+
export function getProbeHomeResolution() {
|
|
68
|
+
return globalThis[RESOLUTION_SLOT];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The degradation row for a redirect that actually happened, or `undefined`
|
|
72
|
+
* when this process was not redirected (or has not resolved yet).
|
|
73
|
+
* `degradation-ledger.ts`'s `getDegradationSummary()` is the only caller — it
|
|
74
|
+
* folds this at READ time rather than having the resolver push a row, because a
|
|
75
|
+
* direct import between `file-utils.ts` and `degradation-ledger.ts` in EITHER
|
|
76
|
+
* direction (a dynamic `import()` included) adds unpinned `no-client-cycles`
|
|
77
|
+
* violations to the cycle named in this module's doc comment.
|
|
78
|
+
*/
|
|
79
|
+
export function getProbeHomeRedirectEvent() {
|
|
80
|
+
return getProbeHomeResolution()?.event;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Where pi-lens writes its LOGS, ledger rows and debug dumps: `~/.pi-lens/` by
|
|
84
|
+
* default, exactly like `getGlobalPiLensDir()` in `file-utils.ts` — but
|
|
85
|
+
* redirected to a per-probe directory when this process looks like an ad-hoc
|
|
86
|
+
* probe rather than a real pi session (#2506).
|
|
87
|
+
*
|
|
88
|
+
* Every log-family writer routes here: `latency.log`, `extension.log`,
|
|
89
|
+
* `sessionstart.log`, `cascade.log`, `read-guard.log`, `tree-sitter.log`,
|
|
90
|
+
* `word-index.log`, `bus-events.log`, `dispositions.log`, `dead-code.log`,
|
|
91
|
+
* `ast-grep-tools.log`, `actionable-warnings.log`, `review-graph.log`, the
|
|
92
|
+
* `logs/*.jsonl` diagnostic dir, the debug handle/heap dumps,
|
|
93
|
+
* `log-cleanup.ts`'s sweep over all of the above, and `smells-rollup.ts`'s
|
|
94
|
+
* read-side tail of `latency.log`/`bus-events.log` (a reader, but it must
|
|
95
|
+
* follow the same root or it tails a file nobody is writing).
|
|
96
|
+
*
|
|
97
|
+
* WHY IT LIVES IN THIS MODULE and not next to its `getGlobalPiLensDir()`
|
|
98
|
+
* sibling in `file-utils.ts` — see this file's header. Fifteen of the writers
|
|
99
|
+
* listed above call this function at their OWN module top level, and
|
|
100
|
+
* `file-utils.ts` is on an import cycle, so resolving from there could observe
|
|
101
|
+
* `file-utils.ts` mid-initialization. Resolving from a module off every cycle
|
|
102
|
+
* cannot.
|
|
103
|
+
*
|
|
104
|
+
* `PI_LENS_HOME` wins over any redirect, which is how
|
|
105
|
+
* `tests/support/vitest-setup.ts` keeps test workers hermetic. Note the
|
|
106
|
+
* redirect still applies under vitest for processes that setup does not cover
|
|
107
|
+
* (globalSetup, and children spawned without the pin): #2516 round 2's
|
|
108
|
+
* `isTestMode()` guard sent exactly those to the developer's real home.
|
|
109
|
+
*
|
|
110
|
+
* Deliberately does NOT write a line to the terminal when it fires. #1333 gives
|
|
111
|
+
* pi sole ownership of the TTY, and
|
|
112
|
+
* `tests/clients/extension-terminal-silence.test.ts` enforces that nothing
|
|
113
|
+
* under `clients/` calls `process.std*.write` or `console.*` (round 2 of
|
|
114
|
+
* #2506 shipped a `process.stderr.write` here and was red in CI for exactly
|
|
115
|
+
* that). Nor can it log through `extension-log.ts`'s
|
|
116
|
+
* `createSubsystemLogger`: that module imports THIS one, so an import back
|
|
117
|
+
* the other way would close a new, unpinned `no-client-cycles` cycle — and
|
|
118
|
+
* `extension.log` is itself redirected by this very function, so the line
|
|
119
|
+
* would land in the probe home nobody reads. The durable signal is the
|
|
120
|
+
* `global-dir-probe-redirect` degradation row instead, folded into
|
|
121
|
+
* `getDegradationSummary()` at read time via
|
|
122
|
+
* `getProbeHomeRedirectEvent()` above.
|
|
123
|
+
*/
|
|
124
|
+
export function getGlobalPiLensLogDir() {
|
|
125
|
+
const override = process.env.PI_LENS_HOME?.trim();
|
|
126
|
+
if (override)
|
|
127
|
+
return path.resolve(override);
|
|
128
|
+
return resolveProbeHomeDir() ?? path.join(os.homedir(), ".pi-lens");
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the probe-home redirect at most ONCE per process, caching both the
|
|
132
|
+
* "yes, here" and the "no redirect" answers (#2506 round 3, F5). Round 2 read
|
|
133
|
+
* `process.cwd()` live on every call, which gave a single process up to three
|
|
134
|
+
* different global dirs as it chdir'd, and re-ran the whole decision on the hot
|
|
135
|
+
* logging path.
|
|
136
|
+
*
|
|
137
|
+
* `undefined` means "no redirect applies"; `getGlobalPiLensLogDir()` above
|
|
138
|
+
* falls back to the real home. This function never consults `PI_LENS_HOME` —
|
|
139
|
+
* that pin is handled by its caller, ahead of this decision, so a pinned
|
|
140
|
+
* process never memoizes a probe answer it would then keep after the pin
|
|
141
|
+
* changed.
|
|
142
|
+
*/
|
|
143
|
+
function resolveProbeHomeDir() {
|
|
144
|
+
const slot = globalThis;
|
|
145
|
+
const cached = slot[RESOLUTION_SLOT];
|
|
146
|
+
if (cached)
|
|
147
|
+
return cached.probeHome;
|
|
148
|
+
const cwd = process.cwd();
|
|
149
|
+
const probeHome = computeProbeHomeDir(cwd);
|
|
150
|
+
slot[RESOLUTION_SLOT] = {
|
|
151
|
+
probeHome,
|
|
152
|
+
event: probeHome ? { probeHome, cwd } : undefined,
|
|
153
|
+
};
|
|
154
|
+
return probeHome;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Clears the memoized decision AND the event it produced, so the next
|
|
158
|
+
* `getGlobalPiLensLogDir()` call re-resolves from scratch. Exercised by
|
|
159
|
+
* `tests/config/global-dir-probe-redirect.test.ts`, which drives the resolver
|
|
160
|
+
* repeatedly under different in-process conditions; without it the first call
|
|
161
|
+
* in a worker would pin the answer for every later case in the same file.
|
|
162
|
+
*/
|
|
163
|
+
export function _resetProbeHomeRedirectStateForTests() {
|
|
164
|
+
globalThis[RESOLUTION_SLOT] = undefined;
|
|
165
|
+
}
|
|
166
|
+
/** The redirect decision itself. */
|
|
167
|
+
function computeProbeHomeDir(cwd) {
|
|
168
|
+
// The explicit force wins first: it is how a probe run from an ordinary
|
|
169
|
+
// project checkout opts in, and it must work even under a harness that has
|
|
170
|
+
// set a test-mode marker but no PI_LENS_HOME.
|
|
171
|
+
if (process.env.PILENS_PROBE === "1") {
|
|
172
|
+
return path.join(cwd, ".pi-lens-probe-home");
|
|
173
|
+
}
|
|
174
|
+
const worktreeRoot = findAgentWorktreeRoot(cwd);
|
|
175
|
+
if (worktreeRoot)
|
|
176
|
+
return path.join(worktreeRoot, ".pi-lens-probe-home");
|
|
177
|
+
if (isUnderRealDir(cwd, os.tmpdir())) {
|
|
178
|
+
return path.join(cwd, ".pi-lens-probe-home");
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The directory of the SPECIFIC agent worktree containing `cwd`, or
|
|
184
|
+
* `undefined` when `cwd` is not inside one.
|
|
185
|
+
*
|
|
186
|
+
* Requires at least one path segment AFTER `worktrees/`, and anchors the
|
|
187
|
+
* probe home at that segment rather than at the live `cwd` (#2506 round 3,
|
|
188
|
+
* F5). Round 2's trailing `(\/|$)` alternative also matched a `cwd` of
|
|
189
|
+
* `.claude/worktrees` ITSELF, which would have put the probe home in the
|
|
190
|
+
* SHARED worktrees parent — every concurrent agent on the box writing into
|
|
191
|
+
* one directory, the cross-agent collision this redirect is supposed to
|
|
192
|
+
* prevent. Anchoring at the worktree also means a probe run from a
|
|
193
|
+
* subdirectory (`<worktree>/clients`) and one run from the worktree root
|
|
194
|
+
* agree on a single probe home instead of scattering one per cwd.
|
|
195
|
+
*/
|
|
196
|
+
function findAgentWorktreeRoot(cwd) {
|
|
197
|
+
const normalized = normalizeFilePath(path.resolve(cwd));
|
|
198
|
+
const match = /(?:^|\/)\.claude\/worktrees\/[^/]+(?:\/|$)/.exec(normalized);
|
|
199
|
+
if (!match)
|
|
200
|
+
return undefined;
|
|
201
|
+
return normalized.slice(0, match.index + match[0].length).replace(/\/$/, "");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* `isUnderDir`, but with both sides resolved through `realpath` first
|
|
205
|
+
* (#2506 round 3, F7).
|
|
206
|
+
*
|
|
207
|
+
* On macOS `os.tmpdir()` reports `/var/folders/...` while a `cwd` created
|
|
208
|
+
* under it resolves to `/private/var/folders/...` (`/var` is a symlink to
|
|
209
|
+
* `/private/var`), so the two NEVER prefix-match unresolved and the tmpdir
|
|
210
|
+
* branch would be dead on every Mac. Windows has the same shape with 8.3
|
|
211
|
+
* short names and substituted drives. Both sides go through the same
|
|
212
|
+
* function, so `realpathSync.native`'s canonical Windows casing applies
|
|
213
|
+
* consistently; the result is used only for this containment test, never to
|
|
214
|
+
* build the returned path, so no casing change can leak into a log path.
|
|
215
|
+
* Best-effort by design: a path that does not exist yet (or an EPERM) falls
|
|
216
|
+
* back to its literal form rather than throwing on the logging hot path.
|
|
217
|
+
*/
|
|
218
|
+
function isUnderRealDir(child, parent) {
|
|
219
|
+
return isUnderDir(toRealPath(child), toRealPath(parent));
|
|
220
|
+
}
|
|
221
|
+
function toRealPath(target) {
|
|
222
|
+
try {
|
|
223
|
+
return fs.realpathSync.native(target);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return target;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared process-bridge registration helper (#2437).
|
|
3
|
+
*
|
|
4
|
+
* `clients/read-bridge.ts` (#1265) and `clients/mutation-bridge.ts` (#2423)
|
|
5
|
+
* each mount a versioned, frozen method-table object at a well-known
|
|
6
|
+
* `globalThis[Symbol.for("pi-lens:...")]` key so a same-process producer can
|
|
7
|
+
* reach pi-lens's bookkeeping without importing its internals. Both bridges
|
|
8
|
+
* carried an identical mount body — a first-wins existence check,
|
|
9
|
+
* `Object.freeze`, and a non-writable/non-configurable `Object.defineProperty`
|
|
10
|
+
* — differing only in their key and method table (named by the #2432 review,
|
|
11
|
+
* refs #2423). This module owns that body once.
|
|
12
|
+
*
|
|
13
|
+
* NOT `clients/process-singletons.ts` (#2146): that module keeps a single
|
|
14
|
+
* MUTABLE, adopt-if-version-compatible value per "family" inside ONE shared
|
|
15
|
+
* container (a different, single key mounts the container itself, and a
|
|
16
|
+
* version mismatch there means "discard and rebuild"). A bridge's contract is
|
|
17
|
+
* the opposite: the documented producer protocol reads
|
|
18
|
+
* `globalThis[Symbol.for("pi-lens:mutation-bridge")]` directly as a frozen,
|
|
19
|
+
* first-wins, unreplaceable property, and a version mismatch is the
|
|
20
|
+
* PRODUCER's problem — an unsupported bridge it should not call — never a
|
|
21
|
+
* signal for pi-lens to silently reset its own mount. Reusing the singleton
|
|
22
|
+
* container would change that documented external contract, so this stays a
|
|
23
|
+
* separate, deliberately smaller leaf built for the bridge shape only.
|
|
24
|
+
*
|
|
25
|
+
* STATIC IMPORTS: none, deliberately. Both `read-bridge.ts` and
|
|
26
|
+
* `mutation-bridge.ts` import this module; it must stay a dependency leaf so
|
|
27
|
+
* neither gains an edge onto anything else in `clients/` (`no-client-cycles`).
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Mount `build()`'s result at `globalThis[key]`, once per process.
|
|
31
|
+
*
|
|
32
|
+
* First-wins: if `key` already exists on `globalThis` — from an earlier call
|
|
33
|
+
* in this process, whether this bridge's own prior registration or a
|
|
34
|
+
* redundant re-activation — `build()` is never invoked and the existing
|
|
35
|
+
* mount is left untouched. The mounted value is frozen and installed
|
|
36
|
+
* non-writable/non-configurable so no later code can silently replace or
|
|
37
|
+
* extend it; the `in` check above is what keeps a redundant call from
|
|
38
|
+
* throwing on that now-frozen property.
|
|
39
|
+
*/
|
|
40
|
+
export function registerProcessBridge(key, build) {
|
|
41
|
+
if (key in globalThis)
|
|
42
|
+
return;
|
|
43
|
+
const bridge = Object.freeze(build());
|
|
44
|
+
Object.defineProperty(globalThis, key, {
|
|
45
|
+
value: bridge,
|
|
46
|
+
writable: false,
|
|
47
|
+
configurable: false,
|
|
48
|
+
enumerable: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The bridge mounted at `key`, or `undefined` when nothing is mounted, the
|
|
53
|
+
* mounted value is not an object, or its `version` does not equal `version`.
|
|
54
|
+
* A version mismatch means "not a bridge this caller recognizes" — never
|
|
55
|
+
* adopted and never reset; the reset-on-mismatch behavior belongs to
|
|
56
|
+
* `process-singletons.ts`'s different contract, not this one.
|
|
57
|
+
*/
|
|
58
|
+
export function getProcessBridge(key, version) {
|
|
59
|
+
const bridge = globalThis[key];
|
|
60
|
+
if (!bridge || typeof bridge !== "object")
|
|
61
|
+
return undefined;
|
|
62
|
+
const candidate = bridge;
|
|
63
|
+
if (candidate.version !== version)
|
|
64
|
+
return undefined;
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|