@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
|
@@ -8,6 +8,7 @@ import { normalizeMapKey } from "../path-utils.js";
|
|
|
8
8
|
import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
|
|
9
9
|
import { freshnessFromMtime } from "../freshness.js";
|
|
10
10
|
import { logLatency } from "../latency-logger.js";
|
|
11
|
+
import { compareOrdinal } from "../string-utils.js";
|
|
11
12
|
import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-session.js";
|
|
12
13
|
import { createDiskBindingCache, } from "./diagnostic-binding.js";
|
|
13
14
|
/**
|
|
@@ -281,7 +282,11 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
|
|
|
281
282
|
* Two invalidation layers:
|
|
282
283
|
* 1. The file's OWN mtime must be unchanged since it was scanned (exact
|
|
283
284
|
* match against `entry.mtimeMs`) — any drift, or a stat failure
|
|
284
|
-
* (deleted/unreadable), invalidates.
|
|
285
|
+
* (deleted/unreadable), invalidates. #2300: when the entry also carries a
|
|
286
|
+
* `sizeBytes`, the current size must match too — an external rewrite that
|
|
287
|
+
* lands in the same mtime bucket with a different length is otherwise
|
|
288
|
+
* invisible to mtime alone. A pre-#2300 entry without `sizeBytes` fails
|
|
289
|
+
* OPEN to the mtime-only check (see the field's doc comment).
|
|
285
290
|
* 2. When a reverse-dependency index is available (`getImports` returns an
|
|
286
291
|
* array rather than `undefined`), every file THIS file imports must not
|
|
287
292
|
* have changed after `entry.scannedAt` either — this is the fix for the
|
|
@@ -326,15 +331,19 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
|
|
|
326
331
|
* (#1491/#1498) rather than just a redundant re-touch.
|
|
327
332
|
*/
|
|
328
333
|
export function isEntryFresh(filePath, entry, getImports) {
|
|
329
|
-
let
|
|
334
|
+
let ownStat;
|
|
330
335
|
try {
|
|
331
|
-
|
|
336
|
+
ownStat = fs.statSync(filePath);
|
|
332
337
|
}
|
|
333
338
|
catch {
|
|
334
339
|
return false; // deleted / unreadable
|
|
335
340
|
}
|
|
336
|
-
if (
|
|
341
|
+
if (ownStat.mtimeMs !== entry.mtimeMs)
|
|
337
342
|
return false;
|
|
343
|
+
// #2300: same axis reused from the stat above — no extra syscall.
|
|
344
|
+
if (entry.sizeBytes !== undefined && ownStat.size !== entry.sizeBytes) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
338
347
|
const imports = getImports(filePath);
|
|
339
348
|
if (imports === undefined) {
|
|
340
349
|
// #1793: an entry recorded WITH dependency knowledge must not fall
|
|
@@ -376,7 +385,7 @@ export function cacheKeyFor(filePath) {
|
|
|
376
385
|
*/
|
|
377
386
|
export function buildScopeKey(clientScope, excludeServerIds) {
|
|
378
387
|
const excluded = excludeServerIds
|
|
379
|
-
? [...excludeServerIds].sort(
|
|
388
|
+
? [...excludeServerIds].sort(compareOrdinal)
|
|
380
389
|
: [];
|
|
381
390
|
return `${clientScope}|${excluded.join(",")}`;
|
|
382
391
|
}
|
|
@@ -542,7 +551,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
542
551
|
scannedAt: entry.scannedAt,
|
|
543
552
|
};
|
|
544
553
|
},
|
|
545
|
-
record(filePath, scopeKey, diagnostics, mtimeMs, contentHash) {
|
|
554
|
+
record(filePath, scopeKey, diagnostics, mtimeMs, contentHash, sizeBytes) {
|
|
546
555
|
entries[cacheKeyFor(filePath)] = {
|
|
547
556
|
diagnostics,
|
|
548
557
|
count: diagnostics.length,
|
|
@@ -556,6 +565,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
556
565
|
// weaker mtime-only path only when that refusal is warranted.
|
|
557
566
|
depIndexAtScan: hasDepKnowledge(filePath),
|
|
558
567
|
...(contentHash !== undefined && { contentHash }),
|
|
568
|
+
...(sizeBytes !== undefined && { sizeBytes }),
|
|
559
569
|
};
|
|
560
570
|
dirty = true;
|
|
561
571
|
},
|
|
@@ -40,22 +40,26 @@
|
|
|
40
40
|
* apart again (#1618 acceptance criterion 6).
|
|
41
41
|
*/
|
|
42
42
|
import { logLatency } from "../latency-logger.js";
|
|
43
|
+
import { getProcessSingleton } from "../process-singletons.js";
|
|
43
44
|
/** Extra headroom beyond the sweep's own wall-clock ceiling before an
|
|
44
45
|
* idle-reset delay (or the max-hold-age failsafe) trusts that a real sweep
|
|
45
46
|
* must be done. Single source for both derivations below AND for every
|
|
46
47
|
* idle-reset path `clients/runtime-turn.ts` derives (base, subagent, and
|
|
47
48
|
* budget-shortened) — never a second independently-tunable literal. */
|
|
48
49
|
export const SWEEP_IDLE_SAFETY_MARGIN_MS = 60_000;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const WORKSPACE_SWEEP_HOLD_FAMILY = "lsp.workspace-sweep-hold";
|
|
51
|
+
const WORKSPACE_SWEEP_HOLD_VERSION = 1;
|
|
52
|
+
function state() {
|
|
53
|
+
return getProcessSingleton(WORKSPACE_SWEEP_HOLD_FAMILY, WORKSPACE_SWEEP_HOLD_VERSION, () => ({ holds: new Map(), nextHoldId: 1, idleWaiters: [] }));
|
|
54
|
+
}
|
|
52
55
|
function flushIdleWaitersIfEmpty() {
|
|
53
|
-
|
|
56
|
+
const current = state();
|
|
57
|
+
if (current.holds.size > 0)
|
|
54
58
|
return;
|
|
55
|
-
if (idleWaiters.length === 0)
|
|
59
|
+
if (current.idleWaiters.length === 0)
|
|
56
60
|
return;
|
|
57
|
-
const waiters = idleWaiters;
|
|
58
|
-
idleWaiters = [];
|
|
61
|
+
const waiters = current.idleWaiters;
|
|
62
|
+
current.idleWaiters = [];
|
|
59
63
|
for (const waiter of waiters) {
|
|
60
64
|
try {
|
|
61
65
|
waiter();
|
|
@@ -77,15 +81,16 @@ export function getWorkspaceSweepMaxHoldAgeMs() {
|
|
|
77
81
|
return getFullScanWallClockMs() + SWEEP_IDLE_SAFETY_MARGIN_MS;
|
|
78
82
|
}
|
|
79
83
|
function reapStaleHolds() {
|
|
80
|
-
|
|
84
|
+
const current = state();
|
|
85
|
+
if (current.holds.size === 0)
|
|
81
86
|
return;
|
|
82
87
|
const maxAgeMs = getWorkspaceSweepMaxHoldAgeMs();
|
|
83
88
|
const now = Date.now();
|
|
84
|
-
for (const [holdId, acquiredAt] of holds) {
|
|
89
|
+
for (const [holdId, acquiredAt] of current.holds) {
|
|
85
90
|
const ageMs = now - acquiredAt;
|
|
86
91
|
if (ageMs <= maxAgeMs)
|
|
87
92
|
continue;
|
|
88
|
-
holds.delete(holdId);
|
|
93
|
+
current.holds.delete(holdId);
|
|
89
94
|
logLatency({
|
|
90
95
|
type: "phase",
|
|
91
96
|
phase: "lsp_workspace_sweep_hold_force_released",
|
|
@@ -104,10 +109,11 @@ function reapStaleHolds() {
|
|
|
104
109
|
* force-released (session reset / max-age reap), is a no-op.
|
|
105
110
|
*/
|
|
106
111
|
export function acquireWorkspaceSweepHold() {
|
|
107
|
-
const
|
|
108
|
-
|
|
112
|
+
const current = state();
|
|
113
|
+
const holdId = current.nextHoldId++;
|
|
114
|
+
current.holds.set(holdId, Date.now());
|
|
109
115
|
return () => {
|
|
110
|
-
if (!holds.delete(holdId))
|
|
116
|
+
if (!state().holds.delete(holdId))
|
|
111
117
|
return;
|
|
112
118
|
flushIdleWaitersIfEmpty();
|
|
113
119
|
};
|
|
@@ -117,7 +123,7 @@ export function acquireWorkspaceSweepHold() {
|
|
|
117
123
|
* "active" past its own max age. */
|
|
118
124
|
export function isWorkspaceSweepActive() {
|
|
119
125
|
reapStaleHolds();
|
|
120
|
-
return holds.size > 0;
|
|
126
|
+
return state().holds.size > 0;
|
|
121
127
|
}
|
|
122
128
|
/**
|
|
123
129
|
* Run `cb` once the hold count next returns to zero. Runs synchronously and
|
|
@@ -126,11 +132,12 @@ export function isWorkspaceSweepActive() {
|
|
|
126
132
|
*/
|
|
127
133
|
export function runWhenWorkspaceSweepIdle(cb) {
|
|
128
134
|
reapStaleHolds();
|
|
129
|
-
|
|
135
|
+
const current = state();
|
|
136
|
+
if (current.holds.size === 0) {
|
|
130
137
|
cb();
|
|
131
138
|
return;
|
|
132
139
|
}
|
|
133
|
-
idleWaiters.push(cb);
|
|
140
|
+
current.idleWaiters.push(cb);
|
|
134
141
|
}
|
|
135
142
|
/**
|
|
136
143
|
* `resetLSPService({reason: "session_start"})` calls this: a session
|
|
@@ -142,23 +149,25 @@ export function runWhenWorkspaceSweepIdle(cb) {
|
|
|
142
149
|
* silent.
|
|
143
150
|
*/
|
|
144
151
|
export function clearWorkspaceSweepHoldForSessionStart() {
|
|
145
|
-
|
|
152
|
+
const current = state();
|
|
153
|
+
if (current.holds.size === 0)
|
|
146
154
|
return;
|
|
147
155
|
logLatency({
|
|
148
156
|
type: "phase",
|
|
149
157
|
phase: "lsp_workspace_sweep_hold_session_reset",
|
|
150
158
|
filePath: "",
|
|
151
159
|
durationMs: 0,
|
|
152
|
-
metadata: { clearedHolds: holds.size },
|
|
160
|
+
metadata: { clearedHolds: current.holds.size },
|
|
153
161
|
});
|
|
154
|
-
holds.clear();
|
|
162
|
+
current.holds.clear();
|
|
155
163
|
flushIdleWaitersIfEmpty();
|
|
156
164
|
}
|
|
157
|
-
/** Test-only: reset the
|
|
165
|
+
/** Test-only: reset the process-singleton hold state between tests. */
|
|
158
166
|
export function _resetWorkspaceSweepHoldForTests() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
const current = state();
|
|
168
|
+
current.holds.clear();
|
|
169
|
+
current.idleWaiters = [];
|
|
170
|
+
current.nextHoldId = 1;
|
|
162
171
|
}
|
|
163
172
|
/**
|
|
164
173
|
* Wall-clock ceiling for one `lens_diagnostics mode=full` scan
|
|
@@ -52,8 +52,22 @@ function combineResults(results) {
|
|
|
52
52
|
}
|
|
53
53
|
function uniqueDetails(files, fileDetails) {
|
|
54
54
|
const byPath = new Map();
|
|
55
|
+
// #2016: `files` and `fileDetails` name the same paths, so without this the
|
|
56
|
+
// map-build loop and the lookup below each pay `realpathSync.native` for the
|
|
57
|
+
// same path (~200 microseconds per call on Windows; POSIX short-circuits, so
|
|
58
|
+
// CI cannot see it). The memo lives for one call, so it has no staleness
|
|
59
|
+
// window at all and needs no freshness design.
|
|
60
|
+
const keyMemo = new Map();
|
|
61
|
+
const keyFor = (filePath) => {
|
|
62
|
+
let key = keyMemo.get(filePath);
|
|
63
|
+
if (key === undefined) {
|
|
64
|
+
key = normalizeMapKey(path.resolve(filePath));
|
|
65
|
+
keyMemo.set(filePath, key);
|
|
66
|
+
}
|
|
67
|
+
return key;
|
|
68
|
+
};
|
|
55
69
|
for (const detail of fileDetails) {
|
|
56
|
-
const key =
|
|
70
|
+
const key = keyFor(detail.filePath);
|
|
57
71
|
const previous = byPath.get(key);
|
|
58
72
|
if (!previous) {
|
|
59
73
|
byPath.set(key, detail);
|
|
@@ -70,7 +84,7 @@ function uniqueDetails(files, fileDetails) {
|
|
|
70
84
|
importsChanged: previous.importsChanged || detail.importsChanged,
|
|
71
85
|
});
|
|
72
86
|
}
|
|
73
|
-
return files.map((filePath) => byPath.get(
|
|
87
|
+
return files.map((filePath) => byPath.get(keyFor(filePath)) ?? {
|
|
74
88
|
filePath,
|
|
75
89
|
// Resource operations have no already-computed text range. A small
|
|
76
90
|
// range is still enough to invalidate the touched-file turn state;
|
|
@@ -24,7 +24,7 @@ import { normalizeMapKey } from "../path-utils.js";
|
|
|
24
24
|
import { loadProjectSnapshot } from "../project-snapshot.js";
|
|
25
25
|
import { buildOrUpdateGraph } from "../review-graph/service.js";
|
|
26
26
|
import { recordDiagnostics } from "../widget-state.js";
|
|
27
|
-
import { deserializeWordIndex,
|
|
27
|
+
import { deserializeWordIndex, removeWordIndexDocumentAsync, scheduleWordIndexPersist, updateWordIndexDocumentForEdit, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
|
|
28
28
|
import { logWordIndex } from "../word-index-logger.js";
|
|
29
29
|
import { createMcpHost } from "./host-shim.js";
|
|
30
30
|
// #536: module-scoped FactStore for the warm-analyze graph seam, mirroring the
|
|
@@ -33,7 +33,9 @@ import { createMcpHost } from "./host-shim.js";
|
|
|
33
33
|
// tiers key off a stable FactStore instance across calls, so a fresh FactStore
|
|
34
34
|
// per call would defeat that reuse. Scoped separately from integration.ts's
|
|
35
35
|
// singleton since this file has no dependency on that module's internal state.
|
|
36
|
-
|
|
36
|
+
// Subject labels this store's capacity-eviction telemetry distinctly from
|
|
37
|
+
// the other five production FactStore instances (#2243 review round 3, F1).
|
|
38
|
+
const warmGraphFacts = new FactStore("mcp-analyze");
|
|
37
39
|
// #536 rider (issue body: "when #348 phase 2 lands, the word-index per-edit
|
|
38
40
|
// update should ride the SAME seam so both indexes stay warm together"):
|
|
39
41
|
// MCP has no RuntimeCoordinator/`runtime.wordIndex` to hold a live index the
|
|
@@ -295,8 +297,9 @@ export async function analyzeFile(filePath, cwd, options = {}) {
|
|
|
295
297
|
// index with no `forward` map (or none loaded) ⇒ no-op (no incremental
|
|
296
298
|
// primitive available — the eventual full rebuild covers it); an
|
|
297
299
|
// oversized file is REMOVED, never partially indexed; the update is
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
+
// cooperative and serialized through the index's own operation queue
|
|
301
|
+
// (#2067 AC4), so this seam no longer holds the event loop for a whole
|
|
302
|
+
// replacement either; a successful update schedules the SAME debounced persist
|
|
300
303
|
// (`scheduleWordIndexPersist`, `PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS`)
|
|
301
304
|
// pi's path uses — no second persist mechanism.
|
|
302
305
|
//
|
|
@@ -313,10 +316,13 @@ export async function analyzeFile(filePath, cwd, options = {}) {
|
|
|
313
316
|
const content = fs.readFileSync(absPath, "utf8");
|
|
314
317
|
const byteLength = Buffer.byteLength(content, "utf-8");
|
|
315
318
|
if (byteLength > WORD_INDEX_MAX_BYTES) {
|
|
316
|
-
|
|
319
|
+
await removeWordIndexDocumentAsync(warmIndex, absPath);
|
|
317
320
|
}
|
|
318
321
|
else {
|
|
319
|
-
|
|
322
|
+
await updateWordIndexDocumentForEdit(warmIndex, {
|
|
323
|
+
path: absPath,
|
|
324
|
+
content,
|
|
325
|
+
});
|
|
320
326
|
}
|
|
321
327
|
scheduleWordIndexPersist(cwd, warmIndex);
|
|
322
328
|
}
|
|
@@ -309,8 +309,13 @@ export function runTurnEndForIpc(cwd) {
|
|
|
309
309
|
const inFlight = inFlightIpcTurnEnds.get(cwd);
|
|
310
310
|
if (inFlight)
|
|
311
311
|
return inFlight;
|
|
312
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
313
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
314
|
+
// evict a live successor a second writer registered under the same cwd
|
|
315
|
+
// mid-flight, after which the next caller starts a duplicate Stop-hook pass.
|
|
312
316
|
const run = runTurnEndForIpcNow(cwd).finally(() => {
|
|
313
|
-
inFlightIpcTurnEnds.
|
|
317
|
+
if (inFlightIpcTurnEnds.get(cwd) === run)
|
|
318
|
+
inFlightIpcTurnEnds.delete(cwd);
|
|
314
319
|
});
|
|
315
320
|
inFlightIpcTurnEnds.set(cwd, run);
|
|
316
321
|
return run;
|
|
@@ -374,3 +379,11 @@ export function _resetTurnEndChain() {
|
|
|
374
379
|
pendingTurnEndDeliveries.clear();
|
|
375
380
|
inFlightIpcTurnEnds.clear();
|
|
376
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Test hook — read the `runTurnEndForIpc` in-flight map directly (#1968's
|
|
384
|
+
* ABA regression: a second writer replacing an entry mid-flight, then a
|
|
385
|
+
* late-settling first run evicting it with a bare delete-by-key).
|
|
386
|
+
*/
|
|
387
|
+
export function _peekInFlightIpcTurnEnds() {
|
|
388
|
+
return inFlightIpcTurnEnds;
|
|
389
|
+
}
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
* `arrayBuffers` is used as the process-wide proxy instead — WASM linear
|
|
30
30
|
* memory backs an ArrayBuffer, so it is already included there.
|
|
31
31
|
*/
|
|
32
|
+
import { countPostingEntries, estimateWordIndexStoreBytes, } from "./word-index-store.js";
|
|
32
33
|
import { getSharedTreeSitterClient } from "./tree-sitter-shared.js";
|
|
33
34
|
import { getReviewGraphWorkspaceCacheSnapshot } from "./review-graph/builder.js";
|
|
34
35
|
import { getDispatchCascadeCacheStats } from "./dispatch/integration.js";
|
|
36
|
+
import { getLspDocumentTextRetentionSnapshot } from "./lsp/client.js";
|
|
35
37
|
/** Every N turns, emit one `memory_sample` latency.log line (#1123 item 2). */
|
|
36
38
|
export const MEMORY_SAMPLE_TURN_INTERVAL = 10;
|
|
37
39
|
/** True on turn 10, 20, 30, ... — never on turn 0 (nothing meaningful is
|
|
@@ -121,16 +123,36 @@ export function collectMemorySampleSubsystems(wordIndex) {
|
|
|
121
123
|
: null;
|
|
122
124
|
const dispatchCaches = getDispatchCascadeCacheStats();
|
|
123
125
|
return {
|
|
126
|
+
lsp: (() => {
|
|
127
|
+
const retention = getLspDocumentTextRetentionSnapshot();
|
|
128
|
+
return {
|
|
129
|
+
clients: retention.clients,
|
|
130
|
+
clientRoots: retention.roots,
|
|
131
|
+
incrementalTextEntries: retention.entries,
|
|
132
|
+
incrementalTextBytes: retention.bytes,
|
|
133
|
+
};
|
|
134
|
+
})(),
|
|
124
135
|
reviewGraph,
|
|
125
136
|
wordIndex: wordIndex
|
|
126
137
|
? {
|
|
127
138
|
docs: wordIndex.docLengths.size,
|
|
139
|
+
fileTable: wordIndex.fileTable.size,
|
|
128
140
|
postings: wordIndex.postings.size,
|
|
141
|
+
// O(distinct tokens), reading only `.length`/`.byteLength` per
|
|
142
|
+
// list — no posting ELEMENT is touched, so this stays inside the
|
|
143
|
+
// module docstring’s bounded-read constraint.
|
|
144
|
+
postingEntries: countPostingEntries(wordIndex),
|
|
145
|
+
residentBytes: estimateWordIndexStoreBytes(wordIndex),
|
|
129
146
|
forwardEntries: wordIndex.forward?.size ?? 0,
|
|
130
147
|
}
|
|
131
148
|
: null,
|
|
132
149
|
treeSitter,
|
|
133
|
-
dispatchCaches
|
|
150
|
+
dispatchCaches: {
|
|
151
|
+
...dispatchCaches,
|
|
152
|
+
// Three isolated-store runs measured 320 bytes per production entry.
|
|
153
|
+
// Keep this O(1); the cache key and Map bookkeeping are included.
|
|
154
|
+
estimatedBytes: dispatchCaches.recentlyCleanNeighborCacheSize * 320,
|
|
155
|
+
},
|
|
134
156
|
};
|
|
135
157
|
}
|
|
136
158
|
/** Assemble one full sample. `mem` is injectable for tests; defaults to a
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Session-local attribution captured before a stale message_end can drain. */
|
|
2
|
+
let lastStableSessionId;
|
|
3
|
+
let previousSessionId;
|
|
4
|
+
/** Remember the stable id used by a live message_end's own cache row. */
|
|
5
|
+
export function noteLiveMessageEndSessionId(sessionId) {
|
|
6
|
+
lastStableSessionId = sessionId;
|
|
7
|
+
}
|
|
8
|
+
/** Return the newest usable id for a stale message_end attribution. */
|
|
9
|
+
export function getLastLiveMessageEndSessionId() {
|
|
10
|
+
return lastStableSessionId ?? previousSessionId;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Rotate the attribution anchor at the real session boundary. A queued stale
|
|
14
|
+
* message_end from the session being replaced may drain after this boundary,
|
|
15
|
+
* so retain exactly one prior anchor while making room for the replacement.
|
|
16
|
+
*/
|
|
17
|
+
export function rotateMessageEndAttribution() {
|
|
18
|
+
// Keep the prior anchor when no live message_end arrived this session:
|
|
19
|
+
// two back-to-back boundaries (reload/resume re-announcing) must not
|
|
20
|
+
// erase the only id a still-queued stale drain can use (#1956 R10).
|
|
21
|
+
if (lastStableSessionId !== undefined) {
|
|
22
|
+
previousSessionId = lastStableSessionId;
|
|
23
|
+
}
|
|
24
|
+
lastStableSessionId = undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Full reset seam used by the session-state registry and test isolation. */
|
|
27
|
+
export function resetMessageEndAttribution() {
|
|
28
|
+
lastStableSessionId = undefined;
|
|
29
|
+
previousSessionId = undefined;
|
|
30
|
+
}
|
|
@@ -34,14 +34,31 @@ function isInsideGitRepo(startDir) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Get current git commit hash (short)
|
|
36
36
|
*/
|
|
37
|
-
function getCurrentCommit() {
|
|
38
|
-
|
|
37
|
+
function getCurrentCommit(startDir) {
|
|
38
|
+
const repoStartDir = path.resolve(startDir);
|
|
39
|
+
if (!isInsideGitRepo(repoStartDir)) {
|
|
39
40
|
return "unknown";
|
|
40
41
|
}
|
|
42
|
+
let spawnDir = repoStartDir;
|
|
43
|
+
while (!fs.existsSync(spawnDir)) {
|
|
44
|
+
const parent = path.dirname(spawnDir);
|
|
45
|
+
if (parent === spawnDir)
|
|
46
|
+
return "unknown";
|
|
47
|
+
spawnDir = parent;
|
|
48
|
+
}
|
|
41
49
|
try {
|
|
50
|
+
// #2095: execSync inherits the child's stderr to THIS process by
|
|
51
|
+
// default (only stdout is captured for the return value), so a
|
|
52
|
+
// failing `git rev-parse` prints its raw "fatal: ..." line straight
|
|
53
|
+
// into the pi TUI — the catch below only ever sees the thrown
|
|
54
|
+
// non-zero-exit error, never the already-inherited stream. The
|
|
55
|
+
// explicit stdio array pipes stderr instead, so a failure is fully
|
|
56
|
+
// contained: silent here, same as the "unknown" fallback it feeds.
|
|
42
57
|
return execSync("git rev-parse --short HEAD", {
|
|
43
58
|
encoding: "utf-8",
|
|
44
59
|
timeout: 5000,
|
|
60
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
+
cwd: spawnDir,
|
|
45
62
|
}).trim();
|
|
46
63
|
}
|
|
47
64
|
catch {
|
|
@@ -99,7 +116,7 @@ export function captureSnapshot(filePath, metrics) {
|
|
|
99
116
|
pendingHistory = loadHistory();
|
|
100
117
|
}
|
|
101
118
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
102
|
-
const commit = getCurrentCommit();
|
|
119
|
+
const commit = getCurrentCommit(path.dirname(filePath));
|
|
103
120
|
const snapshot = {
|
|
104
121
|
commit,
|
|
105
122
|
timestamp: new Date().toISOString(),
|
|
@@ -152,7 +169,7 @@ export function captureSnapshots(files) {
|
|
|
152
169
|
const history = loadHistory();
|
|
153
170
|
for (const file of files) {
|
|
154
171
|
const relativePath = path.relative(process.cwd(), file.filePath);
|
|
155
|
-
const commit = getCurrentCommit();
|
|
172
|
+
const commit = getCurrentCommit(path.dirname(file.filePath));
|
|
156
173
|
const snapshot = {
|
|
157
174
|
commit,
|
|
158
175
|
timestamp: new Date().toISOString(),
|