@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
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 +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -3,8 +3,12 @@ import * as path from "node:path";
|
|
|
3
3
|
import { getProjectDataDir } from "../file-utils.js";
|
|
4
4
|
import { writeFileAtomic } from "../atomic-write.js";
|
|
5
5
|
import { readJsonCache } from "../json-cache-read.js";
|
|
6
|
+
import { createGenerationMap } from "../generation-guard.js";
|
|
6
7
|
import { normalizeMapKey } from "../path-utils.js";
|
|
7
8
|
import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
|
|
9
|
+
import { MTIME_DRIFT_TOLERANCE_MS } from "../blocker-freshness.js";
|
|
10
|
+
import { logLatency } from "../latency-logger.js";
|
|
11
|
+
import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-session.js";
|
|
8
12
|
import { createDiskBindingCache, } from "./diagnostic-binding.js";
|
|
9
13
|
/**
|
|
10
14
|
* #671: per-file cache of the last CONFIRMED `runWorkspaceDiagnostics` sweep
|
|
@@ -36,6 +40,117 @@ const CACHE_FILE = "lsp-workspace-diagnostics.json";
|
|
|
36
40
|
function cachePath(cwd) {
|
|
37
41
|
return path.join(getProjectDataDir(cwd), "cache", CACHE_FILE);
|
|
38
42
|
}
|
|
43
|
+
// #1669 (review round): every cwd that has ever backed a
|
|
44
|
+
// `createWorkspaceDiagnosticsCacheContext` call in this process — the ONLY
|
|
45
|
+
// reliable record of which on-disk cache(s) are actually live. A
|
|
46
|
+
// `workspace/diagnostic/refresh` reaches us at the per-LSP-client layer
|
|
47
|
+
// (`client.ts`), which knows its own `state.root` — a per-SERVER identity
|
|
48
|
+
// root that `resolveServerRoot` (`index.ts`) routinely nests BELOW the real
|
|
49
|
+
// sweep cwd for a monorepo/multi-root project's nested-boundary-marker
|
|
50
|
+
// subproject — but has no way to know which cwd(s) `runWorkspaceDiagnostics`/
|
|
51
|
+
// `tools/lsp-diagnostics.ts` actually swept under. Clearing at `state.root`
|
|
52
|
+
// would miss the real cache file entirely and write a stray empty one at a
|
|
53
|
+
// fabricated path. Recording every real cwd here, at the one funnel both
|
|
54
|
+
// callers already share, lets a refresh invalidate every cache that could
|
|
55
|
+
// legitimately be affected without threading cwd knowledge into client.ts.
|
|
56
|
+
// Bounded defensively: a session touching more than this many distinct sweep
|
|
57
|
+
// roots is already pathological, and dropping new entries past the cap is
|
|
58
|
+
// safe (a "not yet registered" cwd's cache was never able to receive a
|
|
59
|
+
// pre-refresh entry in the first place).
|
|
60
|
+
//
|
|
61
|
+
// Known residual (#1707): a monorepo subproject this process has NOT swept
|
|
62
|
+
// yet, under a cwd DIFFERENT from `state.root`, is reachable by neither this
|
|
63
|
+
// registry nor the `state.root` fallback the refresh handler also applies
|
|
64
|
+
// (`client.ts`) — a refresh arriving before that subproject's first sweep
|
|
65
|
+
// clears nothing for it. Filed rather than silently claimed as solved; the
|
|
66
|
+
// real fix needs `LSPService` to tell a client its actual sweep cwd(s), not
|
|
67
|
+
// just its per-server root.
|
|
68
|
+
const MAX_REGISTERED_CWDS = 64;
|
|
69
|
+
const _registeredCwds = new Set();
|
|
70
|
+
/** Drop every cache this process has ever swept under. Best-effort, matching
|
|
71
|
+
* `clearWorkspaceDiagnosticsCache`'s own fail-open posture. See the
|
|
72
|
+
* `_registeredCwds` doc comment above for the #1707 residual this does not
|
|
73
|
+
* cover. */
|
|
74
|
+
export function clearAllWorkspaceDiagnosticsCaches() {
|
|
75
|
+
for (const cwd of _registeredCwds) {
|
|
76
|
+
clearWorkspaceDiagnosticsCache(cwd);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Clear the client root and every ancestor up to the session workspace root.
|
|
81
|
+
* A language server may identify a nested monorepo member while the sweep
|
|
82
|
+
* stores that member's files in the enclosing workspace cache. The refresh
|
|
83
|
+
* request knows the former, so walk only the bounded ancestor chain rather
|
|
84
|
+
* than writing to unrelated filesystem roots.
|
|
85
|
+
*/
|
|
86
|
+
export function clearWorkspaceDiagnosticsCacheAtAndAbove(cwd, ceiling = process.cwd()) {
|
|
87
|
+
const root = path.resolve(cwd);
|
|
88
|
+
const workspaceRoot = path.resolve(ceiling);
|
|
89
|
+
clearWorkspaceDiagnosticsCache(root);
|
|
90
|
+
const relative = path.relative(workspaceRoot, root);
|
|
91
|
+
if (relative.startsWith(".." + path.sep) ||
|
|
92
|
+
relative === ".." ||
|
|
93
|
+
path.isAbsolute(relative)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (normalizeMapKey(root) === normalizeMapKey(workspaceRoot))
|
|
97
|
+
return;
|
|
98
|
+
let ancestor = path.dirname(root);
|
|
99
|
+
while (true) {
|
|
100
|
+
clearWorkspaceDiagnosticsCache(ancestor);
|
|
101
|
+
if (normalizeMapKey(ancestor) === normalizeMapKey(workspaceRoot))
|
|
102
|
+
return;
|
|
103
|
+
const parent = path.dirname(ancestor);
|
|
104
|
+
if (parent === ancestor)
|
|
105
|
+
return;
|
|
106
|
+
ancestor = parent;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// #1669 (review round): per-cwd epoch, mirroring
|
|
110
|
+
// `clients/review-graph/builder.ts`'s workspace-cache epoch shape — AGENTS.md's
|
|
111
|
+
// canonical pattern for "a durable commit followed by an out-of-guard mirror
|
|
112
|
+
// refresh" (shape 12). A sweep's `createWorkspaceDiagnosticsCacheContext` loads
|
|
113
|
+
// the file ONCE and `persist()` blind-overwrites the whole map at the end; a
|
|
114
|
+
// refresh landing mid-sweep would otherwise be UNDONE the moment that sweep's
|
|
115
|
+
// own (now-stale) in-memory copy is written back. Capture the epoch at
|
|
116
|
+
// context-creation (read) time; refuse to publish if it advanced before
|
|
117
|
+
// `persist()` (write) time — the clear wins, and the next sweep starts clean.
|
|
118
|
+
// Per N4, `lookup()` also checks it on every call, so an in-flight sweep
|
|
119
|
+
// stops serving pre-refresh entries the moment its OWN epoch goes stale, not
|
|
120
|
+
// only at `persist()` time.
|
|
121
|
+
//
|
|
122
|
+
// #1669 review R2: this map is IN-MEMORY ONLY, deliberately — a round-3
|
|
123
|
+
// attempt to also persist it inside the cache file (a `generation` field)
|
|
124
|
+
// was reverted here: nothing actually READ that field to invalidate entries
|
|
125
|
+
// on load, so it was inert machinery (verified by the round-4 review:
|
|
126
|
+
// replacing the disk read with a constant left every test green). A cwd
|
|
127
|
+
// this process has never opened a context for — most concretely a monorepo
|
|
128
|
+
// subproject a sweep hasn't reached yet — still has no epoch here at all;
|
|
129
|
+
// see the `state.root` fallback's doc comment on `clearWorkspaceDiagnosticsCache`
|
|
130
|
+
// below and #1707 for that residual gap, tracked rather than silently
|
|
131
|
+
// claimed as solved.
|
|
132
|
+
//
|
|
133
|
+
// #1754: the per-cwd epochs are the shared `GenerationMap` primitive rather
|
|
134
|
+
// than a hand-rolled Map, so the capture-before-load/check-before-write shape
|
|
135
|
+
// here is the same code the dispatch-availability guards run, and a dropped
|
|
136
|
+
// write is visible in the degradation ledger instead of silent.
|
|
137
|
+
//
|
|
138
|
+
// Two properties this file relies on, both documented on the primitive:
|
|
139
|
+
//
|
|
140
|
+
// - The map issues stamps from ONE monotonic ticket counter, so a cwd dropped
|
|
141
|
+
// at the retained-key bound reads 0 — a stamp no live handle holds — and its
|
|
142
|
+
// outstanding contexts report stale. Fail-closed, which for this cache means
|
|
143
|
+
// "recompute", the same outcome a real refresh produces. The pre-#1754 map
|
|
144
|
+
// was unbounded; the bound is new and costs at most one extra sweep for a
|
|
145
|
+
// cwd that fell off the end.
|
|
146
|
+
// - `normalizeMapKey` runs `realpathSync.native` on Windows. A handle re-runs
|
|
147
|
+
// it only after the map has been invalidated, so an uninterrupted sweep pays
|
|
148
|
+
// that syscall once at context creation rather than once per `lookup()`, and
|
|
149
|
+
// an interrupted one pays it per check to stay correct if the cwd's realpath
|
|
150
|
+
// answer moved.
|
|
151
|
+
const _cacheEpochs = createGenerationMap("workspace-diagnostics-cache", {
|
|
152
|
+
normalizeKey: normalizeMapKey,
|
|
153
|
+
});
|
|
39
154
|
/** Fail-safe on any read/parse/shape problem: return `undefined` so the
|
|
40
155
|
* caller treats a stale/missing/corrupt cache as "nothing cached" — every
|
|
41
156
|
* file then falls through to a fresh touch, same fail-open posture as
|
|
@@ -61,6 +176,104 @@ export function saveWorkspaceDiagnosticsCache(cwd, cache) {
|
|
|
61
176
|
bestEffort: false,
|
|
62
177
|
});
|
|
63
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Drop every entry from the persisted cache so the next sweep from either
|
|
181
|
+
* call site (`runWorkspaceDiagnostics` or `tools/lsp-diagnostics.ts`) pays for
|
|
182
|
+
* a fresh pull instead of replaying entries the server just told us to
|
|
183
|
+
* distrust. Used by the `workspace/diagnostic/refresh` handler (#1669) — a
|
|
184
|
+
* server-initiated signal (typically a project-wide config change) that
|
|
185
|
+
* everything it has already reported may be stale, so this on-disk cache must
|
|
186
|
+
* not survive it either.
|
|
187
|
+
*
|
|
188
|
+
* Best-effort, matching `persist()`'s fail-open policy: a write failure just
|
|
189
|
+
* means the next sweep may reuse stale entries once more, never worth
|
|
190
|
+
* crashing the caller (a live LSP request handler) over.
|
|
191
|
+
*/
|
|
192
|
+
export function clearWorkspaceDiagnosticsCache(cwd) {
|
|
193
|
+
const root = path.resolve(cwd);
|
|
194
|
+
// Bump FIRST: any sweep context already loaded (whose own `persist()`
|
|
195
|
+
// hasn't run yet) must observe an epoch mismatch regardless of exactly
|
|
196
|
+
// when its write lands relative to this clear.
|
|
197
|
+
_cacheEpochs.bump(root);
|
|
198
|
+
try {
|
|
199
|
+
saveWorkspaceDiagnosticsCache(root, {
|
|
200
|
+
version: WORKSPACE_DIAGNOSTICS_CACHE_VERSION,
|
|
201
|
+
entries: {},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// best-effort — see doc comment above.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// #1782: serve-time freshness bound on entries that ASSERT findings
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
/**
|
|
212
|
+
* Ceiling on how long a finding-bearing entry may keep serving INSIDE one
|
|
213
|
+
* session. The session bound alone leaves a multi-day session replaying hour-one
|
|
214
|
+
* findings forever; four hours is short enough that a stale blocker cannot
|
|
215
|
+
* outlive the work that caused it, and long enough that a normal working
|
|
216
|
+
* session re-touches a finding-bearing file only a handful of times.
|
|
217
|
+
*/
|
|
218
|
+
export const WORKSPACE_DIAGNOSTICS_FINDING_MAX_AGE_MS = 4 * 60 * 60_000;
|
|
219
|
+
/**
|
|
220
|
+
* #1782: decide whether an entry has aged out of its right to be SERVED.
|
|
221
|
+
*
|
|
222
|
+
* The bound applies only to entries that assert findings. That asymmetry is
|
|
223
|
+
* deliberate, not an oversight:
|
|
224
|
+
*
|
|
225
|
+
* - A finding-bearing entry makes a POSITIVE claim ("this file has errors
|
|
226
|
+
* right now"). Replayed after the config that produced it changed, it renders
|
|
227
|
+
* as a current blocking gate failure that no gate ever ran — the #1782
|
|
228
|
+
* defect, where five `typescript:2339` errors born under inferred settings
|
|
229
|
+
* (#1640) were still rendered as fresh `blocking` rows 28 hours and one
|
|
230
|
+
* session later, because no newer pull ever re-answered those files and
|
|
231
|
+
* nothing expired them.
|
|
232
|
+
* - A clean entry makes only a NEGATIVE claim: "the file and its dependencies
|
|
233
|
+
* are unchanged". Expiring clean entries too would force a full re-touch of
|
|
234
|
+
* every swept file on the first sweep of every session, which is the exact
|
|
235
|
+
* cost #671 built this cache to avoid, while the harm it would prevent is a
|
|
236
|
+
* missing finding rather than a fabricated blocker.
|
|
237
|
+
*
|
|
238
|
+
* So the re-touch volume this policy adds is bounded by the number of files
|
|
239
|
+
* that previously REPORTED something, not by the size of the repo.
|
|
240
|
+
*
|
|
241
|
+
* #1786 review F4, closed by #1793: `isEntryFresh` used to fail OPEN to
|
|
242
|
+
* mtime-only whenever `getImports` returned `undefined` — no
|
|
243
|
+
* reverse-dependency index this session, which is the normal state of a cold
|
|
244
|
+
* session before the cascade has built one — for EVERY clean entry,
|
|
245
|
+
* regardless of what it had asserted when it was recorded. #1793 closes that
|
|
246
|
+
* for any entry recorded WHILE a dependency index was available
|
|
247
|
+
* (`depIndexAtScan`): such an entry now refuses to serve on a later,
|
|
248
|
+
* index-less session instead of falling back to mtime-only, and the refused
|
|
249
|
+
* entry is evicted rather than left to be refused again forever (see the
|
|
250
|
+
* `lookup` doc comment on the deletion below).
|
|
251
|
+
*
|
|
252
|
+
* ACCEPTED, not residual: an entry recorded on an EQUALLY cold session
|
|
253
|
+
* (`depIndexAtScan` false/absent, including every entry on disk from before
|
|
254
|
+
* this fix) keeps the mtime-only fallback. It never asserted more than
|
|
255
|
+
* mtime-only could verify in the first place, so there is nothing stronger
|
|
256
|
+
* to hold it to — this is exactly the population #1793's acceptance
|
|
257
|
+
* criteria left alone, not a gap this policy silently reopens. #1814 closed
|
|
258
|
+
* a narrower, adjacent gap in the per-file dependency check itself: a
|
|
259
|
+
* session WITH a warm index could still fail-open per-file for a file
|
|
260
|
+
* outside that index's own coverage (see `getImports`'s doc comment below).
|
|
261
|
+
*
|
|
262
|
+
* Pure and exported so the policy is unit-testable without a filesystem.
|
|
263
|
+
*/
|
|
264
|
+
export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStartedAt) {
|
|
265
|
+
const findings = Array.isArray(entry.diagnostics)
|
|
266
|
+
? entry.diagnostics.length
|
|
267
|
+
: 0;
|
|
268
|
+
if (findings === 0)
|
|
269
|
+
return undefined;
|
|
270
|
+
if (entry.scannedAt < sessionStartedAt)
|
|
271
|
+
return "pre-session";
|
|
272
|
+
if (now - entry.scannedAt > WORKSPACE_DIAGNOSTICS_FINDING_MAX_AGE_MS) {
|
|
273
|
+
return "max-age";
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
64
277
|
/**
|
|
65
278
|
* True when `filePath`'s cached entry is still trustworthy enough to reuse
|
|
66
279
|
* instead of paying for a fresh `touchFile`.
|
|
@@ -76,15 +289,41 @@ export function saveWorkspaceDiagnosticsCache(cwd, cache) {
|
|
|
76
289
|
* on file A can change purely because a dependency B's exported shape
|
|
77
290
|
* changed, with zero edits to A's own bytes/mtime.
|
|
78
291
|
*
|
|
79
|
-
* `getImports` returning `undefined` means "
|
|
80
|
-
*
|
|
81
|
-
* `clients/reverse-deps.ts`'s
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
292
|
+
* `getImports` returning `undefined` means "this file's dependency claim
|
|
293
|
+
* can't be verified this session" — either no dependency graph is available
|
|
294
|
+
* at all (no cascade/session-start has built `clients/reverse-deps.ts`'s
|
|
295
|
+
* persisted index yet), OR an index IS available but was never told about
|
|
296
|
+
* THIS file (#1814: the index exists and covers other files, just not this
|
|
297
|
+
* one — `getImports` used to mask that second case with `?? []`, coercing it
|
|
298
|
+
* to an empty array and letting the loop below iterate zero times and report
|
|
299
|
+
* fresh, indistinguishable from a file the index actually confirmed has zero
|
|
300
|
+
* imports). Both are real, expected states — we fail OPEN per-file to
|
|
301
|
+
* mtime-only invalidation in that case, UNLESS the entry itself was recorded
|
|
302
|
+
* while a dependency index WAS available for THIS file (`entry.depIndexAtScan`
|
|
303
|
+
* — see its doc comment for the per-file vs whole-session distinction, #1793
|
|
304
|
+
* review F2). #1793/#1814: such an entry asserted a stronger claim than
|
|
305
|
+
* mtime-only can back up, so serving it through the weaker check just
|
|
306
|
+
* because THIS session can't verify it (whole-session cold, or this file
|
|
307
|
+
* just outside coverage) would resurrect the exact blind spot #1786's
|
|
308
|
+
* review flagged (F4) — a project-wide dependency change invisible for as
|
|
309
|
+
* long as the file's own bytes stay untouched. `lookup` (the sole caller)
|
|
310
|
+
* DELETES an entry this function refuses for that reason, rather than
|
|
311
|
+
* leaving it to be refused again on every later cold sweep (#1793 review
|
|
312
|
+
* F3) — see its own doc comment for why eviction, not a stamp downgrade,
|
|
313
|
+
* bounds the repeat cost. An entry recorded on an equally cold session
|
|
314
|
+
* (`depIndexAtScan` false/absent) never made that stronger claim, so it
|
|
315
|
+
* keeps the mtime-only fallback — matching the same blind spot the
|
|
316
|
+
* cheap-tier `project-diagnostics.json` cache already accepts today; that is
|
|
317
|
+
* accepted design for #1793's acceptance criteria, not a gap this function
|
|
318
|
+
* reopens. Once a reverse-deps index covers this file, this function
|
|
319
|
+
* upgrades to using it automatically — no separate cache format/version
|
|
320
|
+
* needed.
|
|
321
|
+
*
|
|
322
|
+
* #1708 sweep: the dependency check below compares a captured `scannedAt`
|
|
323
|
+
* against each dependency's mtime, the same shape `findingPathFreshness`
|
|
324
|
+
* under-tolerated. `MTIME_DRIFT_TOLERANCE_MS` (`blocker-freshness.ts`) keeps
|
|
325
|
+
* it from over-invalidating on the same host mtime-vs-`Date.now()` skew
|
|
326
|
+
* (#1491/#1498) rather than just a redundant re-touch.
|
|
88
327
|
*/
|
|
89
328
|
export function isEntryFresh(filePath, entry, getImports) {
|
|
90
329
|
let ownMtime;
|
|
@@ -97,12 +336,18 @@ export function isEntryFresh(filePath, entry, getImports) {
|
|
|
97
336
|
if (ownMtime !== entry.mtimeMs)
|
|
98
337
|
return false;
|
|
99
338
|
const imports = getImports(filePath);
|
|
100
|
-
if (imports === undefined)
|
|
101
|
-
|
|
339
|
+
if (imports === undefined) {
|
|
340
|
+
// #1793: an entry recorded WITH dependency knowledge must not fall
|
|
341
|
+
// back to the weaker mtime-only check just because this session is
|
|
342
|
+
// cold — see the doc comment above. An entry that never had that
|
|
343
|
+
// knowledge (false/absent) keeps the existing fail-open behavior.
|
|
344
|
+
return entry.depIndexAtScan !== true;
|
|
345
|
+
}
|
|
102
346
|
for (const dep of imports) {
|
|
103
347
|
try {
|
|
104
|
-
if (fs.statSync(dep).mtimeMs > entry.scannedAt)
|
|
348
|
+
if (fs.statSync(dep).mtimeMs > entry.scannedAt + MTIME_DRIFT_TOLERANCE_MS) {
|
|
105
349
|
return false;
|
|
350
|
+
}
|
|
106
351
|
}
|
|
107
352
|
catch {
|
|
108
353
|
return false; // dependency deleted/unreadable: fail closed
|
|
@@ -132,6 +377,11 @@ export function buildScopeKey(clientScope, excludeServerIds) {
|
|
|
132
377
|
}
|
|
133
378
|
export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
134
379
|
const root = path.resolve(cwd);
|
|
380
|
+
if (_registeredCwds.size < MAX_REGISTERED_CWDS || _registeredCwds.has(root)) {
|
|
381
|
+
_registeredCwds.add(root);
|
|
382
|
+
}
|
|
383
|
+
// #1669: captured at load time — see the epoch doc comment above.
|
|
384
|
+
const epoch = _cacheEpochs.capture(root);
|
|
135
385
|
const existing = loadWorkspaceDiagnosticsCache(root);
|
|
136
386
|
const entries = {
|
|
137
387
|
...(existing?.entries ?? {}),
|
|
@@ -139,19 +389,140 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
139
389
|
const reverseDepsIndex = loadReverseDependencyIndexFromSnapshot({
|
|
140
390
|
cwd: root,
|
|
141
391
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
392
|
+
// #1814: `getImports` returns `undefined` for TWO distinct situations —
|
|
393
|
+
// no reverse-deps index loaded this session at all, OR an index IS loaded
|
|
394
|
+
// but doesn't cover this specific file (never seen by the
|
|
395
|
+
// cascade/session-start scan that built it). #1793/#1800 only closed the
|
|
396
|
+
// first: `?? []` used to coerce the second into an empty array, so
|
|
397
|
+
// `isEntryFresh`'s dependency loop iterated zero times and returned
|
|
398
|
+
// fresh — a file the index never saw was indistinguishable from a file
|
|
399
|
+
// the index confirmed has zero imports (the #1800 review's probe F). Not
|
|
400
|
+
// coercing here lets `isEntryFresh`'s existing `imports === undefined`
|
|
401
|
+
// branch (and its own `entry.depIndexAtScan` check) treat both cases
|
|
402
|
+
// alike, with no separate branch needed.
|
|
403
|
+
const getImports = (filePath) => reverseDepsIndex?.imports[cacheKeyFor(filePath)];
|
|
404
|
+
// #1793 review F2, extended by #1814: per-FILE knowledge is exactly
|
|
405
|
+
// "`getImports` returned something", so it is DERIVED from `getImports`
|
|
406
|
+
// rather than re-checking `reverseDepsIndex.imports[key] !== undefined`
|
|
407
|
+
// in parallel — a hand-duplicated copy of the same coverage check is
|
|
408
|
+
// exactly the kind of drift #1814 fixed (`?? []` masked one of the two
|
|
409
|
+
// call sites but not the other, pre-fix). `record` stamps THIS.
|
|
410
|
+
const hasDepKnowledge = (filePath) => getImports(filePath) !== undefined;
|
|
147
411
|
// #1095: per-sweep disk-fingerprint memo (per file+mtime) for binding verify.
|
|
148
412
|
const diskBindingCache = createDiskBindingCache();
|
|
149
413
|
let dirty = false;
|
|
414
|
+
// #1782 (AC4): one BOUNDED record per context, not one per expired entry —
|
|
415
|
+
// a sweep that ages out 400 ghosts emits a single line carrying the count,
|
|
416
|
+
// the oldest age, and the per-reason split. Flushed from `persist()`, the
|
|
417
|
+
// end-of-sweep hook both call sites already run.
|
|
418
|
+
//
|
|
419
|
+
// #1786 review F6: the counters DRAIN on flush rather than hiding behind a
|
|
420
|
+
// once-only latch. A latch is correct only while every call site persists
|
|
421
|
+
// exactly once; draining stays correct when one does not, and it still emits
|
|
422
|
+
// nothing on a repeat `persist()` with no new expiries, because the count is
|
|
423
|
+
// back to zero. Bounded either way: one record per batch of expiries, never
|
|
424
|
+
// one per entry.
|
|
425
|
+
let expiredCount = 0;
|
|
426
|
+
let oldestExpiredAgeMs = 0;
|
|
427
|
+
const expiredByReason = { "pre-session": 0, "max-age": 0 };
|
|
428
|
+
// #1793: a bounded counter for the NEW refusal path — an entry recorded
|
|
429
|
+
// with dependency knowledge that this (cold) session can't revalidate.
|
|
430
|
+
// Folded into the same expiry-telemetry record/flush shape rather than a
|
|
431
|
+
// second event kind, so a sweep's whole "what did I refuse to serve"
|
|
432
|
+
// picture stays in one line.
|
|
433
|
+
let depIndexColdRefusalCount = 0;
|
|
434
|
+
const flushExpiryTelemetry = () => {
|
|
435
|
+
if (expiredCount === 0 && depIndexColdRefusalCount === 0)
|
|
436
|
+
return;
|
|
437
|
+
logLatency({
|
|
438
|
+
type: "phase",
|
|
439
|
+
phase: "lsp_workspace_diagnostics_cache_expiry",
|
|
440
|
+
filePath: root,
|
|
441
|
+
durationMs: 0,
|
|
442
|
+
metadata: {
|
|
443
|
+
expiredEntries: expiredCount,
|
|
444
|
+
oldestExpiredAgeMs,
|
|
445
|
+
preSession: expiredByReason["pre-session"],
|
|
446
|
+
maxAge: expiredByReason["max-age"],
|
|
447
|
+
depIndexColdRefusals: depIndexColdRefusalCount,
|
|
448
|
+
sessionStartedAt: workspaceDiagnosticsCacheSessionStart(),
|
|
449
|
+
maxAgeMs: WORKSPACE_DIAGNOSTICS_FINDING_MAX_AGE_MS,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
expiredCount = 0;
|
|
453
|
+
oldestExpiredAgeMs = 0;
|
|
454
|
+
expiredByReason["pre-session"] = 0;
|
|
455
|
+
expiredByReason["max-age"] = 0;
|
|
456
|
+
depIndexColdRefusalCount = 0;
|
|
457
|
+
};
|
|
150
458
|
return {
|
|
151
459
|
lookup(filePath, scopeKey) {
|
|
152
|
-
|
|
460
|
+
// #1669 review N4: the epoch guarded `persist()` but not `lookup()` —
|
|
461
|
+
// an in-flight sweep kept REPLAYING disowned entries into its OWN
|
|
462
|
+
// served results for the rest of its run even though the on-disk
|
|
463
|
+
// cache was already correctly cleared (disk stayed clean, but the
|
|
464
|
+
// user still saw stale results for the remainder of THIS sweep).
|
|
465
|
+
// Check on every call, not just at load time: a refresh landing
|
|
466
|
+
// mid-sweep must stop lookups from serving pre-refresh entries
|
|
467
|
+
// immediately, not only prevent them from being written back.
|
|
468
|
+
// `cacheEpoch` is in-memory only (see its doc comment) — this runs
|
|
469
|
+
// per file, and a same-process clear always updates memory
|
|
470
|
+
// synchronously, so this needs no disk read to be authoritative.
|
|
471
|
+
if (!epoch.isCurrent())
|
|
472
|
+
return undefined;
|
|
473
|
+
const key = cacheKeyFor(filePath);
|
|
474
|
+
const entry = entries[key];
|
|
153
475
|
if (!entry || entry.scopeKey !== scopeKey)
|
|
154
476
|
return undefined;
|
|
477
|
+
// #1782: age gate BEFORE the mtime/dependency gate — it is two number
|
|
478
|
+
// comparisons and no syscalls, so an expired entry costs strictly less
|
|
479
|
+
// than a served one did. An entry that asserts findings and predates
|
|
480
|
+
// this session (or has aged past the in-session ceiling) is DROPPED,
|
|
481
|
+
// not served: the caller falls through to a fresh touch, and that
|
|
482
|
+
// re-touch is what re-subjects the file to every live-publish gate,
|
|
483
|
+
// including the #1640 inferred-project demote (which needs a live
|
|
484
|
+
// tsserver for the file and therefore cannot fire on a pure cache hit).
|
|
485
|
+
// Deleting rather than merely refusing keeps the on-disk file from
|
|
486
|
+
// carrying ghosts forever: the #1782 cache was rewritten hours after
|
|
487
|
+
// the entries went stale and still contained them.
|
|
488
|
+
const now = Date.now();
|
|
489
|
+
const expiry = classifyWorkspaceDiagnosticsCacheExpiry(entry, now, workspaceDiagnosticsCacheSessionStart());
|
|
490
|
+
if (expiry) {
|
|
491
|
+
delete entries[key];
|
|
492
|
+
dirty = true;
|
|
493
|
+
expiredCount += 1;
|
|
494
|
+
expiredByReason[expiry] += 1;
|
|
495
|
+
oldestExpiredAgeMs = Math.max(oldestExpiredAgeMs, Math.max(0, now - entry.scannedAt));
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
// #1793, widened by #1814: an entry that asserted dependency
|
|
499
|
+
// knowledge this session can't revalidate is DELETED, not merely
|
|
500
|
+
// refused — mirroring the #1782 age gate just above. Refusing
|
|
501
|
+
// without deleting (the #1793 review's F3 finding) would leave
|
|
502
|
+
// the same stale entry to be refused again on every LATER cold
|
|
503
|
+
// sweep too, and both `record()` call sites (`index.ts`,
|
|
504
|
+
// `tools/lsp-diagnostics.ts`) skip recording an unconfirmed
|
|
505
|
+
// (`timedOut`/`error`) touch — so a file whose forced re-touch
|
|
506
|
+
// happens to time out would pay that timeout budget on EVERY
|
|
507
|
+
// sweep forever, whereas pre-fix it served instantly. Deleting
|
|
508
|
+
// bounds the repeat cost to exactly what a file that was NEVER
|
|
509
|
+
// cached already costs: one re-touch attempt per sweep until a
|
|
510
|
+
// CONFIRMED result lands — a real, accepted, pre-existing cost
|
|
511
|
+
// class, not a new one. The first confirmed re-touch (this
|
|
512
|
+
// session or a later one) re-records the file stamped against
|
|
513
|
+
// THAT session's own dependency knowledge, healing it either
|
|
514
|
+
// way. `!hasDepKnowledge(filePath)` (#1814) covers BOTH "no
|
|
515
|
+
// index this session at all" (#1793's original population) AND
|
|
516
|
+
// "an index exists but doesn't cover this file" (#1814's
|
|
517
|
+
// addition, the probe F gap) — one check for both, since both
|
|
518
|
+
// mean the same thing to this entry: nothing here can revalidate
|
|
519
|
+
// the stronger claim it made when it was recorded.
|
|
520
|
+
if (entry.depIndexAtScan === true && !hasDepKnowledge(filePath)) {
|
|
521
|
+
delete entries[key];
|
|
522
|
+
dirty = true;
|
|
523
|
+
depIndexColdRefusalCount += 1;
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
155
526
|
if (!isEntryFresh(filePath, entry, getImports))
|
|
156
527
|
return undefined;
|
|
157
528
|
return {
|
|
@@ -173,24 +544,47 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
|
|
|
173
544
|
mtimeMs,
|
|
174
545
|
scannedAt: Date.now(),
|
|
175
546
|
scopeKey,
|
|
547
|
+
// #1793: stamp whether THIS FILE actually had dependency
|
|
548
|
+
// knowledge this sweep (not just whether SOME index was
|
|
549
|
+
// loaded — see `hasDepKnowledge`'s doc comment), so a later
|
|
550
|
+
// cold session can refuse to revalidate this entry via the
|
|
551
|
+
// weaker mtime-only path only when that refusal is warranted.
|
|
552
|
+
depIndexAtScan: hasDepKnowledge(filePath),
|
|
176
553
|
...(contentHash !== undefined && { contentHash }),
|
|
177
554
|
};
|
|
178
555
|
dirty = true;
|
|
179
556
|
},
|
|
180
557
|
persist() {
|
|
558
|
+
// #1782: emit the expiry record even when nothing new was recorded —
|
|
559
|
+
// a sweep whose only outcome was aging out ghosts is exactly the run a
|
|
560
|
+
// dogfood needs to see in `latency.log`.
|
|
561
|
+
flushExpiryTelemetry();
|
|
181
562
|
if (!dirty)
|
|
182
563
|
return;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
564
|
+
// #1669: a refresh invalidated this cwd's cache after we loaded it —
|
|
565
|
+
// publishing this sweep's (now-stale-relative-to-the-refresh) in-memory
|
|
566
|
+
// copy would resurrect exactly what the refresh cleared. Drop the
|
|
567
|
+
// write; the next sweep loads clean and recomputes.
|
|
568
|
+
const published = epoch.guardedWrite(root, () => {
|
|
569
|
+
try {
|
|
570
|
+
saveWorkspaceDiagnosticsCache(root, {
|
|
571
|
+
version: WORKSPACE_DIAGNOSTICS_CACHE_VERSION,
|
|
572
|
+
entries,
|
|
573
|
+
});
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
// Best-effort: a failed cache write just means the next sweep pays
|
|
578
|
+
// the full cost again — never worth failing the sweep itself over.
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
// `undefined` means the guard dropped the write as stale: this
|
|
583
|
+
// in-memory copy is worthless, so stop advertising it as pending.
|
|
584
|
+
// `false` means the write itself failed, so `dirty` stays set and the
|
|
585
|
+
// next sweep retries. Same three outcomes as the pre-#1754 guard.
|
|
586
|
+
if (published !== false)
|
|
188
587
|
dirty = false;
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// Best-effort: a failed cache write just means the next sweep pays
|
|
192
|
-
// the full cost again — never worth failing the sweep itself over.
|
|
193
|
-
}
|
|
194
588
|
},
|
|
195
589
|
};
|
|
196
590
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1782: the session clock the workspace-diagnostics cache measures entry age
|
|
3
|
+
* against, in a module of its own.
|
|
4
|
+
*
|
|
5
|
+
* It lives here rather than in `workspace-diagnostics-cache.ts` for one
|
|
6
|
+
* measured reason (#1786 review F5): `handleSessionStart` must re-arm it, and
|
|
7
|
+
* importing the cache module to do so drags `reverse-deps.ts`,
|
|
8
|
+
* `blocker-freshness.ts`, and the generation-guard machinery into the EAGER
|
|
9
|
+
* session-start module graph — 17 ms, about 9% of cold session start. This file
|
|
10
|
+
* imports nothing, so the re-arm stays a plain synchronous call with no await
|
|
11
|
+
* inserted into the middle of the reset sequence and no import cost.
|
|
12
|
+
*
|
|
13
|
+
* The default is module-import time so the very first sweep in a cold extension
|
|
14
|
+
* host already treats every entry written by a PREVIOUS process as pre-session
|
|
15
|
+
* (fail-closed). It must still be re-armed per session: a pi extension host
|
|
16
|
+
* survives `session_start`, so a process-lifetime value would pin the first
|
|
17
|
+
* session's clock for the life of the process (AGENTS.md's
|
|
18
|
+
* session-signal-vs-latch screen). Registered in
|
|
19
|
+
* `tests/support/session-state-registry.ts`, whose conformance suite proves the
|
|
20
|
+
* reset is reachable from `handleSessionStart`.
|
|
21
|
+
*/
|
|
22
|
+
let _sessionStartedAt = Date.now();
|
|
23
|
+
/** Re-arm the session clock. Called by `handleSessionStart`. */
|
|
24
|
+
export function resetWorkspaceDiagnosticsCacheSession(startedAt = Date.now()) {
|
|
25
|
+
_sessionStartedAt = startedAt;
|
|
26
|
+
}
|
|
27
|
+
/** When the current session started, for entry-age comparisons. */
|
|
28
|
+
export function workspaceDiagnosticsCacheSessionStart() {
|
|
29
|
+
return _sessionStartedAt;
|
|
30
|
+
}
|