@herbertgao/pi-extensions 2026.8.11 → 2026.8.13
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 +23 -10
- package/{node_modules/@herbertgao/pi-mermaid-open/LICENSE → THIRD_PARTY_NOTICES.md} +19 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +15 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -3
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +2 -0
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +1 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +4 -4
- package/node_modules/@czottmann/pi-automode/docs/configuration.md +20 -4
- package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +1 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +692 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +181 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +4 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +143 -36
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +103 -148
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +32 -6
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +344 -22
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +1 -0
- package/node_modules/@czottmann/pi-automode/package.json +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +4 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +21 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +19 -19
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +4 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +10 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +21 -15
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +11 -8
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +184 -106
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +31 -11
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +9 -1
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +156 -31
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +17 -0
- package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +14 -0
- package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +8 -0
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +32 -1
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +17 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +23 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +259 -24
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +376 -0
- package/node_modules/@luxusai/pi-hindsight/README.md +92 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +56 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +169 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +107 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +89 -0
- package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +189 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +54 -0
- package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +75 -0
- package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +300 -0
- package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +158 -0
- package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +164 -0
- package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +139 -0
- package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +74 -0
- package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +261 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +151 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +18 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +46 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +383 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +240 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +176 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +178 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +41 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +386 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +39 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +34 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +140 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +61 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +925 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +247 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +547 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +480 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +182 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +86 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +1010 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +175 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +425 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +210 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +817 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +25 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +319 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +196 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +189 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +65 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +260 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +244 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +59 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +82 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +45 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +331 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +354 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +66 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +132 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +228 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +74 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +16 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +648 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +105 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +68 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +71 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +13 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +162 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +131 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +104 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +37 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +893 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +35 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +177 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +7 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +21 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +112 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +111 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +214 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +70 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +392 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +45 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +9 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +860 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +105 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +189 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +299 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +201 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +47 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +239 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +50 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +259 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +77 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +534 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +319 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +325 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +42 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +244 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +56 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +50 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +167 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +207 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +120 -0
- package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +8 -0
- package/node_modules/@luxusai/pi-hindsight/package.json +118 -0
- package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +75 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +143 -46
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +136 -22
- package/node_modules/{@herbertgao → @tifan}/pi-copy-response/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-copy-response/package.json +5 -15
- package/node_modules/{@herbertgao → @tifan}/pi-handoff/README.md +4 -6
- package/node_modules/@tifan/pi-handoff/package.json +43 -0
- package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/index.ts +1 -1
- package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/package.json +5 -15
- package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/README.md +5 -7
- package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/package.json +5 -19
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/README.md +4 -6
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/package.json +6 -16
- package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/src/index.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-recap/README.md +4 -6
- package/node_modules/@tifan/pi-recap/package.json +42 -0
- package/node_modules/{@herbertgao → @tifan}/pi-recap/src/models.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-rename/README.md +13 -8
- package/node_modules/@tifan/pi-rename/package.json +47 -0
- package/node_modules/@tifan/pi-rename/src/herdr-label.ts +4 -0
- package/node_modules/{@herbertgao → @tifan}/pi-rename/src/index.ts +4 -16
- package/node_modules/{@herbertgao → @tifan}/pi-rename/src/models.ts +1 -6
- package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/README.md +2 -4
- package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/package.json +5 -15
- package/node_modules/pi-lens/CHANGELOG.md +96 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +14 -5
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +48 -18
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +16 -4
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +3 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +21 -8
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +24 -8
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +4 -3
- package/node_modules/pi-lens/dist/clients/cache-manager.js +6 -7
- package/node_modules/pi-lens/dist/clients/cache-observability.js +461 -66
- package/node_modules/pi-lens/dist/clients/call-graph.js +238 -63
- package/node_modules/pi-lens/dist/clients/codebase-model.js +13 -5
- package/node_modules/pi-lens/dist/clients/complexity-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -8
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +1 -2
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +3 -1
- package/node_modules/pi-lens/dist/clients/debug-heap.js +8 -1
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +1 -1
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +11 -7
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -2
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +13 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -4
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +31 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +5 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +10 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +25 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +25 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +28 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +212 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +8 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +23 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +7 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +5 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +7 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +13 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +17 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +7 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +24 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +6 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +6 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +5 -3
- package/node_modules/pi-lens/dist/clients/feature-hints.js +8 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +34 -129
- package/node_modules/pi-lens/dist/clients/file-utils.js +17 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +57 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +144 -27
- package/node_modules/pi-lens/dist/clients/freshness.js +38 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +43 -13
- package/node_modules/pi-lens/dist/clients/go-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/grammar-source.js +1 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +30 -4
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +1 -3
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +4 -2
- package/node_modules/pi-lens/dist/clients/installer/index.js +86 -77
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +5 -2
- package/node_modules/pi-lens/dist/clients/instance-registry.js +4 -2
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +29 -15
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +13 -2
- package/node_modules/pi-lens/dist/clients/lens-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-map.js +8 -3
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/config.js +4 -6
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +119 -32
- package/node_modules/pi-lens/dist/clients/lsp/index.js +94 -35
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +4 -1
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +11 -3
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +152 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -7
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +10 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -3
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +10 -20
- package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +1 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +73 -7
- package/node_modules/pi-lens/dist/clients/metrics-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +8 -3
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +7 -1
- package/node_modules/pi-lens/dist/clients/module-report.js +14 -17
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +3 -5
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +220 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +57 -9
- package/node_modules/pi-lens/dist/clients/project-conventions.js +2 -8
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +10 -7
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +3 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +13 -9
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +3 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -2
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +12 -5
- package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +48 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +15 -1
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +472 -66
- package/node_modules/pi-lens/dist/clients/read-expansion.js +12 -2
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +5 -4
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +5 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +1 -1
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +6 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +42 -17
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +8 -2
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +10 -1
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +4 -2
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +2 -2
- package/node_modules/pi-lens/dist/clients/ruff-client.js +6 -5
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +63 -31
- package/node_modules/pi-lens/dist/clients/runtime-context.js +12 -4
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +83 -4
- package/node_modules/pi-lens/dist/clients/runtime-session.js +14 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +59 -3
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +148 -34
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -6
- package/node_modules/pi-lens/dist/clients/rust-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +150 -11
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +1 -3
- package/node_modules/pi-lens/dist/clients/session-summary.js +3 -1
- package/node_modules/pi-lens/dist/clients/sg-runner.js +1 -3
- package/node_modules/pi-lens/dist/clients/source-filter.js +10 -6
- package/node_modules/pi-lens/dist/clients/source-walker.js +1 -1
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +96 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +4 -1
- package/node_modules/pi-lens/dist/clients/tool-definition.js +7 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +26 -11
- package/node_modules/pi-lens/dist/clients/tool-render.js +2 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +10 -4
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +12 -5
- package/node_modules/pi-lens/dist/clients/warm-attach.js +12 -5
- package/node_modules/pi-lens/dist/clients/widget-state.js +27 -11
- package/node_modules/pi-lens/dist/clients/word-index.js +3 -1
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -2
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +9 -1
- package/node_modules/pi-lens/dist/i18n.js +13 -5
- package/node_modules/pi-lens/dist/index.js +10797 -8627
- package/node_modules/pi-lens/dist/mcp/server.js +3 -1
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +68 -12
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +21 -7
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +164 -35
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +24 -7
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/dist/tools/module-report.js +1 -1
- package/node_modules/pi-lens/dist/tools/project-report.js +1 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +1 -1
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -1
- package/node_modules/pi-lens/package.json +160 -156
- package/node_modules/pi-lens/rules/typos/_typos.toml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +31 -13
- package/node_modules/pi-lens/scripts/download-grammars.js +9 -3
- package/node_modules/pi-lens/scripts/rpc-load-check.mjs +30 -9
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +47 -0
- package/node_modules/pi-mcp-adapter/README.md +42 -13
- package/node_modules/pi-mcp-adapter/commands.ts +4 -2
- package/node_modules/pi-mcp-adapter/config.ts +45 -5
- package/node_modules/pi-mcp-adapter/direct-tools.ts +103 -92
- package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js +63 -0
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +356 -0
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +123 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1087 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +19 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +319 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +156 -0
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js +15 -0
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +564 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +287 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +37 -0
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +104 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +59 -0
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +4 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +27 -0
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +32 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +338 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -0
- package/node_modules/pi-mcp-adapter/failure-backoff.ts +23 -0
- package/node_modules/pi-mcp-adapter/index.ts +204 -82
- package/node_modules/pi-mcp-adapter/init.ts +41 -30
- package/node_modules/pi-mcp-adapter/lifecycle.ts +16 -8
- package/node_modules/pi-mcp-adapter/mcp-code.ts +2 -0
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +66 -16
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +95 -7
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +18 -4
- package/node_modules/pi-mcp-adapter/mcp-references.ts +368 -0
- package/node_modules/pi-mcp-adapter/mcp-status.ts +8 -19
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +20 -12
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +276 -0
- package/node_modules/pi-mcp-adapter/package.json +24 -6
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +66 -10
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +63 -12
- package/node_modules/pi-mcp-adapter/state.ts +1 -3
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +16 -2
- package/node_modules/pi-mcp-adapter/types.ts +48 -1
- package/node_modules/pi-mcp-adapter/ui-server.ts +3 -19
- package/node_modules/pi-mcp-adapter/ui-session.ts +2 -1
- package/node_modules/pi-mcp-adapter/utils.ts +21 -19
- package/node_modules/pi-web-access/CHANGELOG.md +26 -0
- package/node_modules/pi-web-access/README.md +71 -14
- package/node_modules/pi-web-access/chrome-cookies.ts +115 -35
- package/node_modules/pi-web-access/credential-source.ts +6 -2
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +89 -9
- package/node_modules/pi-web-access/fetch-params.ts +13 -0
- package/node_modules/pi-web-access/gemini-adc.ts +298 -0
- package/node_modules/pi-web-access/gemini-api.ts +59 -15
- package/node_modules/pi-web-access/gemini-search.ts +58 -18
- package/node_modules/pi-web-access/gemini-url-context.ts +3 -2
- package/node_modules/pi-web-access/gemini-web-config.ts +57 -7
- package/node_modules/pi-web-access/gemini-web.ts +9 -3
- package/node_modules/pi-web-access/github-api.ts +5 -4
- package/node_modules/pi-web-access/github-issue-pr.ts +700 -0
- package/node_modules/pi-web-access/index.ts +403 -297
- package/node_modules/pi-web-access/kimi-search.ts +234 -0
- package/node_modules/pi-web-access/openai-search.ts +118 -21
- package/node_modules/pi-web-access/package.json +3 -2
- package/node_modules/pi-web-access/ssrf-protection.ts +4 -1
- package/node_modules/pi-web-access/utils.ts +307 -1
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +46 -37
- package/node_modules/@herbertgao/pi-copy-response/CHANGELOG.md +0 -49
- package/node_modules/@herbertgao/pi-copy-response/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +0 -83
- package/node_modules/@herbertgao/pi-handoff/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-handoff/package.json +0 -60
- package/node_modules/@herbertgao/pi-inline-skills/CHANGELOG.md +0 -57
- package/node_modules/@herbertgao/pi-inline-skills/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +0 -39
- package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +0 -63
- package/node_modules/@herbertgao/pi-preferred-thinking/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +0 -101
- package/node_modules/@herbertgao/pi-recap/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-recap/package.json +0 -55
- package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +0 -49
- package/node_modules/@herbertgao/pi-rename/LICENSE +0 -22
- package/node_modules/@herbertgao/pi-rename/package.json +0 -63
- package/node_modules/@herbertgao/pi-titlebar-spinner/CHANGELOG.md +0 -25
- package/node_modules/@herbertgao/pi-titlebar-spinner/LICENSE +0 -22
- /package/node_modules/{@herbertgao → @tifan}/pi-copy-response/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/complete-text.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/session-query.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/viewer.mjs +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/index.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/model-picker.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/sanitize.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/tui.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/model-picker.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/naming.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/sanitize.ts +0 -0
- /package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/src/index.ts +0 -0
|
@@ -5,9 +5,11 @@ import { Worker } from "node:worker_threads";
|
|
|
5
5
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
6
6
|
import { writeFileAtomic } from "./atomic-write.js";
|
|
7
7
|
import { getProjectDataDir } from "./file-utils.js";
|
|
8
|
+
import { incrementDegradationCount } from "./degradation-ledger.js";
|
|
8
9
|
import { readJsonCache } from "./json-cache-read.js";
|
|
9
10
|
import { logLatency } from "./latency-logger.js";
|
|
10
11
|
import { normalizeMapKey } from "./path-utils.js";
|
|
12
|
+
import { fingerprintProjectSnapshotJson } from "./project-snapshot-fingerprint.js";
|
|
11
13
|
import { detectProjectConventions, } from "./project-conventions.js";
|
|
12
14
|
import { deserializeWordIndex, serializeWordIndex, } from "./word-index.js";
|
|
13
15
|
// v2: added `wordIndex` (identifier inverted index + BM25, #162). Bumping the
|
|
@@ -97,6 +99,15 @@ function parseSnapshotMeta(value) {
|
|
|
97
99
|
timestamp: typeof meta.timestamp === "string" ? meta.timestamp : "",
|
|
98
100
|
version: meta.version,
|
|
99
101
|
seq: meta.seq,
|
|
102
|
+
fingerprint: typeof meta.fingerprint === "string" &&
|
|
103
|
+
/^[a-f0-9]{64}$/.test(meta.fingerprint)
|
|
104
|
+
? meta.fingerprint
|
|
105
|
+
: undefined,
|
|
106
|
+
gzBytes: typeof meta.gzBytes === "number" &&
|
|
107
|
+
Number.isInteger(meta.gzBytes) &&
|
|
108
|
+
meta.gzBytes > 0
|
|
109
|
+
? meta.gzBytes
|
|
110
|
+
: undefined,
|
|
100
111
|
sequenceIndex: parseSequenceIndex(meta.sequenceIndex),
|
|
101
112
|
};
|
|
102
113
|
}
|
|
@@ -149,7 +160,9 @@ const PROJECT_SNAPSHOT_MAX_WARM_ROOTS = 8;
|
|
|
149
160
|
const PROJECT_SNAPSHOT_IDLE_EVICT_MS_DEFAULT = 20 * 60_000;
|
|
150
161
|
function projectSnapshotIdleEvictMs() {
|
|
151
162
|
const value = Number.parseInt(process.env.PI_LENS_PROJECT_SNAPSHOT_IDLE_EVICT_MS ?? "", 10);
|
|
152
|
-
return Number.isSafeInteger(value) && value > 0
|
|
163
|
+
return Number.isSafeInteger(value) && value > 0
|
|
164
|
+
? value
|
|
165
|
+
: PROJECT_SNAPSHOT_IDLE_EVICT_MS_DEFAULT;
|
|
153
166
|
}
|
|
154
167
|
function clearAuthoritativeSnapshotTimer(entry) {
|
|
155
168
|
if (entry.idleTimer)
|
|
@@ -167,7 +180,8 @@ function scheduleAuthoritativeSnapshotEviction(key, entry) {
|
|
|
167
180
|
const generation = entry.lastUsedAt;
|
|
168
181
|
entry.idleTimer = setTimeout(() => {
|
|
169
182
|
entry.idleTimer = undefined;
|
|
170
|
-
if (authoritativeSnapshots.get(key) !== entry ||
|
|
183
|
+
if (authoritativeSnapshots.get(key) !== entry ||
|
|
184
|
+
entry.lastUsedAt !== generation)
|
|
171
185
|
return;
|
|
172
186
|
deleteAuthoritativeSnapshot(key);
|
|
173
187
|
}, projectSnapshotIdleEvictMs());
|
|
@@ -590,7 +604,11 @@ export function loadProjectSnapshot(cwd) {
|
|
|
590
604
|
export function loadProjectSnapshotWithoutWordIndex(cwd) {
|
|
591
605
|
return loadProjectSnapshotInternal(cwd, false);
|
|
592
606
|
}
|
|
593
|
-
const
|
|
607
|
+
const _snapshotGenerationStates = new Map();
|
|
608
|
+
const _successfulSnapshotPersists = new Map();
|
|
609
|
+
const _failedSnapshotPersists = new Map();
|
|
610
|
+
const _activeSnapshotPersists = new Map();
|
|
611
|
+
const _queuedSnapshotPersists = new Map();
|
|
594
612
|
const _snapshotWorkerRequests = new Map();
|
|
595
613
|
let _snapshotPersistWorker;
|
|
596
614
|
let _snapshotWorkerRequestId = 0;
|
|
@@ -598,6 +616,8 @@ let _snapshotWorkerDisabled = false;
|
|
|
598
616
|
let _snapshotGenerationGateEnabledForTests = true;
|
|
599
617
|
let _snapshotPromotionSeamForTests;
|
|
600
618
|
let _lastSnapshotPersistErrorForTests;
|
|
619
|
+
let _snapshotExiting = false;
|
|
620
|
+
let _snapshotWorkerBodyWritesForTests = 0;
|
|
601
621
|
function snapshotWorkerEnabled() {
|
|
602
622
|
// The synchronous fallback writer is a legitimate degraded mode (hosts that
|
|
603
623
|
// can't spawn a worker); tests also force it so a save→load is fully
|
|
@@ -605,31 +625,233 @@ function snapshotWorkerEnabled() {
|
|
|
605
625
|
const raw = process.env.PI_LENS_SNAPSHOT_PERSIST_SYNC;
|
|
606
626
|
return !(raw === "1" || raw === "true");
|
|
607
627
|
}
|
|
608
|
-
function
|
|
628
|
+
function pendingSnapshotIsCurrent(pending) {
|
|
629
|
+
return (!_snapshotGenerationGateEnabledForTests ||
|
|
630
|
+
_snapshotGenerationStates.get(pending.key)?.generation ===
|
|
631
|
+
pending.generation);
|
|
632
|
+
}
|
|
633
|
+
function rememberSuccessfulSnapshotPersist(key, record) {
|
|
634
|
+
_successfulSnapshotPersists.delete(key);
|
|
635
|
+
_successfulSnapshotPersists.set(key, record);
|
|
636
|
+
while (_successfulSnapshotPersists.size > PROJECT_SNAPSHOT_MAX_WARM_ROOTS) {
|
|
637
|
+
const oldest = _successfulSnapshotPersists.keys().next().value;
|
|
638
|
+
if (oldest === undefined)
|
|
639
|
+
break;
|
|
640
|
+
_successfulSnapshotPersists.delete(oldest);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function assessSnapshotBodyIntegrity(meta, body) {
|
|
644
|
+
if (meta.gzBytes === undefined) {
|
|
645
|
+
// Legacy meta (pre-#2008): nothing to compare against, so the evidence is
|
|
646
|
+
// untrusted until the next successful persist populates gzBytes.
|
|
647
|
+
return { outcome: "legacy-meta", actualBytes: body.size };
|
|
648
|
+
}
|
|
649
|
+
if (!body.gz || body.size !== meta.gzBytes) {
|
|
650
|
+
return {
|
|
651
|
+
outcome: "size-mismatch",
|
|
652
|
+
expectedGzBytes: meta.gzBytes,
|
|
653
|
+
actualBytes: body.size,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
outcome: "ok",
|
|
658
|
+
expectedGzBytes: meta.gzBytes,
|
|
659
|
+
actualBytes: body.size,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Bounded observability for #2008: the degradation ledger keeps one entry per
|
|
664
|
+
* corrupted subject with the exact detection count for the session; every
|
|
665
|
+
* detection also emits one `project_snapshot_body_integrity` latency row
|
|
666
|
+
* naming expected vs actual bytes, so an operator can confirm both the
|
|
667
|
+
* detection and the forced republish from logs alone.
|
|
668
|
+
*/
|
|
669
|
+
function noteSnapshotBodyIntegrityWithheld(args) {
|
|
670
|
+
incrementDegradationCount({
|
|
671
|
+
kind: "snapshot-integrity",
|
|
672
|
+
subject: args.gzPath,
|
|
673
|
+
reason: args.verdict,
|
|
674
|
+
});
|
|
675
|
+
logLatency({
|
|
676
|
+
type: "phase",
|
|
677
|
+
phase: "project_snapshot_body_integrity",
|
|
678
|
+
filePath: args.gzPath,
|
|
679
|
+
durationMs: 0,
|
|
680
|
+
metadata: {
|
|
681
|
+
outcome: "dedupe_withheld",
|
|
682
|
+
reason: args.verdict,
|
|
683
|
+
...(args.seq !== undefined ? { seq: args.seq } : {}),
|
|
684
|
+
...(args.expectedGzBytes === undefined
|
|
685
|
+
? {}
|
|
686
|
+
: { expectedGzBytes: args.expectedGzBytes }),
|
|
687
|
+
actualBytes: args.actualBytes,
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/** Log one row naming a skip that was refused because integrity regressed. */
|
|
692
|
+
function noteSnapshotSkipRefused(args) {
|
|
693
|
+
logLatency({
|
|
694
|
+
type: "phase",
|
|
695
|
+
phase: "project_snapshot_body_integrity",
|
|
696
|
+
filePath: args.gzPath,
|
|
697
|
+
durationMs: 0,
|
|
698
|
+
metadata: {
|
|
699
|
+
outcome: "skip_refused_rewrite",
|
|
700
|
+
reason: args.verdict,
|
|
701
|
+
seq: args.seq,
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* PURE READ of the durable meta+body evidence for a snapshot key (#2008
|
|
707
|
+
* refactor): it must never mutate {@link _successfulSnapshotPersists}. This
|
|
708
|
+
* runs up to three times per persist — admission in saveProjectSnapshot, the
|
|
709
|
+
* dispatch-time refresh, and the skip-honor re-read — including on results
|
|
710
|
+
* whose fingerprints are only partially used, so the seeding write lives in
|
|
711
|
+
* {@link seedSnapshotPersistBaselineFromDurable} and is called only by the
|
|
712
|
+
* seam that owns the persist lifecycle (dispatchSnapshotPersist).
|
|
713
|
+
*/
|
|
714
|
+
function snapshotPersistBaselinesFor(cwd, key) {
|
|
715
|
+
const local = _successfulSnapshotPersists.get(key);
|
|
716
|
+
const meta = readProjectSnapshotMeta(cwd);
|
|
717
|
+
const body = resolveSnapshotBodyPath(cwd);
|
|
718
|
+
// No body means no dedupe evidence. This keeps same-seq deletion repair live.
|
|
719
|
+
if (!meta?.fingerprint || !body) {
|
|
720
|
+
if (!body)
|
|
721
|
+
_successfulSnapshotPersists.delete(key);
|
|
722
|
+
return { fingerprints: [] };
|
|
723
|
+
}
|
|
724
|
+
// #2008: a meta whose recorded gz size no longer matches the on-disk body
|
|
725
|
+
// describes evidence we cannot trust — a truncated/torn gzip under an
|
|
726
|
+
// intact meta used to win same-fingerprint dedupe forever, keeping the
|
|
727
|
+
// corrupt body canonical until seq advanced. Withhold the fingerprints so
|
|
728
|
+
// the pending save republishes (and rewrites) the body.
|
|
729
|
+
const integrity = assessSnapshotBodyIntegrity(meta, body);
|
|
730
|
+
if (integrity.outcome !== "ok") {
|
|
731
|
+
noteSnapshotBodyIntegrityWithheld({
|
|
732
|
+
gzPath: getProjectSnapshotPath(cwd),
|
|
733
|
+
verdict: integrity.outcome,
|
|
734
|
+
seq: meta.seq,
|
|
735
|
+
expectedGzBytes: integrity.expectedGzBytes,
|
|
736
|
+
actualBytes: integrity.actualBytes,
|
|
737
|
+
});
|
|
738
|
+
return { fingerprints: [] };
|
|
739
|
+
}
|
|
740
|
+
const durable = {
|
|
741
|
+
seq: meta.seq,
|
|
742
|
+
fingerprint: meta.fingerprint,
|
|
743
|
+
generatedAt: meta.timestamp,
|
|
744
|
+
generation: _snapshotGenerationStates.get(key)?.generation ?? 0,
|
|
745
|
+
gzBytes: meta.gzBytes,
|
|
746
|
+
};
|
|
747
|
+
const fingerprints = [durable.fingerprint];
|
|
748
|
+
// A sibling process may have advanced durable state from local A to B. An
|
|
749
|
+
// unchanged replay of A is stale work and must not overwrite B. A third
|
|
750
|
+
// semantic state C matches neither fingerprint and still publishes.
|
|
751
|
+
if (local && local.fingerprint !== durable.fingerprint) {
|
|
752
|
+
fingerprints.push(local.fingerprint);
|
|
753
|
+
}
|
|
754
|
+
return { durable, fingerprints, integrity: "ok" };
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Seed the in-process baseline for `key` from freshly-read durable state.
|
|
758
|
+
* Deliberately separate from {@link snapshotPersistBaselinesFor} so the read
|
|
759
|
+
* stays pure; called only at the dispatch seam where the persist lifecycle
|
|
760
|
+
* begins.
|
|
761
|
+
*/
|
|
762
|
+
function seedSnapshotPersistBaselineFromDurable(key, durable) {
|
|
763
|
+
if (!durable || _successfulSnapshotPersists.has(key))
|
|
764
|
+
return;
|
|
765
|
+
rememberSuccessfulSnapshotPersist(key, durable);
|
|
766
|
+
}
|
|
767
|
+
function writeProjectSnapshotMeta(metaPath, snapshot, bodyRecord) {
|
|
768
|
+
writeFileAtomic(metaPath, JSON.stringify({
|
|
769
|
+
timestamp: bodyRecord?.generatedAt ?? snapshot.generatedAt,
|
|
770
|
+
version: snapshot.version,
|
|
771
|
+
seq: snapshot.seq,
|
|
772
|
+
...(bodyRecord
|
|
773
|
+
? {
|
|
774
|
+
fingerprint: bodyRecord.fingerprint,
|
|
775
|
+
...(bodyRecord.gzBytes === undefined
|
|
776
|
+
? {}
|
|
777
|
+
: { gzBytes: bodyRecord.gzBytes }),
|
|
778
|
+
}
|
|
779
|
+
: {}),
|
|
780
|
+
...(snapshot.sequenceIndex
|
|
781
|
+
? { sequenceIndex: snapshot.sequenceIndex }
|
|
782
|
+
: {}),
|
|
783
|
+
}), { bestEffort: false });
|
|
784
|
+
}
|
|
785
|
+
function recordSnapshotPersistFailure(pending, error, bodyPersisted = false) {
|
|
609
786
|
// Honesty (#533): a failed async body write must be surfaced, never left to
|
|
610
787
|
// masquerade as a saved snapshot. The meta gate is already self-healing (an
|
|
611
788
|
// old body under a newer-seq meta is rejected on the body's own embedded
|
|
612
789
|
// seq), and dropping the authoritative entry means the next load reflects
|
|
613
790
|
// what is ACTUALLY on disk rather than the object we failed to persist.
|
|
614
791
|
_lastSnapshotPersistErrorForTests = error;
|
|
615
|
-
|
|
792
|
+
_failedSnapshotPersists.set(pending.key, {
|
|
793
|
+
seq: pending.snapshot.seq,
|
|
794
|
+
generation: pending.generation,
|
|
795
|
+
});
|
|
796
|
+
while (_failedSnapshotPersists.size > PROJECT_SNAPSHOT_MAX_WARM_ROOTS) {
|
|
797
|
+
const oldest = _failedSnapshotPersists.keys().next().value;
|
|
798
|
+
if (oldest === undefined)
|
|
799
|
+
break;
|
|
800
|
+
_failedSnapshotPersists.delete(oldest);
|
|
801
|
+
}
|
|
802
|
+
deleteAuthoritativeSnapshot(pending.key);
|
|
616
803
|
logLatency({
|
|
617
804
|
type: "phase",
|
|
618
805
|
phase: "project_snapshot_persist_failed",
|
|
619
|
-
filePath:
|
|
806
|
+
filePath: pending.gzPath,
|
|
620
807
|
durationMs: 0,
|
|
621
|
-
metadata: {
|
|
808
|
+
metadata: {
|
|
809
|
+
error,
|
|
810
|
+
seq: pending.snapshot.seq,
|
|
811
|
+
outcome: bodyPersisted ? "metadata_repair_required" : "failed",
|
|
812
|
+
...(bodyPersisted ? { bodyPersisted: true } : {}),
|
|
813
|
+
},
|
|
622
814
|
});
|
|
623
815
|
// #1333: the logLatency call above already carries this failure to
|
|
624
816
|
// latency.log — the console.error was a duplicate RAW write into pi's frame.
|
|
625
817
|
}
|
|
626
|
-
function logSnapshotPersistSuccess(pending, stats) {
|
|
818
|
+
function logSnapshotPersistSuccess(pending, fingerprint, stats) {
|
|
819
|
+
rememberSuccessfulSnapshotPersist(pending.key, {
|
|
820
|
+
seq: pending.snapshot.seq,
|
|
821
|
+
fingerprint,
|
|
822
|
+
generatedAt: pending.snapshot.generatedAt,
|
|
823
|
+
generation: pending.generation,
|
|
824
|
+
rawBytes: stats.rawBytes,
|
|
825
|
+
gzBytes: stats.gzBytes,
|
|
826
|
+
});
|
|
827
|
+
_failedSnapshotPersists.delete(pending.key);
|
|
627
828
|
logLatency({
|
|
628
829
|
type: "phase",
|
|
629
830
|
phase: "project_snapshot_persist",
|
|
630
831
|
filePath: pending.gzPath,
|
|
631
832
|
durationMs: stats.serializeMs + stats.writeMs,
|
|
632
|
-
metadata: { seq: pending.snapshot.seq, ...stats },
|
|
833
|
+
metadata: { seq: pending.snapshot.seq, outcome: "executed", ...stats },
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
function logSnapshotPersistDecision(args) {
|
|
837
|
+
logLatency({
|
|
838
|
+
type: "phase",
|
|
839
|
+
phase: "project_snapshot_persist_decision",
|
|
840
|
+
filePath: getProjectSnapshotPath(args.cwd),
|
|
841
|
+
durationMs: 0,
|
|
842
|
+
metadata: {
|
|
843
|
+
seq: args.seq,
|
|
844
|
+
decision: args.decision,
|
|
845
|
+
...(args.fingerprint
|
|
846
|
+
? { fingerprint: args.fingerprint.slice(0, 12) }
|
|
847
|
+
: {}),
|
|
848
|
+
...(args.avoidedRawBytes === undefined
|
|
849
|
+
? {}
|
|
850
|
+
: { avoidedRawBytes: args.avoidedRawBytes }),
|
|
851
|
+
...(args.avoidedGzipBytes === undefined
|
|
852
|
+
? {}
|
|
853
|
+
: { avoidedGzipBytes: args.avoidedGzipBytes }),
|
|
854
|
+
},
|
|
633
855
|
});
|
|
634
856
|
}
|
|
635
857
|
/**
|
|
@@ -692,7 +914,33 @@ function reconcileAuthoritativeAfterWrite(pending, rawBytes) {
|
|
|
692
914
|
// is one extra disk re-parse on the next load.
|
|
693
915
|
}
|
|
694
916
|
}
|
|
917
|
+
function finalizeProjectSnapshotMeta(pending, record) {
|
|
918
|
+
try {
|
|
919
|
+
writeProjectSnapshotMeta(getProjectSnapshotMetaPath(pending.cwd), pending.snapshot, record);
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
catch (err) {
|
|
923
|
+
recordSnapshotPersistFailure(pending, err instanceof Error ? err.message : String(err), true);
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function completeSnapshotPersist(pending) {
|
|
928
|
+
if (_activeSnapshotPersists.get(pending.key) !== pending)
|
|
929
|
+
return;
|
|
930
|
+
_activeSnapshotPersists.delete(pending.key);
|
|
931
|
+
if (_snapshotExiting)
|
|
932
|
+
return;
|
|
933
|
+
const queued = _queuedSnapshotPersists.get(pending.key);
|
|
934
|
+
if (!queued)
|
|
935
|
+
return;
|
|
936
|
+
_queuedSnapshotPersists.delete(pending.key);
|
|
937
|
+
dispatchSnapshotPersist(queued);
|
|
938
|
+
}
|
|
695
939
|
function writeSnapshotBodyOnMainThread(pending, reason) {
|
|
940
|
+
if (!pendingSnapshotIsCurrent(pending)) {
|
|
941
|
+
completeSnapshotPersist(pending);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
696
944
|
if (reason) {
|
|
697
945
|
// We took the synchronous main-thread gzip path (the +656MB-risk path,
|
|
698
946
|
// #950) instead of the worker. Surface it rather than burying it in an
|
|
@@ -717,13 +965,52 @@ function writeSnapshotBodyOnMainThread(pending, reason) {
|
|
|
717
965
|
const json = JSON.stringify(pending.snapshot);
|
|
718
966
|
const serializeMs = performance.now() - serializeStarted;
|
|
719
967
|
const rawBytes = Buffer.byteLength(json);
|
|
968
|
+
const fingerprint = fingerprintProjectSnapshotJson(json, pending.snapshot.generatedAt);
|
|
969
|
+
if (pending.dedupeFingerprints.includes(fingerprint)) {
|
|
970
|
+
// Re-read the tiny durable sidecar before honoring a fingerprint
|
|
971
|
+
// captured at dispatch time (#2008): a body torn between dispatch and
|
|
972
|
+
// execution must fall through to the full write, not skip.
|
|
973
|
+
const current = snapshotPersistBaselinesFor(pending.cwd, pending.key);
|
|
974
|
+
if (current.integrity === "ok") {
|
|
975
|
+
const prior = current.durable ?? pending.durablePersist;
|
|
976
|
+
if (authoritativeSnapshots.get(pending.key)?.snapshot === pending.snapshot) {
|
|
977
|
+
deleteAuthoritativeSnapshot(pending.key);
|
|
978
|
+
}
|
|
979
|
+
logSnapshotPersistDecision({
|
|
980
|
+
cwd: pending.cwd,
|
|
981
|
+
seq: pending.snapshot.seq,
|
|
982
|
+
fingerprint,
|
|
983
|
+
decision: "skipped_unchanged",
|
|
984
|
+
avoidedRawBytes: rawBytes,
|
|
985
|
+
avoidedGzipBytes: prior?.gzBytes,
|
|
986
|
+
});
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (current.integrity !== undefined) {
|
|
990
|
+
noteSnapshotSkipRefused({
|
|
991
|
+
gzPath: pending.gzPath,
|
|
992
|
+
verdict: current.integrity,
|
|
993
|
+
seq: pending.snapshot.seq,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
// integrity === undefined: the meta/body evidence vanished entirely
|
|
997
|
+
// since dispatch — falling through to the full write repairs it, the
|
|
998
|
+
// same contract as deletion-before-dispatch.
|
|
999
|
+
}
|
|
720
1000
|
const writeStarted = performance.now();
|
|
721
1001
|
const gzip = gzipSync(json);
|
|
722
1002
|
fs.mkdirSync(path.dirname(pending.gzPath), { recursive: true });
|
|
723
1003
|
writeFileAtomic(pending.gzPath, gzip, { bestEffort: false });
|
|
724
1004
|
fs.rmSync(pending.legacyPath, { force: true });
|
|
1005
|
+
const metadataFinalized = finalizeProjectSnapshotMeta(pending, {
|
|
1006
|
+
fingerprint,
|
|
1007
|
+
generatedAt: pending.snapshot.generatedAt,
|
|
1008
|
+
gzBytes: gzip.byteLength,
|
|
1009
|
+
});
|
|
1010
|
+
if (!metadataFinalized)
|
|
1011
|
+
return;
|
|
725
1012
|
reconcileAuthoritativeAfterWrite(pending, rawBytes);
|
|
726
|
-
logSnapshotPersistSuccess(pending, {
|
|
1013
|
+
logSnapshotPersistSuccess(pending, fingerprint, {
|
|
727
1014
|
rawBytes,
|
|
728
1015
|
gzBytes: gzip.byteLength,
|
|
729
1016
|
serializeMs,
|
|
@@ -734,7 +1021,10 @@ function writeSnapshotBodyOnMainThread(pending, reason) {
|
|
|
734
1021
|
_lastSnapshotPersistErrorForTests = reason;
|
|
735
1022
|
}
|
|
736
1023
|
catch (err) {
|
|
737
|
-
recordSnapshotPersistFailure(pending
|
|
1024
|
+
recordSnapshotPersistFailure(pending, err instanceof Error ? err.message : String(err));
|
|
1025
|
+
}
|
|
1026
|
+
finally {
|
|
1027
|
+
completeSnapshotPersist(pending);
|
|
738
1028
|
}
|
|
739
1029
|
}
|
|
740
1030
|
/**
|
|
@@ -753,7 +1043,7 @@ function dispatchMainThreadWriteThroughSeam(pending, reason) {
|
|
|
753
1043
|
function handleSnapshotWorkerResult(result) {
|
|
754
1044
|
const pending = _snapshotWorkerRequests.get(result.id);
|
|
755
1045
|
if (!pending) {
|
|
756
|
-
fs.
|
|
1046
|
+
fs.rmSync(result.stagePath, { force: true });
|
|
757
1047
|
return;
|
|
758
1048
|
}
|
|
759
1049
|
_snapshotWorkerRequests.delete(result.id);
|
|
@@ -761,37 +1051,128 @@ function handleSnapshotWorkerResult(result) {
|
|
|
761
1051
|
result.rawBytes === undefined ||
|
|
762
1052
|
result.gzBytes === undefined ||
|
|
763
1053
|
result.serializeMs === undefined ||
|
|
764
|
-
result.writeMs === undefined
|
|
765
|
-
|
|
1054
|
+
result.writeMs === undefined ||
|
|
1055
|
+
result.semanticFingerprint === undefined) {
|
|
1056
|
+
fs.rmSync(result.stagePath, { force: true });
|
|
766
1057
|
dispatchMainThreadWriteThroughSeam(pending, result.error ?? "invalid worker result");
|
|
767
1058
|
return;
|
|
768
1059
|
}
|
|
769
1060
|
// Generation gate: a newer save already superseded this one — discard the
|
|
770
1061
|
// stale stage file rather than promote it over the fresher body.
|
|
1062
|
+
if (!result.skippedUnchanged)
|
|
1063
|
+
_snapshotWorkerBodyWritesForTests++;
|
|
771
1064
|
if (_snapshotGenerationGateEnabledForTests &&
|
|
772
|
-
|
|
1065
|
+
_snapshotGenerationStates.get(pending.key)?.generation !== result.generation) {
|
|
773
1066
|
// The stale stage is part of the promotion transaction: remove it before
|
|
774
1067
|
// returning so a superseded save cannot leave an orphan behind.
|
|
775
|
-
fs.
|
|
1068
|
+
fs.rmSync(result.stagePath, { force: true });
|
|
1069
|
+
completeSnapshotPersist(pending);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
if (result.skippedUnchanged) {
|
|
1073
|
+
// Re-read the tiny durable sidecar at the decision seam. A sibling process
|
|
1074
|
+
// may have advanced B after this request captured A. A stale replay of A
|
|
1075
|
+
// must preserve B's body metadata, not restore the captured A sidecar.
|
|
1076
|
+
const baselines = snapshotPersistBaselinesFor(pending.cwd, pending.key);
|
|
1077
|
+
if (baselines.integrity !== "ok") {
|
|
1078
|
+
// The worker skipped WITHOUT staging a body, trusting the durable
|
|
1079
|
+
// fingerprint captured at dispatch. The re-read says that evidence no
|
|
1080
|
+
// longer passes the #2008 integrity gate — refuse the skip and rewrite
|
|
1081
|
+
// synchronously so a torn/missing body cannot outlive this save. The
|
|
1082
|
+
// sync writer recomputes the same baselines, finds them untrusted, and
|
|
1083
|
+
// performs the full gzip+stage+promote (its finally completes this
|
|
1084
|
+
// pending, so do not complete it twice here).
|
|
1085
|
+
if (baselines.integrity !== undefined) {
|
|
1086
|
+
noteSnapshotSkipRefused({
|
|
1087
|
+
gzPath: pending.gzPath,
|
|
1088
|
+
verdict: baselines.integrity,
|
|
1089
|
+
seq: pending.snapshot.seq,
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
dispatchMainThreadWriteThroughSeam(pending, "snapshot body integrity regressed");
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const prior = baselines.durable ?? pending.durablePersist;
|
|
1096
|
+
if (authoritativeSnapshots.get(pending.key)?.snapshot === pending.snapshot) {
|
|
1097
|
+
deleteAuthoritativeSnapshot(pending.key);
|
|
1098
|
+
}
|
|
1099
|
+
logSnapshotPersistDecision({
|
|
1100
|
+
cwd: pending.cwd,
|
|
1101
|
+
seq: pending.snapshot.seq,
|
|
1102
|
+
fingerprint: result.semanticFingerprint,
|
|
1103
|
+
decision: "skipped_unchanged",
|
|
1104
|
+
avoidedRawBytes: result.rawBytes,
|
|
1105
|
+
avoidedGzipBytes: prior?.gzBytes,
|
|
1106
|
+
});
|
|
1107
|
+
completeSnapshotPersist(pending);
|
|
776
1108
|
return;
|
|
777
1109
|
}
|
|
778
1110
|
try {
|
|
779
1111
|
fs.renameSync(result.stagePath, pending.gzPath);
|
|
780
1112
|
fs.rmSync(pending.legacyPath, { force: true });
|
|
1113
|
+
const metadataFinalized = finalizeProjectSnapshotMeta(pending, {
|
|
1114
|
+
fingerprint: result.semanticFingerprint,
|
|
1115
|
+
generatedAt: pending.snapshot.generatedAt,
|
|
1116
|
+
gzBytes: result.gzBytes,
|
|
1117
|
+
});
|
|
1118
|
+
if (!metadataFinalized) {
|
|
1119
|
+
completeSnapshotPersist(pending);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
781
1122
|
reconcileAuthoritativeAfterWrite(pending, result.rawBytes);
|
|
782
|
-
logSnapshotPersistSuccess(pending, {
|
|
1123
|
+
logSnapshotPersistSuccess(pending, result.semanticFingerprint, {
|
|
783
1124
|
rawBytes: result.rawBytes,
|
|
784
1125
|
gzBytes: result.gzBytes,
|
|
785
1126
|
serializeMs: result.serializeMs,
|
|
786
1127
|
writeMs: result.writeMs,
|
|
787
1128
|
offloaded: true,
|
|
788
1129
|
});
|
|
1130
|
+
completeSnapshotPersist(pending);
|
|
789
1131
|
}
|
|
790
1132
|
catch (err) {
|
|
791
1133
|
fs.rm(result.stagePath, { force: true }, () => { });
|
|
792
1134
|
dispatchMainThreadWriteThroughSeam(pending, err instanceof Error ? err.message : String(err));
|
|
793
1135
|
}
|
|
794
1136
|
}
|
|
1137
|
+
function dispatchSnapshotPersist(pending) {
|
|
1138
|
+
// A queued request may have waited behind the publication that created its
|
|
1139
|
+
// best dedupe evidence. Refresh only the tiny sidecar/body stat here; never
|
|
1140
|
+
// retain the stale admission-time baseline through dispatch.
|
|
1141
|
+
const baselines = snapshotPersistBaselinesFor(pending.cwd, pending.key);
|
|
1142
|
+
pending.durablePersist = baselines.durable;
|
|
1143
|
+
pending.dedupeFingerprints = baselines.fingerprints;
|
|
1144
|
+
seedSnapshotPersistBaselineFromDurable(pending.key, baselines.durable);
|
|
1145
|
+
_activeSnapshotPersists.set(pending.key, pending);
|
|
1146
|
+
if (!snapshotWorkerEnabled()) {
|
|
1147
|
+
dispatchMainThreadWriteThroughSeam(pending, undefined);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const worker = getSnapshotPersistWorker();
|
|
1151
|
+
if (!worker) {
|
|
1152
|
+
dispatchMainThreadWriteThroughSeam(pending, "persist worker unavailable");
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const id = ++_snapshotWorkerRequestId;
|
|
1156
|
+
_snapshotWorkerRequests.set(id, pending);
|
|
1157
|
+
const request = {
|
|
1158
|
+
id,
|
|
1159
|
+
generation: pending.generation,
|
|
1160
|
+
stagePath: pending.stagePath,
|
|
1161
|
+
data: pending.snapshot,
|
|
1162
|
+
priorFingerprints: pending.dedupeFingerprints,
|
|
1163
|
+
testDelayMs: process.env.NODE_ENV === "test"
|
|
1164
|
+
? Number(process.env.PI_LENS_TEST_SNAPSHOT_PERSIST_WORKER_DELAY_MS) ||
|
|
1165
|
+
undefined
|
|
1166
|
+
: undefined,
|
|
1167
|
+
};
|
|
1168
|
+
try {
|
|
1169
|
+
worker.postMessage(request);
|
|
1170
|
+
}
|
|
1171
|
+
catch (err) {
|
|
1172
|
+
_snapshotWorkerRequests.delete(id);
|
|
1173
|
+
dispatchMainThreadWriteThroughSeam(pending, err instanceof Error ? err.message : String(err));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
795
1176
|
function handleSnapshotWorkerDeath(reason) {
|
|
796
1177
|
_snapshotPersistWorker = undefined;
|
|
797
1178
|
_snapshotWorkerDisabled = true;
|
|
@@ -909,12 +1290,27 @@ function ensureSnapshotPersistExitHook() {
|
|
|
909
1290
|
return;
|
|
910
1291
|
_snapshotExitHookInstalled = true;
|
|
911
1292
|
process.once("exit", () => {
|
|
912
|
-
|
|
1293
|
+
_snapshotExiting = true;
|
|
1294
|
+
const latestByKey = new Map();
|
|
1295
|
+
for (const pending of _snapshotWorkerRequests.values()) {
|
|
1296
|
+
latestByKey.set(pending.key, pending);
|
|
1297
|
+
}
|
|
1298
|
+
for (const pending of _queuedSnapshotPersists.values()) {
|
|
1299
|
+
const prior = latestByKey.get(pending.key);
|
|
1300
|
+
// Queued work is later admission order. Equal-seq requests deliberately
|
|
1301
|
+
// share a gate generation, so a tie must still select the queued payload.
|
|
1302
|
+
if (!prior || prior.generation <= pending.generation) {
|
|
1303
|
+
latestByKey.set(pending.key, pending);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
913
1306
|
_snapshotWorkerRequests.clear();
|
|
914
|
-
|
|
1307
|
+
_queuedSnapshotPersists.clear();
|
|
1308
|
+
_activeSnapshotPersists.clear();
|
|
1309
|
+
for (const pending of latestByKey.values()) {
|
|
915
1310
|
// Only the newest generation per key still matters; older ones are
|
|
916
1311
|
// superseded and their stage files are swept on next launch.
|
|
917
|
-
if (
|
|
1312
|
+
if (_snapshotGenerationStates.get(pending.key)?.generation !==
|
|
1313
|
+
pending.generation)
|
|
918
1314
|
continue;
|
|
919
1315
|
writeSnapshotBodyOnMainThread(pending, "exit_hook");
|
|
920
1316
|
}
|
|
@@ -928,8 +1324,18 @@ export function saveProjectSnapshot(cwd, snapshot) {
|
|
|
928
1324
|
const cacheDir = path.dirname(gzPath);
|
|
929
1325
|
const key = normalizeMapKey(cwd);
|
|
930
1326
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
931
|
-
|
|
932
|
-
|
|
1327
|
+
const baselines = snapshotPersistBaselinesFor(cwd, key);
|
|
1328
|
+
const priorPersist = baselines.durable;
|
|
1329
|
+
logSnapshotPersistDecision({
|
|
1330
|
+
cwd,
|
|
1331
|
+
seq: snapshot.seq,
|
|
1332
|
+
decision: "requested",
|
|
1333
|
+
});
|
|
1334
|
+
if (_failedSnapshotPersists.delete(key)) {
|
|
1335
|
+
logSnapshotPersistDecision({ cwd, seq: snapshot.seq, decision: "retry" });
|
|
1336
|
+
}
|
|
1337
|
+
// #958: for a new seq, meta is written FIRST and body SECOND. A crash or
|
|
1338
|
+
// failure between the two writes can now only produce
|
|
933
1339
|
// "meta already claims the new seq, body hasn't caught up yet" (the meta
|
|
934
1340
|
// races ahead). The meta-first gate (isProjectSnapshotMetaStale) reads
|
|
935
1341
|
// that as *fresh* and falls through to parsing the body, whose own
|
|
@@ -941,24 +1347,14 @@ export function saveProjectSnapshot(cwd, snapshot) {
|
|
|
941
1347
|
// away a genuinely fresh snapshot. That direction is not recoverable
|
|
942
1348
|
// until the next save, so it's the one this reorder eliminates.
|
|
943
1349
|
//
|
|
944
|
-
// The meta write
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
seq: snapshot.seq,
|
|
953
|
-
// #1019: mirror the derived sequence index (kept consistent with `seq`
|
|
954
|
-
// because both are stamped from the same runtime moment) so the
|
|
955
|
-
// interactive path can hydrate it from the tiny sidecar. Omitted when
|
|
956
|
-
// absent so a non-runtime side-write (word-index/reverse-deps) that
|
|
957
|
-
// carried no index doesn't stamp an empty one.
|
|
958
|
-
...(snapshot.sequenceIndex
|
|
959
|
-
? { sequenceIndex: snapshot.sequenceIndex }
|
|
960
|
-
: {}),
|
|
961
|
-
}), { bestEffort: false });
|
|
1350
|
+
// The new-seq meta write stays synchronous and throwing. Same-seq work does
|
|
1351
|
+
// not need to advance the freshness gate, so it leaves the current sidecar
|
|
1352
|
+
// untouched until the worker has a semantic verdict. This prevents a stale
|
|
1353
|
+
// local request from replacing a
|
|
1354
|
+
// newer sibling process's same-seq fingerprint before it can coalesce.
|
|
1355
|
+
if (priorPersist?.seq !== snapshot.seq) {
|
|
1356
|
+
writeProjectSnapshotMeta(metaPath, snapshot);
|
|
1357
|
+
}
|
|
962
1358
|
// Record the authoritative in-process write BEFORE handing the body off, so
|
|
963
1359
|
// a merge-read between now and the worker's promotion sees our own object
|
|
964
1360
|
// (the on-disk body still holds the previous generation until promotion). The
|
|
@@ -983,8 +1379,11 @@ export function saveProjectSnapshot(cwd, snapshot) {
|
|
|
983
1379
|
// A stale disk-parse-cache entry for this path must not out-vote the fresh
|
|
984
1380
|
// authoritative write once the latter is dropped (oversized bodies).
|
|
985
1381
|
snapshotParseCache.delete(gzPath);
|
|
986
|
-
const
|
|
987
|
-
|
|
1382
|
+
const generationState = _snapshotGenerationStates.get(key);
|
|
1383
|
+
const generation = generationState?.seq === snapshot.seq
|
|
1384
|
+
? generationState.generation
|
|
1385
|
+
: (generationState?.generation ?? 0) + 1;
|
|
1386
|
+
_snapshotGenerationStates.set(key, { generation, seq: snapshot.seq });
|
|
988
1387
|
const stagePath = `${gzPath}.stage-${process.pid}-${generation}`;
|
|
989
1388
|
const pending = {
|
|
990
1389
|
key,
|
|
@@ -994,36 +1393,29 @@ export function saveProjectSnapshot(cwd, snapshot) {
|
|
|
994
1393
|
stagePath,
|
|
995
1394
|
snapshot,
|
|
996
1395
|
generation,
|
|
1396
|
+
durablePersist: priorPersist,
|
|
1397
|
+
dedupeFingerprints: baselines.fingerprints,
|
|
997
1398
|
};
|
|
998
1399
|
sweepStaleSnapshotStageFiles(cacheDir);
|
|
999
1400
|
ensureSnapshotPersistExitHook();
|
|
1000
|
-
if (
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1401
|
+
if (_activeSnapshotPersists.has(key)) {
|
|
1402
|
+
_queuedSnapshotPersists.set(key, pending);
|
|
1403
|
+
logSnapshotPersistDecision({
|
|
1404
|
+
cwd,
|
|
1405
|
+
seq: snapshot.seq,
|
|
1406
|
+
decision: "coalesced",
|
|
1407
|
+
});
|
|
1007
1408
|
return;
|
|
1008
1409
|
}
|
|
1009
|
-
|
|
1010
|
-
_snapshotWorkerRequests.set(id, pending);
|
|
1011
|
-
const request = {
|
|
1012
|
-
id,
|
|
1013
|
-
generation,
|
|
1014
|
-
stagePath,
|
|
1015
|
-
data: snapshot,
|
|
1016
|
-
testDelayMs: process.env.NODE_ENV === "test"
|
|
1017
|
-
? Number(process.env.PI_LENS_TEST_SNAPSHOT_PERSIST_WORKER_DELAY_MS) ||
|
|
1018
|
-
undefined
|
|
1019
|
-
: undefined,
|
|
1020
|
-
};
|
|
1021
|
-
worker.postMessage(request);
|
|
1410
|
+
dispatchSnapshotPersist(pending);
|
|
1022
1411
|
}
|
|
1023
1412
|
// --- Test hooks for the worker persist path ---------------------------------
|
|
1024
1413
|
/** Test-only: wait until worker requests have either landed or degraded. */
|
|
1025
1414
|
export async function waitForProjectSnapshotPersistsForTests() {
|
|
1026
|
-
for (let attempts = 0; attempts < 200 &&
|
|
1415
|
+
for (let attempts = 0; attempts < 200 &&
|
|
1416
|
+
(_snapshotWorkerRequests.size > 0 ||
|
|
1417
|
+
_activeSnapshotPersists.size > 0 ||
|
|
1418
|
+
_queuedSnapshotPersists.size > 0); attempts++) {
|
|
1027
1419
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1028
1420
|
}
|
|
1029
1421
|
}
|
|
@@ -1032,8 +1424,6 @@ export function flushProjectSnapshotPersistsForTests() {
|
|
|
1032
1424
|
const requests = [..._snapshotWorkerRequests.values()];
|
|
1033
1425
|
_snapshotWorkerRequests.clear();
|
|
1034
1426
|
for (const pending of requests) {
|
|
1035
|
-
if (_snapshotGenerations.get(pending.key) !== pending.generation)
|
|
1036
|
-
continue;
|
|
1037
1427
|
dispatchMainThreadWriteThroughSeam(pending, undefined);
|
|
1038
1428
|
}
|
|
1039
1429
|
}
|
|
@@ -1050,9 +1440,25 @@ export function resetProjectSnapshotPersistWorkerForTests() {
|
|
|
1050
1440
|
_snapshotPromotionSeamForTests = undefined;
|
|
1051
1441
|
_snapshotPersistWorker = undefined;
|
|
1052
1442
|
_snapshotWorkerRequests.clear();
|
|
1053
|
-
|
|
1443
|
+
_snapshotGenerationStates.clear();
|
|
1444
|
+
_successfulSnapshotPersists.clear();
|
|
1445
|
+
_failedSnapshotPersists.clear();
|
|
1446
|
+
_activeSnapshotPersists.clear();
|
|
1447
|
+
_queuedSnapshotPersists.clear();
|
|
1448
|
+
_snapshotExiting = false;
|
|
1449
|
+
_snapshotWorkerBodyWritesForTests = 0;
|
|
1054
1450
|
_lastSnapshotPersistErrorForTests = undefined;
|
|
1055
1451
|
}
|
|
1452
|
+
/** Test-only: expose bounded queue state without retaining snapshot bodies. */
|
|
1453
|
+
export function getProjectSnapshotPersistStateForTests(cwd) {
|
|
1454
|
+
const key = normalizeMapKey(cwd);
|
|
1455
|
+
return {
|
|
1456
|
+
active: _activeSnapshotPersists.has(key),
|
|
1457
|
+
queued: _queuedSnapshotPersists.has(key),
|
|
1458
|
+
successfulFingerprint: _successfulSnapshotPersists.get(key)?.fingerprint,
|
|
1459
|
+
workerBodyWrites: _snapshotWorkerBodyWritesForTests,
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1056
1462
|
/** Test-only mutation switch for proving the supersession invariant. */
|
|
1057
1463
|
export function setProjectSnapshotGenerationGateForTests(enabled) {
|
|
1058
1464
|
_snapshotGenerationGateEnabledForTests = enabled;
|