@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
|
@@ -28,13 +28,34 @@
|
|
|
28
28
|
* behavior), matching the lazy-env-read house style (see
|
|
29
29
|
* `subagent-mode.ts` / `runtime-config.ts`).
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
import { normalizeFilePath } from "./path-utils.js";
|
|
32
|
+
import { getProcessSingleton } from "./process-singletons.js";
|
|
33
|
+
const SESSION_LIFECYCLE_FAMILY = "session-lifecycle.primary-registration";
|
|
34
|
+
/** Bump when {@link SessionLifecycleState}'s shape changes. */
|
|
35
|
+
const SESSION_LIFECYCLE_VERSION = 1;
|
|
36
|
+
function state() {
|
|
37
|
+
return getProcessSingleton(SESSION_LIFECYCLE_FAMILY, SESSION_LIFECYCLE_VERSION, () => ({
|
|
38
|
+
activeCtx: undefined,
|
|
39
|
+
activeSessionId: undefined,
|
|
40
|
+
activeRoot: undefined,
|
|
41
|
+
secondarySessionCount: 0,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** The stable id of the currently registered primary session, if known. */
|
|
45
|
+
export function getActiveSessionId() {
|
|
46
|
+
return state().activeSessionId;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The normalized project root of the currently registered primary session
|
|
50
|
+
* (#2129), or `undefined` when no primary has registered a root yet.
|
|
51
|
+
*
|
|
52
|
+
* This is the process's answer to "which directory does pi-lens actually
|
|
53
|
+
* serve", and it is what `memory_sample` carries as its root discriminator
|
|
54
|
+
* (#2130) so a record from a multi-root host is attributable.
|
|
55
|
+
*/
|
|
56
|
+
export function getActivePrimaryRoot() {
|
|
57
|
+
return state().activeRoot;
|
|
58
|
+
}
|
|
38
59
|
/**
|
|
39
60
|
* PURE classifier — no I/O, no throws, fully unit-testable in isolation.
|
|
40
61
|
*
|
|
@@ -49,19 +70,57 @@ let secondarySessionCount = 0;
|
|
|
49
70
|
* this IS the sequential case pi's own contract covers).
|
|
50
71
|
* 4. Prior exists, `priorCtxActive === true`, different session id →
|
|
51
72
|
* `concurrent-secondary` (positive evidence of a live sibling).
|
|
52
|
-
* 5. Prior exists, `
|
|
73
|
+
* 5. Prior exists, different session id, `sameRoot === false` (positive
|
|
74
|
+
* evidence of a DIFFERENT project root) → `secondary-root` (#2129).
|
|
75
|
+
* 6. Prior exists, `priorCtxActive === undefined` (probe inconclusive) →
|
|
53
76
|
* `sequential-replacement` (fail toward today's behavior).
|
|
77
|
+
*
|
|
78
|
+
* WHY ROOT IDENTITY IS AN INPUT AT ALL (#2129). Branch 3 alone made a subagent
|
|
79
|
+
* temp worktree — a session_start in a DIFFERENT directory, arriving after the
|
|
80
|
+
* host's real session had already been disposed or had an unprobeable ctx —
|
|
81
|
+
* classify as a sequential replacement. It then re-registered itself as the
|
|
82
|
+
* process's primary and ran the full session_start body: `resetLSPService`
|
|
83
|
+
* killed the host's warm LSP fleet, and the whole async battery (opengrep,
|
|
84
|
+
* word-index rebuild, review-graph build) re-ran per temp root over content
|
|
85
|
+
* that had not changed. Two temp roots in one host cost ~50s of opengrep and
|
|
86
|
+
* ~53s of word-index rebuild EACH, and drove host RSS from 290MB to 1.1GB in
|
|
87
|
+
* four minutes.
|
|
88
|
+
*
|
|
89
|
+
* A start in a different root is therefore never allowed to steal primary. It
|
|
90
|
+
* is a `secondary-root`, which the caller treats exactly like a
|
|
91
|
+
* `concurrent-secondary`: skip the destructive resets and the expensive
|
|
92
|
+
* battery, leave the registered primary's ctx/session id/root untouched. The
|
|
93
|
+
* root still gets served — `initLSPConfig` registers session roots lazily,
|
|
94
|
+
* per file (`clients/lsp/session-roots.ts:48`), not from this handler.
|
|
95
|
+
*
|
|
96
|
+
* Ordering note: the root check sits BELOW the `priorCtxActive === true`
|
|
97
|
+
* branch so a live sibling still reports the more specific
|
|
98
|
+
* `concurrent-secondary`, and it deliberately fires even when
|
|
99
|
+
* `priorCtxActive === false`. "The prior ctx was invalidated" is exactly the
|
|
100
|
+
* state a temp-worktree start arrives in, so deferring to it would restore the
|
|
101
|
+
* defect.
|
|
102
|
+
*
|
|
103
|
+
* Accepted trade-off: an in-process SEQUENTIAL replacement that genuinely
|
|
104
|
+
* moves to a new directory (a host that switches sessions across cwds within
|
|
105
|
+
* one process) now takes the reduced path instead of a full start. It keeps
|
|
106
|
+
* working — the LSP still attaches per file — but skips the startup battery
|
|
107
|
+
* for the new root until a same-root start re-registers. `sameRoot` is only
|
|
108
|
+
* ever `false` on positive evidence, and
|
|
109
|
+
* `PI_LENS_CONCURRENT_SESSION_GUARD=0` disables this branch with the rest of
|
|
110
|
+
* the guard.
|
|
54
111
|
*/
|
|
55
112
|
export function classifySessionStart(input) {
|
|
56
|
-
const { hasPrior, priorCtxActive, sameSessionId } = input;
|
|
113
|
+
const { hasPrior, priorCtxActive, sameSessionId, sameRoot } = input;
|
|
57
114
|
if (!hasPrior)
|
|
58
115
|
return "primary";
|
|
59
116
|
if (sameSessionId)
|
|
60
117
|
return "sequential-replacement";
|
|
61
|
-
if (priorCtxActive === false)
|
|
62
|
-
return "sequential-replacement";
|
|
63
118
|
if (priorCtxActive === true)
|
|
64
119
|
return "concurrent-secondary";
|
|
120
|
+
if (sameRoot === false)
|
|
121
|
+
return "secondary-root";
|
|
122
|
+
if (priorCtxActive === false)
|
|
123
|
+
return "sequential-replacement";
|
|
65
124
|
// priorCtxActive === undefined: inconclusive probe — fail-safe.
|
|
66
125
|
return "sequential-replacement";
|
|
67
126
|
}
|
|
@@ -121,15 +180,91 @@ export function probeCtxActive(ctx) {
|
|
|
121
180
|
* `primary` and `sequential-replacement` classifications — a sequential
|
|
122
181
|
* replacement re-registers itself as the (new) primary, matching today's
|
|
123
182
|
* one-active-session-at-a-time behavior. */
|
|
124
|
-
export function registerPrimarySession(ctx, sessionId) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
183
|
+
export function registerPrimarySession(ctx, sessionId, root) {
|
|
184
|
+
const s = state();
|
|
185
|
+
s.activeCtx = ctx;
|
|
186
|
+
s.activeSessionId = sessionId;
|
|
187
|
+
// #2129: a re-registration that carries NO root must not erase a root the
|
|
188
|
+
// previous primary did record — losing it would make every later start's
|
|
189
|
+
// `sameRoot` read `undefined` (unknown) and silently restore the pre-fix
|
|
190
|
+
// "any root may steal primary" behavior.
|
|
191
|
+
if (root !== undefined)
|
|
192
|
+
s.activeRoot = normalizeRootForCompare(root);
|
|
193
|
+
s.secondarySessionCount = 0;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Normalize a project root for identity comparison (#2129).
|
|
197
|
+
*
|
|
198
|
+
* Uses `normalizeFilePath` — the SAME comparator `registerInstance`
|
|
199
|
+
* (`clients/instance-registry.ts:213`) writes roots with — so drive-letter
|
|
200
|
+
* case, separators, and symlinked temp dirs cannot make two spellings of one
|
|
201
|
+
* root look like two roots (catalog shape 1). Never throws: an unresolvable
|
|
202
|
+
* path degrades to `undefined`, which reads as "root unknown" and leaves the
|
|
203
|
+
* classification exactly where it was before this input existed.
|
|
204
|
+
*/
|
|
205
|
+
function normalizeRootForCompare(root) {
|
|
206
|
+
if (typeof root !== "string" || root.length === 0)
|
|
207
|
+
return undefined;
|
|
208
|
+
try {
|
|
209
|
+
return normalizeFilePath(root);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Release the primary registration when the primary session itself shuts down
|
|
217
|
+
* (#2129 review F3).
|
|
218
|
+
*
|
|
219
|
+
* WHY THIS EXISTS. Before root identity was an input, a stale `activeCtx` left
|
|
220
|
+
* behind by a departed primary was benign: the next start probed it, got
|
|
221
|
+
* `false` (dead ctx), and classified `sequential-replacement`, so it took over
|
|
222
|
+
* as the new primary. Root identity made that stale state DECISIVE — a start in
|
|
223
|
+
* a different root behind a dead-but-still-registered primary now classifies
|
|
224
|
+
* `secondary-root` and declines. Without an explicit release, root A's primary
|
|
225
|
+
* ending would mean every later start in root B declines FOREVER: never
|
|
226
|
+
* primary, never a full start, no re-arm.
|
|
227
|
+
*
|
|
228
|
+
* This is the catalog's process-lifetime-latch shape (state that must re-arm at
|
|
229
|
+
* a session boundary must not outlive it). `session_shutdown`'s primary path is
|
|
230
|
+
* that boundary. Deliberately NOT called on a secondary's shutdown — that path
|
|
231
|
+
* returns before the shared teardown precisely because the primary is still
|
|
232
|
+
* live.
|
|
233
|
+
*
|
|
234
|
+
* A concurrent secondary that outlives the primary now classifies `primary` on
|
|
235
|
+
* its later emissions rather than `concurrent-secondary`. That is the fail-safe
|
|
236
|
+
* direction this module has always taken (run the handler), and it is correct
|
|
237
|
+
* here: with the primary gone there is no live sibling to protect.
|
|
238
|
+
*
|
|
239
|
+
* WHAT PROTECTS A SURVIVING SECONDARY (#2130 round 2, remainder N3/F3). Not the
|
|
240
|
+
* count zeroed below, and not this module-level classification at all: it is the
|
|
241
|
+
* per-activation `ownedSessionRole` closure in `index.ts`. Each activation
|
|
242
|
+
* records the role IT was classified as at its own session_start and consults
|
|
243
|
+
* that at its own shutdown, so a secondary whose primary has already released
|
|
244
|
+
* still takes the secondary teardown path.
|
|
245
|
+
*
|
|
246
|
+
* `secondarySessionCount` is therefore an OBSERVABILITY counter, not a guard.
|
|
247
|
+
* Zeroing it here under-reports for the window between the primary's release
|
|
248
|
+
* and the next primary's registration. That is real and accepted: the only
|
|
249
|
+
* reader is `concurrent_session_bind.metadata.secondaryCount` in `latency.log`,
|
|
250
|
+
* and during that window no primary is registered, so every arriving start
|
|
251
|
+
* classifies `primary` and re-registers — which zeroes the count anyway —
|
|
252
|
+
* instead of emitting a bind record. `decrementSecondarySessionCount` clamps at
|
|
253
|
+
* zero, so a late secondary's shutdown cannot underflow it. Documented rather
|
|
254
|
+
* than changed, because a count that outlived the registration it is scoped to
|
|
255
|
+
* would disagree with `getActivePrimaryRoot()` in the same record.
|
|
256
|
+
*/
|
|
257
|
+
export function releasePrimarySession() {
|
|
258
|
+
const s = state();
|
|
259
|
+
s.activeCtx = undefined;
|
|
260
|
+
s.activeSessionId = undefined;
|
|
261
|
+
s.activeRoot = undefined;
|
|
262
|
+
s.secondarySessionCount = 0;
|
|
128
263
|
}
|
|
129
264
|
/** Register a concurrently-bound secondary (subagent) session. Does not
|
|
130
265
|
* touch the primary's ctx/session id. */
|
|
131
266
|
export function registerSecondarySession() {
|
|
132
|
-
secondarySessionCount += 1;
|
|
267
|
+
state().secondarySessionCount += 1;
|
|
133
268
|
}
|
|
134
269
|
/**
|
|
135
270
|
* Classifies a `session_shutdown` firing the same fail-safe way as
|
|
@@ -152,6 +287,27 @@ export function registerSecondarySession() {
|
|
|
152
287
|
* now classifies `primary` (conservative miss — its teardown runs and hurts
|
|
153
288
|
* the parent, same as pre-#473 behavior), because uncertainty must never
|
|
154
289
|
* classify `secondary`.
|
|
290
|
+
*
|
|
291
|
+
* ROOT IDENTITY (#2146 review F1). The probe-based branch above is the only
|
|
292
|
+
* evidence this function had, and it is exactly the evidence that is missing in
|
|
293
|
+
* the state #2146 describes: the host's ctx is already invalidated, which is
|
|
294
|
+
* WHY `secondary-root` fires on the start side. A subagent's teardown therefore
|
|
295
|
+
* read "the primary's ctx is dead, so I must be the primary", and index.ts's
|
|
296
|
+
* primary path called `releasePrimarySession()` and wiped the SHARED process
|
|
297
|
+
* registration. The decline then survived exactly one subagent: the next one
|
|
298
|
+
* classified `primary` and ran the full battery.
|
|
299
|
+
*
|
|
300
|
+
* So this function takes the same root discriminator `decideSessionStart` has,
|
|
301
|
+
* with the same rule and the same fail-safe direction. A shutdown whose root is
|
|
302
|
+
* POSITIVELY different from the registered primary's root is a `secondary`,
|
|
303
|
+
* even when the primary's ctx probes `false` or `undefined` — deferring to the
|
|
304
|
+
* dead-ctx branch is what restored the defect. `undefined` on either side means
|
|
305
|
+
* "root unknown" and changes no verdict.
|
|
306
|
+
*
|
|
307
|
+
* Ordering: the root check sits BELOW the id-unknown guard, not above it. That
|
|
308
|
+
* guard is the #472 fix — a session with an unreadable session id must still
|
|
309
|
+
* tear itself down — and a root comparison cannot establish "different session"
|
|
310
|
+
* when the ids that identify sessions are unavailable.
|
|
155
311
|
*/
|
|
156
312
|
export function noteSessionShutdown(
|
|
157
313
|
// Load-bearing: ctx OBJECT IDENTITY is the definitive discriminator when
|
|
@@ -160,25 +316,40 @@ export function noteSessionShutdown(
|
|
|
160
316
|
// (Note: pi's ExtensionRunner.emit() builds a FRESH ctx object per emit,
|
|
161
317
|
// so identity match is not expected with today's SDK — this check is
|
|
162
318
|
// defense-in-depth for SDK versions/paths that reuse a ctx.)
|
|
163
|
-
ctx, sessionId
|
|
164
|
-
|
|
319
|
+
ctx, sessionId,
|
|
320
|
+
/** This session's own project root (`ctx.cwd`), when readable. `undefined`
|
|
321
|
+
* means "root unknown" and never on its own changes a verdict. */
|
|
322
|
+
root) {
|
|
323
|
+
const s = state();
|
|
324
|
+
if (ctx !== undefined && ctx === s.activeCtx) {
|
|
165
325
|
return "primary";
|
|
166
326
|
}
|
|
167
|
-
if (activeCtx === undefined && activeSessionId === undefined) {
|
|
327
|
+
if (s.activeCtx === undefined && s.activeSessionId === undefined) {
|
|
168
328
|
return "primary";
|
|
169
329
|
}
|
|
170
|
-
if (sessionId !== undefined && sessionId === activeSessionId) {
|
|
330
|
+
if (sessionId !== undefined && sessionId === s.activeSessionId) {
|
|
171
331
|
return "primary";
|
|
172
332
|
}
|
|
173
333
|
// Uncertainty guard: if EITHER side's session id is unknown we cannot
|
|
174
334
|
// positively establish "different session", so never classify secondary.
|
|
175
|
-
if (sessionId === undefined || activeSessionId === undefined) {
|
|
335
|
+
if (sessionId === undefined || s.activeSessionId === undefined) {
|
|
176
336
|
return "primary";
|
|
177
337
|
}
|
|
178
|
-
const primaryStillActive = probeCtxActive(activeCtx);
|
|
338
|
+
const primaryStillActive = probeCtxActive(s.activeCtx);
|
|
179
339
|
if (primaryStillActive === true) {
|
|
180
340
|
return "secondary";
|
|
181
341
|
}
|
|
342
|
+
// #2146 F1: positive evidence of a DIFFERENT root, in a session positively
|
|
343
|
+
// identified as not the primary. Deliberately below the probe-true branch
|
|
344
|
+
// (a live sibling is already answered) and deliberately ABOVE the
|
|
345
|
+
// dead-ctx fail-safe, because a dead primary ctx is precisely the state a
|
|
346
|
+
// subagent teardown arrives in.
|
|
347
|
+
const shutdownRoot = normalizeRootForCompare(root);
|
|
348
|
+
if (s.activeRoot !== undefined &&
|
|
349
|
+
shutdownRoot !== undefined &&
|
|
350
|
+
s.activeRoot !== shutdownRoot) {
|
|
351
|
+
return "secondary";
|
|
352
|
+
}
|
|
182
353
|
// primaryStillActive is false or undefined: fail-safe to primary.
|
|
183
354
|
return "primary";
|
|
184
355
|
}
|
|
@@ -203,27 +374,29 @@ ctx, sessionId) {
|
|
|
203
374
|
export function classifyCurrentSessionEmission(ctx, sessionId) {
|
|
204
375
|
if (!guardEnabled())
|
|
205
376
|
return "primary";
|
|
206
|
-
|
|
377
|
+
const s = state();
|
|
378
|
+
if (s.activeCtx === undefined && s.activeSessionId === undefined)
|
|
207
379
|
return "primary";
|
|
208
|
-
if (ctx !== undefined && ctx === activeCtx)
|
|
380
|
+
if (ctx !== undefined && ctx === s.activeCtx)
|
|
209
381
|
return "primary";
|
|
210
|
-
if (sessionId !== undefined && sessionId === activeSessionId)
|
|
382
|
+
if (sessionId !== undefined && sessionId === s.activeSessionId)
|
|
211
383
|
return "primary";
|
|
212
384
|
// Uncertainty guard: if EITHER side's session id is unknown we cannot
|
|
213
385
|
// positively establish "different session", so never classify secondary.
|
|
214
|
-
if (sessionId === undefined || activeSessionId === undefined)
|
|
386
|
+
if (sessionId === undefined || s.activeSessionId === undefined)
|
|
215
387
|
return "primary";
|
|
216
|
-
const primaryStillActive = probeCtxActive(activeCtx);
|
|
388
|
+
const primaryStillActive = probeCtxActive(s.activeCtx);
|
|
217
389
|
if (primaryStillActive === true)
|
|
218
390
|
return "concurrent-secondary";
|
|
219
391
|
return "primary";
|
|
220
392
|
}
|
|
221
393
|
export function getSecondarySessionCount() {
|
|
222
|
-
return secondarySessionCount;
|
|
394
|
+
return state().secondarySessionCount;
|
|
223
395
|
}
|
|
224
396
|
export function decrementSecondarySessionCount() {
|
|
225
|
-
|
|
226
|
-
|
|
397
|
+
const s = state();
|
|
398
|
+
if (s.secondarySessionCount > 0)
|
|
399
|
+
s.secondarySessionCount -= 1;
|
|
227
400
|
}
|
|
228
401
|
/**
|
|
229
402
|
* Guard-aware wrapper used by callers (index.ts) so the kill switch lives in
|
|
@@ -238,9 +411,14 @@ export function classifySessionStartGuarded(input) {
|
|
|
238
411
|
/** Test-only: clears all module-scope state (house style — see
|
|
239
412
|
* `_resetSubagentModeForTests` / `slow-fs.ts`). */
|
|
240
413
|
export function _resetSessionLifecycleForTests() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
414
|
+
// Resets the PROCESS state, not a module-local copy: a reset that cleared
|
|
415
|
+
// only module scope would leave the real registration behind and make every
|
|
416
|
+
// suite that relies on isolation pass vacuously (#2146, catalog shape 7).
|
|
417
|
+
const s = state();
|
|
418
|
+
s.activeCtx = undefined;
|
|
419
|
+
s.activeSessionId = undefined;
|
|
420
|
+
s.activeRoot = undefined;
|
|
421
|
+
s.secondarySessionCount = 0;
|
|
244
422
|
}
|
|
245
423
|
/**
|
|
246
424
|
* Single entry point `index.ts`'s `session_start` handler delegates to, so
|
|
@@ -253,36 +431,54 @@ export function _resetSessionLifecycleForTests() {
|
|
|
253
431
|
* fake object in tests is safe). `sessionId` is the STABLE session id
|
|
254
432
|
* (`ctx.sessionManager.getSessionId()`), which may be `undefined`.
|
|
255
433
|
*/
|
|
256
|
-
export function decideSessionStart(ctx, sessionId) {
|
|
257
|
-
const
|
|
258
|
-
const
|
|
434
|
+
export function decideSessionStart(ctx, sessionId, root) {
|
|
435
|
+
const s = state();
|
|
436
|
+
const hasPrior = s.activeCtx !== undefined || s.activeSessionId !== undefined;
|
|
437
|
+
const priorCtxActive = hasPrior ? probeCtxActive(s.activeCtx) : undefined;
|
|
259
438
|
// ctx OBJECT IDENTITY: if the SDK ever hands the SAME ctx object to a
|
|
260
439
|
// repeated session_start, that is by definition the same session
|
|
261
440
|
// re-announcing itself — sequential, never concurrent. (Not expected with
|
|
262
441
|
// today's SDK — ExtensionRunner.emit() builds a fresh ctx per emit — but
|
|
263
442
|
// identity is the one signal that can't false-positive, so honor it.)
|
|
264
|
-
const sameCtx = hasPrior && ctx !== undefined && ctx === activeCtx;
|
|
443
|
+
const sameCtx = hasPrior && ctx !== undefined && ctx === s.activeCtx;
|
|
265
444
|
const sameSessionId = sameCtx ||
|
|
266
|
-
(hasPrior && sessionId !== undefined && sessionId === activeSessionId);
|
|
445
|
+
(hasPrior && sessionId !== undefined && sessionId === s.activeSessionId);
|
|
446
|
+
// #2129: compare THIS start's cwd against the registered primary's root.
|
|
447
|
+
// `undefined` on either side means "unknown", never "different" — see
|
|
448
|
+
// `classifySessionStart`'s fail-safe note.
|
|
449
|
+
const incomingRoot = normalizeRootForCompare(root);
|
|
450
|
+
const sameRoot = hasPrior && s.activeRoot !== undefined && incomingRoot !== undefined
|
|
451
|
+
? s.activeRoot === incomingRoot
|
|
452
|
+
: undefined;
|
|
453
|
+
// #2129 review F5: capture the primary root BEFORE any registration mutates
|
|
454
|
+
// it, so the reported value is genuinely the decision-time input the
|
|
455
|
+
// classifier consulted rather than the value this call just wrote.
|
|
456
|
+
const primaryRootAtDecision = s.activeRoot;
|
|
267
457
|
const classification = classifySessionStartGuarded({
|
|
268
458
|
hasPrior,
|
|
269
459
|
priorCtxActive,
|
|
270
460
|
sameSessionId,
|
|
461
|
+
sameRoot,
|
|
271
462
|
});
|
|
272
|
-
if (classification === "concurrent-secondary"
|
|
463
|
+
if (classification === "concurrent-secondary" ||
|
|
464
|
+
classification === "secondary-root") {
|
|
273
465
|
registerSecondarySession();
|
|
274
466
|
return {
|
|
275
467
|
classification,
|
|
276
468
|
runFullSessionStart: false,
|
|
277
|
-
secondaryCount: secondarySessionCount,
|
|
469
|
+
secondaryCount: s.secondarySessionCount,
|
|
470
|
+
sameRoot,
|
|
471
|
+
primaryRoot: primaryRootAtDecision,
|
|
278
472
|
};
|
|
279
473
|
}
|
|
280
474
|
// "primary" or "sequential-replacement": register as the (new) primary
|
|
281
475
|
// and proceed exactly as today.
|
|
282
|
-
registerPrimarySession(ctx, sessionId);
|
|
476
|
+
registerPrimarySession(ctx, sessionId, root);
|
|
283
477
|
return {
|
|
284
478
|
classification,
|
|
285
479
|
runFullSessionStart: true,
|
|
286
|
-
secondaryCount: secondarySessionCount,
|
|
480
|
+
secondaryCount: s.secondarySessionCount,
|
|
481
|
+
sameRoot,
|
|
482
|
+
primaryRoot: primaryRootAtDecision,
|
|
287
483
|
};
|
|
288
484
|
}
|
|
@@ -12,6 +12,7 @@ import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/util
|
|
|
12
12
|
import { getProjectIgnoreGlobs } from "./file-utils.js";
|
|
13
13
|
import { findGlobalBinary } from "./package-manager.js";
|
|
14
14
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
15
|
+
import { truncatedByOutputCap } from "./spawn-output-cap.js";
|
|
15
16
|
import { createSingleFlight } from "./single-flight.js";
|
|
16
17
|
import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
17
18
|
/**
|
|
@@ -87,8 +88,10 @@ function tryParseSgMatches(stdout) {
|
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
// The truncation veto lives at the head of `exec`/`interpretScanResult` now
|
|
92
|
+
// (#2100), so this stays a pure parser rather than re-asking a settled question.
|
|
90
93
|
function tryParseNonZeroSgMatches(result) {
|
|
91
|
-
if (
|
|
94
|
+
if (!result.stdout.trim())
|
|
92
95
|
return null;
|
|
93
96
|
// Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
|
|
94
97
|
// tolerance but String.trim() strips it (matches the pre-refactor guard).
|
|
@@ -322,6 +325,7 @@ export class SgRunner {
|
|
|
322
325
|
cause: provisional ? this.sweepTransientCause : "ok",
|
|
323
326
|
elapsedMs: Date.now() - startedAt,
|
|
324
327
|
latched: !provisional,
|
|
328
|
+
classifiedBy: "probe",
|
|
325
329
|
hostStallMs: this.sweepHostStallMs,
|
|
326
330
|
budgetMs: PROBE_TIMEOUT_MS,
|
|
327
331
|
...(provisional && {
|
|
@@ -582,6 +586,17 @@ export class SgRunner {
|
|
|
582
586
|
totalMatches: 0,
|
|
583
587
|
truncated: false,
|
|
584
588
|
});
|
|
589
|
+
// FIRST (#2100): hitting the cap makes safe-spawn SIGTERM ast-grep, so a
|
|
590
|
+
// truncated run also classifies as a killed spawn. Read after the two
|
|
591
|
+
// gates below, this guard could only ever describe a run that exited
|
|
592
|
+
// before the signal reached it. A timed-out or aborted run carries the
|
|
593
|
+
// flag too and keeps its own classification below.
|
|
594
|
+
if (truncatedByOutputCap(result)) {
|
|
595
|
+
return {
|
|
596
|
+
...empty(),
|
|
597
|
+
error: "Failed to parse output: output was truncated",
|
|
598
|
+
};
|
|
599
|
+
}
|
|
585
600
|
const spawnFailure = this.failureForSpawnResult(result);
|
|
586
601
|
if (spawnFailure) {
|
|
587
602
|
return {
|
|
@@ -622,12 +637,6 @@ export class SgRunner {
|
|
|
622
637
|
}
|
|
623
638
|
if (!result.stdout.trim())
|
|
624
639
|
return empty();
|
|
625
|
-
if (result.outputTruncated) {
|
|
626
|
-
return {
|
|
627
|
-
...empty(),
|
|
628
|
-
error: "Failed to parse output: output was truncated",
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
640
|
{
|
|
632
641
|
const matches = tryParseSgMatches(result.stdout);
|
|
633
642
|
if (matches) {
|
|
@@ -659,6 +668,17 @@ export class SgRunner {
|
|
|
659
668
|
}
|
|
660
669
|
}
|
|
661
670
|
interpretScanResult(result, args) {
|
|
671
|
+
// FIRST (#2100): same reason as `exec` above — the cap kill is our own
|
|
672
|
+
// SIGTERM, so `failureForSpawnResult` answers "cli-failure" for it and
|
|
673
|
+
// the truncation guard below never speaks.
|
|
674
|
+
if (truncatedByOutputCap(result)) {
|
|
675
|
+
return {
|
|
676
|
+
matches: [],
|
|
677
|
+
status: result.status,
|
|
678
|
+
error: "Failed to parse ast-grep scan output: output was truncated",
|
|
679
|
+
failure: "parse-failure",
|
|
680
|
+
};
|
|
681
|
+
}
|
|
662
682
|
const failure = this.failureForSpawnResult(result);
|
|
663
683
|
if (failure) {
|
|
664
684
|
return {
|
|
@@ -697,14 +717,6 @@ export class SgRunner {
|
|
|
697
717
|
}
|
|
698
718
|
if (!result.stdout.trim())
|
|
699
719
|
return { matches: [], status: result.status };
|
|
700
|
-
if (result.outputTruncated) {
|
|
701
|
-
return {
|
|
702
|
-
matches: [],
|
|
703
|
-
status: result.status,
|
|
704
|
-
error: "Failed to parse ast-grep scan output: output was truncated",
|
|
705
|
-
failure: "parse-failure",
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
720
|
{
|
|
709
721
|
const matches = tryParseSgMatches(result.stdout);
|
|
710
722
|
if (matches) {
|