@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
|
@@ -6,16 +6,19 @@
|
|
|
6
6
|
* connects to it to get LSP-complete diagnostics from the warm process instead
|
|
7
7
|
* of running its own cold analysis.
|
|
8
8
|
*
|
|
9
|
-
* This module is the CLIENT + the shared path derivation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* client resolves `undefined` and the caller falls
|
|
9
|
+
* This module is the CLIENT + the shared path derivation + the one-shot line
|
|
10
|
+
* reader the server wires into its socket (deliberately light: node:net +
|
|
11
|
+
* type-only result, so the bin can try the warm path WITHOUT loading the
|
|
12
|
+
* dispatch graph). The analysis engine lives in mcp/server.ts. If the warm
|
|
13
|
+
* path is unavailable, the client resolves `undefined` and the caller falls
|
|
14
|
+
* back to cold local analysis.
|
|
14
15
|
*/
|
|
15
16
|
import * as crypto from "node:crypto";
|
|
17
|
+
import * as fs from "node:fs";
|
|
16
18
|
import * as net from "node:net";
|
|
17
19
|
import * as os from "node:os";
|
|
18
20
|
import * as path from "node:path";
|
|
21
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
19
22
|
export const WARM_DIAGNOSTICS_SCHEMA_VERSION = 1;
|
|
20
23
|
export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
|
|
21
24
|
/**
|
|
@@ -25,19 +28,23 @@ export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
|
|
|
25
28
|
* the same project they meet. Mismatch → the client just falls back to cold.
|
|
26
29
|
*/
|
|
27
30
|
export function ipcPathForCwd(cwd) {
|
|
28
|
-
const
|
|
29
|
-
// sha256 (not for security — just a stable short id for the IPC socket/pipe
|
|
30
|
-
// name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
|
|
31
|
-
const hash = crypto
|
|
32
|
-
.createHash("sha256")
|
|
33
|
-
.update(root)
|
|
34
|
-
.digest("hex")
|
|
35
|
-
.slice(0, 16);
|
|
31
|
+
const hash = workspaceHash(cwd);
|
|
36
32
|
if (process.platform === "win32") {
|
|
37
33
|
return `\\\\.\\pipe\\pi-lens-mcp-${hash}`;
|
|
38
34
|
}
|
|
39
35
|
return path.join(os.tmpdir(), `pi-lens-mcp-${hash}.sock`);
|
|
40
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The single stable per-workspace id both endpoint derivations key on. Every
|
|
39
|
+
* per-workspace side-channel name (socket/pipe, turn-end status file) must come
|
|
40
|
+
* from HERE, so the hook process and the server process cannot drift apart.
|
|
41
|
+
*/
|
|
42
|
+
function workspaceHash(cwd) {
|
|
43
|
+
const root = path.resolve(cwd).toLowerCase();
|
|
44
|
+
// sha256 (not for security — just a stable short id for the IPC socket/pipe
|
|
45
|
+
// name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
|
|
46
|
+
return crypto.createHash("sha256").update(root).digest("hex").slice(0, 16);
|
|
47
|
+
}
|
|
41
48
|
/** PID-scoped endpoint used by pi sessions. The legacy MCP analyze endpoint
|
|
42
49
|
* remains workspace-scoped for compatibility with the PostToolUse hook. */
|
|
43
50
|
export function diagnosticsIpcPathForCwd(cwd, pid) {
|
|
@@ -50,15 +57,16 @@ export function contentHash(content) {
|
|
|
50
57
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
51
58
|
}
|
|
52
59
|
/**
|
|
53
|
-
* Shared one-shot request/response transport for the
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
+
* Shared one-shot request/response transport for the tagged IPC routes (#822):
|
|
61
|
+
* connect to `endpoint`, write one JSON line, read one JSON line back,
|
|
62
|
+
* classify. The endpoint is PID-scoped for the warm-attach routes and
|
|
63
|
+
* workspace-scoped for the hook bin's turn-end route. The per-route `validate`
|
|
64
|
+
* callback returns a failure reason for an on-time but unusable reply (schema
|
|
65
|
+
* skew, staleness) or `undefined` to accept it; transport failures map
|
|
66
|
+
* uniformly to timeout/ipc-error. One transport, N routes — the clients cannot
|
|
67
|
+
* drift apart.
|
|
60
68
|
*/
|
|
61
|
-
function requestOverWarmIpc(
|
|
69
|
+
function requestOverWarmIpc(endpoint, timeoutMs, buildRequest, validate) {
|
|
62
70
|
return new Promise((resolve) => {
|
|
63
71
|
let settled = false;
|
|
64
72
|
const finish = (value) => {
|
|
@@ -70,7 +78,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
|
|
|
70
78
|
resolve(value);
|
|
71
79
|
};
|
|
72
80
|
const deadlineAt = Date.now() + timeoutMs;
|
|
73
|
-
const socket = net.createConnection(
|
|
81
|
+
const socket = net.createConnection(endpoint);
|
|
74
82
|
socket.setEncoding("utf8");
|
|
75
83
|
let buffer = "";
|
|
76
84
|
const timer = setTimeout(() => finish({ available: false, reason: "timeout" }), timeoutMs);
|
|
@@ -108,7 +116,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
|
|
|
108
116
|
}
|
|
109
117
|
export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeoutMs) {
|
|
110
118
|
const expectedHash = contentHash(content);
|
|
111
|
-
return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
|
|
119
|
+
return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
|
|
112
120
|
route: "diagnostics",
|
|
113
121
|
version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
|
|
114
122
|
file,
|
|
@@ -131,7 +139,7 @@ export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeout
|
|
|
131
139
|
});
|
|
132
140
|
}
|
|
133
141
|
export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentHash, ranges, timeoutMs) {
|
|
134
|
-
return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
|
|
142
|
+
return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
|
|
135
143
|
route: "code-actions",
|
|
136
144
|
version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
|
|
137
145
|
file,
|
|
@@ -154,6 +162,99 @@ export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentH
|
|
|
154
162
|
return undefined;
|
|
155
163
|
});
|
|
156
164
|
}
|
|
165
|
+
// v2 adds an explicit receipt acknowledgement. A v1 server must reject rather
|
|
166
|
+
// than consume a pass without the new delivery contract.
|
|
167
|
+
export const WARM_TURN_END_SCHEMA_VERSION = 2;
|
|
168
|
+
/**
|
|
169
|
+
* Ask the warm server to run pi-lens's real turn-end pass. Execution and
|
|
170
|
+
* delivery are separate: the first one-shot connection returns a capability,
|
|
171
|
+
* then a second one-shot connection acknowledges receipt. If the client
|
|
172
|
+
* deadline or either connection loses, the server leaves the durable finding
|
|
173
|
+
* cache untouched for a later Stop. 55 s expires inside Claude Code's 60 s hook
|
|
174
|
+
* timeout.
|
|
175
|
+
*/
|
|
176
|
+
export async function requestWarmTurnEnd(cwd, timeoutMs = 55_000) {
|
|
177
|
+
const startedAt = Date.now();
|
|
178
|
+
const first = await requestOverWarmIpc(ipcPathForCwd(cwd), timeoutMs, () => ({
|
|
179
|
+
route: "turn-end",
|
|
180
|
+
version: WARM_TURN_END_SCHEMA_VERSION,
|
|
181
|
+
cwd,
|
|
182
|
+
}), (result) => result.route === "turn-end" &&
|
|
183
|
+
result.version === WARM_TURN_END_SCHEMA_VERSION
|
|
184
|
+
? undefined
|
|
185
|
+
: "schema-mismatch");
|
|
186
|
+
if (!first.available || !first.response.deliveryId)
|
|
187
|
+
return first;
|
|
188
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
189
|
+
if (remainingMs <= 0)
|
|
190
|
+
return { available: false, reason: "timeout" };
|
|
191
|
+
const ack = await requestOverWarmIpc(ipcPathForCwd(cwd), remainingMs, () => ({
|
|
192
|
+
route: "turn-end-ack",
|
|
193
|
+
version: WARM_TURN_END_SCHEMA_VERSION,
|
|
194
|
+
cwd,
|
|
195
|
+
deliveryId: first.response.deliveryId,
|
|
196
|
+
}), (result) => result.route === "turn-end-ack" && result.acknowledged === true
|
|
197
|
+
? undefined
|
|
198
|
+
: "ipc-error");
|
|
199
|
+
return ack.available
|
|
200
|
+
? { available: true, response: first.response }
|
|
201
|
+
: { available: false, reason: ack.reason };
|
|
202
|
+
}
|
|
203
|
+
/** Per-workspace status file, keyed by the same hash as the IPC endpoint. */
|
|
204
|
+
export function turnEndStatusPathForCwd(cwd) {
|
|
205
|
+
return path.join(os.tmpdir(), `pi-lens-turn-end-${workspaceHash(cwd)}.json`);
|
|
206
|
+
}
|
|
207
|
+
export function readTurnEndStatus(cwd) {
|
|
208
|
+
try {
|
|
209
|
+
const raw = fs.readFileSync(turnEndStatusPathForCwd(cwd), "utf8");
|
|
210
|
+
const parsed = JSON.parse(raw);
|
|
211
|
+
if (!parsed || typeof parsed !== "object")
|
|
212
|
+
return undefined;
|
|
213
|
+
return {
|
|
214
|
+
ran: typeof parsed.ran === "number" ? parsed.ran : 0,
|
|
215
|
+
skipped: typeof parsed.skipped === "number" ? parsed.skipped : 0,
|
|
216
|
+
lastSkipReason: parsed.lastSkipReason,
|
|
217
|
+
lastRunAt: parsed.lastRunAt,
|
|
218
|
+
lastSkipAt: parsed.lastSkipAt,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// never written, unreadable, or corrupt — "no turn-end activity recorded"
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Append one turn-end outcome from the hook process. Never throws.
|
|
228
|
+
*
|
|
229
|
+
* Read-modify-write against a shared per-workspace tmpdir file: two Stop
|
|
230
|
+
* hooks for the same workspace can race this concurrently (separate
|
|
231
|
+
* processes, no lock). `writeFileAtomic` only closes the *publication* half
|
|
232
|
+
* of that — it guarantees this write's own staging+rename can't torn-read
|
|
233
|
+
* (parse-fail and reset counters) or hit a Windows sharing violation against
|
|
234
|
+
* a concurrent writer's rename. It does NOT fix the read-modify-write race
|
|
235
|
+
* itself: two overlapping calls can still both read the same `previous` and
|
|
236
|
+
* publish from it, silently dropping one increment. That's accepted here —
|
|
237
|
+
* this is bounded best-effort telemetry (self-heals, errors already
|
|
238
|
+
* swallowed below), and a lock on this path isn't worth it.
|
|
239
|
+
*/
|
|
240
|
+
export function recordTurnEndOutcome(cwd, outcome) {
|
|
241
|
+
try {
|
|
242
|
+
const now = new Date().toISOString();
|
|
243
|
+
const previous = readTurnEndStatus(cwd) ?? { ran: 0, skipped: 0 };
|
|
244
|
+
const next = outcome.ran
|
|
245
|
+
? { ...previous, ran: previous.ran + 1, lastRunAt: now }
|
|
246
|
+
: {
|
|
247
|
+
...previous,
|
|
248
|
+
skipped: previous.skipped + 1,
|
|
249
|
+
lastSkipReason: outcome.reason,
|
|
250
|
+
lastSkipAt: now,
|
|
251
|
+
};
|
|
252
|
+
writeFileAtomic(turnEndStatusPathForCwd(cwd), `${JSON.stringify(next)}\n`);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// telemetry only — a read-only tmpdir must not break the Stop hook
|
|
256
|
+
}
|
|
257
|
+
}
|
|
157
258
|
/**
|
|
158
259
|
* Ask the warm server to analyze a file. Resolves the server's result, or
|
|
159
260
|
* `undefined` on ANY failure (no server, refused, stale socket, timeout, bad
|
|
@@ -200,3 +301,35 @@ export function requestWarmAnalyze(cwd, file, timeoutMs = 30_000) {
|
|
|
200
301
|
socket.on("close", () => finish(undefined));
|
|
201
302
|
});
|
|
202
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* One-shot line reader for the warm IPC socket (#1219). The clients write
|
|
306
|
+
* exactly one newline-terminated request per connection and read one reply, so
|
|
307
|
+
* the server must dispatch at most one line and ignore anything after it — a
|
|
308
|
+
* `data` handler that keeps re-reading the same buffered line re-dispatches
|
|
309
|
+
* the request on stray bytes. Returns the handler to attach to the socket's
|
|
310
|
+
* `data` event.
|
|
311
|
+
*/
|
|
312
|
+
export function createWarmIpcLineReader(onLine) {
|
|
313
|
+
let buffer = "";
|
|
314
|
+
let dispatched = false;
|
|
315
|
+
return (chunk) => {
|
|
316
|
+
if (dispatched)
|
|
317
|
+
return;
|
|
318
|
+
buffer += chunk;
|
|
319
|
+
const newline = buffer.indexOf("\n");
|
|
320
|
+
if (newline === -1)
|
|
321
|
+
return;
|
|
322
|
+
dispatched = true;
|
|
323
|
+
onLine(buffer.slice(0, newline));
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
export function createWarmIpcRequestQueue() {
|
|
327
|
+
let chain = Promise.resolve();
|
|
328
|
+
return {
|
|
329
|
+
enqueue(work) {
|
|
330
|
+
const next = chain.then(work);
|
|
331
|
+
chain = next.catch(() => undefined);
|
|
332
|
+
return next;
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* doesn't — session-start warms the dominant-language LSP (so warm `analyze` is
|
|
6
6
|
* LSP-complete), establishes the error-debt baseline + complexity baselines, and
|
|
7
7
|
* kicks off knip/jscpd/type-coverage/dep/secrets scans; turn-end runs
|
|
8
|
-
* knip
|
|
8
|
+
* knip incrementally, dep-circular, tests, cascade, and the
|
|
9
9
|
* actionable/code-quality aggregation.
|
|
10
10
|
*
|
|
11
11
|
* The handlers don't return findings — they emit them through the same
|
|
@@ -16,15 +16,16 @@
|
|
|
16
16
|
* persistent RuntimeCoordinator + CacheManager (so baselines/turn-state survive
|
|
17
17
|
* across calls, like a real session), and the bootstrap client bundle.
|
|
18
18
|
*/
|
|
19
|
+
import * as crypto from "node:crypto";
|
|
19
20
|
import * as fs from "node:fs";
|
|
20
21
|
import * as path from "node:path";
|
|
21
22
|
import { AstGrepClient } from "../ast-grep-client.js";
|
|
22
23
|
import { loadBootstrapClients } from "../bootstrap.js";
|
|
23
|
-
import { CacheManager } from "../cache-manager.js";
|
|
24
|
+
import { CacheManager, MCP_TURN_STATE_OWNER_ID, } from "../cache-manager.js";
|
|
24
25
|
import { resetDispatchBaselines } from "../dispatch/integration.js";
|
|
25
26
|
import { resetFormatService } from "../format-service.js";
|
|
26
27
|
import { getLSPService, resetLSPService } from "../lsp/index.js";
|
|
27
|
-
import { consumeSessionStartGuidance, consumeTestFindings, consumeTurnEndFindings, } from "../runtime-context.js";
|
|
28
|
+
import { consumeSessionStartGuidance, consumeTestFindings, consumeTurnEndFindings, peekTestFindings, peekTurnEndFindings, } from "../runtime-context.js";
|
|
28
29
|
import { RuntimeCoordinator } from "../runtime-coordinator.js";
|
|
29
30
|
import { handleSessionStart } from "../runtime-session.js";
|
|
30
31
|
import { handleTurnEnd } from "../runtime-turn.js";
|
|
@@ -105,12 +106,82 @@ export async function runSessionStart(cwd) {
|
|
|
105
106
|
aliveLspClients: getLSPService().getAliveClientCount(),
|
|
106
107
|
};
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* #1274: the delivery map used to be unbounded — every timed-out client and
|
|
111
|
+
* every distinct raw-cwd alias left an entry for the life of the process.
|
|
112
|
+
* Both bounds drop entries WITHOUT committing, which re-arms the findings in
|
|
113
|
+
* the cache: the safe direction, since an un-consumed finding is re-reported on
|
|
114
|
+
* a later Stop while a wrongly-consumed one is gone for good. The TTL is
|
|
115
|
+
* generous next to Claude Code's 60 s hook budget — it exists to bound the map,
|
|
116
|
+
* not to police clients.
|
|
117
|
+
*/
|
|
118
|
+
const TURN_END_DELIVERY_TTL_MS = 10 * 60_000;
|
|
119
|
+
const TURN_END_DELIVERY_MAX = 8;
|
|
120
|
+
const TURN_END_QUEUE_WAIT_MS = 5_000;
|
|
121
|
+
/**
|
|
122
|
+
* A bounded, cancellable serial queue. A turn_end mutates shared runtime and
|
|
123
|
+
* cache state, so concurrent passes are unsafe; a disconnected/hung pass must
|
|
124
|
+
* nevertheless not accumulate an unbounded tail of Stop requests.
|
|
125
|
+
*/
|
|
126
|
+
class TurnEndQueue {
|
|
127
|
+
running = false;
|
|
128
|
+
pending = [];
|
|
129
|
+
enqueue(work, waitMs = TURN_END_QUEUE_WAIT_MS) {
|
|
130
|
+
if (this.pending.length >= 1) {
|
|
131
|
+
return Promise.reject(new Error("turn_end queue is busy"));
|
|
132
|
+
}
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const item = {
|
|
135
|
+
work,
|
|
136
|
+
resolve,
|
|
137
|
+
reject,
|
|
138
|
+
timer: setTimeout(() => {
|
|
139
|
+
const index = this.pending.indexOf(item);
|
|
140
|
+
if (index === -1)
|
|
141
|
+
return;
|
|
142
|
+
this.pending.splice(index, 1);
|
|
143
|
+
reject(new Error("turn_end queue wait timed out"));
|
|
144
|
+
}, waitMs),
|
|
145
|
+
};
|
|
146
|
+
item.timer.unref();
|
|
147
|
+
this.pending.push(item);
|
|
148
|
+
this.drain();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
drain() {
|
|
152
|
+
if (this.running)
|
|
153
|
+
return;
|
|
154
|
+
const item = this.pending.shift();
|
|
155
|
+
if (!item)
|
|
156
|
+
return;
|
|
157
|
+
clearTimeout(item.timer);
|
|
158
|
+
this.running = true;
|
|
159
|
+
void item
|
|
160
|
+
.work()
|
|
161
|
+
.then(item.resolve, item.reject)
|
|
162
|
+
.finally(() => {
|
|
163
|
+
this.running = false;
|
|
164
|
+
this.drain();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
reset() {
|
|
168
|
+
for (const item of this.pending.splice(0)) {
|
|
169
|
+
clearTimeout(item.timer);
|
|
170
|
+
item.reject(new Error("turn_end queue reset"));
|
|
171
|
+
}
|
|
172
|
+
this.running = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const turnEndQueue = new TurnEndQueue();
|
|
176
|
+
const pendingTurnEndDeliveries = new Map();
|
|
177
|
+
/** In-flight Stop-hook passes, keyed by cwd — see `runTurnEndForIpc` (#1274). */
|
|
178
|
+
const inFlightIpcTurnEnds = new Map();
|
|
108
179
|
/**
|
|
109
180
|
* Run pi-lens's real turn_end over the files edited this "turn". The handler
|
|
110
181
|
* reads edited files from turn-state, so we register the caller-supplied files
|
|
111
182
|
* first (a full-file range, importsChanged=true so dep/knip re-check broadly).
|
|
112
183
|
*/
|
|
113
|
-
|
|
184
|
+
async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
|
|
114
185
|
const ctx = await getMcpSessionContext();
|
|
115
186
|
const host = createMcpHost(undefined, cwd);
|
|
116
187
|
let registered = 0;
|
|
@@ -123,9 +194,15 @@ export async function runTurnEnd(cwd, files = []) {
|
|
|
123
194
|
catch {
|
|
124
195
|
continue; // unreadable / deleted — skip
|
|
125
196
|
}
|
|
126
|
-
ctx.cacheManager.addModifiedRange(abs, { start: 1, end: lineCount }, true, cwd,
|
|
197
|
+
ctx.cacheManager.addModifiedRange(abs, { start: 1, end: lineCount }, true, cwd, MCP_TURN_STATE_OWNER_ID, "mcp");
|
|
127
198
|
registered++;
|
|
128
199
|
}
|
|
200
|
+
const owner = {
|
|
201
|
+
kind: "mcp",
|
|
202
|
+
id: MCP_TURN_STATE_OWNER_ID,
|
|
203
|
+
pid: process.pid,
|
|
204
|
+
lastSeen: new Date().toISOString(),
|
|
205
|
+
};
|
|
129
206
|
await handleTurnEnd({
|
|
130
207
|
ctxCwd: cwd,
|
|
131
208
|
getFlag: host.getFlag,
|
|
@@ -136,12 +213,143 @@ export async function runTurnEnd(cwd, files = []) {
|
|
|
136
213
|
deadCodeClients: ctx.clients.deadCodeClients,
|
|
137
214
|
depChecker: ctx.clients.depChecker,
|
|
138
215
|
testRunnerClient: ctx.clients.testRunnerClient,
|
|
216
|
+
owner,
|
|
139
217
|
resetLSPService,
|
|
140
218
|
resetFormatService,
|
|
141
219
|
});
|
|
220
|
+
const outcome = deferredDelivery
|
|
221
|
+
? {
|
|
222
|
+
turnEnd: joinMessages(peekTurnEndFindings(ctx.cacheManager, cwd)),
|
|
223
|
+
tests: joinMessages(peekTestFindings(ctx.cacheManager, cwd)),
|
|
224
|
+
filesRegistered: registered,
|
|
225
|
+
}
|
|
226
|
+
: {
|
|
227
|
+
turnEnd: joinMessages(consumeTurnEndFindings(ctx.cacheManager, cwd)),
|
|
228
|
+
tests: joinMessages(consumeTestFindings(ctx.cacheManager, cwd)),
|
|
229
|
+
filesRegistered: registered,
|
|
230
|
+
};
|
|
142
231
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
232
|
+
outcome,
|
|
233
|
+
commit: () => {
|
|
234
|
+
if (!deferredDelivery)
|
|
235
|
+
return;
|
|
236
|
+
consumeTurnEndFindings(ctx.cacheManager, cwd);
|
|
237
|
+
consumeTestFindings(ctx.cacheManager, cwd);
|
|
238
|
+
},
|
|
146
239
|
};
|
|
147
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Run a normal MCP turn_end. Its caller owns the tool response, so the
|
|
243
|
+
* historical consume-on-return behavior remains unchanged.
|
|
244
|
+
*/
|
|
245
|
+
export function runTurnEnd(cwd, files = []) {
|
|
246
|
+
return turnEndQueue.enqueue(async () => (await runTurnEndNow(cwd, files)).outcome);
|
|
247
|
+
}
|
|
248
|
+
/** A result with no findings needs no durable delivery transaction. */
|
|
249
|
+
function hasTurnEndFindings(outcome) {
|
|
250
|
+
return Boolean(outcome.turnEnd || outcome.tests);
|
|
251
|
+
}
|
|
252
|
+
/** Drop expired deliveries, then the oldest ones over the cap. Never commits. */
|
|
253
|
+
function prunePendingTurnEndDeliveries(now = Date.now()) {
|
|
254
|
+
for (const [deliveryId, delivery] of pendingTurnEndDeliveries) {
|
|
255
|
+
if (now - delivery.createdAt >= TURN_END_DELIVERY_TTL_MS) {
|
|
256
|
+
pendingTurnEndDeliveries.delete(deliveryId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Map iteration is insertion-ordered, so the head is the oldest entry.
|
|
260
|
+
while (pendingTurnEndDeliveries.size > TURN_END_DELIVERY_MAX) {
|
|
261
|
+
const oldest = pendingTurnEndDeliveries.keys().next();
|
|
262
|
+
if (oldest.done)
|
|
263
|
+
break;
|
|
264
|
+
pendingTurnEndDeliveries.delete(oldest.value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function pendingForCwd(cwd) {
|
|
268
|
+
for (const [deliveryId, delivery] of pendingTurnEndDeliveries) {
|
|
269
|
+
if (delivery.cwd === cwd)
|
|
270
|
+
return [deliveryId, delivery];
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Run the Stop-hook pass as a durable delivery transaction. The result is
|
|
276
|
+
* acknowledged through a separate one-shot IPC request. Until that request
|
|
277
|
+
* arrives, the findings cache is deliberately left unconsumed; a client
|
|
278
|
+
* timeout, socket close, or server restart therefore leaves the result for a
|
|
279
|
+
* later Stop request instead of losing it between execution and reply.
|
|
280
|
+
*/
|
|
281
|
+
export function runTurnEndForIpc(cwd) {
|
|
282
|
+
// #1274: the Stop-hook request carries no files and no sessionId, so two
|
|
283
|
+
// overlapping ones are byte-identical and a second pass would be pure waste
|
|
284
|
+
// — worse, the queue only holds ONE waiter, so a third Stop is rejected
|
|
285
|
+
// outright. Identical in-flight requests share the one pass and one
|
|
286
|
+
// deliveryId; the ack is idempotent-by-capability, so the loser of the race
|
|
287
|
+
// simply finds the delivery already committed.
|
|
288
|
+
const inFlight = inFlightIpcTurnEnds.get(cwd);
|
|
289
|
+
if (inFlight)
|
|
290
|
+
return inFlight;
|
|
291
|
+
const run = runTurnEndForIpcNow(cwd).finally(() => {
|
|
292
|
+
inFlightIpcTurnEnds.delete(cwd);
|
|
293
|
+
});
|
|
294
|
+
inFlightIpcTurnEnds.set(cwd, run);
|
|
295
|
+
return run;
|
|
296
|
+
}
|
|
297
|
+
function runTurnEndForIpcNow(cwd) {
|
|
298
|
+
return turnEndQueue.enqueue(async () => {
|
|
299
|
+
prunePendingTurnEndDeliveries();
|
|
300
|
+
const pending = pendingForCwd(cwd);
|
|
301
|
+
if (pending) {
|
|
302
|
+
return {
|
|
303
|
+
outcome: pending[1].transaction.outcome,
|
|
304
|
+
deliveryId: pending[0],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const ctx = await getMcpSessionContext();
|
|
308
|
+
const cachedOutcome = {
|
|
309
|
+
turnEnd: joinMessages(peekTurnEndFindings(ctx.cacheManager, cwd)),
|
|
310
|
+
tests: joinMessages(peekTestFindings(ctx.cacheManager, cwd)),
|
|
311
|
+
filesRegistered: 0,
|
|
312
|
+
};
|
|
313
|
+
const transaction = hasTurnEndFindings(cachedOutcome)
|
|
314
|
+
? {
|
|
315
|
+
outcome: cachedOutcome,
|
|
316
|
+
commit: () => {
|
|
317
|
+
consumeTurnEndFindings(ctx.cacheManager, cwd);
|
|
318
|
+
consumeTestFindings(ctx.cacheManager, cwd);
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
: await runTurnEndNow(cwd, [], true);
|
|
322
|
+
if (!hasTurnEndFindings(transaction.outcome)) {
|
|
323
|
+
return { outcome: transaction.outcome };
|
|
324
|
+
}
|
|
325
|
+
const deliveryId = crypto.randomUUID();
|
|
326
|
+
pendingTurnEndDeliveries.set(deliveryId, {
|
|
327
|
+
cwd,
|
|
328
|
+
transaction,
|
|
329
|
+
createdAt: Date.now(),
|
|
330
|
+
});
|
|
331
|
+
prunePendingTurnEndDeliveries();
|
|
332
|
+
return { outcome: transaction.outcome, deliveryId };
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/** Commit exactly one admitted delivery capability. */
|
|
336
|
+
export function acknowledgeTurnEnd(cwd, deliveryId) {
|
|
337
|
+
prunePendingTurnEndDeliveries();
|
|
338
|
+
const delivery = pendingTurnEndDeliveries.get(deliveryId);
|
|
339
|
+
if (!delivery || delivery.cwd !== cwd)
|
|
340
|
+
return false;
|
|
341
|
+
// #1274: commit BEFORE dropping the capability. Deleting first meant a
|
|
342
|
+
// throwing consume destroyed the only handle to the findings — precisely the
|
|
343
|
+
// loss this two-phase protocol exists to prevent, reintroduced on the error
|
|
344
|
+
// path. Leaving the entry in place on a throw keeps the delivery re-fetchable
|
|
345
|
+
// by a later Stop.
|
|
346
|
+
delivery.transaction.commit();
|
|
347
|
+
pendingTurnEndDeliveries.delete(deliveryId);
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
/** Test hook — drop pending requests so one failed test cannot wedge the rest. */
|
|
351
|
+
export function _resetTurnEndChain() {
|
|
352
|
+
turnEndQueue.reset();
|
|
353
|
+
pendingTurnEndDeliveries.clear();
|
|
354
|
+
inFlightIpcTurnEnds.clear();
|
|
355
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic memory-attribution sample (#1123 item 2, expanded on top of item 1's
|
|
3
|
+
* loop_block windowing — the #1126 sizing study's "instrumentation the next
|
|
4
|
+
* report should add" list is this module's requirements).
|
|
5
|
+
*
|
|
6
|
+
* The #1126 sizing study diagnosed a 1.37 GB instance from code + serialized
|
|
7
|
+
* artifacts alone, because nothing recorded a per-subsystem trajectory over
|
|
8
|
+
* time — the same detection-without-attribution gap loop_block had before
|
|
9
|
+
* #1122/#1125. This module answers "what is resident right now, broken down
|
|
10
|
+
* by subsystem" as a single ndjson `memory_sample` `latency.log` line, emitted
|
|
11
|
+
* every `MEMORY_SAMPLE_TURN_INTERVAL` turns (cheap enough not to need finer
|
|
12
|
+
* throttling).
|
|
13
|
+
*
|
|
14
|
+
* HARD CONSTRAINT: every field here is an O(1) `Map`/array `.size`/`.length`
|
|
15
|
+
* read, or a `process.memoryUsage()` call — nothing here iterates a large
|
|
16
|
+
* structure's contents, and nothing takes a heap snapshot. `PI_LENS_DEBUG_HEAP`
|
|
17
|
+
* (item 4's memory sibling) is a SEPARATE, explicitly opt-in mechanism, out of
|
|
18
|
+
* scope here.
|
|
19
|
+
*
|
|
20
|
+
* WASM linear-memory byte length gap (documented, not silently dropped): the
|
|
21
|
+
* #1126 spec asked for `Module.wasmMemory.buffer.byteLength` off the
|
|
22
|
+
* web-tree-sitter parser runtime. Inspecting the installed web-tree-sitter
|
|
23
|
+
* 0.25.10 package (see `clients/tree-sitter-client.ts#getRuntimeStats`'s
|
|
24
|
+
* docstring) confirmed that value is not reachable through any public export
|
|
25
|
+
* — only through package-internal reflection or by overriding Emscripten's
|
|
26
|
+
* `wasmMemory` module option with a hand-built `WebAssembly.Memory` (a
|
|
27
|
+
* stability risk for an observability-only feature, given a memory-import
|
|
28
|
+
* mismatch would break structural analysis entirely). `process.memoryUsage()`
|
|
29
|
+
* `arrayBuffers` is used as the process-wide proxy instead — WASM linear
|
|
30
|
+
* memory backs an ArrayBuffer, so it is already included there.
|
|
31
|
+
*/
|
|
32
|
+
import { getSharedTreeSitterClient } from "./tree-sitter-shared.js";
|
|
33
|
+
import { getReviewGraphWorkspaceCacheSnapshot } from "./review-graph/builder.js";
|
|
34
|
+
import { getDispatchCascadeCacheStats } from "./dispatch/integration.js";
|
|
35
|
+
/** Every N turns, emit one `memory_sample` latency.log line (#1123 item 2). */
|
|
36
|
+
export const MEMORY_SAMPLE_TURN_INTERVAL = 10;
|
|
37
|
+
/** True on turn 10, 20, 30, ... — never on turn 0 (nothing meaningful is
|
|
38
|
+
* resident yet at session start). Pure so the cadence is unit-testable
|
|
39
|
+
* without driving a real turn loop. */
|
|
40
|
+
export function shouldEmitMemorySample(turnIndex) {
|
|
41
|
+
return turnIndex > 0 && turnIndex % MEMORY_SAMPLE_TURN_INTERVAL === 0;
|
|
42
|
+
}
|
|
43
|
+
/** PURE: reshape Node's `process.memoryUsage()` into this module's field
|
|
44
|
+
* names — testable without touching the real process. */
|
|
45
|
+
export function toMemoryProcessUsage(mem) {
|
|
46
|
+
return {
|
|
47
|
+
rssBytes: mem.rss,
|
|
48
|
+
heapUsedBytes: mem.heapUsed,
|
|
49
|
+
heapTotalBytes: mem.heapTotal,
|
|
50
|
+
externalBytes: mem.external,
|
|
51
|
+
arrayBuffersBytes: mem.arrayBuffers,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Live O(1)/O(bounded-cache-size) subsystem counters. Impure (reads
|
|
56
|
+
* process-global singletons) but every individual read is a `.size`/`.length`
|
|
57
|
+
* access — see the module docstring's hard constraint.
|
|
58
|
+
*/
|
|
59
|
+
export function collectMemorySampleSubsystems(wordIndex) {
|
|
60
|
+
const reviewGraph = getReviewGraphWorkspaceCacheSnapshot();
|
|
61
|
+
const treeSitterClient = getSharedTreeSitterClient();
|
|
62
|
+
const treeSitter = treeSitterClient
|
|
63
|
+
? (() => {
|
|
64
|
+
const runtimeStats = treeSitterClient.getRuntimeStats();
|
|
65
|
+
const cacheStats = treeSitterClient.getParseCacheStats();
|
|
66
|
+
return {
|
|
67
|
+
...runtimeStats,
|
|
68
|
+
treeCacheSize: cacheStats.size,
|
|
69
|
+
treeCacheMaxSize: cacheStats.maxSize,
|
|
70
|
+
treeCacheTotalBytes: cacheStats.totalBytes,
|
|
71
|
+
};
|
|
72
|
+
})()
|
|
73
|
+
: null;
|
|
74
|
+
const dispatchCaches = getDispatchCascadeCacheStats();
|
|
75
|
+
return {
|
|
76
|
+
reviewGraph,
|
|
77
|
+
wordIndex: wordIndex
|
|
78
|
+
? {
|
|
79
|
+
docs: wordIndex.docLengths.size,
|
|
80
|
+
postings: wordIndex.postings.size,
|
|
81
|
+
forwardEntries: wordIndex.forward?.size ?? 0,
|
|
82
|
+
}
|
|
83
|
+
: null,
|
|
84
|
+
treeSitter,
|
|
85
|
+
dispatchCaches,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Assemble one full sample. `mem` is injectable for tests; defaults to a
|
|
89
|
+
* live `process.memoryUsage()` read. */
|
|
90
|
+
export function buildMemorySample(wordIndex, mem = process.memoryUsage()) {
|
|
91
|
+
return {
|
|
92
|
+
process: toMemoryProcessUsage(mem),
|
|
93
|
+
subsystems: collectMemorySampleSubsystems(wordIndex),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const toMb = (bytes) => Math.round(bytes / (1024 * 1024));
|
|
97
|
+
/**
|
|
98
|
+
* PURE: one compact `/lens-health` line reusing the same sample the periodic
|
|
99
|
+
* latency.log emitter builds — RSS + heap/external, plus the two subsystem
|
|
100
|
+
* numbers most useful for spotting an attribution outlier at a glance: the
|
|
101
|
+
* tree-sitter tree-cache byte total (the only subsystem counter here that is
|
|
102
|
+
* ALREADY in bytes, so it's directly comparable to RSS) and the review-graph
|
|
103
|
+
* node/edge counts (the multiple-graph-copies question #1126 called out).
|
|
104
|
+
* Follows #1125's health-line style (a single summary line, no per-subsystem
|
|
105
|
+
* breakdown table — that's what latency.log's `memory_sample` is for).
|
|
106
|
+
*/
|
|
107
|
+
export function formatMemoryHealthLine(sample) {
|
|
108
|
+
const { process: proc, subsystems } = sample;
|
|
109
|
+
const treeCache = subsystems.treeSitter
|
|
110
|
+
? `${toMb(subsystems.treeSitter.treeCacheTotalBytes)}MB (${subsystems.treeSitter.treeCacheSize} trees)`
|
|
111
|
+
: "n/a";
|
|
112
|
+
const graph = `${subsystems.reviewGraph.totalNodes}n/${subsystems.reviewGraph.totalEdges}e (${subsystems.reviewGraph.cacheEntries} cwd)`;
|
|
113
|
+
return (`Memory: RSS ${toMb(proc.rssBytes)}MB · heap ${toMb(proc.heapUsedBytes)}/${toMb(proc.heapTotalBytes)}MB` +
|
|
114
|
+
` · external ${toMb(proc.externalBytes)}MB · tree-sitter cache ${treeCache} · review-graph ${graph}`);
|
|
115
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* These are observational metrics — they inform the human in session summary,
|
|
11
11
|
* they don't gate or interrupt the agent mid-task.
|
|
12
12
|
*/
|
|
13
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
13
14
|
import * as fs from "node:fs";
|
|
14
15
|
import * as path from "node:path";
|
|
15
16
|
// --- Client ---
|
|
@@ -18,7 +19,7 @@ export class MetricsClient {
|
|
|
18
19
|
fileBaselines = new Map();
|
|
19
20
|
constructor(verbose = false) {
|
|
20
21
|
this.log = verbose
|
|
21
|
-
? (
|
|
22
|
+
? createSubsystemLogger("metrics")
|
|
22
23
|
: () => { };
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
@@ -10,6 +10,7 @@ import { execSync } from "node:child_process";
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { getProjectDataDir } from "./file-utils.js";
|
|
13
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
13
14
|
// --- Constants ---
|
|
14
15
|
const HISTORY_FILE = "metrics-history.json";
|
|
15
16
|
const MAX_HISTORY_PER_FILE = 20;
|
|
@@ -82,7 +83,7 @@ export function saveHistory(history) {
|
|
|
82
83
|
}
|
|
83
84
|
history.capturedAt = new Date().toISOString();
|
|
84
85
|
const historyPath = path.join(historyDir, "metrics-history.json");
|
|
85
|
-
|
|
86
|
+
writeFileAtomic(historyPath, JSON.stringify(history, null, 2));
|
|
86
87
|
}
|
|
87
88
|
// In-memory cache to avoid loading/saving on every capture
|
|
88
89
|
let pendingHistory = null;
|