@herbertgao/pi-extensions 2026.8.4 → 2026.8.5
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 +4 -4
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/docs/agent-guide.md +2 -2
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +11 -12
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const waitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
6
|
+
function ownerPidIsLive(pid) {
|
|
7
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
8
|
+
return false;
|
|
9
|
+
try {
|
|
10
|
+
process.kill(pid, 0);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return error.code === "EPERM";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function quarantinePath(lockPath, token) {
|
|
18
|
+
return `${lockPath}.quarantine-${process.pid}-${token}`;
|
|
19
|
+
}
|
|
20
|
+
async function restoreQuarantinedLock(lockPath, quarantined) {
|
|
21
|
+
try {
|
|
22
|
+
await fsp.rename(quarantined, lockPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// A replacement owner may already hold the canonical name. Never overwrite it.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function releaseQuarantineLock(lockPath, token) {
|
|
29
|
+
const quarantined = quarantinePath(lockPath, `release-${token}`);
|
|
30
|
+
let renamed = false;
|
|
31
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
32
|
+
try {
|
|
33
|
+
await fsp.rename(lockPath, quarantined);
|
|
34
|
+
renamed = true;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code !== "ENOENT" ||
|
|
39
|
+
attempt === 2)
|
|
40
|
+
return;
|
|
41
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!renamed)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
|
|
48
|
+
if (owner.token === token) {
|
|
49
|
+
await fsp.rm(quarantined, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function quarantineOwnerIsStale(owner, staleMs) {
|
|
60
|
+
return Number.isInteger(owner.pid) && owner.pid > 0 &&
|
|
61
|
+
Number.isFinite(owner.createdAt) &&
|
|
62
|
+
(!ownerPidIsLive(owner.pid) || Date.now() - owner.createdAt > staleMs);
|
|
63
|
+
}
|
|
64
|
+
async function reclaimQuarantineLock(lockPath, staleMs) {
|
|
65
|
+
const quarantined = quarantinePath(lockPath, `reclaim-${Date.now()}-${randomUUID()}`);
|
|
66
|
+
try {
|
|
67
|
+
await fsp.rename(lockPath, quarantined);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
let stale = false;
|
|
73
|
+
try {
|
|
74
|
+
const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
|
|
75
|
+
stale = quarantineOwnerIsStale(owner, staleMs);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
try {
|
|
79
|
+
stale = Date.now() - (await fsp.stat(quarantined)).mtimeMs > staleMs;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
stale = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (stale) {
|
|
86
|
+
await fsp.rm(quarantined, { recursive: true, force: true });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
async function tryAcquireQuarantineLock(lockPath, staleMs) {
|
|
93
|
+
const owner = {
|
|
94
|
+
pid: process.pid,
|
|
95
|
+
createdAt: Date.now(),
|
|
96
|
+
token: `${process.pid}-${Date.now()}-${randomUUID()}`,
|
|
97
|
+
};
|
|
98
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
99
|
+
try {
|
|
100
|
+
await fsp.mkdir(lockPath);
|
|
101
|
+
try {
|
|
102
|
+
await fsp.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), "utf8");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return () => releaseQuarantineLock(lockPath, owner.token);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error.code !== "EEXIST")
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
let stale;
|
|
115
|
+
try {
|
|
116
|
+
const existing = JSON.parse(await fsp.readFile(path.join(lockPath, "owner.json"), "utf8"));
|
|
117
|
+
stale = quarantineOwnerIsStale(existing, staleMs);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
try {
|
|
121
|
+
stale = Date.now() - (await fsp.stat(lockPath)).mtimeMs > staleMs;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
stale = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!stale || !(await reclaimQuarantineLock(lockPath, staleMs)))
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
export async function acquireQuarantinePidFileLock(lockPath, options) {
|
|
133
|
+
const deadline = Date.now() + options.waitMs;
|
|
134
|
+
for (;;) {
|
|
135
|
+
const release = await tryAcquireQuarantineLock(lockPath, options.staleMs);
|
|
136
|
+
if (release)
|
|
137
|
+
return release;
|
|
138
|
+
const remaining = deadline - Date.now();
|
|
139
|
+
if (remaining <= 0) {
|
|
140
|
+
if (options.onContention === "skip-log") {
|
|
141
|
+
options.logContention();
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(options.timeoutMessage);
|
|
145
|
+
}
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(options.retryMs, remaining)));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function acquireBoundedPidFileLock(lockPath, options) {
|
|
150
|
+
const token = `${process.pid}:${Date.now()}:${randomUUID()}`;
|
|
151
|
+
const deadline = Date.now() + options.waitMs;
|
|
152
|
+
for (;;) {
|
|
153
|
+
try {
|
|
154
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
155
|
+
fs.writeFileSync(fd, token, "utf8");
|
|
156
|
+
fs.closeSync(fd);
|
|
157
|
+
return () => {
|
|
158
|
+
try {
|
|
159
|
+
if (fs.readFileSync(lockPath, "utf8") === token) {
|
|
160
|
+
fs.unlinkSync(lockPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Protected write completed; cleanup is best-effort.
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error.code !== "EEXIST")
|
|
170
|
+
throw error;
|
|
171
|
+
try {
|
|
172
|
+
const [pidText] = fs.readFileSync(lockPath, "utf8").split(":", 1);
|
|
173
|
+
if (!ownerPidIsLive(Number.parseInt(pidText ?? "", 10))) {
|
|
174
|
+
fs.unlinkSync(lockPath);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (lockError) {
|
|
179
|
+
if (lockError.code === "ENOENT")
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (Date.now() >= deadline) {
|
|
183
|
+
if (options.onContention === "skip-log") {
|
|
184
|
+
options.logContention();
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
throw new Error(options.timeoutMessage);
|
|
188
|
+
}
|
|
189
|
+
Atomics.wait(waitArray, 0, 0, options.retryMs);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
import { logBusEvent } from "./bus-events-logger.js";
|
|
21
21
|
import { normalizeFilePath } from "./path-utils.js";
|
|
22
22
|
import { appendRecentTouches } from "./recent-touches.js";
|
|
23
|
+
import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
|
|
23
24
|
export const BUS_FILES_TOUCHED_EVENT = "pilens:files:touched";
|
|
24
25
|
export const BUS_FILES_TOUCHED_VERSION = 1;
|
|
25
|
-
|
|
26
|
+
const liveEmitter = createLiveBusEmitter();
|
|
26
27
|
let hasLoggedFailure = false;
|
|
27
28
|
// Log-once-per-process guards for the two structural-no-op outcomes below
|
|
28
29
|
// (see bus-events-logger.ts's module doc for why these aren't logged on
|
|
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
|
|
|
38
39
|
* no `pi.events`).
|
|
39
40
|
*/
|
|
40
41
|
export function wireBusEmitter(emitFn) {
|
|
41
|
-
|
|
42
|
+
liveEmitter.wire(emitFn);
|
|
43
|
+
}
|
|
44
|
+
export function wireBusEmitterGetter(getter) {
|
|
45
|
+
liveEmitter.wireGetter(getter);
|
|
42
46
|
}
|
|
43
47
|
/** Test-only: reset module state between test files. */
|
|
44
48
|
export function _resetForTests() {
|
|
45
|
-
|
|
49
|
+
liveEmitter.reset();
|
|
46
50
|
hasLoggedFailure = false;
|
|
47
51
|
hasLoggedUnwired = false;
|
|
48
52
|
hasLoggedDisabled = false;
|
|
@@ -102,6 +106,7 @@ export function publishFilesTouched(args) {
|
|
|
102
106
|
}).catch((err) => {
|
|
103
107
|
args.dbg?.(`bus-publish: recent-touches append failed: ${err}`);
|
|
104
108
|
});
|
|
109
|
+
const busEmit = liveEmitter.get();
|
|
105
110
|
if (!busEmit) {
|
|
106
111
|
if (!hasLoggedUnwired) {
|
|
107
112
|
hasLoggedUnwired = true;
|
|
@@ -129,6 +134,7 @@ export function publishFilesTouched(args) {
|
|
|
129
134
|
}));
|
|
130
135
|
}
|
|
131
136
|
busEmit(BUS_FILES_TOUCHED_EVENT, payload);
|
|
137
|
+
hasLoggedFailure = false;
|
|
132
138
|
logBusEvent({
|
|
133
139
|
event: BUS_FILES_TOUCHED_EVENT,
|
|
134
140
|
outcome: "emitted",
|
|
@@ -147,6 +153,7 @@ export function publishFilesTouched(args) {
|
|
|
147
153
|
});
|
|
148
154
|
if (!hasLoggedFailure) {
|
|
149
155
|
hasLoggedFailure = true;
|
|
156
|
+
recordStaleBusFailure(BUS_FILES_TOUCHED_EVENT, err);
|
|
150
157
|
args.dbg?.(`bus-publish: pilens:files:touched emit failed (further failures suppressed): ${err}`);
|
|
151
158
|
}
|
|
152
159
|
}
|
|
@@ -9,6 +9,8 @@ import * as fs from "node:fs";
|
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { getProjectDataDir } from "../file-utils.js";
|
|
11
11
|
import { readJsonCache } from "../json-cache-read.js";
|
|
12
|
+
import { resolvePackagePath } from "../package-root.js";
|
|
13
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
12
14
|
// v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
|
|
13
15
|
// and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
|
|
14
16
|
// v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
|
|
@@ -20,7 +22,37 @@ import { readJsonCache } from "../json-cache-read.js";
|
|
|
20
22
|
// the language's own. A v5 tsx entry was hashed over tsx files only, so a
|
|
21
23
|
// typescript-rule edit never invalidated it; v5 entries self-miss anyway (the
|
|
22
24
|
// hash input set changed), the bump just makes the semantics break explicit.
|
|
23
|
-
|
|
25
|
+
// v7 (#1118): computeRuleHash now CONTENT-hashes project-local rule files
|
|
26
|
+
// instead of trusting mtime+size alone. mtime+size is the review-graph
|
|
27
|
+
// first-filter, not the full gold standard (size:mtimeMs + confirmContentChanged)
|
|
28
|
+
// — a rule-file edit that preserves both (git checkout timestamp restoration, a
|
|
29
|
+
// same-length tweak, a formatter that preserves mtime) replayed a stale
|
|
30
|
+
// compiled set from disk and re-persisted it under the still-matching
|
|
31
|
+
// fingerprint, poisoning every future process. The bump forces every v6 entry
|
|
32
|
+
// (hashed under the old metadata-only formula) to miss once on upgrade, so a
|
|
33
|
+
// silently-poisoned v6 entry can't be trusted by the new code.
|
|
34
|
+
export const CACHE_VERSION = "v7";
|
|
35
|
+
/**
|
|
36
|
+
* Bundled rule files ship with the extension and are immutable within a
|
|
37
|
+
* process — they can't change mid-session, so the cheap mtime+size
|
|
38
|
+
* fingerprint (v6's whole-set formula) is sufficient for them, and there are
|
|
39
|
+
* ~705 of them across all languages, so unconditionally reading+hashing their
|
|
40
|
+
* bytes on every edit would be the unconditional hot-path hashing the
|
|
41
|
+
* event-loop discipline forbids (RuleCache.get runs on the per-edit
|
|
42
|
+
* tree-sitter runner hot path).
|
|
43
|
+
*
|
|
44
|
+
* Project-local rule files (under `<project>/rules/tree-sitter-queries/`,
|
|
45
|
+
* see `ruleFilesForLanguage`) are the opposite: mutable, and a SMALL set (the
|
|
46
|
+
* handful a developer actually maintains) — cheap to content-hash. Splitting
|
|
47
|
+
* on this prefix mirrors the split `yaml-rule-parser.ts`/`ast-grep-napi.ts`
|
|
48
|
+
* already use for the ast-grep side of the same class (#1105): project-origin
|
|
49
|
+
* trees get a content-hash CONFIRM, bundled trees stay mtime-cheap.
|
|
50
|
+
*/
|
|
51
|
+
const BUNDLED_RULES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
|
|
52
|
+
function isBundledRuleFile(resolvedFile) {
|
|
53
|
+
return (resolvedFile === BUNDLED_RULES_ROOT ||
|
|
54
|
+
resolvedFile.startsWith(BUNDLED_RULES_ROOT + path.sep));
|
|
55
|
+
}
|
|
24
56
|
export class RuleCache {
|
|
25
57
|
cacheFile;
|
|
26
58
|
cacheDir;
|
|
@@ -38,9 +70,19 @@ export class RuleCache {
|
|
|
38
70
|
computeRuleHash(ruleFiles) {
|
|
39
71
|
const hash = crypto.createHash("sha256");
|
|
40
72
|
for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
73
|
+
const resolved = path.resolve(file);
|
|
74
|
+
if (!fs.existsSync(resolved))
|
|
75
|
+
continue;
|
|
76
|
+
const stat = fs.statSync(resolved);
|
|
77
|
+
hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
|
|
78
|
+
// Content-CONFIRM only the project-local, mutable subset (a handful of
|
|
79
|
+
// files) — mtime+size alone is a first filter, not proof of freshness,
|
|
80
|
+
// and a preserved-mtime+size edit here would otherwise replay (and
|
|
81
|
+
// re-persist) a stale compiled set. Bundled files (~705, immutable
|
|
82
|
+
// within a process) skip this: the metadata fingerprint stays their
|
|
83
|
+
// whole story, keeping the common no-project-rules case as cheap as v6.
|
|
84
|
+
if (!isBundledRuleFile(resolved)) {
|
|
85
|
+
hash.update(fs.readFileSync(resolved));
|
|
44
86
|
}
|
|
45
87
|
}
|
|
46
88
|
return hash.digest("hex").slice(0, 16);
|
|
@@ -74,7 +116,7 @@ export class RuleCache {
|
|
|
74
116
|
ruleHash: this.computeRuleHash(ruleFiles),
|
|
75
117
|
queries,
|
|
76
118
|
};
|
|
77
|
-
|
|
119
|
+
writeFileAtomic(this.cacheFile, JSON.stringify(entry, null, 2));
|
|
78
120
|
this.pruneStaleVersions();
|
|
79
121
|
}
|
|
80
122
|
catch {
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* All paths are relative to project root (process.cwd()).
|
|
10
10
|
*/
|
|
11
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
11
12
|
import * as fs from "node:fs";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import { getProjectDataDir } from "./file-utils.js";
|
|
15
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
14
16
|
import { readJsonCache } from "./json-cache-read.js";
|
|
15
17
|
import { normalizeMapKey } from "./path-utils.js";
|
|
16
18
|
// --- Defaults ---
|
|
@@ -21,6 +23,8 @@ const DEFAULT_TURN_STATE = {
|
|
|
21
23
|
maxCycles: 3,
|
|
22
24
|
lastUpdated: "",
|
|
23
25
|
};
|
|
26
|
+
export const MCP_TURN_STATE_OWNER_ID = `mcp-${process.pid}`;
|
|
27
|
+
const TURN_OWNER_STALE_MS = 30 * 60 * 1000;
|
|
24
28
|
// --- Helpers ---
|
|
25
29
|
function getLensDir(cwd) {
|
|
26
30
|
return getProjectDataDir(cwd);
|
|
@@ -36,7 +40,7 @@ export class CacheManager {
|
|
|
36
40
|
log;
|
|
37
41
|
constructor(verbose = false) {
|
|
38
42
|
this.log = verbose
|
|
39
|
-
? (
|
|
43
|
+
? createSubsystemLogger("cache")
|
|
40
44
|
: () => { };
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
@@ -107,10 +111,45 @@ export class CacheManager {
|
|
|
107
111
|
timestamp: new Date().toISOString(),
|
|
108
112
|
...extraMeta,
|
|
109
113
|
};
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
writeFileAtomic(cachePath, JSON.stringify(data, null, 2), {
|
|
115
|
+
bestEffort: false,
|
|
116
|
+
});
|
|
117
|
+
writeFileAtomic(metaPath, JSON.stringify(meta, null, 2), {
|
|
118
|
+
bestEffort: false,
|
|
119
|
+
});
|
|
112
120
|
this.log(`Cache written: ${scanner}`);
|
|
113
121
|
}
|
|
122
|
+
/** Inspect a cache without changing the behavior of readCache consumers. */
|
|
123
|
+
inspectCache(scanner, cwd, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
124
|
+
const cachePath = path.join(getCacheDir(cwd), `${scanner}.json`);
|
|
125
|
+
const metaPath = path.join(getCacheDir(cwd), `${scanner}.meta.json`);
|
|
126
|
+
for (const cachePathname of [cachePath, metaPath]) {
|
|
127
|
+
try {
|
|
128
|
+
fs.statSync(cachePathname);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err.code === "ENOENT")
|
|
132
|
+
return "missing";
|
|
133
|
+
return "unreadable";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const meta = readJsonCache(metaPath, (parsed) => parsed);
|
|
138
|
+
if (!meta || typeof meta.timestamp !== "string")
|
|
139
|
+
return "malformed";
|
|
140
|
+
const timestamp = new Date(meta.timestamp).getTime();
|
|
141
|
+
if (!Number.isFinite(timestamp))
|
|
142
|
+
return "malformed";
|
|
143
|
+
const age = Date.now() - timestamp;
|
|
144
|
+
if (age < 0 || age > maxAgeMs)
|
|
145
|
+
return age < 0 ? "malformed" : "stale";
|
|
146
|
+
const data = readJsonCache(cachePath, (parsed) => parsed);
|
|
147
|
+
return data === undefined ? "malformed" : "fresh";
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return "unreadable";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
114
153
|
/**
|
|
115
154
|
* Check if a cache entry is fresh (exists and not expired).
|
|
116
155
|
*/
|
|
@@ -175,16 +214,57 @@ export class CacheManager {
|
|
|
175
214
|
fs.mkdirSync(lensDir, { recursive: true });
|
|
176
215
|
const statePath = getTurnStatePath(cwd);
|
|
177
216
|
state.lastUpdated = new Date().toISOString();
|
|
178
|
-
|
|
217
|
+
writeFileAtomic(statePath, JSON.stringify(state, null, 2));
|
|
218
|
+
}
|
|
219
|
+
/** Return whether a writer may read/write this workspace worklist. */
|
|
220
|
+
getTurnStateAccess(cwd, owner) {
|
|
221
|
+
const state = this.readTurnState(cwd);
|
|
222
|
+
if (!state.owner) {
|
|
223
|
+
if (!state.sessionId || state.sessionId === owner.id)
|
|
224
|
+
return "owned";
|
|
225
|
+
// Pre-owner files have no liveness information. Preserve the existing
|
|
226
|
+
// stale-session eviction behavior for this legacy shape.
|
|
227
|
+
return "available";
|
|
228
|
+
}
|
|
229
|
+
if (state.owner.kind === owner.kind &&
|
|
230
|
+
state.owner.id === owner.id) {
|
|
231
|
+
return "owned";
|
|
232
|
+
}
|
|
233
|
+
return this.isTurnStateOwnerStale(state.owner) ? "available" : "foreign-live";
|
|
234
|
+
}
|
|
235
|
+
isTurnStateOwnerStale(owner) {
|
|
236
|
+
if (owner.pid > 0 && owner.pid !== process.pid) {
|
|
237
|
+
try {
|
|
238
|
+
process.kill(owner.pid, 0);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const lastSeen = Date.parse(owner.lastSeen);
|
|
246
|
+
return !Number.isFinite(lastSeen) || Date.now() - lastSeen > TURN_OWNER_STALE_MS;
|
|
179
247
|
}
|
|
180
248
|
/**
|
|
181
249
|
* Add or update a file's modified ranges in turn state.
|
|
182
|
-
* Merges overlapping ranges.
|
|
250
|
+
* Merges overlapping ranges. `sessionId:null` deliberately preserves the
|
|
251
|
+
* current owner; callers must provide an explicit owner id to claim a stale
|
|
252
|
+
* worklist.
|
|
183
253
|
*/
|
|
184
|
-
addModifiedRange(filePath, range, importsChanged, cwd, sessionId) {
|
|
254
|
+
addModifiedRange(filePath, range, importsChanged, cwd, sessionId, ownerKind = "pi") {
|
|
185
255
|
const state = this.readTurnState(cwd);
|
|
186
|
-
if (sessionId)
|
|
256
|
+
if (sessionId) {
|
|
257
|
+
const owner = {
|
|
258
|
+
kind: ownerKind,
|
|
259
|
+
id: sessionId,
|
|
260
|
+
pid: process.pid,
|
|
261
|
+
lastSeen: new Date().toISOString(),
|
|
262
|
+
};
|
|
263
|
+
if (this.getTurnStateAccess(cwd, owner) === "foreign-live")
|
|
264
|
+
return state;
|
|
187
265
|
state.sessionId = sessionId;
|
|
266
|
+
state.owner = owner;
|
|
267
|
+
}
|
|
188
268
|
const normalizedPath = this.toTurnStateKey(filePath, cwd);
|
|
189
269
|
const existing = state.files[normalizedPath];
|
|
190
270
|
if (existing) {
|
|
@@ -209,19 +289,27 @@ export class CacheManager {
|
|
|
209
289
|
/**
|
|
210
290
|
* Clear turn state (after turn_end processes it).
|
|
211
291
|
*/
|
|
212
|
-
clearTurnState(cwd) {
|
|
292
|
+
clearTurnState(cwd, owner) {
|
|
293
|
+
const currentState = this.readTurnState(cwd);
|
|
294
|
+
const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
|
|
295
|
+
if (!isCurrentOwner && process.pid !== currentState.owner?.pid)
|
|
296
|
+
return false;
|
|
213
297
|
const state = {
|
|
214
298
|
...DEFAULT_TURN_STATE,
|
|
215
299
|
files: {}, // fresh object — DEFAULT_TURN_STATE.files can be polluted by addModifiedRange
|
|
216
300
|
lastUpdated: new Date().toISOString(),
|
|
217
301
|
};
|
|
218
302
|
this.writeTurnState(state, cwd);
|
|
303
|
+
return true;
|
|
219
304
|
}
|
|
220
305
|
/**
|
|
221
306
|
* Increment turn cycle counter.
|
|
222
307
|
*/
|
|
223
|
-
incrementTurnCycle(cwd) {
|
|
308
|
+
incrementTurnCycle(cwd, owner) {
|
|
224
309
|
const state = this.readTurnState(cwd);
|
|
310
|
+
const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
|
|
311
|
+
if (!isCurrentOwner && process.pid !== state.owner?.pid)
|
|
312
|
+
return state;
|
|
225
313
|
state.turnCycles++;
|
|
226
314
|
this.writeTurnState(state, cwd);
|
|
227
315
|
return state;
|