@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
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import { createFileTime } from "./file-time.js";
|
|
14
|
+
import { hashDiagnosticContent } from "./lsp/diagnostic-binding.js";
|
|
14
15
|
import { normalizeFilePath } from "./path-utils.js";
|
|
15
16
|
import { logReadGuardEvent } from "./read-guard-logger.js";
|
|
16
|
-
const READ_GUARD_STATE_VERSION = 1;
|
|
17
|
+
export const READ_GUARD_STATE_VERSION = 1;
|
|
17
18
|
// --- Constants ---
|
|
18
19
|
const DEFAULT_CONFIG = {
|
|
19
20
|
enabled: true,
|
|
@@ -28,6 +29,66 @@ const DEFAULT_CONFIG = {
|
|
|
28
29
|
const OWN_EDIT_STALE_GRACE_MS = Math.max(0, Number.parseInt(process.env.PI_LENS_READ_GUARD_OWN_EDIT_GRACE_MS ?? "120000", 10) || 120000);
|
|
29
30
|
/** Avoid hashing very large reads in the hot path. */
|
|
30
31
|
const READ_HASH_MAX_LINES = Math.max(0, Number.parseInt(process.env.PI_LENS_READ_GUARD_HASH_MAX_LINES ?? "3000", 10) || 3000);
|
|
32
|
+
/**
|
|
33
|
+
* Content bindings are defense in depth on a read-adjacent hot path. Cap the
|
|
34
|
+
* synchronous disk read so bridge registration never hashes an unbounded file.
|
|
35
|
+
*/
|
|
36
|
+
const READ_BINDING_MAX_BYTES = 4 * 1024 * 1024;
|
|
37
|
+
const READ_GUARD_MAX_FILES = 256;
|
|
38
|
+
// Unconsumed reads remain valid until edit or session end, but this high
|
|
39
|
+
// sanity cap prevents a read-only session from growing without bound.
|
|
40
|
+
const READ_GUARD_MAX_UNCONSUMED_FILES = 4096;
|
|
41
|
+
const READ_GUARD_IDLE_EVICT_MS_DEFAULT = 30 * 60_000;
|
|
42
|
+
export function captureReadContentBinding(filePath, offset, limit) {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.statSync(filePath).size > READ_BINDING_MAX_BYTES)
|
|
45
|
+
return undefined;
|
|
46
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
47
|
+
// Allocate at most the classification prefix first. For range bindings,
|
|
48
|
+
// splitting then stops at the requested range end rather than the EOF.
|
|
49
|
+
const lines = splitLinesThrough(content, READ_HASH_MAX_LINES + 1);
|
|
50
|
+
if (lines.length <= READ_HASH_MAX_LINES) {
|
|
51
|
+
return {
|
|
52
|
+
hash: hashDiagnosticContent(content),
|
|
53
|
+
fullFile: true,
|
|
54
|
+
offset: 1,
|
|
55
|
+
limit: lines.length,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const scopedOffset = Math.max(1, offset);
|
|
59
|
+
const scopedLimit = Math.min(Math.max(0, limit), READ_HASH_MAX_LINES);
|
|
60
|
+
if (scopedLimit === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
const rangeLines = splitLinesThrough(content, scopedOffset - 1 + scopedLimit);
|
|
63
|
+
const scopedContent = rangeLines
|
|
64
|
+
.slice(scopedOffset - 1, scopedOffset - 1 + scopedLimit)
|
|
65
|
+
.join("\n");
|
|
66
|
+
return {
|
|
67
|
+
hash: hashDiagnosticContent(scopedContent),
|
|
68
|
+
fullFile: false,
|
|
69
|
+
offset: scopedOffset,
|
|
70
|
+
limit: scopedLimit,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function _currentContentMatchesBindingForTests(filePath, binding) {
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
80
|
+
const boundContent = binding.fullFile
|
|
81
|
+
? content
|
|
82
|
+
: splitLines(content)
|
|
83
|
+
.slice(binding.offset - 1, binding.offset - 1 + binding.limit)
|
|
84
|
+
.join("\n");
|
|
85
|
+
return hashDiagnosticContent(boundContent) === binding.hash;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const currentContentMatchesBinding = _currentContentMatchesBindingForTests;
|
|
31
92
|
// Adaptive relocation window (findRelocation). A globally-unique hash-sequence
|
|
32
93
|
// match always wins; when the content is duplicated elsewhere, we fall back to
|
|
33
94
|
// a match that is unique WITHIN this window of the original position. The window
|
|
@@ -40,6 +101,28 @@ const RELOCATION_WINDOW_MAX = Math.max(RELOCATION_WINDOW_MIN, Number.parseInt(pr
|
|
|
40
101
|
function splitLines(text) {
|
|
41
102
|
return text.split(/\r?\n/);
|
|
42
103
|
}
|
|
104
|
+
/** `splitLines` semantics, but without scanning/allocating beyond `maxLines`. */
|
|
105
|
+
function splitLinesThrough(text, maxLines) {
|
|
106
|
+
if (maxLines <= 0)
|
|
107
|
+
return [];
|
|
108
|
+
const lines = [];
|
|
109
|
+
let start = 0;
|
|
110
|
+
while (lines.length < maxLines) {
|
|
111
|
+
const newline = text.indexOf("\n", start);
|
|
112
|
+
if (newline === -1) {
|
|
113
|
+
lines.push(text.slice(start));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
const end = newline > start && text[newline - 1] === "\r" ? newline - 1 : newline;
|
|
117
|
+
lines.push(text.slice(start, end));
|
|
118
|
+
start = newline + 1;
|
|
119
|
+
if (start === text.length && lines.length < maxLines) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
43
126
|
export function lineContentHash(line) {
|
|
44
127
|
// FNV-1a over whitespace-stripped content. This treats no-op formatter/touch
|
|
45
128
|
// changes as still-valid context while detecting semantic line changes.
|
|
@@ -118,6 +201,10 @@ export class ReadGuard {
|
|
|
118
201
|
config;
|
|
119
202
|
reads = new Map();
|
|
120
203
|
edits = new Map();
|
|
204
|
+
fileLastUsed = new Map();
|
|
205
|
+
fileIdleTimers = new Map();
|
|
206
|
+
/** Reads remain behavior-gating until the corresponding edit is published. */
|
|
207
|
+
consumedReadFiles = new Set();
|
|
121
208
|
fileTime;
|
|
122
209
|
exemptions = new Set(); // One-time exemptions via /lens-allow-edit
|
|
123
210
|
pendingCreations = new Map();
|
|
@@ -147,6 +234,66 @@ export class ReadGuard {
|
|
|
147
234
|
key(filePath) {
|
|
148
235
|
return normalizeFilePath(filePath);
|
|
149
236
|
}
|
|
237
|
+
idleEvictMs() {
|
|
238
|
+
const value = Number.parseInt(process.env.PI_LENS_READ_GUARD_IDLE_EVICT_MS ?? "", 10);
|
|
239
|
+
return Number.isSafeInteger(value) && value > 0 ? value : READ_GUARD_IDLE_EVICT_MS_DEFAULT;
|
|
240
|
+
}
|
|
241
|
+
clearFileTimer(filePath) {
|
|
242
|
+
const timer = this.fileIdleTimers.get(filePath);
|
|
243
|
+
if (timer)
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
this.fileIdleTimers.delete(filePath);
|
|
246
|
+
}
|
|
247
|
+
evictFile(filePath) {
|
|
248
|
+
this.clearFileTimer(filePath);
|
|
249
|
+
this.reads.delete(filePath);
|
|
250
|
+
this.edits.delete(filePath);
|
|
251
|
+
this.fileLastUsed.delete(filePath);
|
|
252
|
+
this.consumedReadFiles.delete(filePath);
|
|
253
|
+
this.writtenThisSession.delete(filePath);
|
|
254
|
+
}
|
|
255
|
+
touchFile(filePath) {
|
|
256
|
+
const now = Date.now();
|
|
257
|
+
this.fileLastUsed.set(filePath, now);
|
|
258
|
+
this.clearFileTimer(filePath);
|
|
259
|
+
// An outstanding read is enforcement state, not a rebuildable cache entry.
|
|
260
|
+
// It must survive idle time and file-cap pressure until the edit consumes it.
|
|
261
|
+
if (this.reads.has(filePath) && !this.consumedReadFiles.has(filePath))
|
|
262
|
+
return;
|
|
263
|
+
const stamp = now;
|
|
264
|
+
const timer = setTimeout(() => {
|
|
265
|
+
if (this.fileLastUsed.get(filePath) !== stamp)
|
|
266
|
+
return;
|
|
267
|
+
// Never turn an outstanding read into a zero-read block through idle
|
|
268
|
+
// eviction. Only consumed reads and rebuildable edit history may expire.
|
|
269
|
+
if (this.reads.has(filePath) && !this.consumedReadFiles.has(filePath))
|
|
270
|
+
return;
|
|
271
|
+
this.evictFile(filePath);
|
|
272
|
+
}, this.idleEvictMs());
|
|
273
|
+
timer.unref?.();
|
|
274
|
+
this.fileIdleTimers.set(filePath, timer);
|
|
275
|
+
}
|
|
276
|
+
enforceFileCap() {
|
|
277
|
+
while (this.reads.size > READ_GUARD_MAX_FILES) {
|
|
278
|
+
const victim = [...this.reads.keys()]
|
|
279
|
+
.filter((filePath) => this.consumedReadFiles.has(filePath))
|
|
280
|
+
.sort((a, b) => (this.fileLastUsed.get(a) ?? 0) - (this.fileLastUsed.get(b) ?? 0))[0];
|
|
281
|
+
if (!victim)
|
|
282
|
+
break;
|
|
283
|
+
this.evictFile(victim);
|
|
284
|
+
}
|
|
285
|
+
while (this.reads.size > READ_GUARD_MAX_UNCONSUMED_FILES) {
|
|
286
|
+
const victim = [...this.reads.keys()]
|
|
287
|
+
.filter((filePath) => !this.consumedReadFiles.has(filePath))
|
|
288
|
+
.sort((a, b) => (this.fileLastUsed.get(a) ?? 0) -
|
|
289
|
+
(this.fileLastUsed.get(b) ?? 0))[0];
|
|
290
|
+
if (!victim)
|
|
291
|
+
break;
|
|
292
|
+
// This is a normal read miss: a later edit must require a fresh read,
|
|
293
|
+
// never silently allow and never become a permanent hard-block.
|
|
294
|
+
this.evictFile(victim);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
150
297
|
// --- Public API ---
|
|
151
298
|
/**
|
|
152
299
|
* Record that a file was read.
|
|
@@ -161,8 +308,11 @@ export class ReadGuard {
|
|
|
161
308
|
captureLineHashes(filePath, record.effectiveOffset, record.effectiveLimit),
|
|
162
309
|
};
|
|
163
310
|
const arr = this.reads.get(storedRecord.filePath) ?? [];
|
|
311
|
+
this.consumedReadFiles.delete(storedRecord.filePath);
|
|
164
312
|
arr.push(storedRecord);
|
|
165
313
|
this.reads.set(storedRecord.filePath, arr);
|
|
314
|
+
this.touchFile(storedRecord.filePath);
|
|
315
|
+
this.enforceFileCap();
|
|
166
316
|
logReadGuardEvent({
|
|
167
317
|
event: "read_recorded",
|
|
168
318
|
sessionId: this.sessionId,
|
|
@@ -181,6 +331,9 @@ export class ReadGuard {
|
|
|
181
331
|
writeIndex: storedRecord.writeIndex,
|
|
182
332
|
readCountForFile: arr.length,
|
|
183
333
|
hashLineCount: Object.keys(storedRecord.lineHashes ?? {}).length,
|
|
334
|
+
...(storedRecord.source !== undefined && {
|
|
335
|
+
source: storedRecord.source,
|
|
336
|
+
}),
|
|
184
337
|
},
|
|
185
338
|
});
|
|
186
339
|
// Also update FileTime stamp for this file
|
|
@@ -228,6 +381,8 @@ export class ReadGuard {
|
|
|
228
381
|
// Canonicalize once: every map lookup below (and every private helper this
|
|
229
382
|
// passes filePath to) must agree with how recordRead keyed the read.
|
|
230
383
|
filePath = this.key(filePath);
|
|
384
|
+
if (this.reads.has(filePath) || this.edits.has(filePath))
|
|
385
|
+
this.touchFile(filePath);
|
|
231
386
|
// Check exemptions
|
|
232
387
|
if (this.exemptions.has(filePath)) {
|
|
233
388
|
this.exemptions.delete(filePath); // One-time use
|
|
@@ -271,6 +426,19 @@ export class ReadGuard {
|
|
|
271
426
|
});
|
|
272
427
|
return verdict;
|
|
273
428
|
}
|
|
429
|
+
const lastBoundRead = [...fileReads]
|
|
430
|
+
.reverse()
|
|
431
|
+
.find((read) => read.contentBinding !== undefined);
|
|
432
|
+
if (lastBoundRead?.contentBinding &&
|
|
433
|
+
!currentContentMatchesBinding(filePath, lastBoundRead.contentBinding)) {
|
|
434
|
+
const verdict = this.blockOrWarn("file-modified", `🔄 RETRYABLE — File modified since read\n\nYou last read \`${filePath}\` at ${new Date(lastBoundRead.timestamp).toISOString()}.\nThe file content no longer matches the bridge-recorded read.\n\nYour mental model is out of sync with the actual file content.\nTo proceed:\n 1. Re-read the file: \`read path="${filePath}"\``, undefined, effectiveMode);
|
|
435
|
+
this.recordVerdict(filePath, "edit", touchedLines, verdict, {
|
|
436
|
+
reasonKind: "file_modified",
|
|
437
|
+
lastReadTimestamp: lastBoundRead.timestamp,
|
|
438
|
+
contentBindingMismatch: true,
|
|
439
|
+
});
|
|
440
|
+
return verdict;
|
|
441
|
+
}
|
|
274
442
|
// 2. FileTime check (actual staleness)
|
|
275
443
|
let ignoredOwnEditStaleness = false;
|
|
276
444
|
let ignoredHashStaleness = false;
|
|
@@ -315,7 +483,9 @@ export class ReadGuard {
|
|
|
315
483
|
// If oldText was resolved (content-verified), the model demonstrably
|
|
316
484
|
// knew the content it's replacing — line drift from prior edits in
|
|
317
485
|
// the session is the likely cause. Downgrade to warn rather than block.
|
|
318
|
-
const outOfRangeMode = options?.oldTextResolved
|
|
486
|
+
const outOfRangeMode = options?.oldTextResolved
|
|
487
|
+
? "warn"
|
|
488
|
+
: effectiveMode;
|
|
319
489
|
const verdict = this.blockOrWarn("out-of-range", `🔄 RETRYABLE — Edit outside read range\n\nYou read \`${filePath}\` lines ${lastRead.effectiveOffset}-${lastReadEnd}${lastRead.enclosingSymbol ? ` (${lastRead.enclosingSymbol.kind} \`${lastRead.enclosingSymbol.name}\`)` : ""}, but your edit touches lines ${editStart}-${editEnd}.\n\nRead the relevant section first, then retry the edit:\n \`read path="${filePath}" offset=${Math.max(1, editStart - 5)} limit=${Math.min(30, editEnd - editStart + 10)}\``, {
|
|
320
490
|
editRange: range,
|
|
321
491
|
readRanges: fileReads.map((r) => ({
|
|
@@ -435,6 +605,10 @@ export class ReadGuard {
|
|
|
435
605
|
filePath = this.key(filePath);
|
|
436
606
|
this.fileTime.read(filePath);
|
|
437
607
|
this.writtenThisSession.add(filePath);
|
|
608
|
+
if (this.reads.has(filePath))
|
|
609
|
+
this.consumedReadFiles.add(filePath);
|
|
610
|
+
this.touchFile(filePath);
|
|
611
|
+
this.enforceFileCap();
|
|
438
612
|
const creation = this.pendingCreations.get(filePath);
|
|
439
613
|
if (creation) {
|
|
440
614
|
this.pendingCreations.delete(filePath);
|
|
@@ -496,7 +670,10 @@ export class ReadGuard {
|
|
|
496
670
|
* Get all read records for a file (for debugging).
|
|
497
671
|
*/
|
|
498
672
|
getReadHistory(filePath) {
|
|
499
|
-
|
|
673
|
+
const key = this.key(filePath);
|
|
674
|
+
if (this.reads.has(key))
|
|
675
|
+
this.touchFile(key);
|
|
676
|
+
return this.reads.get(key) ?? [];
|
|
500
677
|
}
|
|
501
678
|
/**
|
|
502
679
|
* Snapshot the read-set for persistence across a session resume (#1041).
|
|
@@ -579,7 +756,10 @@ export class ReadGuard {
|
|
|
579
756
|
* Get all edit records for a file (for debugging).
|
|
580
757
|
*/
|
|
581
758
|
getEditHistory(filePath) {
|
|
582
|
-
|
|
759
|
+
const key = this.key(filePath);
|
|
760
|
+
if (this.edits.has(key))
|
|
761
|
+
this.touchFile(key);
|
|
762
|
+
return this.edits.get(key) ?? [];
|
|
583
763
|
}
|
|
584
764
|
// --- Private helpers ---
|
|
585
765
|
injectCreationRead(filePath, turnIndex, writeIndex) {
|
|
@@ -926,6 +1106,7 @@ export class ReadGuard {
|
|
|
926
1106
|
return { action: "allow" };
|
|
927
1107
|
}
|
|
928
1108
|
recordEdit(filePath, tool, touchedLines, verdict) {
|
|
1109
|
+
this.touchFile(filePath);
|
|
929
1110
|
const arr = this.edits.get(filePath) ?? [];
|
|
930
1111
|
arr.push({
|
|
931
1112
|
filePath,
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
* process/pidusage involvement — mirrors the pure/impure split in
|
|
44
44
|
* clients/instance-reaper.ts (`decideOrphanReaping` vs `sweepOrphans`).
|
|
45
45
|
*/
|
|
46
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
47
46
|
import * as path from "node:path";
|
|
48
47
|
import pidusage from "pidusage";
|
|
48
|
+
import { spawnCollectStdout } from "./child-unref.js";
|
|
49
49
|
// Read the platform live (not a module-load const) so both the Windows and the
|
|
50
50
|
// POSIX sampling paths are exercisable in unit tests regardless of the host OS.
|
|
51
51
|
function runningOnWindows() {
|
|
@@ -106,32 +106,26 @@ async function findDescendantPidsWindows(rootPid) {
|
|
|
106
106
|
const psScript = "Get-CimInstance Win32_Process " +
|
|
107
107
|
'| Select-Object -Property ProcessId,ParentProcessId ' +
|
|
108
108
|
'| ForEach-Object { "$($_.ProcessId),$($_.ParentProcessId)" }';
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
resolve(result);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
resolve([]);
|
|
109
|
+
const powershell = path.join(process.env.SystemRoot ?? String.raw `C:\Windows`, "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
110
|
+
// Fire-and-forget, per-poll-tick spawn (#1155): `spawnCollectStdout` unrefs
|
|
111
|
+
// the child AND its piped stdout so this one-shot CIM query can never keep
|
|
112
|
+
// a settled `pi --print` process alive past its own close — mirrors the
|
|
113
|
+
// reaper's identical spawn→collect plumbing (#1153/#1160). Sampling still
|
|
114
|
+
// works normally in an interactive/long-lived session: unref only means
|
|
115
|
+
// "don't hold the loop open FOR this alone," the collected stdout is still
|
|
116
|
+
// delivered whenever `close` fires. Resolves to `""` on any spawn/`error`
|
|
117
|
+
// failure, which the parse below turns into an empty pairs list (same
|
|
118
|
+
// result the old inline `resolve([])` error path produced).
|
|
119
|
+
const out = await spawnCollectStdout(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] });
|
|
120
|
+
const pairs = [];
|
|
121
|
+
for (const line of out.split(/\r?\n/)) {
|
|
122
|
+
const [pidStr, ppidStr] = line.split(",");
|
|
123
|
+
const pid = Number(pidStr);
|
|
124
|
+
const ppid = Number(ppidStr);
|
|
125
|
+
if (Number.isFinite(pid) && Number.isFinite(ppid)) {
|
|
126
|
+
pairs.push([pid, ppid]);
|
|
133
127
|
}
|
|
134
|
-
}
|
|
128
|
+
}
|
|
135
129
|
return walkDescendantPids(rootPid, pairs);
|
|
136
130
|
}
|
|
137
131
|
const windowsCpuHistory = new Map();
|
|
@@ -168,67 +162,59 @@ async function sampleProcessesWindows(valid) {
|
|
|
168
162
|
const psScript = `Get-CimInstance Win32_Process -Filter "${filter}" ` +
|
|
169
163
|
"| Select-Object -Property ProcessId,WorkingSetSize,KernelModeTime,UserModeTime " +
|
|
170
164
|
'| ForEach-Object { "$($_.ProcessId),$($_.WorkingSetSize),$($_.KernelModeTime),$($_.UserModeTime)" }';
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
windowsCpuHistory.set(pid, { cpuMs, ts: now });
|
|
210
|
-
seen.add(pid);
|
|
211
|
-
result.set(pid, { rssBytes: workingSet, cpuPercent });
|
|
212
|
-
}
|
|
213
|
-
// Prune stale history so pids that have gone away don't accumulate.
|
|
214
|
-
for (const [pid, entry] of windowsCpuHistory) {
|
|
215
|
-
if (!seen.has(pid) && now - entry.ts > CPU_HISTORY_MAX_AGE_MS) {
|
|
216
|
-
windowsCpuHistory.delete(pid);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
// Parsing must never throw into the resolve path; best-effort.
|
|
165
|
+
const powershell = path.join(process.env.SystemRoot ?? String.raw `C:\Windows`, "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
166
|
+
// Fire-and-forget, per-poll-tick spawn (#1155): `spawnCollectStdout` unrefs
|
|
167
|
+
// the child AND its piped stdout so this one-shot CIM query can never keep
|
|
168
|
+
// a settled `pi --print` process alive past its own close — mirrors the
|
|
169
|
+
// reaper's identical spawn→collect plumbing (#1153/#1160). It also absorbs
|
|
170
|
+
// both failure modes this function used to guard inline — a synchronous
|
|
171
|
+
// `spawn` throw (the `spawn UNKNOWN` crash vector, #620) and an async
|
|
172
|
+
// `error` event — resolving to `""` either way, which the parse below
|
|
173
|
+
// turns into the same empty/partial `result` map the old inline handlers
|
|
174
|
+
// produced. Sampling still works normally in an interactive/long-lived
|
|
175
|
+
// session: unref only means "don't hold the loop open FOR this alone."
|
|
176
|
+
const out = await spawnCollectStdout(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] });
|
|
177
|
+
try {
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const line of out.split(/\r?\n/)) {
|
|
181
|
+
const parts = line.split(",");
|
|
182
|
+
if (parts.length < 4)
|
|
183
|
+
continue;
|
|
184
|
+
const pid = Number(parts[0]);
|
|
185
|
+
const workingSet = Number(parts[1]);
|
|
186
|
+
const kernel100ns = Number(parts[2]);
|
|
187
|
+
const user100ns = Number(parts[3]);
|
|
188
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
189
|
+
continue;
|
|
190
|
+
if (!Number.isFinite(workingSet))
|
|
191
|
+
continue;
|
|
192
|
+
const cpuMs = Math.round(kernel100ns / 1e4) + Math.round(user100ns / 1e4);
|
|
193
|
+
const prev = windowsCpuHistory.get(pid);
|
|
194
|
+
let cpuPercent = 0;
|
|
195
|
+
if (prev) {
|
|
196
|
+
const wallMs = now - prev.ts;
|
|
197
|
+
if (wallMs > 0) {
|
|
198
|
+
cpuPercent = ((cpuMs - prev.cpuMs) / wallMs) * 100;
|
|
199
|
+
if (!Number.isFinite(cpuPercent) || cpuPercent < 0)
|
|
200
|
+
cpuPercent = 0;
|
|
222
201
|
}
|
|
223
|
-
|
|
224
|
-
});
|
|
202
|
+
}
|
|
203
|
+
windowsCpuHistory.set(pid, { cpuMs, ts: now });
|
|
204
|
+
seen.add(pid);
|
|
205
|
+
result.set(pid, { rssBytes: workingSet, cpuPercent });
|
|
225
206
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
// Prune stale history so pids that have gone away don't accumulate.
|
|
208
|
+
for (const [pid, entry] of windowsCpuHistory) {
|
|
209
|
+
if (!seen.has(pid) && now - entry.ts > CPU_HISTORY_MAX_AGE_MS) {
|
|
210
|
+
windowsCpuHistory.delete(pid);
|
|
211
|
+
}
|
|
230
212
|
}
|
|
231
|
-
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Parsing must never throw into the caller; best-effort.
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
232
218
|
}
|
|
233
219
|
/**
|
|
234
220
|
* Sample CPU%/RSS for a set of pids. Best-effort: a pid that can't be resolved
|