@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a tool both failed and produced nothing to parse — it never got as
|
|
3
|
+
* far as linting the file. Callers map this to `skipped`, never `succeeded`.
|
|
4
|
+
*
|
|
5
|
+
* Guarding on `result.error` alone covers only half of it: safe-spawn sets
|
|
6
|
+
* `error` for spawn/timeout/signal failures and resolves a NORMAL exit with no
|
|
7
|
+
* `error` at any status (see `SpawnResult.failure` — "nonzero exit statuses are
|
|
8
|
+
* not spawn failures"). An unknown subcommand, a rejected flag, or a config
|
|
9
|
+
* that fails to load exits non-zero with the message on stderr and an empty
|
|
10
|
+
* stdout, so an `error`-only guard falls through, parses "" into zero
|
|
11
|
+
* diagnostics, and reports a clean file that nothing ever checked.
|
|
12
|
+
*
|
|
13
|
+
* Tools that exit non-zero *because* they found something are unaffected: their
|
|
14
|
+
* findings are on stdout, so the output half of the test is false.
|
|
15
|
+
*
|
|
16
|
+
* `output` defaults to stdout. Runners that parse stderr too pass the exact
|
|
17
|
+
* string they are about to hand the parser, so "nothing to parse" means both
|
|
18
|
+
* streams rather than just one.
|
|
19
|
+
*/
|
|
20
|
+
export function spawnFailedWithNoOutput(result, output = result.stdout) {
|
|
21
|
+
return (Boolean(result.error) || result.status !== 0) && !output?.trim();
|
|
22
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared toolchain-availability lifecycle (#1476 Sonar follow-up).
|
|
3
|
+
*
|
|
4
|
+
* `candidate-probe.ts` factored out the PATH sweep the Go and Rust clients ran,
|
|
5
|
+
* but the LIFECYCLE around it stayed written twice: the transient-aware latch,
|
|
6
|
+
* the in-flight dedupe that keeps concurrent first-time callers to one sweep,
|
|
7
|
+
* the path memo, and the two `availability_decision` records. Two copies of one
|
|
8
|
+
* rule is how #1467's fix missed seven sites, so the rule lives here once and
|
|
9
|
+
* each client contributes only its configuration.
|
|
10
|
+
*
|
|
11
|
+
* Callers keep their own public method names — `isGoAvailableAsync`,
|
|
12
|
+
* `findCargoPathAsync` — because other modules call them; only the body moves.
|
|
13
|
+
*/
|
|
14
|
+
import { createAvailabilityLatch, logAvailabilityDecision, } from "./availability-policy.js";
|
|
15
|
+
import { probeAvailabilityCandidates } from "./candidate-probe.js";
|
|
16
|
+
/**
|
|
17
|
+
* Own one toolchain's availability: sweep the platform candidate list, memoize
|
|
18
|
+
* the path that answered, and park the verdict behind the shared latch so a
|
|
19
|
+
* timed-out probe expires instead of latching "the toolchain is not installed"
|
|
20
|
+
* for the life of the process.
|
|
21
|
+
*/
|
|
22
|
+
export function createToolchainAvailability(config) {
|
|
23
|
+
const availabilityLatch = createAvailabilityLatch();
|
|
24
|
+
let toolPath = null;
|
|
25
|
+
/** Classification of the candidate sweep, for the retry decision. */
|
|
26
|
+
let sweepSawTransient = false;
|
|
27
|
+
let sweepTransientCause = "probe-timeout";
|
|
28
|
+
let sweepHostStallMs = 0;
|
|
29
|
+
let ensureInFlight = null;
|
|
30
|
+
async function findPath() {
|
|
31
|
+
if (toolPath)
|
|
32
|
+
return toolPath;
|
|
33
|
+
const paths = process.platform === "win32" ? config.windowsPaths : config.unixPaths;
|
|
34
|
+
const sweep = await probeAvailabilityCandidates(paths, config.probeArgs, config.budgetMs);
|
|
35
|
+
sweepSawTransient = sweep.sawTransient;
|
|
36
|
+
sweepTransientCause = sweep.transientCause;
|
|
37
|
+
sweepHostStallMs = sweep.hostStallMs;
|
|
38
|
+
if (sweep.foundPath)
|
|
39
|
+
toolPath = sweep.foundPath;
|
|
40
|
+
return sweep.foundPath;
|
|
41
|
+
}
|
|
42
|
+
async function resolveAvailability() {
|
|
43
|
+
const startedAt = Date.now();
|
|
44
|
+
const found = (await findPath()) !== null;
|
|
45
|
+
if (found) {
|
|
46
|
+
availabilityLatch.noteAvailable();
|
|
47
|
+
config.log(`${config.label} found: ${toolPath}`);
|
|
48
|
+
logAvailabilityDecision({
|
|
49
|
+
tool: config.tool,
|
|
50
|
+
verdict: "available",
|
|
51
|
+
outcome: "success",
|
|
52
|
+
cause: "ok",
|
|
53
|
+
elapsedMs: Date.now() - startedAt,
|
|
54
|
+
latched: true,
|
|
55
|
+
hostStallMs: sweepHostStallMs,
|
|
56
|
+
budgetMs: config.budgetMs,
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// A timed-out version probe is evidence about this moment, not about
|
|
61
|
+
// whether the toolchain is installed; it expires instead of latching.
|
|
62
|
+
const outcome = sweepSawTransient ? "transient" : "missing";
|
|
63
|
+
const cause = sweepSawTransient ? sweepTransientCause : "not-found";
|
|
64
|
+
const retryAfterMs = availabilityLatch.noteUnavailable(outcome, cause);
|
|
65
|
+
logAvailabilityDecision({
|
|
66
|
+
tool: config.tool,
|
|
67
|
+
verdict: "unavailable",
|
|
68
|
+
outcome,
|
|
69
|
+
cause,
|
|
70
|
+
elapsedMs: Date.now() - startedAt,
|
|
71
|
+
latched: outcome !== "transient",
|
|
72
|
+
hostStallMs: sweepHostStallMs,
|
|
73
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
74
|
+
budgetMs: config.budgetMs,
|
|
75
|
+
});
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
async function isAvailable() {
|
|
79
|
+
// `read()` returns null when the last verdict was transient and its
|
|
80
|
+
// cooldown expired, which re-enters the candidate sweep (#1476).
|
|
81
|
+
const memo = availabilityLatch.read();
|
|
82
|
+
if (memo !== null)
|
|
83
|
+
return memo;
|
|
84
|
+
// Now that a verdict can expire, concurrent callers arriving just after a
|
|
85
|
+
// cooldown must share ONE sweep rather than each spawning their own.
|
|
86
|
+
if (ensureInFlight)
|
|
87
|
+
return ensureInFlight;
|
|
88
|
+
ensureInFlight = resolveAvailability();
|
|
89
|
+
try {
|
|
90
|
+
return await ensureInFlight;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
ensureInFlight = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { findPath, isAvailable };
|
|
97
|
+
}
|
|
@@ -15,6 +15,7 @@ import { createHash } from "node:crypto";
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import yaml from "../../deps/js-yaml.js";
|
|
18
|
+
import { BoundedLruCache } from "../../bounded-cache.js";
|
|
18
19
|
// --- Constants ---
|
|
19
20
|
/** Overly broad patterns that match everything (cause false positive explosions) */
|
|
20
21
|
export const OVERLY_BROAD_PATTERNS = [
|
|
@@ -28,10 +29,10 @@ export const OVERLY_BROAD_PATTERNS = [
|
|
|
28
29
|
/** Maximum complexity score for rules in blockingOnly mode */
|
|
29
30
|
export const MAX_BLOCKING_RULE_COMPLEXITY = 8;
|
|
30
31
|
// --- Caches ---
|
|
31
|
-
const rulesCache = new
|
|
32
|
-
const blockingRulesCache = new
|
|
33
|
-
const contentRulesCache = new
|
|
34
|
-
const contentBlockingRulesCache = new
|
|
32
|
+
const rulesCache = new BoundedLruCache(64);
|
|
33
|
+
const blockingRulesCache = new BoundedLruCache(64);
|
|
34
|
+
const contentRulesCache = new BoundedLruCache(64);
|
|
35
|
+
const contentBlockingRulesCache = new BoundedLruCache(64);
|
|
35
36
|
// --- Public API ---
|
|
36
37
|
export function clearRulesCache() {
|
|
37
38
|
rulesCache.clear();
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
import { logBusEvent } from "./bus-events-logger.js";
|
|
25
25
|
import { isBusPublishEnabled } from "./bus-publish.js";
|
|
26
26
|
import { normalizeFilePath } from "./path-utils.js";
|
|
27
|
+
import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
|
|
27
28
|
export const BUS_DISPOSITION_EVENT = "pilens:diagnostic:disposition";
|
|
28
29
|
export const BUS_DISPOSITION_VERSION = 1;
|
|
29
|
-
|
|
30
|
+
const liveEmitter = createLiveBusEmitter();
|
|
30
31
|
let hasLoggedFailure = false;
|
|
31
32
|
let hasLoggedUnwired = false;
|
|
32
33
|
let hasLoggedDisabled = false;
|
|
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
|
|
|
38
39
|
* the MCP server path run in (no pi host, no `pi.events`).
|
|
39
40
|
*/
|
|
40
41
|
export function wireDispositionBusEmitter(emitFn) {
|
|
41
|
-
|
|
42
|
+
liveEmitter.wire(emitFn);
|
|
43
|
+
}
|
|
44
|
+
export function wireDispositionBusEmitterGetter(getter) {
|
|
45
|
+
liveEmitter.wireGetter(getter);
|
|
42
46
|
}
|
|
43
47
|
/** Test-only: reset module state between test files. */
|
|
44
48
|
export function _resetDispositionPublishForTests() {
|
|
45
|
-
|
|
49
|
+
liveEmitter.reset();
|
|
46
50
|
hasLoggedFailure = false;
|
|
47
51
|
hasLoggedUnwired = false;
|
|
48
52
|
hasLoggedDisabled = false;
|
|
@@ -63,7 +67,13 @@ export function publishDisposition(args) {
|
|
|
63
67
|
}
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
|
|
70
|
+
const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
|
|
71
|
+
event: BUS_DISPOSITION_EVENT,
|
|
72
|
+
cwd: normalizeFilePath(args.cwd),
|
|
73
|
+
}));
|
|
74
|
+
if (resolution.outcome === "stale-session")
|
|
75
|
+
return;
|
|
76
|
+
if (resolution.outcome === "unwired") {
|
|
67
77
|
if (!hasLoggedUnwired) {
|
|
68
78
|
hasLoggedUnwired = true;
|
|
69
79
|
logBusEvent({
|
|
@@ -74,6 +84,7 @@ export function publishDisposition(args) {
|
|
|
74
84
|
}
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
87
|
+
const busEmit = resolution.emit;
|
|
77
88
|
try {
|
|
78
89
|
const payload = {
|
|
79
90
|
v: BUS_DISPOSITION_VERSION,
|
|
@@ -88,6 +99,7 @@ export function publishDisposition(args) {
|
|
|
88
99
|
reason: args.reason,
|
|
89
100
|
};
|
|
90
101
|
busEmit(BUS_DISPOSITION_EVENT, payload);
|
|
102
|
+
hasLoggedFailure = false;
|
|
91
103
|
logBusEvent({
|
|
92
104
|
event: BUS_DISPOSITION_EVENT,
|
|
93
105
|
outcome: "emitted",
|
|
@@ -100,9 +112,11 @@ export function publishDisposition(args) {
|
|
|
100
112
|
outcome: "emit_failed",
|
|
101
113
|
cwd: normalizeFilePath(args.cwd),
|
|
102
114
|
error: String(err),
|
|
115
|
+
ctxSource: resolution.ctxSource,
|
|
103
116
|
});
|
|
104
117
|
if (!hasLoggedFailure) {
|
|
105
118
|
hasLoggedFailure = true;
|
|
119
|
+
recordStaleBusFailure(BUS_DISPOSITION_EVENT, err);
|
|
106
120
|
args.dbg?.(`disposition-publish: pilens:diagnostic:disposition emit failed (further failures suppressed): ${err}`);
|
|
107
121
|
}
|
|
108
122
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous locked read-modify-write commit seam for behavior-gating JSON
|
|
3
|
+
* stores shared by multiple pi-lens processes.
|
|
4
|
+
*
|
|
5
|
+
* The authoritative read happens only after the bounded PID lock is held; the
|
|
6
|
+
* caller merges only its delta, publication is a throwing atomic replacement,
|
|
7
|
+
* and success telemetry runs only after publication succeeds.
|
|
8
|
+
*
|
|
9
|
+
* Short synchronous commits use a file lock. Awaited commits use the shared
|
|
10
|
+
* quarantine directory lock, preserving bounded contention and stale-owner
|
|
11
|
+
* recovery without allowing a late release to delete a replacement owner.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import fsp from "node:fs/promises";
|
|
15
|
+
import { writeFileAtomic, writeFileAtomicAsync } from "./atomic-write.js";
|
|
16
|
+
import { acquireBoundedPidFileLock, acquireQuarantinePidFileLock, } from "./bounded-pid-file-lock.js";
|
|
17
|
+
function readLocked(path) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readFileSync(path, "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function commitDurableStore(options) {
|
|
26
|
+
const release = options.onContention === "skip-log"
|
|
27
|
+
? acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
28
|
+
waitMs: options.waitMs,
|
|
29
|
+
retryMs: options.retryMs,
|
|
30
|
+
timeoutMessage: options.timeoutMessage,
|
|
31
|
+
onContention: "skip-log",
|
|
32
|
+
logContention: options.logContention,
|
|
33
|
+
})
|
|
34
|
+
: acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
35
|
+
waitMs: options.waitMs,
|
|
36
|
+
retryMs: options.retryMs,
|
|
37
|
+
timeoutMessage: options.timeoutMessage,
|
|
38
|
+
onContention: "throw",
|
|
39
|
+
});
|
|
40
|
+
if (!release)
|
|
41
|
+
return undefined;
|
|
42
|
+
let committed;
|
|
43
|
+
try {
|
|
44
|
+
const current = options.deserialize(readLocked(options.path));
|
|
45
|
+
committed = options.merge(current);
|
|
46
|
+
writeFileAtomic(options.path, options.serialize(committed), {
|
|
47
|
+
bestEffort: false,
|
|
48
|
+
});
|
|
49
|
+
options.afterWriteLocked?.(committed);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
release();
|
|
53
|
+
}
|
|
54
|
+
return committed;
|
|
55
|
+
}
|
|
56
|
+
async function readLockedAsync(path) {
|
|
57
|
+
try {
|
|
58
|
+
return await fsp.readFile(path, "utf8");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error.code === "ENOENT")
|
|
62
|
+
return undefined;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function commitDurableStoreAsync(options) {
|
|
67
|
+
const release = options.onContention === "skip-log"
|
|
68
|
+
? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
69
|
+
waitMs: options.waitMs,
|
|
70
|
+
retryMs: options.retryMs,
|
|
71
|
+
staleMs: options.staleMs,
|
|
72
|
+
timeoutMessage: options.timeoutMessage,
|
|
73
|
+
onContention: "skip-log",
|
|
74
|
+
logContention: options.logContention,
|
|
75
|
+
})
|
|
76
|
+
: await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
77
|
+
waitMs: options.waitMs,
|
|
78
|
+
retryMs: options.retryMs,
|
|
79
|
+
staleMs: options.staleMs,
|
|
80
|
+
timeoutMessage: options.timeoutMessage,
|
|
81
|
+
onContention: "throw",
|
|
82
|
+
});
|
|
83
|
+
if (!release)
|
|
84
|
+
return undefined;
|
|
85
|
+
try {
|
|
86
|
+
const current = options.deserialize(await readLockedAsync(options.path));
|
|
87
|
+
const committed = options.merge(current);
|
|
88
|
+
await writeFileAtomicAsync(options.path, options.serialize(committed), {
|
|
89
|
+
bestEffort: false,
|
|
90
|
+
});
|
|
91
|
+
await options.afterWriteLocked?.(committed);
|
|
92
|
+
return committed;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await release();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
/**
|
|
3
|
+
* The moment pi-lens's module graph started evaluating, captured as this
|
|
4
|
+
* module's first statement (#1374 host-boot vs extension-eval split).
|
|
5
|
+
*
|
|
6
|
+
* This module is otherwise side-effect-free by design (#1434 S3d review): it
|
|
7
|
+
* does nothing but capture a timestamp. `clients/console-guard-install.ts`
|
|
8
|
+
* imports it as its FIRST import so the capture happens before the guard's
|
|
9
|
+
* own install work, and `clients/startup-timing.ts` imports it too, for the
|
|
10
|
+
* constant alone. Before this split, `startup-timing.ts` imported the
|
|
11
|
+
* constant FROM `console-guard-install.ts` — which meant importing
|
|
12
|
+
* startup-timing anywhere (e.g. a test measuring load time) silently
|
|
13
|
+
* installed the console guard as a side effect. Splitting the constant out
|
|
14
|
+
* means importing this module, or `startup-timing.ts`, never installs
|
|
15
|
+
* anything.
|
|
16
|
+
*/
|
|
17
|
+
export const PI_LENS_EVAL_STARTED_MS = performance.now();
|
|
@@ -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
|
}
|