@herbertgao/pi-extensions 2026.8.5 → 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 +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- 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/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- 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 +16 -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 +22 -15
- 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 +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -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 +8 -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 +119 -103
- 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 +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- 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 +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -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 +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- 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 +176 -37
- 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/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- 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/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- 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 +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- 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 +81 -41
- 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 +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- 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 +943 -93
- 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 +106 -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/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- 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/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- 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 +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- 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 +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- 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 +487 -9
- 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-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- 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 +517 -69
- 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 +172 -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 +61 -3
- 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/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- 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 +178 -30
- 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 +85 -15
- 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/runner-findings.js +23 -2
- 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 +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- 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 +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- 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/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- 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/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -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 +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -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 +22 -0
- 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-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-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 +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- 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-laundering-test.yml +11 -0
- 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 +66 -0
- 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-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-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 +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- 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 +32 -0
- 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 +132 -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 +12 -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 +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- 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/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -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-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- 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 +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- 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 +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- 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 +51 -23
- 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/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Past-EOF validity gate for cited diagnostic line numbers (#1641).
|
|
3
|
+
*
|
|
4
|
+
* A live session served diagnostics at lines 407-410 of a file that was 402
|
|
5
|
+
* lines on disk and UNMODIFIED — the LSP's in-memory document had drifted
|
|
6
|
+
* longer than disk without any file write happening (no didChange lost, just
|
|
7
|
+
* an in-memory buffer that grew past what the file actually contains). That
|
|
8
|
+
* makes this a DIFFERENT invariant than #1622/#1627's freshness gate: those
|
|
9
|
+
* compare a cited file's MTIME against a scan timestamp, which says nothing
|
|
10
|
+
* when the file was never touched on disk at all. The cheap, orthogonal check
|
|
11
|
+
* available here is structural: a diagnostic whose start line exceeds the
|
|
12
|
+
* file's CURRENT line count cannot possibly be describing current content,
|
|
13
|
+
* regardless of what any timestamp says.
|
|
14
|
+
*
|
|
15
|
+
* CONTRACT:
|
|
16
|
+
* - A diagnostic citing a line beyond the file's current line count is
|
|
17
|
+
* DEMOTED (`stale: true`), never dropped — the underlying issue may well
|
|
18
|
+
* still be real, just at a coordinate this gate can no longer vouch for.
|
|
19
|
+
* - RE-ARMS, never latches: the verdict is recomputed from the gate's own
|
|
20
|
+
* inputs (current mtime, current line count) on every call, so a
|
|
21
|
+
* TRANSIENT shrink (truncate-then-write, a formatter pass, a checkout)
|
|
22
|
+
* that later restores the line un-demotes on its own next read. A
|
|
23
|
+
* persisted `stale: true` a caller stores between calls is a cache of the
|
|
24
|
+
* last-derived verdict, never an independent source of truth that
|
|
25
|
+
* overrides the next derivation — the #1633-V1 lesson: derive, don't latch.
|
|
26
|
+
* - Fail open: a file that cannot be stat'ed/read, or exceeds the size gate,
|
|
27
|
+
* yields no verdict at all (every diagnostic passes through unchanged).
|
|
28
|
+
* An unreadable/oversized file is not evidence of anything; demoting on
|
|
29
|
+
* it would manufacture false staleness.
|
|
30
|
+
* - The line count matches the LSP's own addressing, not `wc -l`: a
|
|
31
|
+
* document with N newline characters has N+1 ADDRESSABLE lines (the
|
|
32
|
+
* line after the final `\n`, or the single line of an empty document,
|
|
33
|
+
* is a real position a server can anchor a diagnostic to — e.g. TS1005
|
|
34
|
+
* "'}' expected" at EOF). `WidgetDiagnostic.line` is always
|
|
35
|
+
* `range.start.line + 1` from that same addressing, so the gate must
|
|
36
|
+
* use it too or every trailing-newline file demotes its own valid EOF
|
|
37
|
+
* diagnostics.
|
|
38
|
+
* - Cost discipline: one `statSync` per served file; the file's newlines
|
|
39
|
+
* are counted in bounded chunks via a raw fd read (never a full
|
|
40
|
+
* `readFileSync` — no whole-file string materialization, no per-render
|
|
41
|
+
* re-read of unchanged content), gated by a byte-size cap matching
|
|
42
|
+
* `captureReadContentBinding`'s precedent (`read-guard.ts`). The count is
|
|
43
|
+
* memoized in a small SHARED cache (module-scoped, bounded, evicted FIFO
|
|
44
|
+
* past a cap), keyed on BOTH mtime AND size (review round V1) — mtime
|
|
45
|
+
* ALONE is not a reliable cache key on hosts where its resolution is
|
|
46
|
+
* coarser than back-to-back writes: a truncate-then-write, a formatter
|
|
47
|
+
* write-back, or pi-lens's own auto-format immediately followed by the
|
|
48
|
+
* agent's write can land two DIFFERENT contents on the identical
|
|
49
|
+
* `mtimeMs`, and a mtime-only cache serves the first content's count for
|
|
50
|
+
* the second — measured live at 207/300 shrink/restore cycles. Requiring
|
|
51
|
+
* both to match narrows a same-tick collision to same-mtime-AND-same-size
|
|
52
|
+
* (which a shrink/restore cycle never produces, since the whole point is
|
|
53
|
+
* the size changed). A changed file is always recounted. Tests can still
|
|
54
|
+
* inject an isolated {@link LineCountCache} via {@link createLineCountCache}
|
|
55
|
+
* to avoid cross-test bleed.
|
|
56
|
+
* - On a demotion EDGE (a diagnostic that was not already flagged past-EOF
|
|
57
|
+
* and now is), best-effort trigger a document resync (didClose/didOpen or
|
|
58
|
+
* a full-sync didChange) so the desync HEALS instead of re-serving the
|
|
59
|
+
* same stale verdict next time (#1631's resync-before-revalidate rule).
|
|
60
|
+
* Resync is fire-and-forget: a failure here must never block or fail the
|
|
61
|
+
* render path that discovered the drift. Already-demoted entries and the
|
|
62
|
+
* healing direction never re-trigger a resync — only the rising edge does.
|
|
63
|
+
*/
|
|
64
|
+
import * as fs from "node:fs";
|
|
65
|
+
import { logLatency } from "./latency-logger.js";
|
|
66
|
+
import { toProjectRelativePath } from "./path-utils.js";
|
|
67
|
+
import { STALE_LINE_MARKER } from "./stale-marker.js";
|
|
68
|
+
/** Marker rendered in place of a demoted diagnostic's now-untrustworthy line.
|
|
69
|
+
* Shares the base `STALE_LINE_MARKER` vocabulary with sibling freshness
|
|
70
|
+
* gates (#1622/#1633) and adds this gate's own specific reason as a suffix. */
|
|
71
|
+
export const PAST_EOF_STALE_MARKER = `${STALE_LINE_MARKER} — line past EOF`;
|
|
72
|
+
/** How many demoted cited lines a single record names before it stops. */
|
|
73
|
+
const MAX_LOGGED_PAST_EOF_LINES = 3;
|
|
74
|
+
/** Byte-size gate matching `captureReadContentBinding`'s precedent
|
|
75
|
+
* (`clients/read-guard.ts`'s `READ_BINDING_MAX_BYTES`) — a hot render/serve
|
|
76
|
+
* path must never pay to scan an unbounded file. */
|
|
77
|
+
const MAX_GATE_BYTES = 4 * 1024 * 1024;
|
|
78
|
+
/** Chunk size for the bounded newline scan — small enough to keep one read's
|
|
79
|
+
* memory footprint negligible, large enough that a multi-MB file finishes in
|
|
80
|
+
* single-digit reads rather than thousands. */
|
|
81
|
+
const NEWLINE_SCAN_CHUNK_BYTES = 64 * 1024;
|
|
82
|
+
const NEWLINE_BYTE = 0x0a;
|
|
83
|
+
/** Test-only isolation seam. Production code uses the shared default cache. */
|
|
84
|
+
export function createLineCountCache() {
|
|
85
|
+
return new Map();
|
|
86
|
+
}
|
|
87
|
+
// Shared across calls/render passes so N files touched once per session cost
|
|
88
|
+
// N recounts total, not N per render — the actual fix for the cost this gate
|
|
89
|
+
// exists to control. Still cannot go stale: every read re-stats first and a
|
|
90
|
+
// mismatched mtime always recomputes (see the module doc comment). Bounded
|
|
91
|
+
// via FIFO eviction (mirrors `READ_GUARD_MAX_FILES`'s precedent) so a long
|
|
92
|
+
// session's file count cannot grow this without limit.
|
|
93
|
+
const MAX_SHARED_CACHE_ENTRIES = 512;
|
|
94
|
+
const sharedLineCountCache = new Map();
|
|
95
|
+
function rememberInSharedCache(cache, filePath, entry) {
|
|
96
|
+
if (cache === sharedLineCountCache &&
|
|
97
|
+
cache.size >= MAX_SHARED_CACHE_ENTRIES &&
|
|
98
|
+
!cache.has(filePath)) {
|
|
99
|
+
const oldest = cache.keys().next().value;
|
|
100
|
+
if (oldest !== undefined)
|
|
101
|
+
cache.delete(oldest);
|
|
102
|
+
}
|
|
103
|
+
cache.set(filePath, entry);
|
|
104
|
+
}
|
|
105
|
+
/** Test-only: drop the shared memo between test files/cases. */
|
|
106
|
+
export function _resetSharedLineCountCacheForTests() {
|
|
107
|
+
sharedLineCountCache.clear();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Test-only: seed the shared memo with a fabricated entry, deterministically
|
|
111
|
+
* proving a cache HIT (and that the default cache used by callers with no
|
|
112
|
+
* explicit `lineCountCache` is genuinely shared across calls) without relying
|
|
113
|
+
* on a real write's mtime round-tripping through `fs.utimesSync` at full
|
|
114
|
+
* precision — a platform-specific hazard on its own, unrelated to what such a
|
|
115
|
+
* test is actually verifying.
|
|
116
|
+
*/
|
|
117
|
+
export function _seedSharedLineCountCacheForTests(filePath, entry) {
|
|
118
|
+
sharedLineCountCache.set(filePath, entry);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Count newline bytes in `filePath` via bounded raw reads — never a full
|
|
122
|
+
* `readFileSync`/UTF-8 decode of the whole file. `0x0A` (LF) never appears as
|
|
123
|
+
* a continuation byte in UTF-8, so counting it at the byte level is exact
|
|
124
|
+
* regardless of encoding, and multi-byte characters spanning a chunk boundary
|
|
125
|
+
* cannot produce a false match (a continuation byte's high bits can never
|
|
126
|
+
* equal `0x0A`).
|
|
127
|
+
*/
|
|
128
|
+
function countNewlinesChunked(filePath, sizeBytes) {
|
|
129
|
+
const fd = fs.openSync(filePath, "r");
|
|
130
|
+
try {
|
|
131
|
+
const buffer = Buffer.allocUnsafe(Math.min(NEWLINE_SCAN_CHUNK_BYTES, sizeBytes) || 1);
|
|
132
|
+
let newlineCount = 0;
|
|
133
|
+
let position = 0;
|
|
134
|
+
while (position < sizeBytes) {
|
|
135
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position);
|
|
136
|
+
if (bytesRead <= 0)
|
|
137
|
+
break;
|
|
138
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
139
|
+
if (buffer[i] === NEWLINE_BYTE)
|
|
140
|
+
newlineCount++;
|
|
141
|
+
}
|
|
142
|
+
position += bytesRead;
|
|
143
|
+
}
|
|
144
|
+
return newlineCount;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
fs.closeSync(fd);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* LSP-addressable line count for `filePath`: newline count + 1, matching
|
|
152
|
+
* `range.start.line + 1` (a document with zero newlines still has one
|
|
153
|
+
* addressable line — position 0,0 — and a trailing `\n` adds one more,
|
|
154
|
+
* empty, addressable line after it). Memoized in `cache` (the shared default
|
|
155
|
+
* unless a caller injects its own) and invalidated by mtime AND size (V1 —
|
|
156
|
+
* mtime alone is not a reliable cache key on hosts where its resolution is
|
|
157
|
+
* coarser than back-to-back writes). Returns `undefined` when the file
|
|
158
|
+
* cannot be stat'ed, is zero bytes, exceeds the byte-size gate, or
|
|
159
|
+
* cannot be read (deleted, unreadable, permissions, oversized) — callers MUST
|
|
160
|
+
* treat `undefined` as "no verdict", not as "zero lines" (the empty-result-
|
|
161
|
+
* must-distinguish-clean-from-errored screen).
|
|
162
|
+
*/
|
|
163
|
+
export function getCachedLineCount(filePath, cache = sharedLineCountCache) {
|
|
164
|
+
let stat;
|
|
165
|
+
try {
|
|
166
|
+
stat = fs.statSync(filePath);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
// A zero-byte stat can be a truncate-then-write window. It is not evidence
|
|
172
|
+
// that the file has one addressable line, so fail open like an unreadable
|
|
173
|
+
// file rather than manufacturing a verdict from the empty read.
|
|
174
|
+
if (stat.size === 0)
|
|
175
|
+
return undefined;
|
|
176
|
+
const cached = cache.get(filePath);
|
|
177
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
178
|
+
return cached.lineCount;
|
|
179
|
+
}
|
|
180
|
+
if (stat.size > MAX_GATE_BYTES)
|
|
181
|
+
return undefined;
|
|
182
|
+
let newlineCount;
|
|
183
|
+
try {
|
|
184
|
+
newlineCount = countNewlinesChunked(filePath, stat.size);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const lineCount = newlineCount + 1;
|
|
190
|
+
rememberInSharedCache(cache, filePath, {
|
|
191
|
+
mtimeMs: stat.mtimeMs,
|
|
192
|
+
size: stat.size,
|
|
193
|
+
lineCount,
|
|
194
|
+
});
|
|
195
|
+
return lineCount;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Recompute the past-EOF verdict for every diagnostic in `diagnostics`
|
|
199
|
+
* against `filePath`'s CURRENT line count. Sets `stale: true` on a match and
|
|
200
|
+
* `stale: false` when a previously-past-EOF line is back in bounds (a
|
|
201
|
+
* transient shrink healed) — the verdict is always derived fresh, never
|
|
202
|
+
* latched. Pure apart from the cached stat/read in {@link getCachedLineCount}
|
|
203
|
+
* and the bounded `logLatency`/`resync` calls, which fire only on a RISING
|
|
204
|
+
* edge (a diagnostic that was not already flagged and now is) so an
|
|
205
|
+
* unchanged persistent demotion doesn't re-log or re-resync on every render.
|
|
206
|
+
* Never throws, never mutates the input array.
|
|
207
|
+
*/
|
|
208
|
+
export function demotePastEofDiagnostics(args) {
|
|
209
|
+
const { diagnostics } = args;
|
|
210
|
+
if (diagnostics.length === 0) {
|
|
211
|
+
return { diagnostics: [], demotedCount: 0 };
|
|
212
|
+
}
|
|
213
|
+
const lineCount = getCachedLineCount(args.filePath, args.lineCountCache ?? sharedLineCountCache);
|
|
214
|
+
if (lineCount === undefined) {
|
|
215
|
+
// Fail open: no verdict available for this file right now. Leave
|
|
216
|
+
// whatever `stale` value each entry already carries untouched — this
|
|
217
|
+
// gate has nothing to say either direction.
|
|
218
|
+
return { diagnostics: [...diagnostics], demotedCount: 0 };
|
|
219
|
+
}
|
|
220
|
+
const risingEdgeLines = [];
|
|
221
|
+
const out = diagnostics.map((d) => {
|
|
222
|
+
// Another gate's demotion is not this gate's to heal (#1631 drift
|
|
223
|
+
// entries have in-bounds lines; re-deriving here would clear them).
|
|
224
|
+
if (d.stale && d.staleReason !== undefined && d.staleReason !== "past-eof")
|
|
225
|
+
return d;
|
|
226
|
+
const isPastEof = typeof d.line === "number" && d.line > lineCount;
|
|
227
|
+
if (isPastEof === !!d.stale)
|
|
228
|
+
return d; // no transition either direction
|
|
229
|
+
if (isPastEof)
|
|
230
|
+
risingEdgeLines.push(d.line);
|
|
231
|
+
return { ...d, stale: isPastEof, staleReason: isPastEof ? "past-eof" : undefined };
|
|
232
|
+
});
|
|
233
|
+
if (risingEdgeLines.length === 0) {
|
|
234
|
+
return { diagnostics: out, demotedCount: 0 };
|
|
235
|
+
}
|
|
236
|
+
let sizeBytes;
|
|
237
|
+
try {
|
|
238
|
+
sizeBytes = fs.statSync(args.filePath).size;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
sizeBytes = 0;
|
|
242
|
+
}
|
|
243
|
+
logLatency({
|
|
244
|
+
type: "phase",
|
|
245
|
+
phase: "diagnostic_past_eof",
|
|
246
|
+
filePath: args.cwd,
|
|
247
|
+
durationMs: 0,
|
|
248
|
+
metadata: {
|
|
249
|
+
store: args.store,
|
|
250
|
+
file: toProjectRelativePath(args.filePath, args.cwd),
|
|
251
|
+
sizeBytes,
|
|
252
|
+
actualLineCount: lineCount,
|
|
253
|
+
demotedCount: risingEdgeLines.length,
|
|
254
|
+
sampleCitedLines: risingEdgeLines.slice(0, MAX_LOGGED_PAST_EOF_LINES),
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
if (args.resync) {
|
|
258
|
+
try {
|
|
259
|
+
args.resync(args.filePath);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Best-effort — a resync failure must never fail the render path.
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return { diagnostics: out, demotedCount: risingEdgeLines.length };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Default resync trigger (#1641 criterion 2): re-open the file with its
|
|
269
|
+
* CURRENT disk content so the LSP's in-memory document is replaced rather
|
|
270
|
+
* than left to re-publish the same divergent diagnostics next time
|
|
271
|
+
* (`LSPService.openFile` sends a full-sync didChange, or a didClose/didOpen
|
|
272
|
+
* pair for servers that only re-scan on a fresh open — see
|
|
273
|
+
* `handleNotifyOpen`'s `reopenOnResync` branch). Dynamic import (mirrors
|
|
274
|
+
* `clients/module-report-lsp.ts`) so this module — used from widget-state.ts
|
|
275
|
+
* and tools/lens-diagnostics.ts — never statically depends on `clients/lsp`,
|
|
276
|
+
* which itself imports widget-state.ts.
|
|
277
|
+
*
|
|
278
|
+
* Fire-and-forget by design: the caller is a synchronous render path and must
|
|
279
|
+
* not block on (or fail because of) a resync. No LSP service, no client for
|
|
280
|
+
* this file's language, or a transient read/spawn failure all degrade to a
|
|
281
|
+
* silent no-op — the next successful touch/dispatch resyncs anyway.
|
|
282
|
+
*/
|
|
283
|
+
export function resyncDocumentOnPastEof(filePath) {
|
|
284
|
+
void (async () => {
|
|
285
|
+
try {
|
|
286
|
+
const [{ getLSPService }, content] = await Promise.all([
|
|
287
|
+
import("./lsp/index.js"),
|
|
288
|
+
fs.promises.readFile(filePath, "utf-8"),
|
|
289
|
+
]);
|
|
290
|
+
await getLSPService().openFile(filePath, content, {
|
|
291
|
+
preserveDiagnostics: false,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Best-effort heal only.
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
298
|
+
}
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
*/
|
|
62
62
|
import { logBusEvent } from "./bus-events-logger.js";
|
|
63
63
|
import { normalizeFilePath } from "./path-utils.js";
|
|
64
|
-
import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
|
|
64
|
+
import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
|
|
65
65
|
import { isBusPublishEnabled } from "./bus-publish.js";
|
|
66
66
|
export const BUS_DIAGNOSTICS_EVENT = "pilens:diagnostics";
|
|
67
67
|
export const BUS_DIAGNOSTICS_VERSION = 1;
|
|
@@ -149,8 +149,13 @@ export function publishDiagnostics(args) {
|
|
|
149
149
|
}
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
152
|
+
const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
|
|
153
|
+
event: BUS_DIAGNOSTICS_EVENT,
|
|
154
|
+
cwd: normalizeFilePath(args.cwd),
|
|
155
|
+
}));
|
|
156
|
+
if (resolution.outcome === "stale-session")
|
|
157
|
+
return;
|
|
158
|
+
if (resolution.outcome === "unwired") {
|
|
154
159
|
if (!hasLoggedUnwired) {
|
|
155
160
|
hasLoggedUnwired = true;
|
|
156
161
|
logBusEvent({
|
|
@@ -161,6 +166,7 @@ export function publishDiagnostics(args) {
|
|
|
161
166
|
}
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
169
|
+
const busEmit = resolution.emit;
|
|
164
170
|
try {
|
|
165
171
|
const fileEntries = args.files.map((f) => {
|
|
166
172
|
const normPath = normalizeFilePath(f.path);
|
|
@@ -204,6 +210,7 @@ export function publishDiagnostics(args) {
|
|
|
204
210
|
outcome: "emit_failed",
|
|
205
211
|
cwd: normalizeFilePath(args.cwd),
|
|
206
212
|
error: String(err),
|
|
213
|
+
ctxSource: resolution.ctxSource,
|
|
207
214
|
});
|
|
208
215
|
if (!hasLoggedFailure) {
|
|
209
216
|
hasLoggedFailure = true;
|
|
@@ -22,13 +22,14 @@ import { detectFileRole } from "../file-role.js";
|
|
|
22
22
|
import { isTestFile } from "../file-utils.js";
|
|
23
23
|
import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
24
24
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
25
|
-
import { logLatency } from "../latency-logger.js";
|
|
25
|
+
import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
|
|
26
26
|
import { isSpawnableCommand } from "../installer/index.js";
|
|
27
27
|
import { normalizeMapKey } from "../path-utils.js";
|
|
28
28
|
import { loadPiLensProjectConfig } from "../project-lens-config.js";
|
|
29
29
|
import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
|
|
30
30
|
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
31
31
|
import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
|
|
32
|
+
import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./runners/utils/availability-policy.js";
|
|
32
33
|
import { applyDispositions } from "../diagnostic-dispositions.js";
|
|
33
34
|
import { applyInlineSuppressions } from "./inline-suppressions.js";
|
|
34
35
|
import { getToolPlan } from "./plan.js";
|
|
@@ -76,12 +77,49 @@ export function normalizeCacheKey(cmd) {
|
|
|
76
77
|
const normalized = cmd.replace(/\.(cmd|exe)$/i, "").toLowerCase();
|
|
77
78
|
return `session.toolCache.${normalized}`;
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
+
/** Probe budget for the generic `hasTool` availability check, ms. */
|
|
81
|
+
const TOOL_PROBE_TIMEOUT_MS = 5000;
|
|
82
|
+
/**
|
|
83
|
+
* Session fact holding the epoch ms after which a TRANSIENT verdict for a
|
|
84
|
+
* command may be re-probed. Kept beside the boolean fact so both share the
|
|
85
|
+
* session's lifetime — no second global to reset (#1476).
|
|
86
|
+
*/
|
|
87
|
+
function transientRetryKey(command) {
|
|
88
|
+
return `${normalizeCacheKey(command)}.retryAt`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Consecutive transient verdicts for a command, so the cooldown ESCALATES the
|
|
92
|
+
* way the policy documents (30 s, 60 s, 120 s … capped at 5 min) instead of
|
|
93
|
+
* sitting flat at 30 s.
|
|
94
|
+
*
|
|
95
|
+
* This is the highest-traffic availability consumer in the product. A flat
|
|
96
|
+
* cooldown here means a permanently sick host is re-probed every 30 s per
|
|
97
|
+
* command for the whole session — ten times the storm the policy claims to
|
|
98
|
+
* bound, and the one place the bound matters most. `createAvailabilityChecker`
|
|
99
|
+
* tracks the same counter; this keeps the two seams telling one story.
|
|
100
|
+
*/
|
|
101
|
+
function transientAttemptsKey(command) {
|
|
102
|
+
return `${normalizeCacheKey(command)}.transientAttempts`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Is `command` usable right now? Cached per session.
|
|
106
|
+
*
|
|
107
|
+
* Latch policy (#1467/#1476): a `false` from a genuine absence is durable and
|
|
108
|
+
* is remembered for the session. A `false` from a TIMED-OUT probe is not — this
|
|
109
|
+
* is the highest-traffic availability consumer in the codebase, and caching a
|
|
110
|
+
* host stall here silently disabled a healthy tool for every later dispatch in
|
|
111
|
+
* the session. A transient verdict instead holds only for a bounded cooldown,
|
|
112
|
+
* which also stops a sick host from being re-probed on every dispatch.
|
|
113
|
+
*/
|
|
114
|
+
export async function checkToolAvailability(command, facts) {
|
|
80
115
|
const key = normalizeCacheKey(command);
|
|
81
116
|
const cached = facts.getSessionFact(key);
|
|
82
117
|
if (cached !== undefined) {
|
|
83
118
|
return cached;
|
|
84
119
|
}
|
|
120
|
+
const retryAt = facts.getSessionFact(transientRetryKey(command));
|
|
121
|
+
if (retryAt !== undefined && Date.now() < retryAt)
|
|
122
|
+
return false;
|
|
85
123
|
// A command that isn't even on disk can't pass a --version probe; the ~μs
|
|
86
124
|
// stat/PATH walk saves a guaranteed-to-fail spawn round-trip per cold tool.
|
|
87
125
|
if (!(await isSpawnableCommand(command))) {
|
|
@@ -89,12 +127,70 @@ async function checkToolAvailability(command, facts) {
|
|
|
89
127
|
return false;
|
|
90
128
|
}
|
|
91
129
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
// The budget is enforced by a HOST-side timer, so measure the loop stall
|
|
131
|
+
// that overlapped the window and let the shared classifier read it.
|
|
132
|
+
const sampler = startHostStallSampler();
|
|
133
|
+
const startedAt = Date.now();
|
|
134
|
+
let result;
|
|
135
|
+
let hostStallMs;
|
|
136
|
+
try {
|
|
137
|
+
result = await safeSpawnAsync(command, ["--version"], {
|
|
138
|
+
timeout: TOOL_PROBE_TIMEOUT_MS,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
hostStallMs = sampler.stop();
|
|
143
|
+
}
|
|
144
|
+
const elapsedMs = Date.now() - startedAt;
|
|
145
|
+
if (result.status === 0) {
|
|
146
|
+
facts.setSessionFact(key, true);
|
|
147
|
+
// The tool answered: retire the cooldown facts rather than leaving a
|
|
148
|
+
// stale retry deadline behind a `true`.
|
|
149
|
+
facts.setSessionFact(transientRetryKey(command), 0);
|
|
150
|
+
facts.setSessionFact(transientAttemptsKey(command), 0);
|
|
151
|
+
logAvailabilityDecision({
|
|
152
|
+
tool: command,
|
|
153
|
+
verdict: "available",
|
|
154
|
+
outcome: "success",
|
|
155
|
+
cause: "ok",
|
|
156
|
+
elapsedMs,
|
|
157
|
+
latched: true,
|
|
158
|
+
hostStallMs,
|
|
159
|
+
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
160
|
+
});
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
// `missing` for anything unclassified preserves the pre-#1476 meaning of
|
|
164
|
+
// a non-zero exit: durable, cached for the session.
|
|
165
|
+
const { outcome, cause, evidence } = classifyProbeFailure(result, {
|
|
166
|
+
hostStallMs,
|
|
167
|
+
unclassifiedFailureOutcome: "missing",
|
|
168
|
+
});
|
|
169
|
+
let retryAfterMs;
|
|
170
|
+
if (outcome === "transient") {
|
|
171
|
+
const attempts = (facts.getSessionFact(transientAttemptsKey(command)) ?? 0) + 1;
|
|
172
|
+
facts.setSessionFact(transientAttemptsKey(command), attempts);
|
|
173
|
+
retryAfterMs = transientRetryDelayMs(attempts, cause);
|
|
174
|
+
facts.setSessionFact(transientRetryKey(command), Date.now() + retryAfterMs);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
facts.setSessionFact(key, false);
|
|
178
|
+
facts.setSessionFact(transientAttemptsKey(command), 0);
|
|
179
|
+
}
|
|
180
|
+
logAvailabilityDecision({
|
|
181
|
+
tool: command,
|
|
182
|
+
verdict: "unavailable",
|
|
183
|
+
outcome,
|
|
184
|
+
cause,
|
|
185
|
+
elapsedMs,
|
|
186
|
+
latched: outcome !== "transient",
|
|
187
|
+
hostStallMs,
|
|
188
|
+
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
189
|
+
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
190
|
+
classifiedBy: "probe",
|
|
191
|
+
evidence,
|
|
94
192
|
});
|
|
95
|
-
|
|
96
|
-
facts.setSessionFact(key, available);
|
|
97
|
-
return available;
|
|
193
|
+
return false;
|
|
98
194
|
}
|
|
99
195
|
catch {
|
|
100
196
|
facts.setSessionFact(key, false);
|
|
@@ -128,7 +224,10 @@ export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, mo
|
|
|
128
224
|
/** Authoritative workspace root; `cwd` may be a nested language root. */
|
|
129
225
|
projectRoot,
|
|
130
226
|
/** Ordered per-file pipeline token, when this is a post-write dispatch. */
|
|
131
|
-
writeIndex
|
|
227
|
+
writeIndex,
|
|
228
|
+
/** Runtime telemetry identity, when known (#1448) — threaded to the
|
|
229
|
+
* worklog append; see DispatchContext.telemetryModel's doc. */
|
|
230
|
+
telemetryModel, telemetryProvider) {
|
|
132
231
|
const absoluteFilePath = resolveRunnerPath(cwd, filePath);
|
|
133
232
|
const normalizedProjectRoot = normalizeMapKey(path.resolve(projectRoot ?? cwd));
|
|
134
233
|
const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
|
|
@@ -150,6 +249,8 @@ writeIndex) {
|
|
|
150
249
|
blockingOnly,
|
|
151
250
|
modifiedRanges,
|
|
152
251
|
writeIndex,
|
|
252
|
+
telemetryModel,
|
|
253
|
+
telemetryProvider,
|
|
153
254
|
async hasTool(command) {
|
|
154
255
|
return checkToolAvailability(command, facts);
|
|
155
256
|
},
|
|
@@ -307,6 +408,38 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
307
408
|
const relevant = runnerLatencies.filter((r) => primary.runnerIds.includes(r.runnerId));
|
|
308
409
|
if (relevant.length === 0)
|
|
309
410
|
return undefined;
|
|
411
|
+
// #1867 catalog shape 4: this is correlation, not classification. The LSP
|
|
412
|
+
// touch already decided which scanner publications were absent for these
|
|
413
|
+
// bytes; the runner and latency assembly only preserve that exact set.
|
|
414
|
+
const unconfirmedServerIds = [
|
|
415
|
+
...new Set(relevant.flatMap((r) => r.unconfirmedServerIds ?? [])),
|
|
416
|
+
];
|
|
417
|
+
if (unconfirmedServerIds.length > 0) {
|
|
418
|
+
// The marker describes this exact silent-scanner set. A scanner can
|
|
419
|
+
// recover while another goes dark on the same file, so the set belongs
|
|
420
|
+
// in the session dedupe identity rather than only kind and path.
|
|
421
|
+
const silentScannerSet = [...new Set(unconfirmedServerIds)]
|
|
422
|
+
.map(normalizeMapKey)
|
|
423
|
+
// Code-unit comparator: the sorted set is a dedupe KEY, so ordering
|
|
424
|
+
// must be deterministic across locales — localeCompare is not.
|
|
425
|
+
.sort((a, b) => Number(a > b) - Number(a < b))
|
|
426
|
+
.join(",");
|
|
427
|
+
const onceKey = `${ctx.kind}:${normalizeMapKey(ctx.filePath)}:${silentScannerSet}`;
|
|
428
|
+
if (coverageNoticeSeen.has(onceKey))
|
|
429
|
+
return undefined;
|
|
430
|
+
coverageNoticeSeen.add(onceKey);
|
|
431
|
+
const shown = unconfirmedServerIds.slice(0, 4);
|
|
432
|
+
const remainder = unconfirmedServerIds.length - shown.length;
|
|
433
|
+
const marker = `${shown.join(", ")}${remainder > 0 ? ` +${remainder}` : ""}`;
|
|
434
|
+
return {
|
|
435
|
+
id: `coverage-partial:${ctx.kind}:${path.basename(ctx.filePath)}`,
|
|
436
|
+
message: `coverage: ${marker} silent — diagnostics are incomplete (not a clean result).`,
|
|
437
|
+
filePath: ctx.filePath,
|
|
438
|
+
severity: "warning",
|
|
439
|
+
semantic: "warning",
|
|
440
|
+
tool: "pi-lens",
|
|
441
|
+
};
|
|
442
|
+
}
|
|
310
443
|
// Check primary runners first
|
|
311
444
|
const primaryHasCoverage = relevant.some((r) => r.status === "succeeded" || r.status === "failed");
|
|
312
445
|
if (primaryHasCoverage)
|
|
@@ -481,6 +614,9 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
481
614
|
status: result.status,
|
|
482
615
|
diagnosticCount: result.diagnostics.length,
|
|
483
616
|
semantic: result.semantic ?? semantic,
|
|
617
|
+
...(result.unconfirmedServerIds !== undefined && {
|
|
618
|
+
unconfirmedServerIds: result.unconfirmedServerIds,
|
|
619
|
+
}),
|
|
484
620
|
});
|
|
485
621
|
logLatency({
|
|
486
622
|
type: "runner",
|
|
@@ -638,11 +774,16 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
638
774
|
const blockers = visibleDiagnostics.filter((d) => d.semantic === "blocking");
|
|
639
775
|
const warnings = visibleDiagnostics.filter((d) => d.semantic === "warning" || d.semantic === "none");
|
|
640
776
|
const fixedItems = visibleDiagnostics.filter((d) => d.semantic === "fixed");
|
|
641
|
-
// Append fixed and fixable diagnostics to the persistent worklog
|
|
777
|
+
// Append fixed and fixable diagnostics to the persistent worklog, attributed
|
|
778
|
+
// to the runtime's active model/provider when known (#1448).
|
|
779
|
+
const worklogIdentity = {
|
|
780
|
+
model: ctx.telemetryModel,
|
|
781
|
+
provider: ctx.telemetryProvider,
|
|
782
|
+
};
|
|
642
783
|
if (fixedItems.length > 0) {
|
|
643
784
|
import("../fix-worklog.js")
|
|
644
785
|
.then(({ appendToWorklog }) => {
|
|
645
|
-
appendToWorklog(ctx.cwd, fixedItems, true);
|
|
786
|
+
appendToWorklog(ctx.cwd, fixedItems, true, worklogIdentity);
|
|
646
787
|
})
|
|
647
788
|
.catch(() => { });
|
|
648
789
|
}
|
|
@@ -650,7 +791,7 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
650
791
|
if (fixableWarnings.length > 0) {
|
|
651
792
|
import("../fix-worklog.js")
|
|
652
793
|
.then(({ appendToWorklog }) => {
|
|
653
|
-
appendToWorklog(ctx.cwd, fixableWarnings, false);
|
|
794
|
+
appendToWorklog(ctx.cwd, fixableWarnings, false, worklogIdentity);
|
|
654
795
|
})
|
|
655
796
|
.catch(() => { });
|
|
656
797
|
}
|
|
@@ -755,6 +896,16 @@ function normalizeDiagnosticFilePath(ctx, rawPath) {
|
|
|
755
896
|
async function runRunner(ctx, runner, defaultSemantic) {
|
|
756
897
|
const timeoutMs = Math.max(runner.timeoutMs ?? RUNNER_TIMEOUT_MS, getRunnerTimeoutFloorMs());
|
|
757
898
|
let timer;
|
|
899
|
+
// #1723: mark this runner as the in-flight phase for the WHOLE race below,
|
|
900
|
+
// not just the runner's own promise — a synchronous CPU hog inside
|
|
901
|
+
// `runner.run` (ast-grep-napi, tree-sitter, …) blocks the event loop before
|
|
902
|
+
// it ever gets to log its own completion, so `recentPhases` (which only
|
|
903
|
+
// records FINISHED phases) can't name it. `phaseFinished` clears on every
|
|
904
|
+
// exit from this race (success, runner error, or timeout) via try/finally,
|
|
905
|
+
// so a `loop_block` sampled after this call returns never sees a stale
|
|
906
|
+
// pointer. See `phaseStarted`'s doc comment for the identity-token
|
|
907
|
+
// reasoning and the cost note (negligible next to a runner invocation).
|
|
908
|
+
const phaseToken = phaseStarted(runner.id);
|
|
758
909
|
try {
|
|
759
910
|
const result = await Promise.race([
|
|
760
911
|
runner.run(ctx).finally(() => clearTimeout(timer)),
|
|
@@ -784,6 +935,9 @@ async function runRunner(ctx, runner, defaultSemantic) {
|
|
|
784
935
|
failureMessage: message.slice(0, 200),
|
|
785
936
|
};
|
|
786
937
|
}
|
|
938
|
+
finally {
|
|
939
|
+
phaseFinished(phaseToken);
|
|
940
|
+
}
|
|
787
941
|
}
|
|
788
942
|
// --- Simple Integration Helper ---
|
|
789
943
|
/**
|