@herbertgao/pi-extensions 2026.8.6 → 2026.8.7
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 +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +2 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +120 -111
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +10 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +909 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
- package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
- package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +67738 -55739
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/docs/agent-guide.md +6 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +19 -5
- package/node_modules/pi-lens/docs/globalconfig.md +3 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +19 -1
- package/node_modules/pi-lens/docs/usage.md +1 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
- package/node_modules/pi-mcp-adapter/README.md +47 -5
- package/node_modules/pi-mcp-adapter/cli.js +163 -1
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +25 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +3 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +23 -0
- package/node_modules/pi-web-access/README.md +27 -9
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/curator-page.ts +6 -3
- package/node_modules/pi-web-access/curator-server.ts +4 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +28 -3
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/index.ts +41 -57
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +44 -22
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/package.json +13 -13
|
@@ -9,14 +9,21 @@ const writer = createNdjsonLogger({
|
|
|
9
9
|
filePath: LATENCY_LOG_FILE,
|
|
10
10
|
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
11
11
|
});
|
|
12
|
+
/** Bound on the `recentPhases` ring below — keeps the attribution record small. */
|
|
13
|
+
export const RECENT_PHASE_CAP = 5;
|
|
12
14
|
/**
|
|
13
|
-
* Most recent non-`loop_block`
|
|
14
|
-
* attribution (#1122 / #1123 item 1): the
|
|
15
|
-
* turn_end and cannot see *what* stalled the
|
|
16
|
-
* that ran as a starting point for root-causing
|
|
17
|
-
*
|
|
15
|
+
* Most recent non-`loop_block` phases seen by `logLatency`, newest first, for
|
|
16
|
+
* cheap block attribution (#1122 / #1123 item 1, widened #1723): the
|
|
17
|
+
* event-loop-block probe fires at turn_end and cannot see *what* stalled the
|
|
18
|
+
* loop, so it stamps the phases that ran as a starting point for root-causing
|
|
19
|
+
* a genuine block. A single `lastPhase` pointer names only the phase that
|
|
20
|
+
* finished right before the block — useless when the CPU hog itself never got
|
|
21
|
+
* to log a completion (it was busy blocking the loop). Keeping a short,
|
|
22
|
+
* bounded ring instead gives the block's neighborhood, not just one frame of
|
|
23
|
+
* it, while staying O(1) per call and fixed-size in memory. Tracked before the
|
|
24
|
+
* test-mode guard so it is deterministic and unit-testable.
|
|
18
25
|
*/
|
|
19
|
-
let
|
|
26
|
+
let recentPhases = [];
|
|
20
27
|
/**
|
|
21
28
|
* Phases excluded from `lastPhase` attribution alongside `loop_block`.
|
|
22
29
|
* #1412 L3: `lsp_typescript_project_identity` is the classic-TS first-open
|
|
@@ -33,8 +40,14 @@ let lastPhase;
|
|
|
33
40
|
* zero-duration siblings of the same shape, added alongside them.
|
|
34
41
|
*
|
|
35
42
|
* `lsp_aux_wait_outcome` (#1458) is DIFFERENT from every entry above: its
|
|
36
|
-
* `durationMs` is a REAL bounded wait
|
|
37
|
-
*
|
|
43
|
+
* `durationMs` is a REAL bounded wait, not zero-duration decision telemetry.
|
|
44
|
+
* WHICH wait depends on the row's `waitShape` (#1533), so read that field before
|
|
45
|
+
* comparing two rows: `"aux_grace"` measures only the post-primary auxiliary
|
|
46
|
+
* grace (up to a few seconds), while `"aggregate"` — the `clientScope: "all"`
|
|
47
|
+
* producer, which arms no grace of its own — measures the whole diagnostics wait
|
|
48
|
+
* from before the primaries settled. The `"aggregate"` number is therefore
|
|
49
|
+
* systematically larger for the same auxiliary; it is not a regression. It is
|
|
50
|
+
* still
|
|
38
51
|
* excluded because it is a WAIT-OUTCOME RECORD written after the aux wait
|
|
39
52
|
* already completed, not the stall itself — that wait ran inside the
|
|
40
53
|
* `lsp_touch_file`/diagnostics phase surrounding it, so letting this summary
|
|
@@ -51,7 +64,15 @@ let lastPhase;
|
|
|
51
64
|
*
|
|
52
65
|
* #1461 slice 1: `finding_dead_path_drop` is the same shape — the record a
|
|
53
66
|
* delivery seam writes when it drops findings whose cited path no longer
|
|
54
|
-
* exists.
|
|
67
|
+
* exists. #1622 adds `finding_stale_line_demote`, its sibling for findings
|
|
68
|
+
* whose cited file was edited after the scan.
|
|
69
|
+
*
|
|
70
|
+
* #1459: the three scanner-coverage records are summary rows written from inside
|
|
71
|
+
* `lsp_touch_file`, and two of them carry ANOTHER write's age as `durationMs`
|
|
72
|
+
* (`lsp_notify_resync_deferred`, `lsp_notify_write_late_landed`) rather than
|
|
73
|
+
* their own work. `lsp_scanner_coverage_gap` reports the touch's elapsed time for
|
|
74
|
+
* joinability. Letting any of them win `lastPhase` would attribute a `loop_block`
|
|
75
|
+
* to the record instead of to the touch that is actually stalled.
|
|
55
76
|
*/
|
|
56
77
|
const LAST_PHASE_EXCLUDED = new Set([
|
|
57
78
|
"loop_block",
|
|
@@ -65,6 +86,12 @@ const LAST_PHASE_EXCLUDED = new Set([
|
|
|
65
86
|
"tool_set_mutation",
|
|
66
87
|
"availability_decision",
|
|
67
88
|
"finding_dead_path_drop",
|
|
89
|
+
"finding_stale_line_demote",
|
|
90
|
+
"lsp_scanner_coverage_gap",
|
|
91
|
+
"lsp_notify_resync_deferred",
|
|
92
|
+
"lsp_notify_write_late_landed",
|
|
93
|
+
"degradation_ledger",
|
|
94
|
+
"path_attribution_verified_rollup",
|
|
68
95
|
]);
|
|
69
96
|
/**
|
|
70
97
|
* The last non-`loop_block` phase logged, or undefined if none yet. Carries its
|
|
@@ -74,12 +101,414 @@ const LAST_PHASE_EXCLUDED = new Set([
|
|
|
74
101
|
* trusting it as the cause (it is a breadcrumb, not proof).
|
|
75
102
|
*/
|
|
76
103
|
export function getLastLoggedPhase() {
|
|
77
|
-
return
|
|
104
|
+
return recentPhases[0];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The last `limit` non-`loop_block` phases logged, newest first (#1723).
|
|
108
|
+
* Bounded to `RECENT_PHASE_CAP` regardless of `limit` — a caller cannot
|
|
109
|
+
* request an unbounded ring. Same staleness caveat as `getLastLoggedPhase`:
|
|
110
|
+
* these are breadcrumbs from whatever ran most recently, not proof of cause.
|
|
111
|
+
*/
|
|
112
|
+
export function getRecentLoggedPhases(limit = RECENT_PHASE_CAP) {
|
|
113
|
+
return recentPhases.slice(0, Math.min(limit, RECENT_PHASE_CAP));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Test-only: the ring's actual storage length, bypassing the read-side
|
|
117
|
+
* `Math.min` clamp in `getRecentLoggedPhases` entirely. Needed because that
|
|
118
|
+
* clamp and the write-side `.slice(0, RECENT_PHASE_CAP)` in `logLatency` are
|
|
119
|
+
* a compensating pair — as long as ONE of them is intact, the other's
|
|
120
|
+
* deletion is invisible to any test that only observes
|
|
121
|
+
* `getRecentLoggedPhases()` output length. This pins the write-side guard
|
|
122
|
+
* specifically: if its `.slice` is deleted, storage grows past the cap and
|
|
123
|
+
* this returns the unclamped size regardless of what the read side does.
|
|
124
|
+
*/
|
|
125
|
+
export function _recentPhasesStorageLengthForTest() {
|
|
126
|
+
return recentPhases.length;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Test-only: seed the ring directly, bypassing the write-side cap in
|
|
130
|
+
* `logLatency`. Lets a test put the ring in a state the normal write path can
|
|
131
|
+
* never produce (more than `RECENT_PHASE_CAP` entries), so the read-side
|
|
132
|
+
* clamp in `getRecentLoggedPhases` can be pinned independently of the
|
|
133
|
+
* write-side guard (see `_recentPhasesStorageLengthForTest` above for why
|
|
134
|
+
* that independence matters).
|
|
135
|
+
*/
|
|
136
|
+
export function _setRecentPhasesForTest(entries) {
|
|
137
|
+
recentPhases = entries;
|
|
138
|
+
}
|
|
139
|
+
/** Bound on the closed-bracket ring below — same size discipline as `recentPhases`. */
|
|
140
|
+
export const CLOSED_BRACKET_CAP = RECENT_PHASE_CAP;
|
|
141
|
+
/**
|
|
142
|
+
* Phases currently executing, keyed by their own start token (#1723 review
|
|
143
|
+
* round: this replaces an earlier single-slot design that broke two ways.
|
|
144
|
+
* `dispatchForFile` runs its runner groups in PARALLEL (`Promise.all`,
|
|
145
|
+
* dispatcher.ts), so overlap is the NORMAL case, not an edge case:
|
|
146
|
+
* F1 — a single slot holds only the LAST starter. A cheap idle runner
|
|
147
|
+
* starting after a CPU hog wins the slot outright; a block gets
|
|
148
|
+
* attributed to the innocent idle runner while the hog stays
|
|
149
|
+
* anonymous.
|
|
150
|
+
* F2 — the inverse: a quick sibling that starts SECOND but FINISHES FIRST
|
|
151
|
+
* clears the slot the moment its own `finally` runs, wiping out the
|
|
152
|
+
* still-running long phase's attribution — the identity-token check
|
|
153
|
+
* only guarded the OTHER stale-clear direction (an EARLIER phase's
|
|
154
|
+
* late finish clobbering a LATER phase), not this one.
|
|
155
|
+
* A `Map` keyed by the token itself (not by phase name — two calls can share
|
|
156
|
+
* a name) fixes both: every bracket owns its own entry regardless of what
|
|
157
|
+
* else is live, and `phaseFinished` only ever removes ITS OWN entry. `Map`
|
|
158
|
+
* iteration order is insertion order, so the oldest surviving entry — read by
|
|
159
|
+
* `getCurrentPhase` — is the longest-running phase still open: the one most
|
|
160
|
+
* likely to own a long block.
|
|
161
|
+
*/
|
|
162
|
+
const liveBrackets = new Map();
|
|
163
|
+
/**
|
|
164
|
+
* Recently CLOSED brackets, newest first, bounded to `CLOSED_BRACKET_CAP`
|
|
165
|
+
* (#1723 review round, F3 — the decisive finding). `phaseFinished` runs
|
|
166
|
+
* inside a `finally`, which resumes as a MICROTASK, while the host schedules
|
|
167
|
+
* `turn_end` as a MACROTASK — and microtasks always fully drain before the
|
|
168
|
+
* next macrotask runs. For the motivating SYNCHRONOUS block, this means the
|
|
169
|
+
* offending phase has ALREADY closed (its `finally` already ran) by the time
|
|
170
|
+
* `turn_end` gets to sample: `liveBrackets` alone reads EMPTY for exactly the
|
|
171
|
+
* case this feature exists to catch. Keeping a short history of recently
|
|
172
|
+
* closed brackets lets `getPhaseForWindow` below check whether a block's time
|
|
173
|
+
* window overlaps a bracket that already closed, not only ones still open.
|
|
174
|
+
*/
|
|
175
|
+
let closedBrackets = [];
|
|
176
|
+
/**
|
|
177
|
+
* Mark a phase as started. Returns a token the caller must pass back to
|
|
178
|
+
* `phaseFinished` — the token itself is the map key, so it identifies this
|
|
179
|
+
* bracket uniquely even against a same-named sibling running concurrently.
|
|
180
|
+
* Cost per call: one object allocation, one `Date.now()`/`toISOString()`
|
|
181
|
+
* call, and one `Map.set` (O(1), no scan of any collection) — negligible
|
|
182
|
+
* next to the runner/phase work it brackets. Measured against this PR's own
|
|
183
|
+
* call site (dispatcher.ts's per-runner `runRunner`): a native ast-grep or
|
|
184
|
+
* tree-sitter scan, or a subprocess spawn, is orders of magnitude more
|
|
185
|
+
* expensive than one map insert.
|
|
186
|
+
*/
|
|
187
|
+
export function phaseStarted(phase) {
|
|
188
|
+
const token = { phase, startedAt: new Date().toISOString() };
|
|
189
|
+
liveBrackets.set(token, true);
|
|
190
|
+
return token;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Close a bracket: removes it from `liveBrackets` (one `Map.delete`, O(1))
|
|
194
|
+
* and records it on the closed-bracket ring, bounded to `CLOSED_BRACKET_CAP`
|
|
195
|
+
* with the oldest entry dropped first — see the `closedBrackets` doc comment
|
|
196
|
+
* above for why a closed history is load-bearing, not just nice-to-have
|
|
197
|
+
* (#1723 review F3). `Map.delete` reports whether it actually removed
|
|
198
|
+
* anything, so a stale or duplicate `phaseFinished` call (the token isn't
|
|
199
|
+
* live — already closed, or never was) is a no-op and can't push a phantom
|
|
200
|
+
* entry onto the closed ring.
|
|
201
|
+
*
|
|
202
|
+
* Callers MUST pair this with `phaseStarted` via try/finally — an exception
|
|
203
|
+
* or an abandoned promise that skips this call leaves a phantom entry in
|
|
204
|
+
* `liveBrackets` forever, silently misattributing every LATER loop_block to
|
|
205
|
+
* stale work (catalog: a bracket that must clear on finish, never left to a
|
|
206
|
+
* process-lifetime latch). `resetCurrentPhaseForSession` is the session-
|
|
207
|
+
* boundary backstop for the case where even try/finally doesn't run (a torn-
|
|
208
|
+
* down activation whose in-flight phase never gets a chance to unwind).
|
|
209
|
+
*
|
|
210
|
+
* #1723 review round 3 (N3) — the honest residual: a bracket leaked this way
|
|
211
|
+
* (e.g. a torn-down concurrent secondary, see `resetCurrentPhaseForSession`)
|
|
212
|
+
* has no age or size cap of its own and sits in `liveBrackets` until the next
|
|
213
|
+
* FULL session start clears it. It does NOT go unnoticed in the meantime: a
|
|
214
|
+
* leaked bracket is old by construction, so its `elapsedMs` is large, and
|
|
215
|
+
* `getPhaseForWindow`'s tie-break below prefers the SMALLER-`elapsedMs`
|
|
216
|
+
* candidate on an overlap tie — a genuine culprit whose own lifetime roughly
|
|
217
|
+
* matches the block window beats a stale bracket that merely contains it.
|
|
218
|
+
* The leak is real; it is demoted, not silently trusted.
|
|
219
|
+
*/
|
|
220
|
+
export function phaseFinished(token) {
|
|
221
|
+
if (!liveBrackets.delete(token))
|
|
222
|
+
return;
|
|
223
|
+
closedBrackets = [
|
|
224
|
+
{ phase: token.phase, startedAt: token.startedAt, closedAt: new Date().toISOString() },
|
|
225
|
+
...closedBrackets,
|
|
226
|
+
].slice(0, CLOSED_BRACKET_CAP);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The OLDEST phase still executing, if any — the longest-running open
|
|
230
|
+
* bracket. #1723 review round 3: this has NO production consumer — `turn_end`
|
|
231
|
+
* (index.ts) reads `getPhaseForWindow` below instead, since this function
|
|
232
|
+
* alone cannot see a phase that already closed, which is exactly what
|
|
233
|
+
* happens for a synchronous block by the time anything samples it (F3).
|
|
234
|
+
* Kept deliberately as a TEST SEAM: several regression tests assert "which
|
|
235
|
+
* phase is currently open" directly, without constructing a window, which is
|
|
236
|
+
* simpler to read and still exercises the same `liveBrackets` map the
|
|
237
|
+
* production path shares. Remove if it stops earning its keep.
|
|
238
|
+
*/
|
|
239
|
+
export function getCurrentPhase() {
|
|
240
|
+
const oldest = liveBrackets.keys().next();
|
|
241
|
+
return oldest.done ? undefined : oldest.value;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A candidate bracket is ignored outright if its OWN lifetime (`elapsedMs`)
|
|
245
|
+
* is under this fraction of the window's length (#1723 review round 3, N4).
|
|
246
|
+
* Overlap alone is not enough: the bounded closed-bracket ring can churn a
|
|
247
|
+
* real culprit out (busy siblings filling `CLOSED_BRACKET_CAP`), leaving only
|
|
248
|
+
* a 1ms bracket that happens to have SOME positive overlap with an 18-second
|
|
249
|
+
* window — reporting it would be a CONFIDENT WRONG ANSWER, worse than no
|
|
250
|
+
* answer. 5%: a genuine cause's own duration should be a meaningful fraction
|
|
251
|
+
* of the block it explains, not a sliver of it. Below that, absent-but-honest
|
|
252
|
+
* (return `undefined`) beats present-but-wrong.
|
|
253
|
+
*/
|
|
254
|
+
const MIN_PLAUSIBLE_ELAPSED_FRACTION = 0.05;
|
|
255
|
+
/**
|
|
256
|
+
* Attribute a block's time window `[windowStartMs, windowEndMs]` to whichever
|
|
257
|
+
* bracket — live OR recently closed — best explains it (#1723 review, F3:
|
|
258
|
+
* the decisive finding). A live bracket's end, for overlap purposes, is
|
|
259
|
+
* "now" (it is still running); a closed bracket's end is its own `closedAt`.
|
|
260
|
+
*
|
|
261
|
+
* Ranked by CONTAINMENT FRACTION (`overlapMs / max(elapsedMs, windowLengthMs)`),
|
|
262
|
+
* not raw overlap (#1723 review round 4, N1 — after round 3's elapsedMs
|
|
263
|
+
* tie-break turned out not to engage in practice). Raw overlap is capped at
|
|
264
|
+
* the window's own length, and a LIVE bracket's end is always `nowMs` — the
|
|
265
|
+
* window's own end — so a long-lived, still-running INNOCENT bracket always
|
|
266
|
+
* scores the maximum possible raw overlap. A genuine CLOSED culprit can only
|
|
267
|
+
* match that by closing at the exact sample instant, which `turn_end` never
|
|
268
|
+
* does: it samples milliseconds AFTER the culprit's `finally` runs, so the
|
|
269
|
+
* culprit's raw overlap is always slightly short of the window's full length
|
|
270
|
+
* — round 3's "exact tie" tie-break never engaged, and the innocent bracket
|
|
271
|
+
* won every time on raw overlap alone. Fraction fixes this structurally: a
|
|
272
|
+
* culprit whose own lifetime roughly IS the window scores close to 1.0
|
|
273
|
+
* regardless of a few milliseconds of sampling lag, while a long-lived
|
|
274
|
+
* innocent bracket that merely CONTAINS the window scores meaningfully
|
|
275
|
+
* lower (its `elapsedMs` denominator is bigger).
|
|
276
|
+
*
|
|
277
|
+
* The denominator is `max(elapsedMs, windowLengthMs)`, not bare `elapsedMs`
|
|
278
|
+
* (#1723 review round 5, R1 — the fraction metric's own blind spot). A bare
|
|
279
|
+
* `elapsedMs` denominator meant a SHORT bracket wholly INSIDE the window
|
|
280
|
+
* scored a perfect, undeserved 1.0 (`overlapMs === elapsedMs` whenever a
|
|
281
|
+
* bracket never spills past either window edge) and beat a real culprit
|
|
282
|
+
* whose bracket spans slightly MORE than the window — e.g. a 1s innocent
|
|
283
|
+
* bracket fully inside an 18.27s window outscored a genuine 19.27s-lifetime
|
|
284
|
+
* culprit that explains the whole window and then some. Using the WINDOW's
|
|
285
|
+
* length as a floor on the denominator caps the achievable fraction at
|
|
286
|
+
* `overlapMs / windowLengthMs` for any bracket no bigger than the window
|
|
287
|
+
* itself, so a small bracket can no longer out-rank a big one purely by
|
|
288
|
+
* being small; a bracket LARGER than the window still divides by its own
|
|
289
|
+
* `elapsedMs`, so the round-4 sampling-lag fix (a big bracket's fraction
|
|
290
|
+
* tracks how much of ITS OWN lifetime overlaps) is unaffected. Free
|
|
291
|
+
* improvement noted while verifying this: it also further demotes an N3
|
|
292
|
+
* leaked bracket when something is competing against it (its `elapsedMs`
|
|
293
|
+
* denominator, already large, only grows).
|
|
294
|
+
*
|
|
295
|
+
* The round-3 elapsedMs comparison is kept as the tie-break for a genuine
|
|
296
|
+
* FRACTION tie (two candidates scoring identically) — still reachable under
|
|
297
|
+
* this metric: two brackets that both partially overlap the window by the
|
|
298
|
+
* SAME amount (`overlapMs` equal) but have different lifetimes both share the
|
|
299
|
+
* `windowLengthMs` denominator (since both are ≤ the window), so their
|
|
300
|
+
* fractions tie while their `elapsedMs` differs. See
|
|
301
|
+
* `tests/clients/latency-logger.test.ts` for a constructed example.
|
|
302
|
+
*
|
|
303
|
+
* The tie-break compares within an EPSILON band (`FRACTION_TIE_EPSILON` =
|
|
304
|
+
* `1e-4`), not exact `===` equality (#1723 review round 6, R3). Co-starting
|
|
305
|
+
* — two brackets opening with the identical `startedAt` millisecond string
|
|
306
|
+
* — is the NORMAL path, not an edge case: `dispatchForFile` launches its
|
|
307
|
+
* runner groups via `Promise.all`, and a runner's `when` precondition awaits
|
|
308
|
+
* only microtasks, so this happens routinely. Closed form for a culprit
|
|
309
|
+
* that closes `lag` ms before the sample versus a co-started bracket that
|
|
310
|
+
* stays live: the culprit scores `1 - lag/W`, the co-started bracket scores
|
|
311
|
+
* `1/(1 + lag/W)` — the co-started bracket wins by `(lag/W)^2`, which at a
|
|
312
|
+
* real `W` = 18 270ms and `lag` = 5ms is ~7e-8 — far below millisecond
|
|
313
|
+
* timestamp resolution, so an EXACT tie never actually engages and the
|
|
314
|
+
* "winner" becomes floating-point rounding noise instead. `1e-4` is well
|
|
315
|
+
* above any realistic `(lag/W)^2` term (a co-started pair collapses into it)
|
|
316
|
+
* and well below the gap between two genuinely different candidates in every
|
|
317
|
+
* test/probe this module has been checked against.
|
|
318
|
+
*
|
|
319
|
+
* The tie-break is a TWO-PASS selection over every candidate, not a
|
|
320
|
+
* single-pass streaming comparison (#1723 review round 7, S1 — a blocker).
|
|
321
|
+
* A single running `bestFraction`, reassigned on every accepted candidate
|
|
322
|
+
* INCLUDING a tie-win, chains: "near" is not transitive, so three candidates
|
|
323
|
+
* at fractions 0.600000 / 0.600080 / 0.600160 (adjacent gaps inside the
|
|
324
|
+
* epsilon band, but the OUTER gap between the first and third outside it)
|
|
325
|
+
* let the running best walk across the whole band one hop at a time — the
|
|
326
|
+
* final answer then depends on SCAN ORDER, reachable at roughly 1.5ms of
|
|
327
|
+
* overlap difference in an 18s window, which is routine under `Promise.all`
|
|
328
|
+
* co-start with three or more candidates. Reseeding `bestFraction` via
|
|
329
|
+
* `Math.max(...)` alone does not fix this — the defect is the chaining
|
|
330
|
+
* itself, not the seed value. The fix: pass 1 finds the maximum fraction
|
|
331
|
+
* across ALL candidates (a plain reduction, immune to order); pass 2 keeps
|
|
332
|
+
* only the candidates within the epsilon band OF THAT MAXIMUM — not of each
|
|
333
|
+
* other, and not of a running value that could itself have drifted — and
|
|
334
|
+
* picks the smallest `elapsedMs` among them. Order-independent by
|
|
335
|
+
* construction: the three-candidate example above always excludes the
|
|
336
|
+
* first candidate (its gap from the true maximum exceeds the epsilon, even
|
|
337
|
+
* though its gap from its immediate neighbor does not), regardless of scan
|
|
338
|
+
* order.
|
|
339
|
+
*
|
|
340
|
+
* Composes with N4's plausibility floor below in the natural order: the
|
|
341
|
+
* floor FILTERS candidates (an implausibly short bracket is never even a
|
|
342
|
+
* candidate for fraction ranking, and never enters the `candidates` array at
|
|
343
|
+
* all); fraction then RANKS the survivors via the two passes above. A
|
|
344
|
+
* bracket that clears the floor always has a well-defined fraction in (0, 1].
|
|
345
|
+
*
|
|
346
|
+
* A candidate needs a STRICTLY POSITIVE overlap: `if (fraction <= 0) return;`
|
|
347
|
+
* enforces that explicitly, before the candidate is even collected — pass 1's
|
|
348
|
+
* `maxFraction` is seeded at 0 and only ever updated by a STRICTLY GREATER
|
|
349
|
+
* value, so without this guard a lone zero-or-negative-fraction candidate
|
|
350
|
+
* would sit "within the epsilon band of the seeded 0" in pass 2 and be
|
|
351
|
+
* wrongly attributed. #1723 review N2 deleted an EARLIER version of this
|
|
352
|
+
* guard as dead code under the round-2 single-pass design, where a strict
|
|
353
|
+
* `>` comparison against a 0-seeded running best already excluded it with no
|
|
354
|
+
* help needed — that reasoning does not carry over to the two-pass shape,
|
|
355
|
+
* where pass 2's job is specifically to find candidates NEAR a seed value.
|
|
356
|
+
*
|
|
357
|
+
* This is the seam that makes the SYNCHRONOUS motivating case attributable at
|
|
358
|
+
* all: the offending phase's `finally` (a microtask) always runs before
|
|
359
|
+
* `turn_end` (a macrotask) samples, so by the time this is called the
|
|
360
|
+
* bracket has typically already moved from `liveBrackets` to
|
|
361
|
+
* `closedBrackets` — checking only the live set reads empty for exactly that
|
|
362
|
+
* case, which is why `getCurrentPhase` alone was not enough.
|
|
363
|
+
*
|
|
364
|
+
* KNOWN RESIDUAL (#1723 review round 4, stated not fixed): a leaked bracket
|
|
365
|
+
* (see `phaseFinished`'s doc comment on N3) with NO competing candidate still
|
|
366
|
+
* wins any window it fully contains, however many times over its own
|
|
367
|
+
* lifetime exceeds the window — fraction ranking only demotes a leak when
|
|
368
|
+
* something else is competing for the same window. A ceiling on elapsedMs
|
|
369
|
+
* relative to the window length (e.g. reject a candidate whose lifetime is
|
|
370
|
+
* some large multiple of the window) would close this, at the cost of a
|
|
371
|
+
* second magic number; left open pending a concrete false-positive to size
|
|
372
|
+
* it against.
|
|
373
|
+
*
|
|
374
|
+
* Caveat for an automated consumer (e.g. #1549's loop_block ↔
|
|
375
|
+
* lsp_diagnostics_timeout correlation): the window's own end is `now` at
|
|
376
|
+
* SAMPLE time, which assumes the block ended right when `turn_end` read
|
|
377
|
+
* `getEventLoopStats()` — a reasonable approximation, not a measured fact.
|
|
378
|
+
* `startedAt`/`elapsedMs`/`stillRunning` on the returned attribution let a
|
|
379
|
+
* human reader judge that assumption per-record; an automated correlation
|
|
380
|
+
* should account for the same slack rather than treating the window edges as
|
|
381
|
+
* exact.
|
|
382
|
+
*/
|
|
383
|
+
/**
|
|
384
|
+
* #1723 review round 6, R3 / round 7, S1: how close two fractions must be to
|
|
385
|
+
* count as the SAME answer rather than a genuinely different one. See
|
|
386
|
+
* `getPhaseForWindow`'s doc comment for the closed-form derivation (a
|
|
387
|
+
* co-started bracket's fraction gap from the true culprit is order
|
|
388
|
+
* `(lag/W)^2`, ~7e-8 at production scale) and why a single-pass streaming
|
|
389
|
+
* comparison against this band is NOT enough on its own (S1, below).
|
|
390
|
+
*/
|
|
391
|
+
const FRACTION_TIE_EPSILON = 1e-4;
|
|
392
|
+
export function getPhaseForWindow(windowStartMs, windowEndMs) {
|
|
393
|
+
const nowMs = Date.now();
|
|
394
|
+
const windowLengthMs = windowEndMs - windowStartMs;
|
|
395
|
+
const minPlausibleElapsedMs = windowLengthMs * MIN_PLAUSIBLE_ELAPSED_FRACTION;
|
|
396
|
+
const candidates = [];
|
|
397
|
+
const consider = (phase, startedAt, endMs, stillRunning) => {
|
|
398
|
+
const startMs = Date.parse(startedAt);
|
|
399
|
+
const elapsedMs = endMs - startMs;
|
|
400
|
+
if (elapsedMs < minPlausibleElapsedMs)
|
|
401
|
+
return; // N4: implausibly short to be the cause
|
|
402
|
+
const overlapMs = Math.min(endMs, windowEndMs) - Math.max(startMs, windowStartMs);
|
|
403
|
+
// #1723 review round 5, R1: divide by max(elapsedMs, windowLengthMs), not
|
|
404
|
+
// elapsedMs alone — see the function doc comment for why a bare
|
|
405
|
+
// elapsedMs denominator let a short bracket wholly INSIDE the window
|
|
406
|
+
// score a perfect, undeserved 1.0.
|
|
407
|
+
const fraction = overlapMs / Math.max(elapsedMs, windowLengthMs);
|
|
408
|
+
if (fraction <= 0)
|
|
409
|
+
return; // no meaningful overlap — never a candidate
|
|
410
|
+
candidates.push({ phase, startedAt, stillRunning, elapsedMs, fraction });
|
|
411
|
+
};
|
|
412
|
+
for (const token of liveBrackets.keys()) {
|
|
413
|
+
consider(token.phase, token.startedAt, nowMs, true);
|
|
414
|
+
}
|
|
415
|
+
for (const closed of closedBrackets) {
|
|
416
|
+
consider(closed.phase, closed.startedAt, Date.parse(closed.closedAt), false);
|
|
417
|
+
}
|
|
418
|
+
if (candidates.length === 0)
|
|
419
|
+
return undefined;
|
|
420
|
+
// #1723 review round 7, S1 (blocker): a single-pass streaming comparison
|
|
421
|
+
// against `FRACTION_TIE_EPSILON` (round 6's shape) CHAINS — each accepted
|
|
422
|
+
// candidate becomes the new `bestFraction`, including a tie-win, and
|
|
423
|
+
// "near" is not transitive. A chain of near-ties whose adjacent gaps sit
|
|
424
|
+
// inside the epsilon band but whose OUTER gap does not (e.g. three
|
|
425
|
+
// candidates at fractions 0.600000 / 0.600080 / 0.600160, an 18s window)
|
|
426
|
+
// lets the running "best" walk across the whole band one hop at a time,
|
|
427
|
+
// so the final answer depends on SCAN ORDER — reachable at roughly 1.5ms
|
|
428
|
+
// of overlap difference in an 18s window, routine under `Promise.all`
|
|
429
|
+
// co-start. `bestFraction = Math.max(...)` alone does NOT fix this: the
|
|
430
|
+
// bug is the CHAINING, not the seed value.
|
|
431
|
+
//
|
|
432
|
+
// Fixed with two independent passes instead, order-independent by
|
|
433
|
+
// construction:
|
|
434
|
+
// Pass 1 finds the MAXIMUM fraction across every candidate — a plain
|
|
435
|
+
// reduction, immune to scan order.
|
|
436
|
+
// Pass 2 keeps only the candidates within `FRACTION_TIE_EPSILON` OF
|
|
437
|
+
// THAT MAXIMUM (not of each other, and not of a running "best" that
|
|
438
|
+
// could itself have already drifted) and picks the smallest
|
|
439
|
+
// `elapsedMs` among THEM. In the three-candidate example above, only
|
|
440
|
+
// the two candidates within the band of the true maximum (0.600160)
|
|
441
|
+
// compete; the third, whose gap from the maximum exceeds the epsilon
|
|
442
|
+
// even though its gap from its IMMEDIATE neighbor does not, is
|
|
443
|
+
// correctly excluded regardless of which order the three were
|
|
444
|
+
// scanned in.
|
|
445
|
+
let maxFraction = 0;
|
|
446
|
+
for (const candidate of candidates) {
|
|
447
|
+
if (candidate.fraction > maxFraction)
|
|
448
|
+
maxFraction = candidate.fraction;
|
|
449
|
+
}
|
|
450
|
+
let best;
|
|
451
|
+
for (const candidate of candidates) {
|
|
452
|
+
if (Math.abs(candidate.fraction - maxFraction) > FRACTION_TIE_EPSILON)
|
|
453
|
+
continue;
|
|
454
|
+
if (best === undefined || candidate.elapsedMs < best.elapsedMs)
|
|
455
|
+
best = candidate;
|
|
456
|
+
}
|
|
457
|
+
if (best === undefined)
|
|
458
|
+
return undefined;
|
|
459
|
+
return {
|
|
460
|
+
phase: best.phase,
|
|
461
|
+
startedAt: best.startedAt,
|
|
462
|
+
stillRunning: best.stillRunning,
|
|
463
|
+
elapsedMs: best.elapsedMs,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Test-only: the closed-bracket ring's actual storage length, mirroring
|
|
468
|
+
* `_recentPhasesStorageLengthForTest` above — pins that `phaseFinished`'s
|
|
469
|
+
* `.slice(0, CLOSED_BRACKET_CAP)` guard is intact independent of any
|
|
470
|
+
* read-side behavior (#1723 review: "ring unbounded" mutation).
|
|
471
|
+
*/
|
|
472
|
+
export function _closedBracketsStorageLengthForTest() {
|
|
473
|
+
return closedBrackets.length;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Session-boundary backstop: unconditionally clears BOTH the live-bracket map
|
|
477
|
+
* and the closed-bracket ring. `phaseFinished` is the normal clear path
|
|
478
|
+
* (paired via try/finally at each call site), but this module's state is
|
|
479
|
+
* process-lifetime, not session-scoped, so a phase abandoned mid-flight by a
|
|
480
|
+
* torn-down activation (an abort that skips straight past the `finally`, a
|
|
481
|
+
* session replaced mid-scan) would otherwise leave a phantom bracket live
|
|
482
|
+
* forever — misattributing every loop_block in every LATER session to that
|
|
483
|
+
* one stale phase.
|
|
484
|
+
*
|
|
485
|
+
* #1723 review F4 — placement is deliberate, and DIFFERENT from
|
|
486
|
+
* `warmDispatchAtSessionStart()`: call this only once `session_start` has
|
|
487
|
+
* confirmed it is running a FULL session start (`decideSessionStart(...)
|
|
488
|
+
* .runFullSessionStart`), i.e. from BEHIND the #473 concurrent-secondary
|
|
489
|
+
* gate, not before it. The reason is the opposite of what an unconditional,
|
|
490
|
+
* "re-arm on every session_start" placement would give: this module's state
|
|
491
|
+
* is process-shared across every concurrently-live activation in the same
|
|
492
|
+
* process, exactly like the LSP fleet / runtime generation #473 already
|
|
493
|
+
* protects — a concurrent secondary's `session_start` firing while the
|
|
494
|
+
* PARENT activation has genuinely in-flight brackets must not wipe the
|
|
495
|
+
* parent's live attribution out from under it. Only a real sequential
|
|
496
|
+
* session replacement (new/resume/fork/reload confirmed by
|
|
497
|
+
* `decideSessionStart`) may safely assume no other activation still owns
|
|
498
|
+
* live brackets in this process. A concurrent secondary's OWN torn-down
|
|
499
|
+
* brackets are still covered: each caller's `phaseFinished` runs in its own
|
|
500
|
+
* try/finally, and if that activation is torn down hard enough to skip even
|
|
501
|
+
* that, the accepted cost is a stale bracket that the NEXT full session start
|
|
502
|
+
* clears — not a wrong clear of a sibling that is still working.
|
|
503
|
+
*/
|
|
504
|
+
export function resetCurrentPhaseForSession() {
|
|
505
|
+
liveBrackets.clear();
|
|
506
|
+
closedBrackets = [];
|
|
78
507
|
}
|
|
79
508
|
export function logLatency(entry) {
|
|
80
509
|
const ts = new Date().toISOString();
|
|
81
510
|
if (entry.type === "phase" && entry.phase && !LAST_PHASE_EXCLUDED.has(entry.phase)) {
|
|
82
|
-
|
|
511
|
+
recentPhases = [{ phase: entry.phase, ts }, ...recentPhases].slice(0, RECENT_PHASE_CAP);
|
|
83
512
|
}
|
|
84
513
|
if (isTestMode()) {
|
|
85
514
|
return;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function createLazyImport(load) {
|
|
2
|
+
let cached;
|
|
3
|
+
return {
|
|
4
|
+
get() {
|
|
5
|
+
// `Promise.resolve().then(load)` (not a bare `load()`) so a loader
|
|
6
|
+
// that throws SYNCHRONOUSLY — rather than returning a rejected
|
|
7
|
+
// promise — still yields a rejected promise instead of throwing out
|
|
8
|
+
// of `get()` itself. `import(...)` never throws synchronously, but a
|
|
9
|
+
// caller-supplied `load` is not guaranteed to keep that contract.
|
|
10
|
+
return (cached ??= Promise.resolve()
|
|
11
|
+
.then(() => load())
|
|
12
|
+
.catch((err) => {
|
|
13
|
+
cached = undefined;
|
|
14
|
+
throw err;
|
|
15
|
+
}));
|
|
16
|
+
},
|
|
17
|
+
resetForTests() {
|
|
18
|
+
cached = undefined;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The degradation ledger's field-truncation policy, as a leaf module (#1816).
|
|
3
|
+
*
|
|
4
|
+
* It lives here rather than in `degradation-ledger.ts` because reason BUILDERS
|
|
5
|
+
* (`formatToolFailure`, `firstOutputLine`) need the bound without importing the
|
|
6
|
+
* ledger itself. Test files routinely `vi.mock` the ledger to observe recorded
|
|
7
|
+
* degradations; a builder that reached through that mock for a constant would
|
|
8
|
+
* break every one of them.
|
|
9
|
+
*/
|
|
10
|
+
/** The single truncation bound for every ledger field, and for the reasons callers build. */
|
|
11
|
+
export const LEDGER_FIELD_MAX = 200;
|
|
12
|
+
export function normalizeForLedger(value) {
|
|
13
|
+
return String(value ?? "unknown");
|
|
14
|
+
}
|
|
15
|
+
/** Bound a value to `LEDGER_FIELD_MAX`, marking the elision. */
|
|
16
|
+
export function truncateForLedger(value) {
|
|
17
|
+
const text = normalizeForLedger(value);
|
|
18
|
+
return text.length > LEDGER_FIELD_MAX
|
|
19
|
+
? `${text.slice(0, LEDGER_FIELD_MAX)}…`
|
|
20
|
+
: text;
|
|
21
|
+
}
|
|
@@ -172,6 +172,14 @@ export const LENS_FLAGS = [
|
|
|
172
172
|
default: false,
|
|
173
173
|
scope: "global",
|
|
174
174
|
},
|
|
175
|
+
{
|
|
176
|
+
name: "lens-turn-end-madge",
|
|
177
|
+
description: "Run the per-turn-end madge circular-dependency check on import-changed files. Off by default: the pass only writes debug output, and user-facing madge diagnostics come from the session-start scan cache + lens_diagnostics. Also via turnEnd.madge.enabled=true in ~/.pi-lens/config.json.",
|
|
178
|
+
configKey: "turnEnd.madge.enabled",
|
|
179
|
+
negated: false,
|
|
180
|
+
default: false,
|
|
181
|
+
scope: "global",
|
|
182
|
+
},
|
|
175
183
|
];
|
|
176
184
|
const byName = new Map(LENS_FLAGS.map((spec) => [spec.name, spec]));
|
|
177
185
|
export function getLensFlagSpec(name) {
|