@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
|
@@ -10,11 +10,50 @@
|
|
|
10
10
|
* of how late the loop services its own timer, i.e. how long it was blocked —
|
|
11
11
|
* with **no per-event JS overhead**. `max` ≈ the worst synchronous block since
|
|
12
12
|
* the last reset.
|
|
13
|
+
*
|
|
14
|
+
* ## System-stall contamination (#1122 / #1123)
|
|
15
|
+
*
|
|
16
|
+
* `monitorEventLoopDelay` measures timer lag with the monotonic clock
|
|
17
|
+
* (`uv_hrtime`, backed by `QueryPerformanceCounter` on Windows). When the whole
|
|
18
|
+
* process is frozen or descheduled — machine sleep / Modern Standby, or paging
|
|
19
|
+
* thrash under commit-charge exhaustion — its next timer fires late by the
|
|
20
|
+
* *entire* wall-clock gap, and the histogram records that gap as a "block".
|
|
21
|
+
* Two distinct system stalls were confirmed against the Windows System event
|
|
22
|
+
* log: (1) a 290,179 ms block that lined up exactly with a 14:33:05Z→14:37:55Z
|
|
23
|
+
* Modern Standby window (Kernel-Power 506/507), reported byte-identical by two
|
|
24
|
+
* independent pids because the HDR histogram quantizes ~290 s into one bucket;
|
|
25
|
+
* (2) a later silent host exit with zero sleep events but twelve
|
|
26
|
+
* Resource-Exhaustion-Detector (id 2004) events at 97% commit charge — the
|
|
27
|
+
* process was paging, not sleeping. Both are machine artifacts, not pi-lens
|
|
28
|
+
* work; latency.log also held multi-*hour* "blocks" that can only be overnight
|
|
29
|
+
* sleep.
|
|
30
|
+
*
|
|
31
|
+
* Comparing a wall clock to a monotonic clock does NOT catch these: on Windows
|
|
32
|
+
* both advance across Modern Standby (the histogram's monotonic delta already
|
|
33
|
+
* equalled the wall gap). The reliable discriminator is **CPU consumption**: a
|
|
34
|
+
* genuine synchronous block of D ms burns ≈ D ms of main-thread CPU, so the
|
|
35
|
+
* window that contains it must have consumed at least ~D ms of CPU. A frozen or
|
|
36
|
+
* thrashing process consumes ~0 CPU across the gap, so when the worst block
|
|
37
|
+
* exceeds all the CPU the window could account for, it was a stall, not work.
|
|
38
|
+
* We window per turn (so the CPU accounting is bounded and each block is
|
|
39
|
+
* attributable to its turn) and tag system-stall-suspected samples instead of
|
|
40
|
+
* letting them poison the "worst real block" high-water.
|
|
13
41
|
*/
|
|
42
|
+
import { logExtension } from "./extension-log.js";
|
|
14
43
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
15
44
|
const NS_PER_MS = 1e6;
|
|
45
|
+
const US_PER_MS = 1e3;
|
|
16
46
|
let histogram;
|
|
17
47
|
let monitorUnavailable = false;
|
|
48
|
+
// Per-window (per-turn) baselines for CPU-vs-wall accounting. Captured when the
|
|
49
|
+
// monitor starts and re-captured on every reset, so each window's CPU budget is
|
|
50
|
+
// measured against exactly the histogram window it will be compared to.
|
|
51
|
+
let windowStartWallMs = 0;
|
|
52
|
+
let windowStartCpuMs = 0;
|
|
53
|
+
const cpuTotalMs = () => {
|
|
54
|
+
const c = process.cpuUsage();
|
|
55
|
+
return (c.user + c.system) / US_PER_MS;
|
|
56
|
+
};
|
|
18
57
|
/**
|
|
19
58
|
* Start the monitor (idempotent). Call once, as early as possible, so startup
|
|
20
59
|
* blocks are captured. Cheap — the sampling is native; nothing runs per event.
|
|
@@ -32,26 +71,76 @@ export function startEventLoopMonitor(resolutionMs = 20) {
|
|
|
32
71
|
const h = monitorEventLoopDelay({ resolution: resolutionMs });
|
|
33
72
|
h.enable();
|
|
34
73
|
histogram = h;
|
|
74
|
+
windowStartWallMs = Date.now();
|
|
75
|
+
windowStartCpuMs = cpuTotalMs();
|
|
35
76
|
}
|
|
36
77
|
catch (err) {
|
|
37
78
|
monitorUnavailable = true;
|
|
38
|
-
|
|
79
|
+
logExtension({
|
|
80
|
+
subsystem: "event-loop-monitor",
|
|
81
|
+
message: `event-loop occupancy telemetry disabled (runtime lacks monitorEventLoopDelay): ${err?.message ?? String(err)}`,
|
|
82
|
+
});
|
|
39
83
|
}
|
|
40
84
|
}
|
|
41
85
|
const safeMs = (ns) => Number.isFinite(ns) ? Math.round((ns / NS_PER_MS) * 10) / 10 : 0;
|
|
86
|
+
/**
|
|
87
|
+
* Classify a worst-block sample as genuine CPU work or a suspend/freeze
|
|
88
|
+
* artifact. Pure so the discrimination is testable without the (vitest-flaky)
|
|
89
|
+
* native histogram or a real machine sleep.
|
|
90
|
+
*
|
|
91
|
+
* A block is system-stall-suspected only when it is both (a) larger than any
|
|
92
|
+
* plausible synchronous pi-lens stall (`floorMs`, default 20 s — the real tier
|
|
93
|
+
* observed in latency.log tops out ~15 s) and (b) unaccounted for by the
|
|
94
|
+
* window's CPU budget (`windowCpuMs + slopMs < maxMs`). The floor keeps a real
|
|
95
|
+
* multi-second I/O-bound block (low CPU, but genuine) from being mislabeled,
|
|
96
|
+
* while a frozen or paging process — ~0 CPU across a minutes-to-hours gap —
|
|
97
|
+
* always trips. Sub-floor blocks are never auto-tagged, but the logged
|
|
98
|
+
* `windowCpuMs`/`windowWallMs` still expose the CPU-vs-wall ratio so a reviewer
|
|
99
|
+
* can spot a shorter paging stall by hand.
|
|
100
|
+
*
|
|
101
|
+
* KNOWN AMBIGUITY (honest, not fully resolvable from CPU alone): a genuine
|
|
102
|
+
* pi-lens block that is BLOCKED IN A SYSCALL for >20 s — e.g. a `readdirSync` /
|
|
103
|
+
* `statSync` stalled on a OneDrive/cloud-backed path fetching a dehydrated file,
|
|
104
|
+
* or an antivirus-throttled read — also consumes ~0 CPU while wall time
|
|
105
|
+
* advances, so it is CPU-indistinguishable from a suspend and WILL be tagged
|
|
106
|
+
* `suspectSystemStall` and excluded from the health high-waters. That is the
|
|
107
|
+
* conservative choice (a >20 s synchronous FS stall is itself a P0-worthy bug we
|
|
108
|
+
* do NOT want silently counted as normal), and it is not silenced: such a sample
|
|
109
|
+
* still re-logs every turn with its `lastPhase` attribution, which is the
|
|
110
|
+
* forensic breadcrumb for exactly this class. Sharper corroboration (a magnitude
|
|
111
|
+
* ceiling, a `maxMs`-vs-`windowWallMs` ratio) is tracked as a follow-up note on
|
|
112
|
+
* #1123, not decided here.
|
|
113
|
+
*/
|
|
114
|
+
export function isSuspendSuspectedBlock(maxMs, windowCpuMs, floorMs = 20000, slopMs = 1000) {
|
|
115
|
+
if (maxMs < floorMs)
|
|
116
|
+
return false;
|
|
117
|
+
return windowCpuMs + slopMs < maxMs;
|
|
118
|
+
}
|
|
42
119
|
/** Current occupancy stats, or undefined if the monitor was never started. */
|
|
43
120
|
export function getEventLoopStats() {
|
|
44
121
|
if (!histogram)
|
|
45
122
|
return undefined;
|
|
123
|
+
const maxMs = safeMs(histogram.max);
|
|
124
|
+
const windowWallMs = Math.max(0, Date.now() - windowStartWallMs);
|
|
125
|
+
const windowCpuMs = Math.max(0, cpuTotalMs() - windowStartCpuMs);
|
|
46
126
|
return {
|
|
47
|
-
maxMs
|
|
127
|
+
maxMs,
|
|
48
128
|
p99Ms: safeMs(histogram.percentile(99)),
|
|
49
129
|
meanMs: safeMs(histogram.mean),
|
|
130
|
+
windowWallMs: Math.round(windowWallMs),
|
|
131
|
+
windowCpuMs: Math.round(windowCpuMs),
|
|
132
|
+
suspectSystemStall: isSuspendSuspectedBlock(maxMs, windowCpuMs),
|
|
50
133
|
};
|
|
51
134
|
}
|
|
52
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Reset the histogram and re-baseline the CPU/wall window — called at turn
|
|
137
|
+
* boundaries so each window's worst block is attributable to that turn and its
|
|
138
|
+
* CPU budget is measured over the same span (#192 intent, wired in #1122).
|
|
139
|
+
*/
|
|
53
140
|
export function resetEventLoopMonitor() {
|
|
54
141
|
histogram?.reset();
|
|
142
|
+
windowStartWallMs = Date.now();
|
|
143
|
+
windowStartCpuMs = cpuTotalMs();
|
|
55
144
|
}
|
|
56
145
|
/**
|
|
57
146
|
* Decide whether a freeze is worth persisting to `latency.log`. Pure so the
|
|
@@ -68,4 +157,6 @@ export function _stopEventLoopMonitorForTest() {
|
|
|
68
157
|
histogram?.disable();
|
|
69
158
|
histogram = undefined;
|
|
70
159
|
monitorUnavailable = false;
|
|
160
|
+
windowStartWallMs = 0;
|
|
161
|
+
windowStartCpuMs = 0;
|
|
71
162
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-safe diagnostic sink for the in-pi extension path (#1333).
|
|
3
|
+
*
|
|
4
|
+
* pi owns the terminal: it runs the TTY in raw mode and repaints with
|
|
5
|
+
* cursor-addressed diffs, so ANY byte an extension writes to stdout/stderr
|
|
6
|
+
* lands mid-frame and desyncs pi's model of the screen — the layout stays
|
|
7
|
+
* broken until a full repaint. `clients/`, `tools/`, `index.ts` and `i18n.ts`
|
|
8
|
+
* are all reachable from the extension entry, so none of them may call
|
|
9
|
+
* `console.*` or `process.std*.write`. (`mcp/`, `scripts/` and `bin/` DO own
|
|
10
|
+
* their stdout contract and keep their writes.)
|
|
11
|
+
*
|
|
12
|
+
* This module is the general-purpose replacement sink: one `createNdjsonLogger`
|
|
13
|
+
* instance over `<global pi-lens dir>/extension.log`, per the single-writer
|
|
14
|
+
* invariant in AGENTS.md. Subsystems that already own a log (tree-sitter,
|
|
15
|
+
* review-graph, cascade, latency, sessionstart) route to THAT log instead —
|
|
16
|
+
* this file is for the areas that had no sink at all.
|
|
17
|
+
*
|
|
18
|
+
* Three-channels rule (#482/#484/#485): everything written here is
|
|
19
|
+
* LOG-audience. A genuinely user-facing degradation must ALSO reach the human
|
|
20
|
+
* through the host's own render path (`ctx.ui.notify` / a display-only session
|
|
21
|
+
* entry) — never a raw write.
|
|
22
|
+
*/
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import { isTestMode } from "./env-utils.js";
|
|
25
|
+
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
26
|
+
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
27
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
28
|
+
export const EXTENSION_LOG_FILE = path.join(getGlobalPiLensDir(), "extension.log");
|
|
29
|
+
const writer = createNdjsonLogger({
|
|
30
|
+
filePath: EXTENSION_LOG_FILE,
|
|
31
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
32
|
+
});
|
|
33
|
+
export function logExtension(entry) {
|
|
34
|
+
if (isTestMode())
|
|
35
|
+
return;
|
|
36
|
+
writer.log({
|
|
37
|
+
ts: new Date().toISOString(),
|
|
38
|
+
pid: process.pid,
|
|
39
|
+
level: entry.level ?? "error",
|
|
40
|
+
subsystem: entry.subsystem,
|
|
41
|
+
message: entry.message,
|
|
42
|
+
...(entry.metadata ? { metadata: entry.metadata } : {}),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function createSubsystemLogger(subsystem, defaultLevel = "debug") {
|
|
46
|
+
const at = (level) => (message, metadata) => {
|
|
47
|
+
logExtension({ subsystem, message, level, metadata });
|
|
48
|
+
};
|
|
49
|
+
const logger = at(defaultLevel);
|
|
50
|
+
logger.error = at("error");
|
|
51
|
+
logger.warn = at("warn");
|
|
52
|
+
logger.debug = at("debug");
|
|
53
|
+
return logger;
|
|
54
|
+
}
|
|
55
|
+
/** No-op with the `SubsystemLogger` shape, for the verbose-off branch. */
|
|
56
|
+
export function noopSubsystemLogger() {
|
|
57
|
+
const noop = (() => { });
|
|
58
|
+
noop.error = () => { };
|
|
59
|
+
noop.warn = () => { };
|
|
60
|
+
noop.debug = () => { };
|
|
61
|
+
return noop;
|
|
62
|
+
}
|
|
63
|
+
export function getExtensionLogPath() {
|
|
64
|
+
return EXTENSION_LOG_FILE;
|
|
65
|
+
}
|
|
66
|
+
/** Resolve once all enqueued extension-log writes are on disk (tests/shutdown). */
|
|
67
|
+
export function flushExtensionLog() {
|
|
68
|
+
return writer.flush();
|
|
69
|
+
}
|
|
70
|
+
/** Teardown-only: force queued entries to disk before the process exits. */
|
|
71
|
+
export function flushExtensionLogSync() {
|
|
72
|
+
writer.flushSync();
|
|
73
|
+
}
|
|
74
|
+
// --- Defense in depth: the console reroute -----------------------------------
|
|
75
|
+
const CONSOLE_METHODS = [
|
|
76
|
+
"log",
|
|
77
|
+
"info",
|
|
78
|
+
"warn",
|
|
79
|
+
"error",
|
|
80
|
+
"debug",
|
|
81
|
+
"trace",
|
|
82
|
+
"dir",
|
|
83
|
+
];
|
|
84
|
+
let consoleGuardInstalled = false;
|
|
85
|
+
function formatConsoleArgs(args) {
|
|
86
|
+
return args
|
|
87
|
+
.map((arg) => {
|
|
88
|
+
if (typeof arg === "string")
|
|
89
|
+
return arg;
|
|
90
|
+
if (arg instanceof Error)
|
|
91
|
+
return arg.stack ?? arg.message;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.stringify(arg) ?? String(arg);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return String(arg);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.join(" ");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Patch every console method on the extension entry path so a transitively
|
|
103
|
+
* loaded dependency cannot write raw bytes into pi's frame — the pi-side
|
|
104
|
+
* mirror of `mcp/server.ts`'s `console.log = console.error` guard, which
|
|
105
|
+
* protects the JSON-RPC stdout channel for the same structural reason.
|
|
106
|
+
*
|
|
107
|
+
* This is a NET, not the fix: pi-lens's own sites are migrated to real sinks
|
|
108
|
+
* with real schemas. Idempotent, and inert under test mode (vitest owns the
|
|
109
|
+
* console) and under `PI_LENS_CONSOLE_GUARD=0`.
|
|
110
|
+
*
|
|
111
|
+
* Returns true when the patch was applied by this call.
|
|
112
|
+
*/
|
|
113
|
+
export function installConsoleGuard() {
|
|
114
|
+
if (consoleGuardInstalled)
|
|
115
|
+
return false;
|
|
116
|
+
if (isTestMode())
|
|
117
|
+
return false;
|
|
118
|
+
if (process.env.PI_LENS_CONSOLE_GUARD === "0")
|
|
119
|
+
return false;
|
|
120
|
+
consoleGuardInstalled = true;
|
|
121
|
+
const target = console;
|
|
122
|
+
for (const method of CONSOLE_METHODS) {
|
|
123
|
+
if (typeof target[method] !== "function")
|
|
124
|
+
continue;
|
|
125
|
+
target[method] = (...args) => {
|
|
126
|
+
logExtension({
|
|
127
|
+
subsystem: "console",
|
|
128
|
+
level: method === "warn" ? "warn" : method === "error" ? "error" : "debug",
|
|
129
|
+
message: formatConsoleArgs(args),
|
|
130
|
+
metadata: { method },
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
/** Test-only: forget that the guard was installed. */
|
|
137
|
+
export function _resetConsoleGuardForTests() {
|
|
138
|
+
consoleGuardInstalled = false;
|
|
139
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host run-mode awareness (#1334 S2).
|
|
3
|
+
*
|
|
4
|
+
* pi tells every extension which mode it is running in — verified against the
|
|
5
|
+
* pinned host types (`@earendil-works/pi-coding-agent/dist/core/extensions/
|
|
6
|
+
* types.d.ts`):
|
|
7
|
+
*
|
|
8
|
+
* export type ExtensionMode = "tui" | "rpc" | "json" | "print"; // line 208
|
|
9
|
+
* interface ExtensionContext { mode: ExtensionMode; hasUI: boolean; … }
|
|
10
|
+
* // lines 212-215
|
|
11
|
+
*
|
|
12
|
+
* The host's own comment on `mode` is the contract this module encodes:
|
|
13
|
+
* *"Use `"tui"` to guard terminal-only UI such as custom components."*
|
|
14
|
+
*
|
|
15
|
+
* pi-lens previously derived terminal ownership from nothing at all — it
|
|
16
|
+
* mounted its widget and chattered through `ui.notify` in every mode, including
|
|
17
|
+
* `print` and `json`, where the process is a one-shot producing machine- or
|
|
18
|
+
* pipe-consumed output and there is no live TUI to render into.
|
|
19
|
+
*
|
|
20
|
+
* Two independent questions, deliberately NOT the same predicate:
|
|
21
|
+
*
|
|
22
|
+
* - *Is there a TUI to mount a custom component into?* Only `"tui"`. `rpc`
|
|
23
|
+
* has `hasUI: true` (dialogs work over the protocol) but no terminal
|
|
24
|
+
* component surface, so the widget must not mount there either.
|
|
25
|
+
* - *Should proactive chatter reach the human?* No in `print` and `json` —
|
|
26
|
+
* one-shot/machine output. Yes in `tui` and `rpc`.
|
|
27
|
+
*
|
|
28
|
+
* This is complementary to, and independent of, the #1338/#1333 console guard:
|
|
29
|
+
* that establishes "never write raw to the terminal"; this decides whether the
|
|
30
|
+
* HOST's own render path should be used at all in a given mode.
|
|
31
|
+
*
|
|
32
|
+
* Stateless by design: `mode` lives on the ctx pi hands to every event and
|
|
33
|
+
* command handler, so each call site reads it from the ctx it already has —
|
|
34
|
+
* no latched global to go stale across a session replacement.
|
|
35
|
+
*/
|
|
36
|
+
const KNOWN_MODES = new Set(["tui", "rpc", "json", "print"]);
|
|
37
|
+
/**
|
|
38
|
+
* Feature-detected read of `ctx.mode`.
|
|
39
|
+
*
|
|
40
|
+
* Returns `"unknown"` for a host that predates the field, or for any value
|
|
41
|
+
* outside the four documented modes (a future pi may add one; treating an
|
|
42
|
+
* unrecognized mode as "unknown" keeps current behavior rather than guessing
|
|
43
|
+
* a suppression that could hide output).
|
|
44
|
+
*/
|
|
45
|
+
export function readExtensionMode(ctx) {
|
|
46
|
+
try {
|
|
47
|
+
const mode = ctx?.mode;
|
|
48
|
+
if (typeof mode !== "string" || !KNOWN_MODES.has(mode))
|
|
49
|
+
return "unknown";
|
|
50
|
+
return mode;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether a terminal-only custom component (the pi-lens diagnostics widget) can
|
|
58
|
+
* be mounted. `"unknown"` mounts — older hosts must behave exactly as before.
|
|
59
|
+
*/
|
|
60
|
+
export function supportsTuiWidget(mode) {
|
|
61
|
+
return mode === "tui" || mode === "unknown";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether proactive `ui.notify` chatter should be suppressed (logged only).
|
|
65
|
+
* True only for the two non-interactive one-shot modes.
|
|
66
|
+
*/
|
|
67
|
+
export function suppressesUserNotify(mode) {
|
|
68
|
+
return mode === "print" || mode === "json";
|
|
69
|
+
}
|
|
70
|
+
/** Short human-readable reason for a suppression, for the ndjson/debug log. */
|
|
71
|
+
export function modeSuppressionNote(mode) {
|
|
72
|
+
return `suppressed in "${mode}" mode (no interactive TUI)`;
|
|
73
|
+
}
|
|
@@ -88,6 +88,9 @@ export const KIND_EXTENSIONS = {
|
|
|
88
88
|
".hs",
|
|
89
89
|
".lhs",
|
|
90
90
|
],
|
|
91
|
+
"helm-template": [
|
|
92
|
+
".tpl",
|
|
93
|
+
],
|
|
91
94
|
html: [
|
|
92
95
|
".htm",
|
|
93
96
|
".html",
|
|
@@ -169,6 +172,7 @@ export const KIND_EXTENSIONS = {
|
|
|
169
172
|
".tf",
|
|
170
173
|
".tfvars",
|
|
171
174
|
],
|
|
175
|
+
terragrunt: [],
|
|
172
176
|
toml: [
|
|
173
177
|
".toml",
|
|
174
178
|
],
|
|
@@ -181,6 +185,36 @@ export const KIND_EXTENSIONS = {
|
|
|
181
185
|
".zon",
|
|
182
186
|
],
|
|
183
187
|
};
|
|
188
|
+
/** Return whether a path has an extension registered for the given file kind. */
|
|
189
|
+
export function hasKindExtension(filePath, kind) {
|
|
190
|
+
const extension = extname(filePath).toLowerCase();
|
|
191
|
+
return KIND_EXTENSIONS[kind].some((candidate) => candidate === extension);
|
|
192
|
+
}
|
|
193
|
+
// Fact providers intentionally stay on the ordinary JS/TS source extensions.
|
|
194
|
+
// Vue/Svelte files are classified as jsts for project-wide discovery, but their
|
|
195
|
+
// embedded-language contents are not valid inputs for these tree-sitter facts.
|
|
196
|
+
// Keep that one policy decision here so the function and import providers agree.
|
|
197
|
+
const JSTS_FACT_EXTENSIONS = new Set(KIND_EXTENSIONS.jsts.filter((extension) => ![".vue", ".svelte"].includes(extension)));
|
|
198
|
+
/** Whether the JS/TS fact providers should parse this path. */
|
|
199
|
+
export function isJstsFactFile(filePath) {
|
|
200
|
+
return JSTS_FACT_EXTENSIONS.has(extname(filePath).toLowerCase());
|
|
201
|
+
}
|
|
202
|
+
// Bash access tracking also accepts common text/config files which do not have
|
|
203
|
+
// a semantic FileKind. Every source extension is derived from KIND_EXTENSIONS;
|
|
204
|
+
// only this explicit legacy text/config allowlist lives outside that registry.
|
|
205
|
+
const NON_KIND_READABLE_EXTENSIONS = new Set([
|
|
206
|
+
".txt",
|
|
207
|
+
".env",
|
|
208
|
+
".cfg",
|
|
209
|
+
".conf",
|
|
210
|
+
".ini",
|
|
211
|
+
".xml",
|
|
212
|
+
]);
|
|
213
|
+
/** Whether bash file-access parsing should treat a path as source-like. */
|
|
214
|
+
export function isReadableSourceFile(filePath) {
|
|
215
|
+
const extension = extname(filePath).toLowerCase();
|
|
216
|
+
return (Object.keys(KIND_EXTENSIONS).some((kind) => hasKindExtension(filePath, kind)) || NON_KIND_READABLE_EXTENSIONS.has(extension));
|
|
217
|
+
}
|
|
184
218
|
// --- Shared Project Root Markers ---
|
|
185
219
|
/**
|
|
186
220
|
* .NET project/solution root-marker globs (refs #895). Single source of truth
|
|
@@ -200,6 +234,19 @@ export const DOTNET_FSHARP_ROOT_MARKERS = [
|
|
|
200
234
|
"*.fsproj",
|
|
201
235
|
"*.sln",
|
|
202
236
|
];
|
|
237
|
+
/**
|
|
238
|
+
* Terragrunt's unit/root entrypoint filenames, lowercase. Single source of truth
|
|
239
|
+
* for every subsystem that has to agree on what counts as a terragrunt file —
|
|
240
|
+
* SPECIAL_FILENAMES below, tool-policy.ts's linter and formatter policies,
|
|
241
|
+
* formatters.ts's terragruntHclFormatter, and language-profile.ts's project/root
|
|
242
|
+
* markers. Same rule as the .NET markers above: never hand-copy this list at a
|
|
243
|
+
* call site. tests/clients/terragrunt-filenames.test.ts asserts every consumer
|
|
244
|
+
* honors every name here, so a drifting call site fails CI.
|
|
245
|
+
*/
|
|
246
|
+
export const TERRAGRUNT_FILENAMES = [
|
|
247
|
+
"terragrunt.hcl",
|
|
248
|
+
"root.hcl",
|
|
249
|
+
];
|
|
203
250
|
// Reverse map: extension → file kind (for fast lookup)
|
|
204
251
|
const EXT_TO_KIND = new Map();
|
|
205
252
|
for (const [kind, exts] of Object.entries(KIND_EXTENSIONS)) {
|
|
@@ -218,6 +265,10 @@ const SPECIAL_FILENAMES = [
|
|
|
218
265
|
{ pattern: /^CMakeLists\.txt$/i, kind: "cmake" },
|
|
219
266
|
{ pattern: /^Makefile$/i, kind: "shell" },
|
|
220
267
|
{ pattern: /^Dockerfile(\.\w+)?$/i, kind: "docker" },
|
|
268
|
+
...TERRAGRUNT_FILENAMES.map((name) => ({
|
|
269
|
+
pattern: new RegExp(`^${name.replaceAll(".", "\\.")}$`, "i"),
|
|
270
|
+
kind: "terragrunt",
|
|
271
|
+
})),
|
|
221
272
|
];
|
|
222
273
|
// --- Detection Functions ---
|
|
223
274
|
/**
|
|
@@ -294,6 +345,7 @@ export const CODE_KINDS = new Set([
|
|
|
294
345
|
"gleam",
|
|
295
346
|
"go",
|
|
296
347
|
"haskell",
|
|
348
|
+
"helm-template",
|
|
297
349
|
"java",
|
|
298
350
|
"jsts",
|
|
299
351
|
"kotlin",
|
|
@@ -310,6 +362,7 @@ export const CODE_KINDS = new Set([
|
|
|
310
362
|
"sql",
|
|
311
363
|
"swift",
|
|
312
364
|
"terraform",
|
|
365
|
+
"terragrunt",
|
|
313
366
|
"zig",
|
|
314
367
|
]);
|
|
315
368
|
/** Data/doc/markup/config kinds — still enumerated (that's #894's point), but
|
|
@@ -376,11 +429,13 @@ export function getFileKindLabel(kind) {
|
|
|
376
429
|
lua: "Lua",
|
|
377
430
|
zig: "Zig",
|
|
378
431
|
haskell: "Haskell",
|
|
432
|
+
"helm-template": "Helm template",
|
|
379
433
|
elixir: "Elixir",
|
|
380
434
|
gleam: "Gleam",
|
|
381
435
|
ocaml: "OCaml",
|
|
382
436
|
clojure: "Clojure",
|
|
383
437
|
terraform: "Terraform",
|
|
438
|
+
terragrunt: "Terragrunt",
|
|
384
439
|
nix: "Nix",
|
|
385
440
|
toml: "TOML",
|
|
386
441
|
};
|
|
@@ -444,11 +499,13 @@ export function getLanguageId(kind) {
|
|
|
444
499
|
lua: "lua",
|
|
445
500
|
zig: "zig",
|
|
446
501
|
haskell: "haskell",
|
|
502
|
+
"helm-template": "helm",
|
|
447
503
|
elixir: "elixir",
|
|
448
504
|
gleam: "gleam",
|
|
449
505
|
ocaml: "ocaml",
|
|
450
506
|
clojure: "clojure",
|
|
451
507
|
terraform: "terraform",
|
|
508
|
+
terragrunt: "terragrunt",
|
|
452
509
|
nix: "nix",
|
|
453
510
|
toml: "toml",
|
|
454
511
|
};
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* can skip checks that don't apply (e.g. no complexity checks on generated
|
|
6
6
|
* files, no logic-density checks on re-export barrels).
|
|
7
7
|
*/
|
|
8
|
-
import { basename, dirname } from "node:path";
|
|
8
|
+
import { basename, dirname, win32 } from "node:path";
|
|
9
9
|
import { isGeneratedOrArtifact } from "./generated-artifacts.js";
|
|
10
|
+
import { isWindowsPath } from "./path-utils.js";
|
|
10
11
|
// --- Detection ---
|
|
11
12
|
const RE_EXPORT_LINE = /^export\s+(type\s+)?\*\s+from\s+|^export\s*\{[^}]*\}\s+from\s+/;
|
|
12
13
|
function isReExportBarrel(content) {
|
|
@@ -36,10 +37,24 @@ function isTypeStub(content) {
|
|
|
36
37
|
/**
|
|
37
38
|
* Classify the structural role of a file. Pass `content` for higher
|
|
38
39
|
* accuracy (enables generated-code detection, barrel detection, etc.).
|
|
40
|
+
*
|
|
41
|
+
* `detectFileRole` is platform-native by design — normal same-OS paths
|
|
42
|
+
* (POSIX on Linux, win32 on Windows) go through the module-default
|
|
43
|
+
* `basename`/`dirname`, unchanged from before. But a WINDOWS-SHAPED path
|
|
44
|
+
* (drive letter or UNC prefix, per `isWindowsPath`) is parsed with
|
|
45
|
+
* `path.win32.basename`/`dirname` regardless of the running OS (refs #1152,
|
|
46
|
+
* the #1150 class): on Linux the module-default `dirname` finds no `/` in a
|
|
47
|
+
* `C:\...` path, collapses to `"."`, and the dir-based branches below
|
|
48
|
+
* (`/tests/`, `/spec/`, …) silently misclassify. On win32 this branch is a
|
|
49
|
+
* no-op — `win32.basename`/`dirname` already equal the module default there
|
|
50
|
+
* — so native Windows classification is unchanged.
|
|
39
51
|
*/
|
|
40
52
|
export function detectFileRole(filePath, content) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
53
|
+
const windowsShaped = isWindowsPath(filePath);
|
|
54
|
+
const base = (windowsShaped ? win32.basename(filePath) : basename(filePath)).toLowerCase();
|
|
55
|
+
const dir = (windowsShaped ? win32.dirname(filePath) : dirname(filePath))
|
|
56
|
+
.replace(/\\/g, "/")
|
|
57
|
+
.toLowerCase();
|
|
43
58
|
// --- Test ---
|
|
44
59
|
if (base.includes(".test.") ||
|
|
45
60
|
base.includes(".spec.") ||
|
|
@@ -265,11 +265,19 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
265
265
|
const patternsForDir = (dir) => {
|
|
266
266
|
if (dir === resolvedRoot)
|
|
267
267
|
return patterns;
|
|
268
|
-
const
|
|
269
|
-
|
|
268
|
+
const gitignoreSig = gitignoreSignature(dir);
|
|
269
|
+
// #1105: gate on size too. `findPiLensConfigInDir` returns `size` alongside
|
|
270
|
+
// `mtimeMs` (both from one stat), and `gitignoreSignature` reads both for
|
|
271
|
+
// the nested `.gitignore`, so an mtime-preserving, length-changing edit to
|
|
272
|
+
// EITHER nested source can no longer replay stale patterns for this subtree.
|
|
273
|
+
const pilensInfo = findPiLensConfigInDir(dir);
|
|
274
|
+
const pilensMtime = pilensInfo?.mtimeMs ?? -1;
|
|
275
|
+
const pilensSize = pilensInfo?.size ?? -1;
|
|
270
276
|
const cached = nestedCache.get(dir);
|
|
271
|
-
if (cached?.gitignoreMtimeMs ===
|
|
272
|
-
cached?.
|
|
277
|
+
if (cached?.gitignoreMtimeMs === gitignoreSig.mtimeMs &&
|
|
278
|
+
cached?.gitignoreSize === gitignoreSig.size &&
|
|
279
|
+
cached?.pilensMtimeMs === pilensMtime &&
|
|
280
|
+
cached?.pilensSize === pilensSize) {
|
|
273
281
|
return cached.patterns;
|
|
274
282
|
}
|
|
275
283
|
const nestedConfig = loadPiLensConfigInDir(dir);
|
|
@@ -278,15 +286,17 @@ function buildProjectIgnoreMatcher(resolvedRoot, patterns) {
|
|
|
278
286
|
...parseGitignoreContent(nestedConfig.ignore.join("\n"), "pilens"),
|
|
279
287
|
];
|
|
280
288
|
nestedCache.set(dir, {
|
|
281
|
-
gitignoreMtimeMs:
|
|
289
|
+
gitignoreMtimeMs: gitignoreSig.mtimeMs,
|
|
290
|
+
gitignoreSize: gitignoreSig.size,
|
|
282
291
|
pilensMtimeMs: pilensMtime,
|
|
292
|
+
pilensSize: pilensSize,
|
|
283
293
|
patterns: nextPatterns,
|
|
284
294
|
});
|
|
285
295
|
return nextPatterns;
|
|
286
296
|
};
|
|
287
297
|
// Per-matcher path → pattern-verdict memo. The matcher itself is cached by
|
|
288
|
-
// `getProjectIgnoreMatcher` keyed on `.gitignore` mtime, so this
|
|
289
|
-
// lifetime is bounded to a single set of ignore rules — when any
|
|
298
|
+
// `getProjectIgnoreMatcher` keyed on `.gitignore` size+mtime (#1105), so this
|
|
299
|
+
// Map's lifetime is bounded to a single set of ignore rules — when any
|
|
290
300
|
// `.gitignore` changes, the matcher is rebuilt and the memo is dropped
|
|
291
301
|
// with it. Without this memo, every background scan (comment scan, knip,
|
|
292
302
|
// jscpd, call-graph, source-filter, pipeline) recomputes O(ancestorDirs ×
|
|
@@ -402,26 +412,20 @@ export function createProjectIgnoreMatcher(rootDir, extraPatterns = [], globalPa
|
|
|
402
412
|
return buildProjectIgnoreMatcher(resolvedRoot, patterns);
|
|
403
413
|
}
|
|
404
414
|
const projectIgnoreMatcherCache = new Map();
|
|
405
|
-
|
|
406
|
-
* mtime of the global `~/.pi-lens/config.json` (or the PI_LENS_CONFIG_PATH
|
|
407
|
-
* override). Part of the ignore-matcher cache key so editing global ignore
|
|
408
|
-
* patterns takes effect without a restart (#252). -1 when absent.
|
|
409
|
-
*/
|
|
410
|
-
function globalConfigMtimeMs() {
|
|
415
|
+
function fileFreshnessSignature(filePath) {
|
|
411
416
|
try {
|
|
412
|
-
|
|
417
|
+
const stat = fs.statSync(filePath);
|
|
418
|
+
return { mtimeMs: stat.mtimeMs, size: stat.size };
|
|
413
419
|
}
|
|
414
420
|
catch {
|
|
415
|
-
return -1;
|
|
421
|
+
return { mtimeMs: -1, size: -1 };
|
|
416
422
|
}
|
|
417
423
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return -1;
|
|
424
|
-
}
|
|
424
|
+
function globalConfigSignature() {
|
|
425
|
+
return fileFreshnessSignature(getPiLensGlobalConfigPath());
|
|
426
|
+
}
|
|
427
|
+
function gitignoreSignature(rootDir) {
|
|
428
|
+
return fileFreshnessSignature(path.join(rootDir, ".gitignore"));
|
|
425
429
|
}
|
|
426
430
|
/**
|
|
427
431
|
* The project config file found by the same upward walk as the loader. Cache
|
|
@@ -432,32 +436,39 @@ function gitignoreMtimeMs(rootDir) {
|
|
|
432
436
|
function lensConfigInfo(rootDir) {
|
|
433
437
|
const info = findPiLensProjectConfig(rootDir);
|
|
434
438
|
return info
|
|
435
|
-
? { info, path: info.path, mtimeMs: info.mtimeMs }
|
|
436
|
-
: { info, path: undefined, mtimeMs: -1 };
|
|
439
|
+
? { info, path: info.path, mtimeMs: info.mtimeMs, size: info.size }
|
|
440
|
+
: { info, path: undefined, mtimeMs: -1, size: -1 };
|
|
437
441
|
}
|
|
438
442
|
export function getProjectIgnoreMatcher(rootDir) {
|
|
439
443
|
const resolvedRoot = resolveGitIgnoreRoot(rootDir);
|
|
440
|
-
const
|
|
444
|
+
const gitignoreSig = gitignoreSignature(resolvedRoot);
|
|
441
445
|
const lensConfig = lensConfigInfo(resolvedRoot);
|
|
442
|
-
const
|
|
446
|
+
const globalSig = globalConfigSignature();
|
|
443
447
|
const cached = projectIgnoreMatcherCache.get(resolvedRoot);
|
|
444
|
-
if (cached?.gitignoreMtimeMs ===
|
|
448
|
+
if (cached?.gitignoreMtimeMs === gitignoreSig.mtimeMs &&
|
|
449
|
+
cached?.gitignoreSize === gitignoreSig.size &&
|
|
445
450
|
cached?.lensConfigPath === lensConfig.path &&
|
|
446
451
|
cached?.lensConfigMtimeMs === lensConfig.mtimeMs &&
|
|
447
|
-
cached?.
|
|
452
|
+
cached?.lensConfigSize === lensConfig.size &&
|
|
453
|
+
cached?.globalConfigMtimeMs === globalSig.mtimeMs &&
|
|
454
|
+
cached?.globalConfigSize === globalSig.size) {
|
|
448
455
|
return cached.matcher;
|
|
449
456
|
}
|
|
450
457
|
// Load both configs fresh on cache miss. On a cache HIT (the common case)
|
|
451
|
-
// none of this runs — the only per-call cost is the
|
|
452
|
-
//
|
|
453
|
-
//
|
|
458
|
+
// none of this runs — the only per-call cost is the size:mtimeMs stats above
|
|
459
|
+
// (size is free from the same stat). The project loader is itself
|
|
460
|
+
// size:mtimeMs-cached; the global loader re-parses, but only here on miss
|
|
461
|
+
// (when some tracked signature changed).
|
|
454
462
|
const projectConfig = loadPiLensProjectConfig(resolvedRoot, lensConfig.info);
|
|
455
463
|
const matcher = createProjectIgnoreMatcher(resolvedRoot, projectConfig.ignore, getGlobalIgnorePatterns());
|
|
456
464
|
projectIgnoreMatcherCache.set(resolvedRoot, {
|
|
457
|
-
gitignoreMtimeMs:
|
|
465
|
+
gitignoreMtimeMs: gitignoreSig.mtimeMs,
|
|
466
|
+
gitignoreSize: gitignoreSig.size,
|
|
458
467
|
lensConfigPath: lensConfig.path,
|
|
459
468
|
lensConfigMtimeMs: lensConfig.mtimeMs,
|
|
460
|
-
|
|
469
|
+
lensConfigSize: lensConfig.size,
|
|
470
|
+
globalConfigMtimeMs: globalSig.mtimeMs,
|
|
471
|
+
globalConfigSize: globalSig.size,
|
|
461
472
|
matcher,
|
|
462
473
|
});
|
|
463
474
|
return matcher;
|
|
@@ -465,10 +476,21 @@ export function getProjectIgnoreMatcher(rootDir) {
|
|
|
465
476
|
export function isPathIgnoredByProject(filePath, rootDir, isDirectory = false) {
|
|
466
477
|
return getProjectIgnoreMatcher(rootDir).isIgnored(filePath, isDirectory);
|
|
467
478
|
}
|
|
479
|
+
const projectIgnoreGlobsCache = new Map();
|
|
468
480
|
export function getProjectIgnoreGlobs(rootDir) {
|
|
469
|
-
|
|
481
|
+
const resolvedRoot = path.resolve(rootDir);
|
|
482
|
+
const signature = gitignoreSignature(resolvedRoot);
|
|
483
|
+
const cached = projectIgnoreGlobsCache.get(resolvedRoot);
|
|
484
|
+
if (cached &&
|
|
485
|
+
cached.mtimeMs === signature.mtimeMs &&
|
|
486
|
+
cached.size === signature.size) {
|
|
487
|
+
return cached.globs;
|
|
488
|
+
}
|
|
489
|
+
const globs = readGitignorePatterns(resolvedRoot)
|
|
470
490
|
.filter((pattern) => !pattern.negated)
|
|
471
491
|
.flatMap((pattern) => expandGitignorePattern(pattern));
|
|
492
|
+
projectIgnoreGlobsCache.set(resolvedRoot, { ...signature, globs });
|
|
493
|
+
return globs;
|
|
472
494
|
}
|
|
473
495
|
/**
|
|
474
496
|
* Read simple directory-name entries from a root .gitignore.
|