@herbertgao/pi-extensions 2026.8.3 → 2026.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -4
- package/examples/pi-footer.json +275 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/docs/agent-guide.md +2 -2
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +17 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -3,30 +3,36 @@ import * as path from "node:path";
|
|
|
3
3
|
import { deadCodeIssueCount } from "./dead-code-client.js";
|
|
4
4
|
import { logDeadCodeScan } from "./dead-code-logger.js";
|
|
5
5
|
import { getDiagnosticTracker } from "./diagnostic-tracker.js";
|
|
6
|
+
import { resetDegradationLedger } from "./degradation-ledger.js";
|
|
7
|
+
import { resetDispatchAvailabilityState } from "./dispatch/runners/utils/runner-helpers.js";
|
|
6
8
|
import { clearAllSessions as clearFileTimeSessions } from "./file-time.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
|
|
10
|
-
import { resetWorkspaceTopology } from "./workspace-topology.js";
|
|
9
|
+
import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
|
|
10
|
+
import { getGlobalPiLensDir, getKnipIgnorePatterns, getProjectDataDir, } from "./file-utils.js";
|
|
11
11
|
import { GitleaksClient } from "./gitleaks-client.js";
|
|
12
|
-
import { TrivyClient } from "./trivy-client.js";
|
|
13
12
|
import { GovulncheckClient, } from "./govulncheck-client.js";
|
|
14
13
|
import { canRunStartupHeavyScans } from "./language-policy.js";
|
|
15
14
|
import { detectProjectLanguageProfile, getDefaultStartupTools, } from "./language-profile.js";
|
|
16
15
|
import { logLatency } from "./latency-logger.js";
|
|
17
|
-
import { logWordIndex } from "./word-index-logger.js";
|
|
18
16
|
import { runLogCleanup } from "./log-cleanup.js";
|
|
19
|
-
import { setSessionLanguages } from "./widget-state.js";
|
|
20
17
|
import { initLSPConfig, loadLSPConfig } from "./lsp/config.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
18
|
+
import { loadLspService } from "./lsp-lazy.js";
|
|
19
|
+
import { isAtOrAboveHomeDir } from "./path-utils.js";
|
|
20
|
+
import { isPrintMode } from "./print-mode.js";
|
|
21
|
+
import { readLatestProjectSequence, readLatestProjectSequenceAsync, } from "./project-changes.js";
|
|
23
22
|
import { getProjectSnapshotPath, hydrateRuntimeFromProjectSnapshot, isProjectSnapshotFresh, isProjectSnapshotMetaStale, loadProjectSnapshot, PROJECT_SNAPSHOT_VERSION, readProjectSnapshotMeta, saveRuntimeProjectSnapshot, } from "./project-snapshot.js";
|
|
23
|
+
import { clearTsconfigPathsCache } from "./review-graph/tsconfig-paths.js";
|
|
24
24
|
import { scanProjectRules } from "./rules-scanner.js";
|
|
25
|
-
import {
|
|
25
|
+
import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
|
|
26
26
|
import { getSlowFsVerdict, isSlowFs, slowFsDegradationNotice, } from "./slow-fs.js";
|
|
27
|
-
import {
|
|
27
|
+
import { countRecentSmells, formatSmellsSessionStartLine, resetSmellsSessionState, } from "./smells-rollup.js";
|
|
28
28
|
import { findNearestProjectRoot, getStartupScanMaxEntries, isStartupScanVerdictFresh, resolveStartupScanContext, } from "./startup-scan.js";
|
|
29
|
+
import { getSubagentIdentity, isSubagentSession, subagentLightModeNotice, } from "./subagent-mode.js";
|
|
30
|
+
import { sweepAtomicWriteStages } from "./instance-reaper.js";
|
|
31
|
+
import { TrivyClient } from "./trivy-client.js";
|
|
29
32
|
import { isWarmAttached } from "./warm-attach.js";
|
|
33
|
+
import { setSessionLanguages } from "./widget-state.js";
|
|
34
|
+
import { logWordIndex } from "./word-index-logger.js";
|
|
35
|
+
import { resetWorkspaceTopology } from "./workspace-topology.js";
|
|
30
36
|
function resolveSnapshotRoot(cwd) {
|
|
31
37
|
const resolvedCwd = path.resolve(cwd);
|
|
32
38
|
const nearest = findNearestProjectRoot(resolvedCwd);
|
|
@@ -106,20 +112,135 @@ function resolveStartupMode() {
|
|
|
106
112
|
if (envMode === "full" || envMode === "minimal" || envMode === "quick") {
|
|
107
113
|
return envMode;
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
if (argv.includes("--print") || argv.includes("-p")) {
|
|
115
|
+
if (isPrintMode()) {
|
|
111
116
|
return "quick";
|
|
112
117
|
}
|
|
113
118
|
return "full";
|
|
114
119
|
}
|
|
115
120
|
// --- Session-start helpers ---
|
|
121
|
+
// #1162: bound the session_start sequence read to this budget. The default
|
|
122
|
+
// mirrors the issue's ~250ms figure; overridable for tests (mirrors the
|
|
123
|
+
// `PI_LENS_WARMUP_DELAY_MS` precedent above) so a regression can use a tiny
|
|
124
|
+
// budget instead of racing a real 250ms wall-clock wait.
|
|
125
|
+
function sequenceReadBudgetMs() {
|
|
126
|
+
const raw = Number(process.env.PI_LENS_SEQUENCE_READ_BUDGET_MS ?? 250);
|
|
127
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 250;
|
|
128
|
+
}
|
|
129
|
+
// #1162 review follow-up (P3): the sentinel used ONLY for the snapshot
|
|
130
|
+
// freshness check (`isProjectSnapshotFresh`/`isProjectSnapshotMetaStale`,
|
|
131
|
+
// both `=== currentProjectSeq` equality) when a sequence read timed out.
|
|
132
|
+
// Every real `projectSeq`/`snapshot.seq` is >= 0 (both derive from
|
|
133
|
+
// `Math.max(0, ...)`/a monotonic fold starting at 0), so this value can
|
|
134
|
+
// never collide with a legitimate seq — including a project's real
|
|
135
|
+
// first-ever snapshot persisted at `seq === 0`, which the cold-seed's OWN
|
|
136
|
+
// `projectSeq: 0` would otherwise alias. Deliberately kept separate from
|
|
137
|
+
// `runtime.projectSeq` (which a timed-out read still seeds to plain `0`):
|
|
138
|
+
// the reseed-advancement guard on the deferred continuation below needs
|
|
139
|
+
// `runtime.projectSeq` to read exactly 0 immediately after a cold seed so
|
|
140
|
+
// `> 0` reliably means "a bump happened in the stall window", which this
|
|
141
|
+
// sentinel is never fed into.
|
|
142
|
+
const UNKNOWN_PROJECT_SEQ = -1;
|
|
143
|
+
/**
|
|
144
|
+
* Bound the sequence read that gates snapshot freshness (#1162). Normally
|
|
145
|
+
* ~2ms, but the underlying read is a blocking `fs.readFileSync` that can
|
|
146
|
+
* balloon under host I/O pressure (2125ms observed) with NO escape hatch —
|
|
147
|
+
* and a `setTimeout`/`Promise.race` timeout can never preempt a *synchronous*
|
|
148
|
+
* read (the thread only returns to the event loop once the OS call
|
|
149
|
+
* returns). The fix is to read asynchronously (`readLatestProjectSequenceAsync`,
|
|
150
|
+
* which uses `fs.promises.readFile` and genuinely yields) so a timeout CAN
|
|
151
|
+
* win the race.
|
|
152
|
+
*
|
|
153
|
+
* On the healthy path (the overwhelming common case) the async read settles
|
|
154
|
+
* first and this adds ~zero overhead beyond one microtask hop. On a stalled
|
|
155
|
+
* read, the timeout wins: the caller proceeds immediately with a cold
|
|
156
|
+
* sequence (`{ projectSeq: 0, fileSeqByPath: empty }` — byte-identical to
|
|
157
|
+
* what a full replay of a missing/empty log already produces, so this is
|
|
158
|
+
* exactly the existing cold-start case, not a new code path) while the real
|
|
159
|
+
* read keeps running in the background. When it resolves, it re-seeds the
|
|
160
|
+
* runtime so the warm-start optimization is recovered for the NEXT
|
|
161
|
+
* session_start/turn instead of lost for the rest of the process — unless
|
|
162
|
+
* the session has since moved on (`runtime.isCurrentSession`) or this is a
|
|
163
|
+
* one-shot `pi --print` process (`isPrintMode()`), which has no future
|
|
164
|
+
* session in-process to benefit and where letting background work outlive
|
|
165
|
+
* settle is exactly the referenced-handle retention class #1154/#1153 just
|
|
166
|
+
* closed. The timeout timer itself is `.unref()`'d and always cleared on
|
|
167
|
+
* both race outcomes, so it never holds the process open either.
|
|
168
|
+
*
|
|
169
|
+
* Never silent (shape 10): the caller's `session_start_sequence_read`
|
|
170
|
+
* latency line always carries `timedOut`, and a successful deferred reseed
|
|
171
|
+
* logs its own `session_start_sequence_read_deferred_reseed` phase.
|
|
172
|
+
*/
|
|
173
|
+
async function readSequenceWithBudget(args) {
|
|
174
|
+
const { snapshotRoot, base, cwd, runtime, sessionGeneration, dbg } = args;
|
|
175
|
+
const readPromise = readLatestProjectSequenceAsync(snapshotRoot, base);
|
|
176
|
+
let timeoutHandle;
|
|
177
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
178
|
+
timeoutHandle = setTimeout(() => resolve({ timedOut: true }), sequenceReadBudgetMs());
|
|
179
|
+
timeoutHandle.unref();
|
|
180
|
+
});
|
|
181
|
+
const raced = await Promise.race([
|
|
182
|
+
readPromise.then((latestSeq) => ({ timedOut: false, latestSeq })),
|
|
183
|
+
timeoutPromise,
|
|
184
|
+
]);
|
|
185
|
+
if (timeoutHandle)
|
|
186
|
+
clearTimeout(timeoutHandle);
|
|
187
|
+
if (!raced.timedOut) {
|
|
188
|
+
return { latestSeq: raced.latestSeq, timedOut: false };
|
|
189
|
+
}
|
|
190
|
+
dbg(`session_start: sequence read exceeded ${sequenceReadBudgetMs()}ms budget — falling back to cold-start sequence`);
|
|
191
|
+
if (!isPrintMode()) {
|
|
192
|
+
readPromise
|
|
193
|
+
.then((latestSeq) => {
|
|
194
|
+
if (!runtime.isCurrentSession(sessionGeneration))
|
|
195
|
+
return;
|
|
196
|
+
// #1162 review follow-up (P3): `isCurrentSession` alone catches a
|
|
197
|
+
// CROSS-session move-on but not a SAME-session advancement in the
|
|
198
|
+
// stall window. Interleaving: timeout -> cold seed sets
|
|
199
|
+
// projectSeq=0 -> handler returns -> agent edits file X ->
|
|
200
|
+
// `bumpFileSeq(X)` advances `_projectSeq`/`_fileSeq[X]` -> this
|
|
201
|
+
// deferred read completes. Reseeding here would blindly `.clear()`
|
|
202
|
+
// and repopulate `_fileSeq`/`_fileLastProjectSeq` from the STALE
|
|
203
|
+
// pre-edit snapshot, erasing the in-window bump for file X (a
|
|
204
|
+
// transient inconsistency that self-heals into a full sweep per
|
|
205
|
+
// `seedProjectSequence`'s empty-changed-map invariant — safe, but
|
|
206
|
+
// worth not doing). The cold seed always sets `projectSeq` to
|
|
207
|
+
// exactly 0, so `runtime.projectSeq > 0` here can ONLY be true if a
|
|
208
|
+
// `bumpFileSeq` ran since — i.e. an in-window edit — never a false
|
|
209
|
+
// positive from a legitimately-empty log (which leaves it at 0, and
|
|
210
|
+
// the reseed below is then still wanted: it recovers the real,
|
|
211
|
+
// possibly non-empty, result that arrived too late for the budget).
|
|
212
|
+
if (runtime.projectSeq > 0) {
|
|
213
|
+
dbg("session_start: deferred sequence read completed, but the session already advanced (in-window edit) — skipping reseed to avoid clobbering it");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const reseedStartedAt = Date.now();
|
|
217
|
+
runtime.seedProjectSequence?.(latestSeq.projectSeq, latestSeq.fileSeqByPath);
|
|
218
|
+
logLatency({
|
|
219
|
+
type: "phase",
|
|
220
|
+
phase: "session_start_sequence_read_deferred_reseed",
|
|
221
|
+
filePath: cwd,
|
|
222
|
+
startedAt: new Date(reseedStartedAt).toISOString(),
|
|
223
|
+
durationMs: Date.now() - reseedStartedAt,
|
|
224
|
+
metadata: { entries: latestSeq.fileSeqByPath.size, deferred: true },
|
|
225
|
+
});
|
|
226
|
+
dbg(`session_start: deferred sequence read completed — reseeded projectSeq=${latestSeq.projectSeq} fileSeqEntries=${latestSeq.fileSeqByPath.size}`);
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
dbg(`session_start: deferred sequence read failed: ${err}`);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
latestSeq: { projectSeq: 0, fileSeqByPath: new Map() },
|
|
234
|
+
timedOut: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
116
237
|
async function igniteWarmFiles(cwd, warmFiles, runtime, sessionGeneration, dbg) {
|
|
117
238
|
try {
|
|
118
239
|
dbg(`session_start lsp-warm: ${warmFiles.length} warm file(s) configured`);
|
|
119
240
|
await initLSPConfig(cwd);
|
|
120
241
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
121
242
|
return;
|
|
122
|
-
const lspService = getLSPService();
|
|
243
|
+
const lspService = (await loadLspService()).getLSPService();
|
|
123
244
|
const total = warmFiles.length;
|
|
124
245
|
let loaded = 0;
|
|
125
246
|
let errors = 0;
|
|
@@ -170,7 +291,7 @@ async function igniteDominantLanguageWarm(cwd, runtime, sessionGeneration, dbg)
|
|
|
170
291
|
await initLSPConfig(cwd);
|
|
171
292
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
172
293
|
return;
|
|
173
|
-
const lspService = getLSPService();
|
|
294
|
+
const lspService = (await loadLspService()).getLSPService();
|
|
174
295
|
const { collectSourceFilesAsync } = await import("./source-filter.js");
|
|
175
296
|
const { CODE_KINDS, detectFileKind } = await import("./file-kinds.js");
|
|
176
297
|
// Async, event-loop-yielding walk (deferred off the interactive path).
|
|
@@ -307,14 +428,14 @@ async function collectTodoBaselineItems(scanner, analysisRoot, stillCurrent) {
|
|
|
307
428
|
const files = await getSourceFilesAsync(analysisRoot, true);
|
|
308
429
|
if (!stillCurrent())
|
|
309
430
|
return items;
|
|
310
|
-
|
|
431
|
+
const deadline = createDeadline(8);
|
|
311
432
|
for (const file of files) {
|
|
312
433
|
if (!stillCurrent())
|
|
313
434
|
return items;
|
|
314
435
|
scanOneTodoFile(scanner, file, items);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
436
|
+
// scanFile cost scales with the file contents, so a count cadence cannot
|
|
437
|
+
// bound the event-loop block across differently-sized corpora.
|
|
438
|
+
await yieldIfOverBudget(deadline);
|
|
318
439
|
}
|
|
319
440
|
}
|
|
320
441
|
catch {
|
|
@@ -328,6 +449,7 @@ async function buildOrRefreshWordIndex(args) {
|
|
|
328
449
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
329
450
|
return;
|
|
330
451
|
const startMs = Date.now();
|
|
452
|
+
let rebuildPreflightFiles;
|
|
331
453
|
const latestSeq = readLatestProjectSequence(snapshotRoot, snapshotSequenceBase(snapshotRoot));
|
|
332
454
|
const effectiveSeq = runtime.projectSeq ?? latestSeq.projectSeq;
|
|
333
455
|
const snapshot = loadProjectSnapshot(snapshotRoot);
|
|
@@ -339,35 +461,55 @@ async function buildOrRefreshWordIndex(args) {
|
|
|
339
461
|
const result = await refreshWordIndexIncrementally(index, analysisRoot, () => runtime.isCurrentSession(sessionGeneration));
|
|
340
462
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
341
463
|
return;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
464
|
+
if (result.mode === "full-required") {
|
|
465
|
+
rebuildPreflightFiles = result.preflightFiles;
|
|
466
|
+
dbg(`session_start word-index: incremental preflight selected full rebuild (${result.reason})`);
|
|
467
|
+
logWordIndex({
|
|
468
|
+
phase: "incremental_fallback",
|
|
469
|
+
cwd: snapshotRoot,
|
|
470
|
+
trigger: "session_start",
|
|
471
|
+
reason: result.reason,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
runtime.wordIndex = index;
|
|
476
|
+
// A stale project seq must be advanced even when mtimes prove every
|
|
477
|
+
// indexed document reusable. Fresh snapshots with no changes avoid
|
|
478
|
+
// an unnecessary rewrite of the large shared snapshot.
|
|
479
|
+
if (!isProjectSnapshotFresh(snapshot, effectiveSeq) ||
|
|
480
|
+
result.refreshed > 0 ||
|
|
481
|
+
result.dropped > 0 ||
|
|
482
|
+
snapshot.wordIndex.truncated !== index.truncated) {
|
|
483
|
+
saveRuntimeProjectSnapshot({ cwd: snapshotRoot, runtime, dbg });
|
|
484
|
+
}
|
|
485
|
+
dbg(`session_start word-index: incremental (seq=${effectiveSeq}, refreshed=${result.refreshed}, dropped=${result.dropped}, skipped=${result.skipped}, reused=${result.reused}, ${Date.now() - startMs}ms)`);
|
|
486
|
+
// M2, #958: the incremental-vs-full decision + honest coverage
|
|
487
|
+
// (indexedFileCount/truncated/skipped), independent of host `dbg`.
|
|
488
|
+
logWordIndex({
|
|
489
|
+
phase: "incremental_refresh",
|
|
490
|
+
cwd: snapshotRoot,
|
|
491
|
+
trigger: "session_start",
|
|
492
|
+
durationMs: Date.now() - startMs,
|
|
493
|
+
indexedFileCount: index.docCount,
|
|
494
|
+
tokens: index.postings.size,
|
|
495
|
+
truncated: index.truncated,
|
|
496
|
+
refreshed: result.refreshed,
|
|
497
|
+
dropped: result.dropped,
|
|
498
|
+
skipped: result.skipped,
|
|
499
|
+
reused: result.reused,
|
|
500
|
+
});
|
|
501
|
+
return result;
|
|
351
502
|
}
|
|
352
|
-
dbg(`session_start word-index: incremental (seq=${effectiveSeq}, refreshed=${result.refreshed}, dropped=${result.dropped}, skipped=${result.skipped}, reused=${result.reused}, ${Date.now() - startMs}ms)`);
|
|
353
|
-
// M2, #958: the incremental-vs-full decision + honest coverage
|
|
354
|
-
// (indexedFileCount/truncated/skipped), independent of host `dbg`.
|
|
355
|
-
logWordIndex({
|
|
356
|
-
phase: "incremental_refresh",
|
|
357
|
-
cwd: snapshotRoot,
|
|
358
|
-
trigger: "session_start",
|
|
359
|
-
durationMs: Date.now() - startMs,
|
|
360
|
-
indexedFileCount: index.docCount,
|
|
361
|
-
tokens: index.postings.size,
|
|
362
|
-
truncated: index.truncated,
|
|
363
|
-
refreshed: result.refreshed,
|
|
364
|
-
dropped: result.dropped,
|
|
365
|
-
skipped: result.skipped,
|
|
366
|
-
reused: result.reused,
|
|
367
|
-
});
|
|
368
|
-
return result;
|
|
369
503
|
}
|
|
370
504
|
catch (err) {
|
|
505
|
+
// Supersession is an expected transition, not a failure: a newer
|
|
506
|
+
// session_start bumped the generation mid-refresh. Return undefined the
|
|
507
|
+
// way master's synchronous path did instead of reporting a fallback and
|
|
508
|
+
// re-walking for a rebuild whose result this generation may not publish.
|
|
509
|
+
if (!runtime.isCurrentSession(sessionGeneration)) {
|
|
510
|
+
dbg("session_start word-index: incremental refresh superseded");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
371
513
|
dbg(`session_start word-index: incremental refresh failed; falling back to full rebuild (${err})`);
|
|
372
514
|
logWordIndex({
|
|
373
515
|
phase: "incremental_fallback",
|
|
@@ -382,11 +524,30 @@ async function buildOrRefreshWordIndex(args) {
|
|
|
382
524
|
// implementation backs this task, the quick-mode warmup call below, AND the
|
|
383
525
|
// stateless cold-query background trigger in word-index.ts — a bound/skip
|
|
384
526
|
// -rule change lands once, not in three copies.
|
|
385
|
-
const {
|
|
386
|
-
const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration));
|
|
527
|
+
const { buildWordIndexAsync, collectWordIndexDocs } = await import("./word-index.js");
|
|
528
|
+
const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration), rebuildPreflightFiles);
|
|
387
529
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
388
530
|
return;
|
|
389
|
-
|
|
531
|
+
// #1197 review finding 2: `buildWordIndexAsync` THROWS on supersession, and
|
|
532
|
+
// the quick-mode warmup caller (below) awaits this function OUTSIDE any
|
|
533
|
+
// per-step try/catch — an escaping throw there skips the rest of warmup,
|
|
534
|
+
// including the #947 LSP pre-warm, and resets `__piLensWarmupScheduled`.
|
|
535
|
+
// Supersession is an expected transition (master's synchronous path just
|
|
536
|
+
// returned), so absorb it here and let the caller continue.
|
|
537
|
+
let rebuiltIndex;
|
|
538
|
+
try {
|
|
539
|
+
rebuiltIndex = await buildWordIndexAsync(docs, () => runtime.isCurrentSession(sessionGeneration));
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
if (!runtime.isCurrentSession(sessionGeneration)) {
|
|
543
|
+
dbg("session_start word-index: rebuild superseded");
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
548
|
+
if (!runtime.isCurrentSession(sessionGeneration))
|
|
549
|
+
return;
|
|
550
|
+
runtime.wordIndex = rebuiltIndex;
|
|
390
551
|
saveRuntimeProjectSnapshot({ cwd: snapshotRoot, runtime, dbg });
|
|
391
552
|
dbg(`session_start word-index: rebuilt (absent/stale, seq=${effectiveSeq}) ` +
|
|
392
553
|
`${runtime.wordIndex.docCount} files, ${runtime.wordIndex.postings.size} tokens (${Date.now() - startMs}ms)`);
|
|
@@ -433,29 +594,38 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
|
|
|
433
594
|
const queuedAt = Date.now();
|
|
434
595
|
dbg(`session_start task ${name}: scheduled`);
|
|
435
596
|
runtime.markStartupScanInFlight(name, sessionGeneration);
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
597
|
+
const completion = new Promise((resolve) => {
|
|
598
|
+
const fire = () => {
|
|
599
|
+
const startedAt = Date.now();
|
|
600
|
+
dbg(`session_start task ${name}: start queuedMs=${startedAt - queuedAt}`);
|
|
601
|
+
void task()
|
|
602
|
+
.then(() => {
|
|
603
|
+
dbg(`session_start task ${name}: success runMs=${Date.now() - startedAt} queuedMs=${startedAt - queuedAt}`);
|
|
604
|
+
})
|
|
605
|
+
.catch((err) => {
|
|
606
|
+
dbg(`session_start: ${name} background scan failed: ${err}`);
|
|
607
|
+
dbg(`session_start task ${name}: failed runMs=${Date.now() - startedAt} queuedMs=${startedAt - queuedAt}`);
|
|
608
|
+
})
|
|
609
|
+
.finally(() => {
|
|
610
|
+
runtime.clearStartupScanInFlight(name, sessionGeneration);
|
|
611
|
+
dbg(`session_start task ${name}: end`);
|
|
612
|
+
resolve();
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
const delay = taskDeferMsByName[name] ?? 0;
|
|
616
|
+
if (delay > 0) {
|
|
617
|
+
// #1154: unref to match the repo-wide convention that every
|
|
618
|
+
// background timer is `.unref()`'d. Full-mode deferred scans only
|
|
619
|
+
// run in long-lived/interactive sessions today (never a one-shot),
|
|
620
|
+
// so this is defensive — but a referenced 5s timer would keep a
|
|
621
|
+
// process open were a full-mode one-shot ever introduced.
|
|
622
|
+
setTimeout(fire, delay).unref?.();
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
setImmediate(fire);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
return completion;
|
|
459
629
|
};
|
|
460
630
|
const canRunJsTsHeavyScans = canRunStartupHeavyScans(languageProfile, "jsts");
|
|
461
631
|
const scanNames = ["todo", "dead-code"];
|
|
@@ -779,50 +949,65 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
|
|
|
779
949
|
dbg(`session_start trivy: ${result.findings.length} CVE findings (${Date.now() - startMs}ms)`);
|
|
780
950
|
});
|
|
781
951
|
// call-graph — build function-level call graph from review graph data
|
|
782
|
-
|
|
952
|
+
let callGraphIdentity;
|
|
953
|
+
const callGraphTask = runTask("call-graph", async () => {
|
|
783
954
|
const { FactStore } = await import("./dispatch/fact-store.js");
|
|
784
|
-
const { buildOrUpdateGraph, extractSymbolsAndRefsFromGraph,
|
|
785
|
-
const { buildCallGraph, saveCallGraph, loadCallGraph
|
|
955
|
+
const { buildOrUpdateGraph, extractSymbolsAndRefsFromGraph, getReviewGraphCacheIdentity, } = await import("./review-graph/builder.js");
|
|
956
|
+
const { buildCallGraph, saveCallGraph, loadCallGraph } = await import("./call-graph.js");
|
|
786
957
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
787
958
|
return;
|
|
788
959
|
const startMs = Date.now();
|
|
789
|
-
//
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const cachedFiles = [...cached.fileMtimes.keys()];
|
|
793
|
-
const stale = staleFiles(cached.fileMtimes, cachedFiles);
|
|
794
|
-
// #260: a stale REVIEW-graph version must force a rebuild even when the
|
|
795
|
-
// (separate) call-graph cache is fresh — otherwise an upgrade that
|
|
796
|
-
// invalidated the persisted graph (e.g. v2→v3 test exclusion) leaves
|
|
797
|
-
// reads cold until the next edit. The version check is cheap (file head).
|
|
798
|
-
if (stale.length === 0 &&
|
|
799
|
-
cachedFiles.length > 0 &&
|
|
800
|
-
!isReviewGraphMigrationNeeded(analysisRoot)) {
|
|
801
|
-
runtime.callGraph = cached.graph;
|
|
802
|
-
dbg(`session_start call-graph: loaded from cache (${cached.graph.edges.length} edges, ${Date.now() - startMs}ms)`);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
// Build from the review graph (reuses already-parsed data, no re-parse)
|
|
960
|
+
// Build (or hydrate) the canonical review graph first. The call graph is a
|
|
961
|
+
// derived projection of that graph, so its freshness is the review graph's
|
|
962
|
+
// version/signature—not a second source walk and mtime policy.
|
|
807
963
|
const sessionFacts = new FactStore();
|
|
808
964
|
const graph = await buildOrUpdateGraph(analysisRoot, [], sessionFacts);
|
|
809
965
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
810
966
|
return;
|
|
811
|
-
const
|
|
812
|
-
|
|
967
|
+
const identity = getReviewGraphCacheIdentity(analysisRoot, graph);
|
|
968
|
+
if (!identity) {
|
|
969
|
+
dbg("session_start call-graph: canonical review-graph identity unavailable");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
callGraphIdentity = {
|
|
973
|
+
reviewGraphVersion: identity.version,
|
|
974
|
+
reviewGraphSignature: identity.signature,
|
|
975
|
+
};
|
|
976
|
+
const cached = loadCallGraph(snapshotRoot, {
|
|
977
|
+
reviewGraphVersion: identity.version,
|
|
978
|
+
reviewGraphSignature: identity.signature,
|
|
979
|
+
});
|
|
980
|
+
if (cached) {
|
|
981
|
+
runtime.callGraph = cached.graph;
|
|
982
|
+
dbg(`session_start call-graph: loaded from cache (${cached.graph.edges.length} edges, ${cached.graph.callers.size} callee entries, ${Date.now() - startMs}ms)`);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
// Build from the canonical review graph (reuses already-parsed data, no
|
|
986
|
+
// duplicate parser or source walk).
|
|
987
|
+
const { allSymbols, allRefs, coverage } = extractSymbolsAndRefsFromGraph(graph);
|
|
988
|
+
const callGraph = buildCallGraph(allSymbols, allRefs, coverage);
|
|
813
989
|
runtime.callGraph = callGraph;
|
|
814
|
-
|
|
815
|
-
|
|
990
|
+
saveCallGraph(snapshotRoot, callGraph, {
|
|
991
|
+
reviewGraphVersion: identity.version,
|
|
992
|
+
reviewGraphSignature: identity.signature,
|
|
993
|
+
});
|
|
816
994
|
dbg(`session_start call-graph: built ${callGraph.edges.length} edges, ${callGraph.callers.size} callee entries (${Date.now() - startMs}ms)`);
|
|
817
995
|
});
|
|
818
996
|
// codebase-model — build mental model from call graph (internal-only until validated)
|
|
997
|
+
// Keep this deferred, but await the call-graph task's completion rather than
|
|
998
|
+
// racing its independently deferred timer. Otherwise a slow graph build leaves
|
|
999
|
+
// `runtime.callGraph` unset at this task's start and loses the model for the
|
|
1000
|
+
// entire session (#1070).
|
|
819
1001
|
runTask("codebase-model", async () => {
|
|
1002
|
+
await callGraphTask;
|
|
820
1003
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
821
1004
|
return;
|
|
822
1005
|
if (!runtime.callGraph)
|
|
823
|
-
return;
|
|
824
|
-
const { buildCodebaseModel, saveCodebaseModel } = await import("./codebase-model.js");
|
|
825
|
-
|
|
1006
|
+
return;
|
|
1007
|
+
const { buildCodebaseModel, saveCodebaseModel, DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET } = await import("./codebase-model.js");
|
|
1008
|
+
if (!callGraphIdentity)
|
|
1009
|
+
return;
|
|
1010
|
+
const model = buildCodebaseModel(runtime.callGraph, analysisRoot, DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, callGraphIdentity);
|
|
826
1011
|
saveCodebaseModel(snapshotRoot, model);
|
|
827
1012
|
const top3 = model.entries
|
|
828
1013
|
.slice(0, 3)
|
|
@@ -912,6 +1097,7 @@ export const SESSION_START_GUIDANCE = [
|
|
|
912
1097
|
"• Situational (activate via pi_lens_activate_tools): lsp_navigation, ast_grep_search, ast_grep_replace, ast_grep_dump.",
|
|
913
1098
|
];
|
|
914
1099
|
export async function handleSessionStart(deps) {
|
|
1100
|
+
resetDegradationLedger();
|
|
915
1101
|
const handlerEnteredAt = Date.now();
|
|
916
1102
|
const sessionStartMs = deps.sessionStartFiredAt ?? handlerEnteredAt;
|
|
917
1103
|
const cwdForTelemetry = deps.ctxCwd ?? process.cwd();
|
|
@@ -935,8 +1121,7 @@ export async function handleSessionStart(deps) {
|
|
|
935
1121
|
filePath: cwdForTelemetry,
|
|
936
1122
|
phase: "session_start_prehandler",
|
|
937
1123
|
startedAt: new Date(deps.sessionStartFiredAt).toISOString(),
|
|
938
|
-
durationMs: (deps.handlerEnteredAt ?? handlerEnteredAt) -
|
|
939
|
-
deps.sessionStartFiredAt,
|
|
1124
|
+
durationMs: (deps.handlerEnteredAt ?? handlerEnteredAt) - deps.sessionStartFiredAt,
|
|
940
1125
|
metadata: { reason: deps.sessionReason },
|
|
941
1126
|
});
|
|
942
1127
|
}
|
|
@@ -977,14 +1162,31 @@ export async function handleSessionStart(deps) {
|
|
|
977
1162
|
startupMode = deps.startupModeOverride ?? "quick";
|
|
978
1163
|
}
|
|
979
1164
|
processGlobals.__piLensFirstSessionDone = true;
|
|
1165
|
+
// #1154: quick mode is entered on BOTH `pi -p`/`--print` (a one-shot that
|
|
1166
|
+
// exits right after this turn) and an interactive process's first
|
|
1167
|
+
// session_start (forced quick to protect keystroke latency, then warms
|
|
1168
|
+
// caches for the NEXT /new). The warmup only benefits a *future* session in
|
|
1169
|
+
// the same process — a one-shot print invocation has none. Worse, in a
|
|
1170
|
+
// one-shot the warmup is a referenced-handle keep-alive: the +2s timer and
|
|
1171
|
+
// the LSP-prewarm children / language-profile source walk it launches
|
|
1172
|
+
// outlive settle with no session_shutdown teardown, holding the process
|
|
1173
|
+
// open (the #1097/#1110/#1122-hyp-A class). So skip warmup entirely in
|
|
1174
|
+
// print mode; interactive first-sessions (not print) still warm.
|
|
980
1175
|
if (startupMode === "quick" &&
|
|
1176
|
+
!isPrintMode() &&
|
|
981
1177
|
process.env.PI_LENS_COLD_START_QUICK !== "0" &&
|
|
982
1178
|
!processGlobals.__piLensWarmupScheduled) {
|
|
983
1179
|
processGlobals.__piLensWarmupScheduled = true;
|
|
984
1180
|
const warmupDelayMs = Number(process.env.PI_LENS_WARMUP_DELAY_MS ?? 2000);
|
|
985
1181
|
const warmupCwd = deps.ctxCwd ?? process.cwd();
|
|
986
1182
|
const warmupDbg = deps.dbg;
|
|
987
|
-
|
|
1183
|
+
// #1154: `.unref()` the warmup timer so — even for a warmup that IS
|
|
1184
|
+
// scheduled (an interactive first-session) — the pending timer alone can
|
|
1185
|
+
// never hold the loop open. Interactive processes stay alive for other
|
|
1186
|
+
// reasons, so the warmup still fires; a one-shot never reaches here (the
|
|
1187
|
+
// isPrintMode gate above), and repo convention is that every background
|
|
1188
|
+
// timer is unref'd (this file previously had none).
|
|
1189
|
+
const warmupTimer = setTimeout(() => {
|
|
988
1190
|
const warmupStartedAt = Date.now();
|
|
989
1191
|
void (async () => {
|
|
990
1192
|
try {
|
|
@@ -1115,7 +1317,9 @@ export async function handleSessionStart(deps) {
|
|
|
1115
1317
|
// #957 review: honor explicit warmFiles (#203) like the full
|
|
1116
1318
|
// path does — configured projects warm exactly what they
|
|
1117
1319
|
// asked for; dominant-language warm is the fallback.
|
|
1118
|
-
const lspConfig = await loadLSPConfig(warmupCwd).catch(() => ({
|
|
1320
|
+
const lspConfig = await loadLSPConfig(warmupCwd).catch(() => ({
|
|
1321
|
+
warmFiles: [],
|
|
1322
|
+
}));
|
|
1119
1323
|
const warmFiles = lspConfig.warmFiles ?? [];
|
|
1120
1324
|
if (warmFiles.length > 0) {
|
|
1121
1325
|
await igniteWarmFiles(warmupCwd, warmFiles, deps.runtime, deps.runtime.sessionGeneration, warmupDbg);
|
|
@@ -1150,6 +1354,7 @@ export async function handleSessionStart(deps) {
|
|
|
1150
1354
|
}
|
|
1151
1355
|
})();
|
|
1152
1356
|
}, warmupDelayMs);
|
|
1357
|
+
warmupTimer.unref?.();
|
|
1153
1358
|
}
|
|
1154
1359
|
const allowBootstrapTasks = startupMode === "full";
|
|
1155
1360
|
const quickMode = startupMode === "quick";
|
|
@@ -1174,10 +1379,22 @@ export async function handleSessionStart(deps) {
|
|
|
1174
1379
|
// previously session-lived with no reset hook at all).
|
|
1175
1380
|
resetWorkspaceTopology();
|
|
1176
1381
|
clearTsconfigPathsCache();
|
|
1177
|
-
// #817:
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1382
|
+
// #817/#1199: Windows command resolution is cached per (command, canonical
|
|
1383
|
+
// effective child PATH/PATHEXT/cwd/per-drive provenance); drop it each
|
|
1384
|
+
// session so environment changes (e.g.
|
|
1385
|
+
// a tool installed mid-session or a differently-scoped managed bin) are
|
|
1386
|
+
// picked up fresh.
|
|
1180
1387
|
resetSafeSpawnWindowsCommandCache();
|
|
1388
|
+
// #1266: install-failure suppression in dispatch runner availability
|
|
1389
|
+
// checkers (`noteInstallFailure`) is meant to last only until "the next
|
|
1390
|
+
// session", per #1222 — but nothing called the reset helper, so a
|
|
1391
|
+
// transient install failure (npm registry blip, locked file) suppressed
|
|
1392
|
+
// the tool for the rest of the process lifetime. Clear it here, same
|
|
1393
|
+
// boundary as the other per-session caches on this line.
|
|
1394
|
+
resetDispatchAvailabilityState();
|
|
1395
|
+
// #1123 item 3: a fresh session can re-report smells that a prior session
|
|
1396
|
+
// already surfaced once (see `checkSmellsAndNoteOnce`'s once-per-session gate).
|
|
1397
|
+
resetSmellsSessionState();
|
|
1181
1398
|
runtime.resetForSession(sessionStartMs);
|
|
1182
1399
|
logLatency({
|
|
1183
1400
|
type: "phase",
|
|
@@ -1226,22 +1443,53 @@ export async function handleSessionStart(deps) {
|
|
|
1226
1443
|
}
|
|
1227
1444
|
const hasWorkspaceCwd = typeof ctxCwd === "string" && ctxCwd.length > 0;
|
|
1228
1445
|
const cwd = ctxCwd ?? process.cwd();
|
|
1446
|
+
// #1228: generic atomic-write stages are shared by several project stores,
|
|
1447
|
+
// so the review-graph-specific sweep cannot own this namespace. Sweep the
|
|
1448
|
+
// project data roots and machine-global registry root once per session start;
|
|
1449
|
+
// this is fire-and-forget and bounded so it never delays startup.
|
|
1450
|
+
const projectDataDir = getProjectDataDir(cwd);
|
|
1451
|
+
void sweepAtomicWriteStages([
|
|
1452
|
+
projectDataDir,
|
|
1453
|
+
path.join(projectDataDir, "cache"),
|
|
1454
|
+
path.join(projectDataDir, "sessions"),
|
|
1455
|
+
getGlobalPiLensDir(),
|
|
1456
|
+
]).catch(() => {
|
|
1457
|
+
// best-effort lifecycle cleanup — never fail session_start
|
|
1458
|
+
});
|
|
1229
1459
|
if (quickMode) {
|
|
1230
1460
|
runtime.projectRoot = cwd;
|
|
1231
1461
|
const sequenceReadStartedAt = Date.now();
|
|
1232
1462
|
const snapshotRoot = resolveSnapshotRoot(cwd);
|
|
1233
|
-
const latestSeq =
|
|
1463
|
+
const { latestSeq, timedOut } = await readSequenceWithBudget({
|
|
1464
|
+
snapshotRoot,
|
|
1465
|
+
base: snapshotSequenceBase(snapshotRoot),
|
|
1466
|
+
cwd,
|
|
1467
|
+
runtime,
|
|
1468
|
+
sessionGeneration: runtime.sessionGeneration,
|
|
1469
|
+
dbg,
|
|
1470
|
+
});
|
|
1234
1471
|
logLatency({
|
|
1235
1472
|
type: "phase",
|
|
1236
1473
|
phase: "session_start_sequence_read",
|
|
1237
1474
|
filePath: cwd,
|
|
1238
1475
|
startedAt: new Date(sequenceReadStartedAt).toISOString(),
|
|
1239
1476
|
durationMs: Date.now() - sequenceReadStartedAt,
|
|
1240
|
-
metadata: { entries: latestSeq.fileSeqByPath.size },
|
|
1477
|
+
metadata: { entries: latestSeq.fileSeqByPath.size, timedOut },
|
|
1241
1478
|
});
|
|
1242
1479
|
runtime.seedProjectSequence?.(latestSeq.projectSeq, latestSeq.fileSeqByPath);
|
|
1243
1480
|
const effectiveSeq = runtime.projectSeq ?? latestSeq.projectSeq;
|
|
1244
1481
|
dbg(`session_start sequence: projectSeq=${effectiveSeq} fileSeqEntries=${latestSeq.fileSeqByPath.size}`);
|
|
1482
|
+
// #1162 review follow-up (P3): a timed-out read's cold sentinel is
|
|
1483
|
+
// `projectSeq: 0`, which is indistinguishable from a project's real
|
|
1484
|
+
// first-ever snapshot (persisted with `seq === 0` before any change was
|
|
1485
|
+
// logged). Feeding `effectiveSeq` (0) straight into the freshness check
|
|
1486
|
+
// would let that legit seq-0 snapshot match and hydrate as FRESH even
|
|
1487
|
+
// though the on-disk log has since moved past it. Use a value the
|
|
1488
|
+
// freshness check can never match (`snapshot.seq` is always >= 0) ONLY
|
|
1489
|
+
// for this gate when the read timed out — `runtime.projectSeq` itself
|
|
1490
|
+
// stays 0 (untouched), which is what Fix 1's advancement guard above
|
|
1491
|
+
// relies on to detect an in-window bump.
|
|
1492
|
+
const freshnessSeq = timedOut ? UNKNOWN_PROJECT_SEQ : effectiveSeq;
|
|
1245
1493
|
const snapshotLoadStartedAt = Date.now();
|
|
1246
1494
|
const snapshotPath = getProjectSnapshotPath(snapshotRoot);
|
|
1247
1495
|
let snapshotBytes = 0;
|
|
@@ -1253,11 +1501,11 @@ export async function handleSessionStart(deps) {
|
|
|
1253
1501
|
}
|
|
1254
1502
|
const snapshotGate = loadSnapshotBodyUnlessStale({
|
|
1255
1503
|
root: snapshotRoot,
|
|
1256
|
-
currentProjectSeq:
|
|
1504
|
+
currentProjectSeq: freshnessSeq,
|
|
1257
1505
|
dbg,
|
|
1258
1506
|
});
|
|
1259
1507
|
const snapshot = snapshotGate.snapshot;
|
|
1260
|
-
const snapshotFresh = isProjectSnapshotFresh(snapshot,
|
|
1508
|
+
const snapshotFresh = isProjectSnapshotFresh(snapshot, freshnessSeq);
|
|
1261
1509
|
logLatency({
|
|
1262
1510
|
type: "phase",
|
|
1263
1511
|
phase: "session_start_snapshot_load",
|
|
@@ -1269,12 +1517,13 @@ export async function handleSessionStart(deps) {
|
|
|
1269
1517
|
fresh: snapshotFresh,
|
|
1270
1518
|
seq: snapshot?.seq ?? null,
|
|
1271
1519
|
...(snapshotGate.skippedStale ? { skippedStale: true } : {}),
|
|
1520
|
+
...(timedOut ? { sequenceUnknown: true } : {}),
|
|
1272
1521
|
},
|
|
1273
1522
|
});
|
|
1274
1523
|
logProjectSnapshotProbe({
|
|
1275
1524
|
dbg,
|
|
1276
1525
|
root: snapshotRoot,
|
|
1277
|
-
currentProjectSeq:
|
|
1526
|
+
currentProjectSeq: freshnessSeq,
|
|
1278
1527
|
snapshot,
|
|
1279
1528
|
});
|
|
1280
1529
|
if (snapshotFresh) {
|
|
@@ -1297,6 +1546,7 @@ export async function handleSessionStart(deps) {
|
|
|
1297
1546
|
durationMs: totalDurationMs,
|
|
1298
1547
|
metadata: { mode: startupMode, reason: deps.sessionReason },
|
|
1299
1548
|
});
|
|
1549
|
+
emitSmellsSessionStartLine(dbg, sessionStartMs);
|
|
1300
1550
|
return;
|
|
1301
1551
|
}
|
|
1302
1552
|
const tools = [];
|
|
@@ -1309,20 +1559,39 @@ export async function handleSessionStart(deps) {
|
|
|
1309
1559
|
dbg(`session_start: cleaned stale tsbuildinfo: ${cleaned.join(", ")}`);
|
|
1310
1560
|
}
|
|
1311
1561
|
}
|
|
1562
|
+
// Captured here (not at first use, below) because #1162's bounded read
|
|
1563
|
+
// needs it to gate the deferred reseed against a stale/moved-on session —
|
|
1564
|
+
// it's stable for the rest of this call (no further `resetForSession`
|
|
1565
|
+
// between here and the later uses of the same generation).
|
|
1566
|
+
const sessionGeneration = runtime.sessionGeneration;
|
|
1312
1567
|
const sequenceReadStartedAt = Date.now();
|
|
1313
1568
|
const snapshotRoot = resolveSnapshotRoot(cwd);
|
|
1314
|
-
const latestSeq =
|
|
1569
|
+
const { latestSeq, timedOut } = await readSequenceWithBudget({
|
|
1570
|
+
snapshotRoot,
|
|
1571
|
+
base: snapshotSequenceBase(snapshotRoot),
|
|
1572
|
+
cwd,
|
|
1573
|
+
runtime,
|
|
1574
|
+
sessionGeneration,
|
|
1575
|
+
dbg,
|
|
1576
|
+
});
|
|
1315
1577
|
logLatency({
|
|
1316
1578
|
type: "phase",
|
|
1317
1579
|
phase: "session_start_sequence_read",
|
|
1318
1580
|
filePath: cwd,
|
|
1319
1581
|
startedAt: new Date(sequenceReadStartedAt).toISOString(),
|
|
1320
1582
|
durationMs: Date.now() - sequenceReadStartedAt,
|
|
1321
|
-
metadata: { entries: latestSeq.fileSeqByPath.size },
|
|
1583
|
+
metadata: { entries: latestSeq.fileSeqByPath.size, timedOut },
|
|
1322
1584
|
});
|
|
1323
1585
|
runtime.seedProjectSequence?.(latestSeq.projectSeq, latestSeq.fileSeqByPath);
|
|
1324
1586
|
const effectiveSeq = runtime.projectSeq ?? latestSeq.projectSeq;
|
|
1325
1587
|
dbg(`session_start sequence: projectSeq=${effectiveSeq} fileSeqEntries=${latestSeq.fileSeqByPath.size}`);
|
|
1588
|
+
// #1162 review follow-up (P3) — see the matching comment in the quick-mode
|
|
1589
|
+
// path above: a timed-out read's cold sentinel (`projectSeq: 0`) must
|
|
1590
|
+
// never satisfy the freshness check, or a project's real first-ever
|
|
1591
|
+
// snapshot (persisted at `seq === 0`) would hydrate as fresh despite the
|
|
1592
|
+
// on-disk log having moved past it. `runtime.projectSeq` itself is left
|
|
1593
|
+
// at 0, which Fix 1's advancement guard above depends on.
|
|
1594
|
+
const freshnessSeq = timedOut ? UNKNOWN_PROJECT_SEQ : effectiveSeq;
|
|
1326
1595
|
const snapshotLoadStartedAt = Date.now();
|
|
1327
1596
|
const snapshotPath = getProjectSnapshotPath(snapshotRoot);
|
|
1328
1597
|
let snapshotBytes = 0;
|
|
@@ -1334,11 +1603,11 @@ export async function handleSessionStart(deps) {
|
|
|
1334
1603
|
}
|
|
1335
1604
|
const snapshotGate = loadSnapshotBodyUnlessStale({
|
|
1336
1605
|
root: snapshotRoot,
|
|
1337
|
-
currentProjectSeq:
|
|
1606
|
+
currentProjectSeq: freshnessSeq,
|
|
1338
1607
|
dbg,
|
|
1339
1608
|
});
|
|
1340
1609
|
const snapshot = snapshotGate.snapshot;
|
|
1341
|
-
const snapshotFresh = isProjectSnapshotFresh(snapshot,
|
|
1610
|
+
const snapshotFresh = isProjectSnapshotFresh(snapshot, freshnessSeq);
|
|
1342
1611
|
logLatency({
|
|
1343
1612
|
type: "phase",
|
|
1344
1613
|
phase: "session_start_snapshot_load",
|
|
@@ -1350,12 +1619,13 @@ export async function handleSessionStart(deps) {
|
|
|
1350
1619
|
fresh: snapshotFresh,
|
|
1351
1620
|
seq: snapshot?.seq ?? null,
|
|
1352
1621
|
...(snapshotGate.skippedStale ? { skippedStale: true } : {}),
|
|
1622
|
+
...(timedOut ? { sequenceUnknown: true } : {}),
|
|
1353
1623
|
},
|
|
1354
1624
|
});
|
|
1355
1625
|
logProjectSnapshotProbe({
|
|
1356
1626
|
dbg,
|
|
1357
1627
|
root: snapshotRoot,
|
|
1358
|
-
currentProjectSeq:
|
|
1628
|
+
currentProjectSeq: freshnessSeq,
|
|
1359
1629
|
snapshot,
|
|
1360
1630
|
});
|
|
1361
1631
|
const freshSnapshot = snapshotFresh ? snapshot : null;
|
|
@@ -1523,7 +1793,8 @@ export async function handleSessionStart(deps) {
|
|
|
1523
1793
|
dbg("session_start: no project rules found");
|
|
1524
1794
|
}
|
|
1525
1795
|
cacheManager.writeCache("session-start-guidance", { content: agentStartupGuidance.join("\n") }, analysisRoot);
|
|
1526
|
-
|
|
1796
|
+
// `sessionGeneration` was captured earlier (#1162's bounded sequence read
|
|
1797
|
+
// needs it before this point) and is stable across this whole call.
|
|
1527
1798
|
if (!allowBootstrapTasks) {
|
|
1528
1799
|
dbg("session_start: skipping startup background scans (startup mode)");
|
|
1529
1800
|
}
|
|
@@ -1595,4 +1866,21 @@ export async function handleSessionStart(deps) {
|
|
|
1595
1866
|
durationMs: totalDurationMs,
|
|
1596
1867
|
metadata: { mode: startupMode, reason: deps.sessionReason },
|
|
1597
1868
|
});
|
|
1869
|
+
emitSmellsSessionStartLine(dbg, sessionStartMs);
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* #1123 item 3: one bounded `session_start` line surfacing smells the manual
|
|
1873
|
+
* `npm run logs:smells` analyzer would otherwise only catch on demand — see
|
|
1874
|
+
* `clients/smells-rollup.ts` for the tail-scan cost bound and threshold
|
|
1875
|
+
* gating. Never throws: a rollup miss must not break session_start.
|
|
1876
|
+
*/
|
|
1877
|
+
function emitSmellsSessionStartLine(dbg, sessionStartMs) {
|
|
1878
|
+
try {
|
|
1879
|
+
const line = formatSmellsSessionStartLine(countRecentSmells(undefined, sessionStartMs));
|
|
1880
|
+
if (line)
|
|
1881
|
+
dbg(line);
|
|
1882
|
+
}
|
|
1883
|
+
catch {
|
|
1884
|
+
// best-effort — smells rollup must never break session_start
|
|
1885
|
+
}
|
|
1598
1886
|
}
|