@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
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 +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- 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/feature/context.ts +74 -5
- 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 +53 -14
- 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 +18 -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 +20 -4
- 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/fleet-list.ts +15 -6
- 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/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -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 +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -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/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- 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 +136 -24
- 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-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- 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/cascade-format.js +57 -2
- 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 +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- 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 +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- 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 +1172 -683
- 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 +47 -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/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- 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 +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -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 +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -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/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- 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 +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- 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 +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- 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 +72 -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 +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- 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 +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -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 +18 -3
- 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 +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- 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/model-provider.js +53 -0
- 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 +76 -15
- 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/fresh-fetch.js +1 -1
- 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/runner-adapters/runner-findings.js +23 -2
- 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/query.js +24 -0
- 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/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -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 +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -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 +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -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 +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- 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/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- 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/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- 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 +14 -3
- 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 +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -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 +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -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-chained-type-assertions-test.yml +8 -0
- 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-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-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- 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-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-chained-type-assertions.yml +21 -0
- 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-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-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -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 +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -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 +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- 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/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -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 +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- 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 +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -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
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
import * as nodeFs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
|
|
3
4
|
import { deadCodeIssueCount } from "./dead-code-client.js";
|
|
4
5
|
import { logDeadCodeScan } from "./dead-code-logger.js";
|
|
6
|
+
import { resetDegradationLedger } from "./degradation-ledger.js";
|
|
5
7
|
import { getDiagnosticTracker } from "./diagnostic-tracker.js";
|
|
8
|
+
import { resetDispatchAvailabilityState } from "./dispatch/runners/utils/runner-helpers.js";
|
|
6
9
|
import { clearAllSessions as clearFileTimeSessions } from "./file-time.js";
|
|
7
|
-
import { getKnipIgnorePatterns } from "./file-utils.js";
|
|
8
|
-
import { clearTsconfigPathsCache } from "./review-graph/tsconfig-paths.js";
|
|
9
|
-
import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
|
|
10
|
-
import { resetWorkspaceTopology } from "./workspace-topology.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";
|
|
13
|
+
import { sweepAtomicWriteStages } from "./instance-reaper.js";
|
|
14
14
|
import { canRunStartupHeavyScans } from "./language-policy.js";
|
|
15
15
|
import { detectProjectLanguageProfile, getDefaultStartupTools, } from "./language-profile.js";
|
|
16
16
|
import { logLatency } from "./latency-logger.js";
|
|
17
|
-
import { logWordIndex } from "./word-index-logger.js";
|
|
18
17
|
import { runLogCleanup } from "./log-cleanup.js";
|
|
19
|
-
import { setSessionLanguages } from "./widget-state.js";
|
|
20
18
|
import { initLSPConfig, loadLSPConfig } from "./lsp/config.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
19
|
+
import { loadLspService } from "./lsp-lazy.js";
|
|
20
|
+
import { isAtOrAboveHomeDir } from "./path-utils.js";
|
|
21
|
+
import { isPrintMode } from "./print-mode.js";
|
|
22
|
+
import { readLatestProjectSequence, readLatestProjectSequenceAsync, } from "./project-changes.js";
|
|
23
23
|
import { getProjectSnapshotPath, hydrateRuntimeFromProjectSnapshot, isProjectSnapshotFresh, isProjectSnapshotMetaStale, loadProjectSnapshot, PROJECT_SNAPSHOT_VERSION, readProjectSnapshotMeta, saveRuntimeProjectSnapshot, } from "./project-snapshot.js";
|
|
24
|
+
import { clearTsconfigPathsCache } from "./review-graph/tsconfig-paths.js";
|
|
24
25
|
import { scanProjectRules } from "./rules-scanner.js";
|
|
25
|
-
import {
|
|
26
|
+
import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
|
|
26
27
|
import { getSlowFsVerdict, isSlowFs, slowFsDegradationNotice, } from "./slow-fs.js";
|
|
27
|
-
import {
|
|
28
|
+
import { countRecentSmells, formatSmellsSessionStartLine, resetSmellsSessionState, } from "./smells-rollup.js";
|
|
28
29
|
import { findNearestProjectRoot, getStartupScanMaxEntries, isStartupScanVerdictFresh, resolveStartupScanContext, } from "./startup-scan.js";
|
|
30
|
+
import { getSubagentIdentity, isSubagentSession, subagentLightModeNotice, } from "./subagent-mode.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,52 +449,96 @@ 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;
|
|
455
|
+
const snapshotLoadStartMs = Date.now();
|
|
333
456
|
const snapshot = loadProjectSnapshot(snapshotRoot);
|
|
457
|
+
const snapshotLoadMs = Date.now() - snapshotLoadStartMs;
|
|
334
458
|
if (snapshot?.wordIndex) {
|
|
335
459
|
const { deserializeWordIndex, refreshWordIndexIncrementally } = await import("./word-index.js");
|
|
460
|
+
const deserializeStartMs = Date.now();
|
|
336
461
|
const index = deserializeWordIndex(snapshot.wordIndex);
|
|
462
|
+
const deserializeMs = Date.now() - deserializeStartMs;
|
|
337
463
|
if (index) {
|
|
338
464
|
try {
|
|
339
465
|
const result = await refreshWordIndexIncrementally(index, analysisRoot, () => runtime.isCurrentSession(sessionGeneration));
|
|
340
466
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
341
467
|
return;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
468
|
+
if (result.mode === "full-required") {
|
|
469
|
+
rebuildPreflightFiles = result.preflightFiles;
|
|
470
|
+
dbg(`session_start word-index: incremental preflight selected full rebuild (${result.reason})`);
|
|
471
|
+
logWordIndex({
|
|
472
|
+
phase: "incremental_fallback",
|
|
473
|
+
cwd: snapshotRoot,
|
|
474
|
+
trigger: "session_start",
|
|
475
|
+
reason: result.reason,
|
|
476
|
+
phaseDurationsMs: {
|
|
477
|
+
snapshotLoadMs,
|
|
478
|
+
deserializeMs,
|
|
479
|
+
...result.timings,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
runtime.wordIndex = index;
|
|
485
|
+
let snapshotSaveSyncMs = 0;
|
|
486
|
+
// A stale project seq must be advanced even when mtimes prove every
|
|
487
|
+
// indexed document reusable. Fresh snapshots with no changes avoid
|
|
488
|
+
// an unnecessary rewrite of the large shared snapshot.
|
|
489
|
+
if (!isProjectSnapshotFresh(snapshot, effectiveSeq) ||
|
|
490
|
+
result.refreshed > 0 ||
|
|
491
|
+
result.dropped > 0 ||
|
|
492
|
+
snapshot.wordIndex.truncated !== index.truncated) {
|
|
493
|
+
const serializeSaveEnqueueStartMs = Date.now();
|
|
494
|
+
saveRuntimeProjectSnapshot({ cwd: snapshotRoot, runtime, dbg });
|
|
495
|
+
snapshotSaveSyncMs = Date.now() - serializeSaveEnqueueStartMs;
|
|
496
|
+
}
|
|
497
|
+
const phases = `snapshot-load=${snapshotLoadMs}ms, deserialize=${deserializeMs}ms, ` +
|
|
498
|
+
`source-walk=${result.timings.sourceWalkMs}ms, stat-walk=${result.timings.statWalkMs}ms, ` +
|
|
499
|
+
`refresh-reads=${result.timings.refreshReadsMs}ms, ` +
|
|
500
|
+
`snapshot-save-sync=${snapshotSaveSyncMs}ms`;
|
|
501
|
+
dbg(`session_start word-index: incremental (seq=${effectiveSeq}, refreshed=${result.refreshed}, dropped=${result.dropped}, skipped=${result.skipped}, reused=${result.reused}, ${Date.now() - startMs}ms, phases: ${phases})`);
|
|
502
|
+
// M2, #958: the incremental-vs-full decision + honest coverage
|
|
503
|
+
// (indexedFileCount/truncated/skipped), independent of host `dbg`.
|
|
504
|
+
logWordIndex({
|
|
505
|
+
phase: "incremental_refresh",
|
|
506
|
+
cwd: snapshotRoot,
|
|
507
|
+
trigger: "session_start",
|
|
508
|
+
durationMs: Date.now() - startMs,
|
|
509
|
+
indexedFileCount: index.docCount,
|
|
510
|
+
tokens: index.postings.size,
|
|
511
|
+
truncated: index.truncated,
|
|
512
|
+
phaseDurationsMs: {
|
|
513
|
+
snapshotLoadMs,
|
|
514
|
+
deserializeMs,
|
|
515
|
+
...result.timings,
|
|
516
|
+
snapshotSaveSyncMs,
|
|
517
|
+
},
|
|
518
|
+
refreshed: result.refreshed,
|
|
519
|
+
dropped: result.dropped,
|
|
520
|
+
skipped: result.skipped,
|
|
521
|
+
reused: result.reused,
|
|
522
|
+
});
|
|
523
|
+
return result;
|
|
351
524
|
}
|
|
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
525
|
}
|
|
370
526
|
catch (err) {
|
|
527
|
+
// Supersession is an expected transition, not a failure: a newer
|
|
528
|
+
// session_start bumped the generation mid-refresh. Return undefined the
|
|
529
|
+
// way master's synchronous path did instead of reporting a fallback and
|
|
530
|
+
// re-walking for a rebuild whose result this generation may not publish.
|
|
531
|
+
if (!runtime.isCurrentSession(sessionGeneration)) {
|
|
532
|
+
dbg("session_start word-index: incremental refresh superseded");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
371
535
|
dbg(`session_start word-index: incremental refresh failed; falling back to full rebuild (${err})`);
|
|
372
536
|
logWordIndex({
|
|
373
537
|
phase: "incremental_fallback",
|
|
374
538
|
cwd: snapshotRoot,
|
|
375
539
|
trigger: "session_start",
|
|
376
540
|
reason: err instanceof Error ? err.message : String(err),
|
|
541
|
+
phaseDurationsMs: { snapshotLoadMs, deserializeMs },
|
|
377
542
|
});
|
|
378
543
|
}
|
|
379
544
|
}
|
|
@@ -382,11 +547,30 @@ async function buildOrRefreshWordIndex(args) {
|
|
|
382
547
|
// implementation backs this task, the quick-mode warmup call below, AND the
|
|
383
548
|
// stateless cold-query background trigger in word-index.ts — a bound/skip
|
|
384
549
|
// -rule change lands once, not in three copies.
|
|
385
|
-
const {
|
|
386
|
-
const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration));
|
|
550
|
+
const { buildWordIndexAsync, collectWordIndexDocs } = await import("./word-index.js");
|
|
551
|
+
const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration), rebuildPreflightFiles);
|
|
387
552
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
388
553
|
return;
|
|
389
|
-
|
|
554
|
+
// #1197 review finding 2: `buildWordIndexAsync` THROWS on supersession, and
|
|
555
|
+
// the quick-mode warmup caller (below) awaits this function OUTSIDE any
|
|
556
|
+
// per-step try/catch — an escaping throw there skips the rest of warmup,
|
|
557
|
+
// including the #947 LSP pre-warm, and resets `__piLensWarmupScheduled`.
|
|
558
|
+
// Supersession is an expected transition (master's synchronous path just
|
|
559
|
+
// returned), so absorb it here and let the caller continue.
|
|
560
|
+
let rebuiltIndex;
|
|
561
|
+
try {
|
|
562
|
+
rebuiltIndex = await buildWordIndexAsync(docs, () => runtime.isCurrentSession(sessionGeneration));
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
if (!runtime.isCurrentSession(sessionGeneration)) {
|
|
566
|
+
dbg("session_start word-index: rebuild superseded");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
if (!runtime.isCurrentSession(sessionGeneration))
|
|
572
|
+
return;
|
|
573
|
+
runtime.wordIndex = rebuiltIndex;
|
|
390
574
|
saveRuntimeProjectSnapshot({ cwd: snapshotRoot, runtime, dbg });
|
|
391
575
|
dbg(`session_start word-index: rebuilt (absent/stale, seq=${effectiveSeq}) ` +
|
|
392
576
|
`${runtime.wordIndex.docCount} files, ${runtime.wordIndex.postings.size} tokens (${Date.now() - startMs}ms)`);
|
|
@@ -433,29 +617,38 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
|
|
|
433
617
|
const queuedAt = Date.now();
|
|
434
618
|
dbg(`session_start task ${name}: scheduled`);
|
|
435
619
|
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
|
-
|
|
620
|
+
const completion = new Promise((resolve) => {
|
|
621
|
+
const fire = () => {
|
|
622
|
+
const startedAt = Date.now();
|
|
623
|
+
dbg(`session_start task ${name}: start queuedMs=${startedAt - queuedAt}`);
|
|
624
|
+
void task()
|
|
625
|
+
.then(() => {
|
|
626
|
+
dbg(`session_start task ${name}: success runMs=${Date.now() - startedAt} queuedMs=${startedAt - queuedAt}`);
|
|
627
|
+
})
|
|
628
|
+
.catch((err) => {
|
|
629
|
+
dbg(`session_start: ${name} background scan failed: ${err}`);
|
|
630
|
+
dbg(`session_start task ${name}: failed runMs=${Date.now() - startedAt} queuedMs=${startedAt - queuedAt}`);
|
|
631
|
+
})
|
|
632
|
+
.finally(() => {
|
|
633
|
+
runtime.clearStartupScanInFlight(name, sessionGeneration);
|
|
634
|
+
dbg(`session_start task ${name}: end`);
|
|
635
|
+
resolve();
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
const delay = taskDeferMsByName[name] ?? 0;
|
|
639
|
+
if (delay > 0) {
|
|
640
|
+
// #1154: unref to match the repo-wide convention that every
|
|
641
|
+
// background timer is `.unref()`'d. Full-mode deferred scans only
|
|
642
|
+
// run in long-lived/interactive sessions today (never a one-shot),
|
|
643
|
+
// so this is defensive — but a referenced 5s timer would keep a
|
|
644
|
+
// process open were a full-mode one-shot ever introduced.
|
|
645
|
+
setTimeout(fire, delay).unref?.();
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
setImmediate(fire);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
return completion;
|
|
459
652
|
};
|
|
460
653
|
const canRunJsTsHeavyScans = canRunStartupHeavyScans(languageProfile, "jsts");
|
|
461
654
|
const scanNames = ["todo", "dead-code"];
|
|
@@ -779,50 +972,65 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
|
|
|
779
972
|
dbg(`session_start trivy: ${result.findings.length} CVE findings (${Date.now() - startMs}ms)`);
|
|
780
973
|
});
|
|
781
974
|
// call-graph — build function-level call graph from review graph data
|
|
782
|
-
|
|
975
|
+
let callGraphIdentity;
|
|
976
|
+
const callGraphTask = runTask("call-graph", async () => {
|
|
783
977
|
const { FactStore } = await import("./dispatch/fact-store.js");
|
|
784
|
-
const { buildOrUpdateGraph, extractSymbolsAndRefsFromGraph,
|
|
785
|
-
const { buildCallGraph, saveCallGraph, loadCallGraph
|
|
978
|
+
const { buildOrUpdateGraph, extractSymbolsAndRefsFromGraph, getReviewGraphCacheIdentity, } = await import("./review-graph/builder.js");
|
|
979
|
+
const { buildCallGraph, saveCallGraph, loadCallGraph } = await import("./call-graph.js");
|
|
786
980
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
787
981
|
return;
|
|
788
982
|
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)
|
|
983
|
+
// Build (or hydrate) the canonical review graph first. The call graph is a
|
|
984
|
+
// derived projection of that graph, so its freshness is the review graph's
|
|
985
|
+
// version/signature—not a second source walk and mtime policy.
|
|
807
986
|
const sessionFacts = new FactStore();
|
|
808
987
|
const graph = await buildOrUpdateGraph(analysisRoot, [], sessionFacts);
|
|
809
988
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
810
989
|
return;
|
|
811
|
-
const
|
|
812
|
-
|
|
990
|
+
const identity = getReviewGraphCacheIdentity(analysisRoot, graph);
|
|
991
|
+
if (!identity) {
|
|
992
|
+
dbg("session_start call-graph: canonical review-graph identity unavailable");
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
callGraphIdentity = {
|
|
996
|
+
reviewGraphVersion: identity.version,
|
|
997
|
+
reviewGraphSignature: identity.signature,
|
|
998
|
+
};
|
|
999
|
+
const cached = loadCallGraph(snapshotRoot, {
|
|
1000
|
+
reviewGraphVersion: identity.version,
|
|
1001
|
+
reviewGraphSignature: identity.signature,
|
|
1002
|
+
});
|
|
1003
|
+
if (cached) {
|
|
1004
|
+
runtime.callGraph = cached.graph;
|
|
1005
|
+
dbg(`session_start call-graph: loaded from cache (${cached.graph.edges.length} edges, ${cached.graph.callers.size} callee entries, ${Date.now() - startMs}ms)`);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
// Build from the canonical review graph (reuses already-parsed data, no
|
|
1009
|
+
// duplicate parser or source walk).
|
|
1010
|
+
const { allSymbols, allRefs, coverage } = extractSymbolsAndRefsFromGraph(graph);
|
|
1011
|
+
const callGraph = buildCallGraph(allSymbols, allRefs, coverage);
|
|
813
1012
|
runtime.callGraph = callGraph;
|
|
814
|
-
|
|
815
|
-
|
|
1013
|
+
saveCallGraph(snapshotRoot, callGraph, {
|
|
1014
|
+
reviewGraphVersion: identity.version,
|
|
1015
|
+
reviewGraphSignature: identity.signature,
|
|
1016
|
+
});
|
|
816
1017
|
dbg(`session_start call-graph: built ${callGraph.edges.length} edges, ${callGraph.callers.size} callee entries (${Date.now() - startMs}ms)`);
|
|
817
1018
|
});
|
|
818
1019
|
// codebase-model — build mental model from call graph (internal-only until validated)
|
|
1020
|
+
// Keep this deferred, but await the call-graph task's completion rather than
|
|
1021
|
+
// racing its independently deferred timer. Otherwise a slow graph build leaves
|
|
1022
|
+
// `runtime.callGraph` unset at this task's start and loses the model for the
|
|
1023
|
+
// entire session (#1070).
|
|
819
1024
|
runTask("codebase-model", async () => {
|
|
1025
|
+
await callGraphTask;
|
|
820
1026
|
if (!runtime.isCurrentSession(sessionGeneration))
|
|
821
1027
|
return;
|
|
822
1028
|
if (!runtime.callGraph)
|
|
823
|
-
return;
|
|
824
|
-
const { buildCodebaseModel, saveCodebaseModel } = await import("./codebase-model.js");
|
|
825
|
-
|
|
1029
|
+
return;
|
|
1030
|
+
const { buildCodebaseModel, saveCodebaseModel, DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, } = await import("./codebase-model.js");
|
|
1031
|
+
if (!callGraphIdentity)
|
|
1032
|
+
return;
|
|
1033
|
+
const model = buildCodebaseModel(runtime.callGraph, analysisRoot, DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, callGraphIdentity);
|
|
826
1034
|
saveCodebaseModel(snapshotRoot, model);
|
|
827
1035
|
const top3 = model.entries
|
|
828
1036
|
.slice(0, 3)
|
|
@@ -912,6 +1120,7 @@ export const SESSION_START_GUIDANCE = [
|
|
|
912
1120
|
"• Situational (activate via pi_lens_activate_tools): lsp_navigation, ast_grep_search, ast_grep_replace, ast_grep_dump.",
|
|
913
1121
|
];
|
|
914
1122
|
export async function handleSessionStart(deps) {
|
|
1123
|
+
resetDegradationLedger();
|
|
915
1124
|
const handlerEnteredAt = Date.now();
|
|
916
1125
|
const sessionStartMs = deps.sessionStartFiredAt ?? handlerEnteredAt;
|
|
917
1126
|
const cwdForTelemetry = deps.ctxCwd ?? process.cwd();
|
|
@@ -935,8 +1144,7 @@ export async function handleSessionStart(deps) {
|
|
|
935
1144
|
filePath: cwdForTelemetry,
|
|
936
1145
|
phase: "session_start_prehandler",
|
|
937
1146
|
startedAt: new Date(deps.sessionStartFiredAt).toISOString(),
|
|
938
|
-
durationMs: (deps.handlerEnteredAt ?? handlerEnteredAt) -
|
|
939
|
-
deps.sessionStartFiredAt,
|
|
1147
|
+
durationMs: (deps.handlerEnteredAt ?? handlerEnteredAt) - deps.sessionStartFiredAt,
|
|
940
1148
|
metadata: { reason: deps.sessionReason },
|
|
941
1149
|
});
|
|
942
1150
|
}
|
|
@@ -977,14 +1185,31 @@ export async function handleSessionStart(deps) {
|
|
|
977
1185
|
startupMode = deps.startupModeOverride ?? "quick";
|
|
978
1186
|
}
|
|
979
1187
|
processGlobals.__piLensFirstSessionDone = true;
|
|
1188
|
+
// #1154: quick mode is entered on BOTH `pi -p`/`--print` (a one-shot that
|
|
1189
|
+
// exits right after this turn) and an interactive process's first
|
|
1190
|
+
// session_start (forced quick to protect keystroke latency, then warms
|
|
1191
|
+
// caches for the NEXT /new). The warmup only benefits a *future* session in
|
|
1192
|
+
// the same process — a one-shot print invocation has none. Worse, in a
|
|
1193
|
+
// one-shot the warmup is a referenced-handle keep-alive: the +2s timer and
|
|
1194
|
+
// the LSP-prewarm children / language-profile source walk it launches
|
|
1195
|
+
// outlive settle with no session_shutdown teardown, holding the process
|
|
1196
|
+
// open (the #1097/#1110/#1122-hyp-A class). So skip warmup entirely in
|
|
1197
|
+
// print mode; interactive first-sessions (not print) still warm.
|
|
980
1198
|
if (startupMode === "quick" &&
|
|
1199
|
+
!isPrintMode() &&
|
|
981
1200
|
process.env.PI_LENS_COLD_START_QUICK !== "0" &&
|
|
982
1201
|
!processGlobals.__piLensWarmupScheduled) {
|
|
983
1202
|
processGlobals.__piLensWarmupScheduled = true;
|
|
984
1203
|
const warmupDelayMs = Number(process.env.PI_LENS_WARMUP_DELAY_MS ?? 2000);
|
|
985
1204
|
const warmupCwd = deps.ctxCwd ?? process.cwd();
|
|
986
1205
|
const warmupDbg = deps.dbg;
|
|
987
|
-
|
|
1206
|
+
// #1154: `.unref()` the warmup timer so — even for a warmup that IS
|
|
1207
|
+
// scheduled (an interactive first-session) — the pending timer alone can
|
|
1208
|
+
// never hold the loop open. Interactive processes stay alive for other
|
|
1209
|
+
// reasons, so the warmup still fires; a one-shot never reaches here (the
|
|
1210
|
+
// isPrintMode gate above), and repo convention is that every background
|
|
1211
|
+
// timer is unref'd (this file previously had none).
|
|
1212
|
+
const warmupTimer = setTimeout(() => {
|
|
988
1213
|
const warmupStartedAt = Date.now();
|
|
989
1214
|
void (async () => {
|
|
990
1215
|
try {
|
|
@@ -1115,7 +1340,9 @@ export async function handleSessionStart(deps) {
|
|
|
1115
1340
|
// #957 review: honor explicit warmFiles (#203) like the full
|
|
1116
1341
|
// path does — configured projects warm exactly what they
|
|
1117
1342
|
// asked for; dominant-language warm is the fallback.
|
|
1118
|
-
const lspConfig = await loadLSPConfig(warmupCwd).catch(() => ({
|
|
1343
|
+
const lspConfig = await loadLSPConfig(warmupCwd).catch(() => ({
|
|
1344
|
+
warmFiles: [],
|
|
1345
|
+
}));
|
|
1119
1346
|
const warmFiles = lspConfig.warmFiles ?? [];
|
|
1120
1347
|
if (warmFiles.length > 0) {
|
|
1121
1348
|
await igniteWarmFiles(warmupCwd, warmFiles, deps.runtime, deps.runtime.sessionGeneration, warmupDbg);
|
|
@@ -1150,6 +1377,7 @@ export async function handleSessionStart(deps) {
|
|
|
1150
1377
|
}
|
|
1151
1378
|
})();
|
|
1152
1379
|
}, warmupDelayMs);
|
|
1380
|
+
warmupTimer.unref?.();
|
|
1153
1381
|
}
|
|
1154
1382
|
const allowBootstrapTasks = startupMode === "full";
|
|
1155
1383
|
const quickMode = startupMode === "quick";
|
|
@@ -1174,10 +1402,22 @@ export async function handleSessionStart(deps) {
|
|
|
1174
1402
|
// previously session-lived with no reset hook at all).
|
|
1175
1403
|
resetWorkspaceTopology();
|
|
1176
1404
|
clearTsconfigPathsCache();
|
|
1177
|
-
// #817:
|
|
1178
|
-
//
|
|
1179
|
-
//
|
|
1405
|
+
// #817/#1199: Windows command resolution is cached per (command, canonical
|
|
1406
|
+
// effective child PATH/PATHEXT/cwd/per-drive provenance); drop it each
|
|
1407
|
+
// session so environment changes (e.g.
|
|
1408
|
+
// a tool installed mid-session or a differently-scoped managed bin) are
|
|
1409
|
+
// picked up fresh.
|
|
1180
1410
|
resetSafeSpawnWindowsCommandCache();
|
|
1411
|
+
// #1266: install-failure suppression in dispatch runner availability
|
|
1412
|
+
// checkers (`noteInstallFailure`) is meant to last only until "the next
|
|
1413
|
+
// session", per #1222 — but nothing called the reset helper, so a
|
|
1414
|
+
// transient install failure (npm registry blip, locked file) suppressed
|
|
1415
|
+
// the tool for the rest of the process lifetime. Clear it here, same
|
|
1416
|
+
// boundary as the other per-session caches on this line.
|
|
1417
|
+
resetDispatchAvailabilityState();
|
|
1418
|
+
// #1123 item 3: a fresh session can re-report smells that a prior session
|
|
1419
|
+
// already surfaced once (see `checkSmellsAndNoteOnce`'s once-per-session gate).
|
|
1420
|
+
resetSmellsSessionState();
|
|
1181
1421
|
runtime.resetForSession(sessionStartMs);
|
|
1182
1422
|
logLatency({
|
|
1183
1423
|
type: "phase",
|
|
@@ -1226,22 +1466,53 @@ export async function handleSessionStart(deps) {
|
|
|
1226
1466
|
}
|
|
1227
1467
|
const hasWorkspaceCwd = typeof ctxCwd === "string" && ctxCwd.length > 0;
|
|
1228
1468
|
const cwd = ctxCwd ?? process.cwd();
|
|
1469
|
+
// #1228: generic atomic-write stages are shared by several project stores,
|
|
1470
|
+
// so the review-graph-specific sweep cannot own this namespace. Sweep the
|
|
1471
|
+
// project data roots and machine-global registry root once per session start;
|
|
1472
|
+
// this is fire-and-forget and bounded so it never delays startup.
|
|
1473
|
+
const projectDataDir = getProjectDataDir(cwd);
|
|
1474
|
+
void sweepAtomicWriteStages([
|
|
1475
|
+
projectDataDir,
|
|
1476
|
+
path.join(projectDataDir, "cache"),
|
|
1477
|
+
path.join(projectDataDir, "sessions"),
|
|
1478
|
+
getGlobalPiLensDir(),
|
|
1479
|
+
]).catch(() => {
|
|
1480
|
+
// best-effort lifecycle cleanup — never fail session_start
|
|
1481
|
+
});
|
|
1229
1482
|
if (quickMode) {
|
|
1230
1483
|
runtime.projectRoot = cwd;
|
|
1231
1484
|
const sequenceReadStartedAt = Date.now();
|
|
1232
1485
|
const snapshotRoot = resolveSnapshotRoot(cwd);
|
|
1233
|
-
const latestSeq =
|
|
1486
|
+
const { latestSeq, timedOut } = await readSequenceWithBudget({
|
|
1487
|
+
snapshotRoot,
|
|
1488
|
+
base: snapshotSequenceBase(snapshotRoot),
|
|
1489
|
+
cwd,
|
|
1490
|
+
runtime,
|
|
1491
|
+
sessionGeneration: runtime.sessionGeneration,
|
|
1492
|
+
dbg,
|
|
1493
|
+
});
|
|
1234
1494
|
logLatency({
|
|
1235
1495
|
type: "phase",
|
|
1236
1496
|
phase: "session_start_sequence_read",
|
|
1237
1497
|
filePath: cwd,
|
|
1238
1498
|
startedAt: new Date(sequenceReadStartedAt).toISOString(),
|
|
1239
1499
|
durationMs: Date.now() - sequenceReadStartedAt,
|
|
1240
|
-
metadata: { entries: latestSeq.fileSeqByPath.size },
|
|
1500
|
+
metadata: { entries: latestSeq.fileSeqByPath.size, timedOut },
|
|
1241
1501
|
});
|
|
1242
1502
|
runtime.seedProjectSequence?.(latestSeq.projectSeq, latestSeq.fileSeqByPath);
|
|
1243
1503
|
const effectiveSeq = runtime.projectSeq ?? latestSeq.projectSeq;
|
|
1244
1504
|
dbg(`session_start sequence: projectSeq=${effectiveSeq} fileSeqEntries=${latestSeq.fileSeqByPath.size}`);
|
|
1505
|
+
// #1162 review follow-up (P3): a timed-out read's cold sentinel is
|
|
1506
|
+
// `projectSeq: 0`, which is indistinguishable from a project's real
|
|
1507
|
+
// first-ever snapshot (persisted with `seq === 0` before any change was
|
|
1508
|
+
// logged). Feeding `effectiveSeq` (0) straight into the freshness check
|
|
1509
|
+
// would let that legit seq-0 snapshot match and hydrate as FRESH even
|
|
1510
|
+
// though the on-disk log has since moved past it. Use a value the
|
|
1511
|
+
// freshness check can never match (`snapshot.seq` is always >= 0) ONLY
|
|
1512
|
+
// for this gate when the read timed out — `runtime.projectSeq` itself
|
|
1513
|
+
// stays 0 (untouched), which is what Fix 1's advancement guard above
|
|
1514
|
+
// relies on to detect an in-window bump.
|
|
1515
|
+
const freshnessSeq = timedOut ? UNKNOWN_PROJECT_SEQ : effectiveSeq;
|
|
1245
1516
|
const snapshotLoadStartedAt = Date.now();
|
|
1246
1517
|
const snapshotPath = getProjectSnapshotPath(snapshotRoot);
|
|
1247
1518
|
let snapshotBytes = 0;
|
|
@@ -1253,11 +1524,11 @@ export async function handleSessionStart(deps) {
|
|
|
1253
1524
|
}
|
|
1254
1525
|
const snapshotGate = loadSnapshotBodyUnlessStale({
|
|
1255
1526
|
root: snapshotRoot,
|
|
1256
|
-
currentProjectSeq:
|
|
1527
|
+
currentProjectSeq: freshnessSeq,
|
|
1257
1528
|
dbg,
|
|
1258
1529
|
});
|
|
1259
1530
|
const snapshot = snapshotGate.snapshot;
|
|
1260
|
-
const snapshotFresh = isProjectSnapshotFresh(snapshot,
|
|
1531
|
+
const snapshotFresh = isProjectSnapshotFresh(snapshot, freshnessSeq);
|
|
1261
1532
|
logLatency({
|
|
1262
1533
|
type: "phase",
|
|
1263
1534
|
phase: "session_start_snapshot_load",
|
|
@@ -1269,12 +1540,13 @@ export async function handleSessionStart(deps) {
|
|
|
1269
1540
|
fresh: snapshotFresh,
|
|
1270
1541
|
seq: snapshot?.seq ?? null,
|
|
1271
1542
|
...(snapshotGate.skippedStale ? { skippedStale: true } : {}),
|
|
1543
|
+
...(timedOut ? { sequenceUnknown: true } : {}),
|
|
1272
1544
|
},
|
|
1273
1545
|
});
|
|
1274
1546
|
logProjectSnapshotProbe({
|
|
1275
1547
|
dbg,
|
|
1276
1548
|
root: snapshotRoot,
|
|
1277
|
-
currentProjectSeq:
|
|
1549
|
+
currentProjectSeq: freshnessSeq,
|
|
1278
1550
|
snapshot,
|
|
1279
1551
|
});
|
|
1280
1552
|
if (snapshotFresh) {
|
|
@@ -1297,6 +1569,7 @@ export async function handleSessionStart(deps) {
|
|
|
1297
1569
|
durationMs: totalDurationMs,
|
|
1298
1570
|
metadata: { mode: startupMode, reason: deps.sessionReason },
|
|
1299
1571
|
});
|
|
1572
|
+
emitSmellsSessionStartLine(dbg, sessionStartMs);
|
|
1300
1573
|
return;
|
|
1301
1574
|
}
|
|
1302
1575
|
const tools = [];
|
|
@@ -1309,20 +1582,39 @@ export async function handleSessionStart(deps) {
|
|
|
1309
1582
|
dbg(`session_start: cleaned stale tsbuildinfo: ${cleaned.join(", ")}`);
|
|
1310
1583
|
}
|
|
1311
1584
|
}
|
|
1585
|
+
// Captured here (not at first use, below) because #1162's bounded read
|
|
1586
|
+
// needs it to gate the deferred reseed against a stale/moved-on session —
|
|
1587
|
+
// it's stable for the rest of this call (no further `resetForSession`
|
|
1588
|
+
// between here and the later uses of the same generation).
|
|
1589
|
+
const sessionGeneration = runtime.sessionGeneration;
|
|
1312
1590
|
const sequenceReadStartedAt = Date.now();
|
|
1313
1591
|
const snapshotRoot = resolveSnapshotRoot(cwd);
|
|
1314
|
-
const latestSeq =
|
|
1592
|
+
const { latestSeq, timedOut } = await readSequenceWithBudget({
|
|
1593
|
+
snapshotRoot,
|
|
1594
|
+
base: snapshotSequenceBase(snapshotRoot),
|
|
1595
|
+
cwd,
|
|
1596
|
+
runtime,
|
|
1597
|
+
sessionGeneration,
|
|
1598
|
+
dbg,
|
|
1599
|
+
});
|
|
1315
1600
|
logLatency({
|
|
1316
1601
|
type: "phase",
|
|
1317
1602
|
phase: "session_start_sequence_read",
|
|
1318
1603
|
filePath: cwd,
|
|
1319
1604
|
startedAt: new Date(sequenceReadStartedAt).toISOString(),
|
|
1320
1605
|
durationMs: Date.now() - sequenceReadStartedAt,
|
|
1321
|
-
metadata: { entries: latestSeq.fileSeqByPath.size },
|
|
1606
|
+
metadata: { entries: latestSeq.fileSeqByPath.size, timedOut },
|
|
1322
1607
|
});
|
|
1323
1608
|
runtime.seedProjectSequence?.(latestSeq.projectSeq, latestSeq.fileSeqByPath);
|
|
1324
1609
|
const effectiveSeq = runtime.projectSeq ?? latestSeq.projectSeq;
|
|
1325
1610
|
dbg(`session_start sequence: projectSeq=${effectiveSeq} fileSeqEntries=${latestSeq.fileSeqByPath.size}`);
|
|
1611
|
+
// #1162 review follow-up (P3) — see the matching comment in the quick-mode
|
|
1612
|
+
// path above: a timed-out read's cold sentinel (`projectSeq: 0`) must
|
|
1613
|
+
// never satisfy the freshness check, or a project's real first-ever
|
|
1614
|
+
// snapshot (persisted at `seq === 0`) would hydrate as fresh despite the
|
|
1615
|
+
// on-disk log having moved past it. `runtime.projectSeq` itself is left
|
|
1616
|
+
// at 0, which Fix 1's advancement guard above depends on.
|
|
1617
|
+
const freshnessSeq = timedOut ? UNKNOWN_PROJECT_SEQ : effectiveSeq;
|
|
1326
1618
|
const snapshotLoadStartedAt = Date.now();
|
|
1327
1619
|
const snapshotPath = getProjectSnapshotPath(snapshotRoot);
|
|
1328
1620
|
let snapshotBytes = 0;
|
|
@@ -1334,11 +1626,11 @@ export async function handleSessionStart(deps) {
|
|
|
1334
1626
|
}
|
|
1335
1627
|
const snapshotGate = loadSnapshotBodyUnlessStale({
|
|
1336
1628
|
root: snapshotRoot,
|
|
1337
|
-
currentProjectSeq:
|
|
1629
|
+
currentProjectSeq: freshnessSeq,
|
|
1338
1630
|
dbg,
|
|
1339
1631
|
});
|
|
1340
1632
|
const snapshot = snapshotGate.snapshot;
|
|
1341
|
-
const snapshotFresh = isProjectSnapshotFresh(snapshot,
|
|
1633
|
+
const snapshotFresh = isProjectSnapshotFresh(snapshot, freshnessSeq);
|
|
1342
1634
|
logLatency({
|
|
1343
1635
|
type: "phase",
|
|
1344
1636
|
phase: "session_start_snapshot_load",
|
|
@@ -1350,12 +1642,13 @@ export async function handleSessionStart(deps) {
|
|
|
1350
1642
|
fresh: snapshotFresh,
|
|
1351
1643
|
seq: snapshot?.seq ?? null,
|
|
1352
1644
|
...(snapshotGate.skippedStale ? { skippedStale: true } : {}),
|
|
1645
|
+
...(timedOut ? { sequenceUnknown: true } : {}),
|
|
1353
1646
|
},
|
|
1354
1647
|
});
|
|
1355
1648
|
logProjectSnapshotProbe({
|
|
1356
1649
|
dbg,
|
|
1357
1650
|
root: snapshotRoot,
|
|
1358
|
-
currentProjectSeq:
|
|
1651
|
+
currentProjectSeq: freshnessSeq,
|
|
1359
1652
|
snapshot,
|
|
1360
1653
|
});
|
|
1361
1654
|
const freshSnapshot = snapshotFresh ? snapshot : null;
|
|
@@ -1523,7 +1816,8 @@ export async function handleSessionStart(deps) {
|
|
|
1523
1816
|
dbg("session_start: no project rules found");
|
|
1524
1817
|
}
|
|
1525
1818
|
cacheManager.writeCache("session-start-guidance", { content: agentStartupGuidance.join("\n") }, analysisRoot);
|
|
1526
|
-
|
|
1819
|
+
// `sessionGeneration` was captured earlier (#1162's bounded sequence read
|
|
1820
|
+
// needs it before this point) and is stable across this whole call.
|
|
1527
1821
|
if (!allowBootstrapTasks) {
|
|
1528
1822
|
dbg("session_start: skipping startup background scans (startup mode)");
|
|
1529
1823
|
}
|
|
@@ -1595,4 +1889,21 @@ export async function handleSessionStart(deps) {
|
|
|
1595
1889
|
durationMs: totalDurationMs,
|
|
1596
1890
|
metadata: { mode: startupMode, reason: deps.sessionReason },
|
|
1597
1891
|
});
|
|
1892
|
+
emitSmellsSessionStartLine(dbg, sessionStartMs);
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* #1123 item 3: one bounded `session_start` line surfacing smells the manual
|
|
1896
|
+
* `npm run logs:smells` analyzer would otherwise only catch on demand — see
|
|
1897
|
+
* `clients/smells-rollup.ts` for the tail-scan cost bound and threshold
|
|
1898
|
+
* gating. Never throws: a rollup miss must not break session_start.
|
|
1899
|
+
*/
|
|
1900
|
+
function emitSmellsSessionStartLine(dbg, sessionStartMs) {
|
|
1901
|
+
try {
|
|
1902
|
+
const line = formatSmellsSessionStartLine(countRecentSmells(undefined, sessionStartMs));
|
|
1903
|
+
if (line)
|
|
1904
|
+
dbg(line);
|
|
1905
|
+
}
|
|
1906
|
+
catch {
|
|
1907
|
+
// best-effort — smells rollup must never break session_start
|
|
1908
|
+
}
|
|
1598
1909
|
}
|