@herbertgao/pi-extensions 2026.8.13 โ 2026.8.15
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/README.md +19 -14
- package/THIRD_PARTY_NOTICES.md +0 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@herbertgao/{pi-stash โ pi-bark}/LICENSE +4 -5
- package/node_modules/@herbertgao/pi-bark/README.md +44 -0
- package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
- package/node_modules/@herbertgao/pi-bark/package.json +53 -0
- package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +125 -88
- package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +5 -8
- package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
- package/node_modules/@tifan/pi-handoff/README.md +36 -13
- package/node_modules/@tifan/pi-handoff/package.json +2 -2
- package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
- package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
- package/node_modules/@tifan/pi-recap/README.md +1 -1
- package/node_modules/@tifan/pi-recap/package.json +2 -2
- package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
- package/node_modules/@tifan/pi-rename/README.md +42 -21
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
- package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
- package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
- package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
- package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
- package/node_modules/pi-lens/CHANGELOG.md +320 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
- package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
- package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
- package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
- package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
- package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
- package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
- package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
- package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
- package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
- package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
- package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
- package/node_modules/pi-lens/dist/index.js +11243 -6651
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
- package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +6 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/docs/agent-guide.md +4 -2
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
- package/node_modules/pi-lens/docs/astplayground.md +26 -17
- package/node_modules/pi-lens/package.json +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
- package/node_modules/pi-mcp-adapter/README.md +5 -2
- package/node_modules/pi-mcp-adapter/cli.js +4 -4
- package/node_modules/pi-mcp-adapter/commands.ts +1 -1
- package/node_modules/pi-mcp-adapter/config.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +90 -6
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
- package/node_modules/pi-mcp-adapter/package.json +1 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
- package/node_modules/pi-mcp-adapter/types.ts +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +42 -0
- package/node_modules/pi-web-access/README.md +26 -10
- package/node_modules/pi-web-access/curator-page.ts +12 -3
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/extract.ts +88 -13
- package/node_modules/pi-web-access/gemini-search.ts +10 -5
- package/node_modules/pi-web-access/github-extract.ts +47 -3
- package/node_modules/pi-web-access/index.ts +40 -40
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +61 -8
- package/node_modules/pi-web-access/xcrawl.ts +264 -0
- package/package.json +19 -22
- package/node_modules/@herbertgao/pi-stash/README.md +0 -34
- package/node_modules/@herbertgao/pi-stash/package.json +0 -51
- package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
- package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
- package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
- package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { Minimatch } from "./deps/minimatch.js";
|
|
8
8
|
import { isInSpawnTimeoutCooldown, noteSpawnTimeout, } from "./spawn-timeout-cooldown.js";
|
|
9
9
|
import { collectTrackedFiles, getTrackedFilesSnapshot, } from "./git-tracked-ignore.js";
|
|
10
10
|
import { getGlobalIgnorePatterns, getPiLensGlobalConfigPath, } from "./lens-config.js";
|
|
11
|
-
import { normalizeEphemeralMapKey, normalizeFilePath } from "./path-utils.js";
|
|
11
|
+
import { isUnderDir, normalizeEphemeralMapKey, normalizeFilePath, } from "./path-utils.js";
|
|
12
12
|
import { findPiLensConfigInDir, findPiLensProjectConfig, loadPiLensConfigInDir, loadPiLensProjectConfig, } from "./project-lens-config.js";
|
|
13
13
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
14
14
|
/**
|
|
@@ -213,19 +213,18 @@ function expandGitignorePattern(pattern) {
|
|
|
213
213
|
return [body];
|
|
214
214
|
return [body, `**/${body}`];
|
|
215
215
|
}
|
|
216
|
-
function matchesGitignorePattern(pattern, relativePath, isDirectory) {
|
|
216
|
+
function matchesGitignorePattern(pattern, relativePath, isDirectory, matchExpanded) {
|
|
217
217
|
const candidate = stripLeadingSlashes(normalizeIgnorePath(relativePath));
|
|
218
218
|
if (!candidate)
|
|
219
219
|
return false;
|
|
220
220
|
const candidates = isDirectory ? [candidate, `${candidate}/`] : [candidate];
|
|
221
|
-
const options = { dot: true, nocase: process.platform === "win32" };
|
|
222
221
|
return expandGitignorePattern(pattern).some((expanded) => {
|
|
223
222
|
if (isDirectory && expanded.endsWith("/**")) {
|
|
224
223
|
const prefix = expanded.slice(0, -3);
|
|
225
224
|
if (candidate === prefix || candidate.startsWith(`${prefix}/`))
|
|
226
225
|
return true;
|
|
227
226
|
}
|
|
228
|
-
return candidates.some((value) =>
|
|
227
|
+
return candidates.some((value) => matchExpanded(value, expanded));
|
|
229
228
|
});
|
|
230
229
|
}
|
|
231
230
|
export function readGitignorePatterns(rootDir, layer = "gitignore") {
|
|
@@ -252,6 +251,14 @@ function ancestorDirsBetween(rootDir, targetDir) {
|
|
|
252
251
|
return dirs;
|
|
253
252
|
}
|
|
254
253
|
function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
254
|
+
const consumedIgnoreSources = new Map();
|
|
255
|
+
// `signature` lets a caller that already statted the file reuse that one
|
|
256
|
+
// stat (#2016's class on this seam: never re-derive a value you just
|
|
257
|
+
// computed). Callers with no signature in hand still pay their own stat.
|
|
258
|
+
const rememberIgnoreSource = (filePath, signature = fileFreshnessSignature(filePath)) => {
|
|
259
|
+
consumedIgnoreSources.set(filePath, { path: filePath, ...signature });
|
|
260
|
+
};
|
|
261
|
+
rememberIgnoreSource(path.join(resolvedRoot, ".gitignore"));
|
|
255
262
|
const nestedCache = new Map();
|
|
256
263
|
// #783: layer a NESTED `.pi-lens.json`'s `ignore` field the same way a
|
|
257
264
|
// nested `.gitignore` is already layered โ each ancestor directory between
|
|
@@ -266,7 +273,26 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
266
273
|
const patternsForDir = (dir) => {
|
|
267
274
|
if (dir === resolvedRoot)
|
|
268
275
|
return patterns;
|
|
276
|
+
const cached = nestedCache.get(dir);
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
// #2071: inside the cadence window this directory's rules are frozen, so
|
|
279
|
+
// no stat runs and every verdict already memoized under `dir` stays
|
|
280
|
+
// derivable from exactly these patterns. Before this gate the freshness
|
|
281
|
+
// check ran per call while `patternMemo` did not, which is what let a
|
|
282
|
+
// fresh path and a memoized path in the same directory return opposite
|
|
283
|
+
// verdicts for the same rule. Externally edited nested sources are
|
|
284
|
+
// therefore picked up within PROJECT_IGNORE_FRESHNESS_CADENCE_MS, the
|
|
285
|
+
// same bound #2159 already accepted for the root sources.
|
|
286
|
+
// pi-authored `.gitignore` writes keep their instant path through
|
|
287
|
+
// `invalidateProjectIgnoreMatcherForPath`.
|
|
288
|
+
if (cached !== undefined &&
|
|
289
|
+
now - cached.checkedAtMs < PROJECT_IGNORE_FRESHNESS_CADENCE_MS) {
|
|
290
|
+
return cached.patterns;
|
|
291
|
+
}
|
|
269
292
|
const gitignoreSig = gitignoreSignature(dir);
|
|
293
|
+
// One stat, two consumers. `rememberIgnoreSource` used to stat the same
|
|
294
|
+
// file a second time on the line below this one.
|
|
295
|
+
rememberIgnoreSource(path.join(dir, ".gitignore"), gitignoreSig);
|
|
270
296
|
// #1105: gate on size too. `findPiLensConfigInDir` returns `size` alongside
|
|
271
297
|
// `mtimeMs` (both from one stat), and `gitignoreSignature` reads both for
|
|
272
298
|
// the nested `.gitignore`, so an mtime-preserving, length-changing edit to
|
|
@@ -274,11 +300,13 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
274
300
|
const pilensInfo = findPiLensConfigInDir(dir);
|
|
275
301
|
const pilensMtime = pilensInfo?.mtimeMs ?? -1;
|
|
276
302
|
const pilensSize = pilensInfo?.size ?? -1;
|
|
277
|
-
const cached = nestedCache.get(dir);
|
|
278
303
|
if (cached?.gitignoreMtimeMs === gitignoreSig.mtimeMs &&
|
|
279
304
|
cached?.gitignoreSize === gitignoreSig.size &&
|
|
280
305
|
cached?.pilensMtimeMs === pilensMtime &&
|
|
281
306
|
cached?.pilensSize === pilensSize) {
|
|
307
|
+
// Unchanged: re-arm the shared clock so the next window is measured
|
|
308
|
+
// from this check, not from the build that first populated the entry.
|
|
309
|
+
cached.checkedAtMs = now;
|
|
282
310
|
return cached.patterns;
|
|
283
311
|
}
|
|
284
312
|
const nestedConfig = loadPiLensConfigInDir(dir);
|
|
@@ -287,12 +315,19 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
287
315
|
...parseGitignoreContent(nestedConfig.ignore.join("\n"), "pilens"),
|
|
288
316
|
];
|
|
289
317
|
nestedCache.set(dir, {
|
|
318
|
+
checkedAtMs: now,
|
|
290
319
|
gitignoreMtimeMs: gitignoreSig.mtimeMs,
|
|
291
320
|
gitignoreSize: gitignoreSig.size,
|
|
292
321
|
pilensMtimeMs: pilensMtime,
|
|
293
322
|
pilensSize: pilensSize,
|
|
294
323
|
patterns: nextPatterns,
|
|
295
324
|
});
|
|
325
|
+
// #2071: drift is the SINGLE trigger. The rules under `dir` just changed,
|
|
326
|
+
// so every verdict computed under the superseded rules dies in the same
|
|
327
|
+
// step that supersedes them. `cached === undefined` is a first build, not
|
|
328
|
+
// a change, and has no verdicts to drop.
|
|
329
|
+
if (cached !== undefined)
|
|
330
|
+
dropVerdictsUnder(dir);
|
|
296
331
|
return nextPatterns;
|
|
297
332
|
};
|
|
298
333
|
// Per-matcher path โ pattern-verdict memo. The matcher itself is cached by
|
|
@@ -317,6 +352,44 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
317
352
|
// already flagged as ignored, so it doesn't reintroduce the per-file cost
|
|
318
353
|
// this memo exists to avoid for the common (not-ignored) case.
|
|
319
354
|
const patternMemo = new Map();
|
|
355
|
+
/**
|
|
356
|
+
* Drop every memoized verdict at or below `subtree`, leaving `nestedCache`
|
|
357
|
+
* alone. `patternsForDir` calls this the moment it rebuilds a directory's
|
|
358
|
+
* patterns, which is why it must NOT evict the entry that rebuild just
|
|
359
|
+
* wrote. `invalidateSubtree` layers the eviction on top for the write-hook
|
|
360
|
+
* path, where the caller knows the source changed but has not re-read it.
|
|
361
|
+
*/
|
|
362
|
+
function dropVerdictsUnder(subtree) {
|
|
363
|
+
const resolvedSubtree = path.resolve(subtree);
|
|
364
|
+
for (const key of patternMemo.keys()) {
|
|
365
|
+
const memoPath = key.slice(2);
|
|
366
|
+
const relative = path.relative(resolvedSubtree, memoPath);
|
|
367
|
+
if (!relative ||
|
|
368
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative))) {
|
|
369
|
+
patternMemo.delete(key);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const invalidateSubtree = (subtree) => {
|
|
374
|
+
dropVerdictsUnder(subtree);
|
|
375
|
+
nestedCache.delete(path.resolve(subtree));
|
|
376
|
+
};
|
|
377
|
+
// Compile expanded gitignore globs once per matcher instance. Relative-path
|
|
378
|
+
// resolution remains outside this cache, so nested ignore scopes cannot leak
|
|
379
|
+
// verdicts between sibling trees. The instance lifetime bounds this map.
|
|
380
|
+
const gitignoreGlobOptions = {
|
|
381
|
+
dot: true,
|
|
382
|
+
nocase: process.platform === "win32",
|
|
383
|
+
};
|
|
384
|
+
const compiledGlobs = new Map();
|
|
385
|
+
const matchExpanded = (value, expanded) => {
|
|
386
|
+
let compiled = compiledGlobs.get(expanded);
|
|
387
|
+
if (compiled === undefined) {
|
|
388
|
+
compiled = new Minimatch(expanded, gitignoreGlobOptions);
|
|
389
|
+
compiledGlobs.set(expanded, compiled);
|
|
390
|
+
}
|
|
391
|
+
return compiled.match(value);
|
|
392
|
+
};
|
|
320
393
|
// #703 perf follow-up: `normalizeEphemeralMapKey` (cheap slash-fold +
|
|
321
394
|
// Windows-lowercase, zero fs I/O), NOT `normalizeMapKey` (realpath-backed).
|
|
322
395
|
// This runs on every `isIgnored` call that reaches this branch โ walks
|
|
@@ -343,6 +416,13 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
343
416
|
return {
|
|
344
417
|
rootDir: resolvedRoot,
|
|
345
418
|
patterns,
|
|
419
|
+
getConsumedIgnoreSources: () => [...consumedIgnoreSources.values()],
|
|
420
|
+
refreshConsumedIgnoreSource(filePath) {
|
|
421
|
+
// The freshness sweep refreshes this baseline after invalidation.
|
|
422
|
+
if (consumedIgnoreSources.has(filePath))
|
|
423
|
+
rememberIgnoreSource(filePath);
|
|
424
|
+
},
|
|
425
|
+
invalidateSubtree,
|
|
346
426
|
ensureTrackedIndex() {
|
|
347
427
|
return collectTrackedFiles(resolvedRoot).then(() => undefined);
|
|
348
428
|
},
|
|
@@ -370,7 +450,7 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
370
450
|
const relative = path.relative(dir, resolved);
|
|
371
451
|
const normalized = normalizeIgnorePath(relative);
|
|
372
452
|
for (const pattern of dirPatterns) {
|
|
373
|
-
if (!matchesGitignorePattern(pattern, normalized, isDirectory))
|
|
453
|
+
if (!matchesGitignorePattern(pattern, normalized, isDirectory, matchExpanded))
|
|
374
454
|
continue;
|
|
375
455
|
ignored = !pattern.negated;
|
|
376
456
|
layer = pattern.layer;
|
|
@@ -413,6 +493,8 @@ export function createProjectIgnoreMatcher(rootDir, extraPatterns = [], globalPa
|
|
|
413
493
|
return buildProjectIgnoreMatcher(resolvedRoot, patterns);
|
|
414
494
|
}
|
|
415
495
|
const projectIgnoreMatcherCache = new Map();
|
|
496
|
+
/** Nested ignore sources are checked at most once per root per cadence window. */
|
|
497
|
+
export const PROJECT_IGNORE_FRESHNESS_CADENCE_MS = 2_000;
|
|
416
498
|
function fileFreshnessSignature(filePath) {
|
|
417
499
|
try {
|
|
418
500
|
const stat = fs.statSync(filePath);
|
|
@@ -428,6 +510,12 @@ function globalConfigSignature() {
|
|
|
428
510
|
function gitignoreSignature(rootDir) {
|
|
429
511
|
return fileFreshnessSignature(path.join(rootDir, ".gitignore"));
|
|
430
512
|
}
|
|
513
|
+
function hasIgnoreSourceDrift(sources) {
|
|
514
|
+
return sources.some((source) => {
|
|
515
|
+
const current = fileFreshnessSignature(source.path);
|
|
516
|
+
return current.mtimeMs !== source.mtimeMs || current.size !== source.size;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
431
519
|
/**
|
|
432
520
|
* The project config file found by the same upward walk as the loader. Cache
|
|
433
521
|
* invalidation must track the actual file found, not only a file directly under
|
|
@@ -446,6 +534,44 @@ export function getProjectIgnoreMatcher(rootDir) {
|
|
|
446
534
|
const lensConfig = lensConfigInfo(resolvedRoot);
|
|
447
535
|
const globalSig = globalConfigSignature();
|
|
448
536
|
const cached = projectIgnoreMatcherCache.get(resolvedRoot);
|
|
537
|
+
// A path can be discovered during the previous matcher lookup. Publish only
|
|
538
|
+
// previously unseen sources so a walk cannot replace a pre-edit baseline.
|
|
539
|
+
//
|
|
540
|
+
// This runs BEFORE the sweep below, and the order is load-bearing. A nested
|
|
541
|
+
// source can only be published on a call AFTER the one that consumed it.
|
|
542
|
+
// With the publish second, the sweep never saw a source during the window in
|
|
543
|
+
// which it was discovered, yet still reset `lastIgnoreFreshnessCheckMs`, so
|
|
544
|
+
// the first sweep that could see it ran a FULL cadence later. Pickup was up
|
|
545
|
+
// to 2x cadence, not 1x: measured stale at 2 s and 3 s, fresh at 4 s. The
|
|
546
|
+
// baseline is protected by the `known` set, not by the ordering, so moving
|
|
547
|
+
// the publish earlier cannot let a walk overwrite a pre-edit baseline.
|
|
548
|
+
if (cached) {
|
|
549
|
+
const known = new Set(cached.ignoreSources.map((source) => source.path));
|
|
550
|
+
for (const source of cached.matcher.getConsumedIgnoreSources()) {
|
|
551
|
+
if (!known.has(source.path))
|
|
552
|
+
cached.ignoreSources.push(source);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (cached &&
|
|
556
|
+
Date.now() - cached.lastIgnoreFreshnessCheckMs >=
|
|
557
|
+
PROJECT_IGNORE_FRESHNESS_CADENCE_MS) {
|
|
558
|
+
cached.lastIgnoreFreshnessCheckMs = Date.now();
|
|
559
|
+
if (hasIgnoreSourceDrift(cached.ignoreSources)) {
|
|
560
|
+
for (const source of cached.ignoreSources) {
|
|
561
|
+
if (hasIgnoreSourceDrift([source])) {
|
|
562
|
+
const sourceIndex = cached.ignoreSources.findIndex((cachedSource) => cachedSource.path === source.path);
|
|
563
|
+
invalidateProjectIgnoreMatcherForPath(source.path);
|
|
564
|
+
cached.matcher.refreshConsumedIgnoreSource(source.path);
|
|
565
|
+
const refreshedSource = cached.matcher
|
|
566
|
+
.getConsumedIgnoreSources()
|
|
567
|
+
.find((current) => current.path === source.path);
|
|
568
|
+
if (sourceIndex !== -1 && refreshedSource !== undefined) {
|
|
569
|
+
cached.ignoreSources.splice(sourceIndex, 1, refreshedSource);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
449
575
|
if (cached?.gitignoreMtimeMs === gitignoreSig.mtimeMs &&
|
|
450
576
|
cached?.gitignoreSize === gitignoreSig.size &&
|
|
451
577
|
cached?.lensConfigPath === lensConfig.path &&
|
|
@@ -463,6 +589,8 @@ export function getProjectIgnoreMatcher(rootDir) {
|
|
|
463
589
|
const projectConfig = loadPiLensProjectConfig(resolvedRoot, lensConfig.info);
|
|
464
590
|
const matcher = createProjectIgnoreMatcher(resolvedRoot, projectConfig.ignore, getGlobalIgnorePatterns());
|
|
465
591
|
projectIgnoreMatcherCache.set(resolvedRoot, {
|
|
592
|
+
ignoreSources: [...matcher.getConsumedIgnoreSources()],
|
|
593
|
+
lastIgnoreFreshnessCheckMs: Date.now(),
|
|
466
594
|
gitignoreMtimeMs: gitignoreSig.mtimeMs,
|
|
467
595
|
gitignoreSize: gitignoreSig.size,
|
|
468
596
|
lensConfigPath: lensConfig.path,
|
|
@@ -474,6 +602,28 @@ export function getProjectIgnoreMatcher(rootDir) {
|
|
|
474
602
|
});
|
|
475
603
|
return matcher;
|
|
476
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Invalidate the cached matcher state affected by a written `.gitignore`.
|
|
607
|
+
*
|
|
608
|
+
* Root changes discard the whole matcher because they change its base pattern
|
|
609
|
+
* set. Nested changes evict only verdicts in that directory's subtree; the
|
|
610
|
+
* matcher and compiled-glob memo remain reusable for unrelated trees.
|
|
611
|
+
*/
|
|
612
|
+
export function invalidateProjectIgnoreMatcherForPath(filePath) {
|
|
613
|
+
const resolvedPath = path.resolve(filePath);
|
|
614
|
+
if (path.basename(resolvedPath).toLowerCase() !== ".gitignore")
|
|
615
|
+
return;
|
|
616
|
+
for (const [cachedRoot, cached] of projectIgnoreMatcherCache) {
|
|
617
|
+
if (!isUnderDir(resolvedPath, cachedRoot))
|
|
618
|
+
continue;
|
|
619
|
+
if (normalizeFilePath(resolvedPath) ===
|
|
620
|
+
normalizeFilePath(path.join(cachedRoot, ".gitignore"))) {
|
|
621
|
+
projectIgnoreMatcherCache.delete(cachedRoot);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
cached.matcher.invalidateSubtree(path.dirname(resolvedPath));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
477
627
|
export function isPathIgnoredByProject(filePath, rootDir, isDirectory = false) {
|
|
478
628
|
return getProjectIgnoreMatcher(rootDir).isIgnored(filePath, isDirectory);
|
|
479
629
|
}
|
|
@@ -240,6 +240,7 @@ export const DELIVERY_SURFACES = {
|
|
|
240
240
|
// `late_auxiliary_findings` latency record rather than silenced.
|
|
241
241
|
"runtime-turn:late-auxiliary-findings": gated(RUNTIME_TURN_FILE, "Turn-end late-auxiliary LSP findings (collect-later probe of aux " +
|
|
242
242
|
"client caches whose grace window expired).", ["gateFindingsByPathFreshness"], ['store: "late-auxiliary-findings"']),
|
|
243
|
+
"runtime-turn:late-runner-findings": gated(RUNTIME_TURN_FILE, "Turn-end CLI runner findings collected after the post-write path.", ["gateFindingsByPathFreshness"], ['store: "late-runner-findings"'], { evidenceMin: 2 }),
|
|
243
244
|
"runtime-turn:cascade-blocker": labeled(RUNTIME_TURN_FILE, "Turn-end ๐งช cascade neighbor blocker.", "Cascade results settle synchronously this turn where possible " +
|
|
244
245
|
"(`settleCascadeRuns`), but an unsettled compute can carry over to a " +
|
|
245
246
|
"later turn (bounded by a carry cap) โ this round does not freshness-" +
|
|
@@ -319,6 +320,10 @@ export const DELIVERY_SURFACES = {
|
|
|
319
320
|
"git-guard:commit-blocked": labeled("clients/git-guard.ts", "Git-guard commit/push ๐ด COMMIT BLOCKED verdict (--lens-guard).", "Synchronous preflight rejection returned inline with the failed git " +
|
|
320
321
|
"command โ no stored state is delivered, so nothing can go stale between " +
|
|
321
322
|
"detection and delivery.", "live"),
|
|
323
|
+
"shared-checkout-guard:worktree-mutation-blocked": labeled("clients/shared-checkout-guard.ts", "Shared-checkout ๐ด WORKING-TREE CHANGE BLOCKED verdict (--lens-checkout-guard).", "Synchronous preflight rejection returned inline with the failed git " +
|
|
324
|
+
"command. Both inputs are read at decision time โ the instance registry " +
|
|
325
|
+
"and `git status` โ so no stored finding is replayed and nothing can go " +
|
|
326
|
+
"stale between detection and delivery.", "live"),
|
|
322
327
|
"read-guard-tool-lines:preflight-errors": labeled("clients/read-guard-tool-lines.ts", "Read-guard hashline BLOCKED / RE-READ REQUIRED preflight errors.", "Computed fresh per edit attempt and returned as that attempt's rejection โ " +
|
|
323
328
|
"no cached findings are replayed, so there is no staleness window.", "live"),
|
|
324
329
|
"tool-call:duplicate-export-blocker": labeled("clients/runtime-tool-call.ts", "Duplicate-export STOP rejection returned inline with the failed edit.", "Synchronous preflight rejection computed per edit attempt; no stored state.", "live"),
|
|
@@ -17,6 +17,7 @@ import { normalizeMapKey } from "./path-utils.js";
|
|
|
17
17
|
import { TERRAGRUNT_FILENAMES } from "./file-kinds.js";
|
|
18
18
|
import { detectIndentation, hasDetectableIndentation, } from "./dispatch/indent-detect.js";
|
|
19
19
|
import { logLatency } from "./latency-logger.js";
|
|
20
|
+
import { compareOrdinal } from "./string-utils.js";
|
|
20
21
|
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
21
22
|
import { findGlobalBinary, findLocalBinUpwards } from "./package-manager.js";
|
|
22
23
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
@@ -224,6 +225,9 @@ function noteCooldownServedVerdict(command, latch) {
|
|
|
224
225
|
retryAfterMs: Math.max(1, retryAtMs - Date.now()),
|
|
225
226
|
budgetMs: WHICH_BUDGET_MS,
|
|
226
227
|
servedFromCooldown: true,
|
|
228
|
+
// No probe ran here: the latch's own remembered cause is replayed
|
|
229
|
+
// as-is, so the call site is the one asserting it (#2209).
|
|
230
|
+
classifiedBy: "caller",
|
|
227
231
|
});
|
|
228
232
|
}
|
|
229
233
|
async function which(command) {
|
|
@@ -261,6 +265,7 @@ async function which(command) {
|
|
|
261
265
|
latched: true,
|
|
262
266
|
hostStallMs,
|
|
263
267
|
budgetMs: WHICH_BUDGET_MS,
|
|
268
|
+
classifiedBy: "probe",
|
|
264
269
|
});
|
|
265
270
|
return resolved;
|
|
266
271
|
}
|
|
@@ -281,9 +286,15 @@ async function which(command) {
|
|
|
281
286
|
...(proberRan && { unclassifiedFailureOutcome: "missing" }),
|
|
282
287
|
});
|
|
283
288
|
let { outcome, cause } = classified;
|
|
289
|
+
// This call site OVERRIDES classifyProbeFailure's own answer below, so a
|
|
290
|
+
// row it forced is a caller assertion, not a probe classification โ the
|
|
291
|
+
// scrutiny availability-policy.ts:253-258 reserves for "caller" rows must
|
|
292
|
+
// not be laundered away by mislabeling this one "probe" (#2226 review F1).
|
|
293
|
+
let outcomeForced = false;
|
|
284
294
|
if (!proberRan && outcome !== "transient") {
|
|
285
295
|
outcome = "transient";
|
|
286
296
|
cause = "probe-rejected";
|
|
297
|
+
outcomeForced = true;
|
|
287
298
|
}
|
|
288
299
|
const retryAfterMs = entry.latch.noteUnavailable(outcome, cause);
|
|
289
300
|
if (outcome === "transient")
|
|
@@ -303,6 +314,7 @@ async function which(command) {
|
|
|
303
314
|
hostStallMs,
|
|
304
315
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
305
316
|
budgetMs: WHICH_BUDGET_MS,
|
|
317
|
+
classifiedBy: outcomeForced ? "caller" : "probe",
|
|
306
318
|
});
|
|
307
319
|
return null;
|
|
308
320
|
}
|
|
@@ -1413,9 +1425,7 @@ const FORMATTER_CONFIG_FILES = [
|
|
|
1413
1425
|
];
|
|
1414
1426
|
async function formatterConfigSignature(cwd) {
|
|
1415
1427
|
const paths = await findUp(FORMATTER_CONFIG_FILES, cwd);
|
|
1416
|
-
const parts = await Promise.all(paths
|
|
1417
|
-
.sort((a, b) => a.localeCompare(b))
|
|
1418
|
-
.map(async (filePath) => {
|
|
1428
|
+
const parts = await Promise.all(paths.sort(compareOrdinal).map(async (filePath) => {
|
|
1419
1429
|
try {
|
|
1420
1430
|
const stat = await fs.stat(filePath);
|
|
1421
1431
|
return `${filePath}:${stat.mtimeMs}:${stat.size}`;
|
|
@@ -1459,6 +1469,20 @@ export async function getFormattersForFile(filePath, cwd) {
|
|
|
1459
1469
|
}
|
|
1460
1470
|
if (cached.entries.has(cacheKey)) {
|
|
1461
1471
|
const enabledNames = cached.entries.get(cacheKey);
|
|
1472
|
+
const selectedFormatterName = enabledNames && enabledNames.length > 0 ? enabledNames[0] : null;
|
|
1473
|
+
logLatency({
|
|
1474
|
+
type: "phase",
|
|
1475
|
+
phase: "formatter_selected",
|
|
1476
|
+
filePath: filePath,
|
|
1477
|
+
durationMs: 0,
|
|
1478
|
+
metadata: {
|
|
1479
|
+
formatter: selectedFormatterName,
|
|
1480
|
+
reason: "cache",
|
|
1481
|
+
cached: true,
|
|
1482
|
+
outcome: "hit",
|
|
1483
|
+
cwd,
|
|
1484
|
+
},
|
|
1485
|
+
});
|
|
1462
1486
|
if (!enabledNames || enabledNames.length === 0)
|
|
1463
1487
|
return [];
|
|
1464
1488
|
// Return cached formatters by name (preserves priority order)
|
|
@@ -1601,6 +1625,7 @@ export async function getFormattersForFile(filePath, cwd) {
|
|
|
1601
1625
|
metadata: {
|
|
1602
1626
|
formatter: selected?.name ?? null,
|
|
1603
1627
|
reason: selectionReason,
|
|
1628
|
+
outcome: "miss",
|
|
1604
1629
|
cwd,
|
|
1605
1630
|
...(provisional && {
|
|
1606
1631
|
cached: false,
|