@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
|
@@ -14,9 +14,10 @@ import { isFileKind } from "./file-kinds.js";
|
|
|
14
14
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
15
15
|
import { findGlobalBinary } from "./package-manager.js";
|
|
16
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
17
|
+
import { createSingleFlight } from "./single-flight.js";
|
|
17
18
|
import { biomeConfigArgs } from "./tool-policy.js";
|
|
18
19
|
import { resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
19
|
-
import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
20
|
+
import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
|
|
20
21
|
// --- Client ---
|
|
21
22
|
const PROBE_TIMEOUT_MS = 10_000;
|
|
22
23
|
export class BiomeClient {
|
|
@@ -39,6 +40,8 @@ export class BiomeClient {
|
|
|
39
40
|
*/
|
|
40
41
|
lastProbeElapsedMs = 0;
|
|
41
42
|
lastProbeHostStallMs = 0;
|
|
43
|
+
/** Raw facts from the last probe, carried into the decision record (#1500). */
|
|
44
|
+
lastProbeEvidence;
|
|
42
45
|
// Per-cwd cache of the resolved biome binary. Keying by cwd matters in
|
|
43
46
|
// monorepos where different sub-packages each ship their own biome
|
|
44
47
|
// installation; sharing one slot across the whole client would cause
|
|
@@ -48,7 +51,11 @@ export class BiomeClient {
|
|
|
48
51
|
// (lives under ~/.pi-lens/tools), so it's stored separately from the
|
|
49
52
|
// per-cwd cache and used as a final fallback before npx.
|
|
50
53
|
autoInstalledBinaryPath = null;
|
|
51
|
-
|
|
54
|
+
/**
|
|
55
|
+
* At-most-one probe/auto-install in flight, via the shared primitive
|
|
56
|
+
* (#1753). One instance owns one question, so the key is a constant.
|
|
57
|
+
*/
|
|
58
|
+
ensureFlight = createSingleFlight();
|
|
52
59
|
log;
|
|
53
60
|
constructor(verbose = false) {
|
|
54
61
|
this.log = verbose
|
|
@@ -70,13 +77,14 @@ export class BiomeClient {
|
|
|
70
77
|
const cached = this.localBinaryByCwd.get(resolveCwd);
|
|
71
78
|
if (cached)
|
|
72
79
|
return { cmd: cached, args: [] };
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
+
// Walk up from cwd looking for node_modules/.bin/biome BEFORE trusting
|
|
81
|
+
// `autoInstalledBinaryPath` (#1731). That field is set once, the first
|
|
82
|
+
// time `ensureAvailable()` auto-installs for ANY cwd this session, and
|
|
83
|
+
// every later call for every OTHER cwd short-circuited on it — so a
|
|
84
|
+
// project that ships its own biome never won once the session's first
|
|
85
|
+
// managed install had already happened. Project-local-first (discipline
|
|
86
|
+
// B, #1721) means the project's pinned version and config resolution
|
|
87
|
+
// always outrank a managed copy, autoinstalled or not.
|
|
80
88
|
// On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
|
|
81
89
|
const isWin = process.platform === "win32";
|
|
82
90
|
const piLensBin = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
|
|
@@ -84,20 +92,29 @@ export class BiomeClient {
|
|
|
84
92
|
? [
|
|
85
93
|
path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
|
|
86
94
|
path.join(resolveCwd, "node_modules", ".bin", "biome"),
|
|
87
|
-
path.join(piLensBin, "biome.cmd"),
|
|
88
|
-
path.join(piLensBin, "biome"),
|
|
89
95
|
]
|
|
90
|
-
: [
|
|
91
|
-
path.join(resolveCwd, "node_modules", ".bin", "biome"),
|
|
92
|
-
path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
|
|
93
|
-
path.join(piLensBin, "biome"),
|
|
94
|
-
];
|
|
96
|
+
: [path.join(resolveCwd, "node_modules", ".bin", "biome")];
|
|
95
97
|
for (const p of candidates) {
|
|
96
98
|
if (fs.existsSync(p)) {
|
|
97
99
|
this.localBinaryByCwd.set(resolveCwd, p);
|
|
98
100
|
return { cmd: p, args: [] };
|
|
99
101
|
}
|
|
100
102
|
}
|
|
103
|
+
if (this.autoInstalledBinaryPath) {
|
|
104
|
+
return { cmd: this.autoInstalledBinaryPath, args: [] };
|
|
105
|
+
}
|
|
106
|
+
// Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
|
|
107
|
+
// so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when the
|
|
108
|
+
// tool is already installed but not in the project's node_modules.
|
|
109
|
+
const managedCandidates = isWin
|
|
110
|
+
? [path.join(piLensBin, "biome.cmd"), path.join(piLensBin, "biome")]
|
|
111
|
+
: [path.join(piLensBin, "biome")];
|
|
112
|
+
for (const p of managedCandidates) {
|
|
113
|
+
if (fs.existsSync(p)) {
|
|
114
|
+
this.localBinaryByCwd.set(resolveCwd, p);
|
|
115
|
+
return { cmd: p, args: [] };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
101
118
|
// Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx —
|
|
102
119
|
// catches `pnpm add -g @biomejs/biome` installs that PATH misses (#375).
|
|
103
120
|
const global = await findGlobalBinary("biome");
|
|
@@ -116,10 +133,11 @@ export class BiomeClient {
|
|
|
116
133
|
* Ensure Biome is available, auto-installing if necessary.
|
|
117
134
|
* Prefer this over isAvailable() for auto-install behavior.
|
|
118
135
|
*
|
|
119
|
-
* Re-entrancy safe: concurrent first-time callers share
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
136
|
+
* Re-entrancy safe: concurrent first-time callers share one flight, so
|
|
137
|
+
* probing/auto-install isn't duplicated. The share and the clear-in-finally
|
|
138
|
+
* belong to `singleFlight` (#1753); this method owns only the latch
|
|
139
|
+
* short-circuit above it. #1690 exists because the hand-rolled clear here
|
|
140
|
+
* was never exercised by a test.
|
|
123
141
|
*
|
|
124
142
|
* The memo returns `null` when the last verdict was transient and its
|
|
125
143
|
* cooldown expired, which re-enters the probe: "biome is not installed" is a
|
|
@@ -129,15 +147,7 @@ export class BiomeClient {
|
|
|
129
147
|
const memo = this.availabilityLatch.read();
|
|
130
148
|
if (memo !== null)
|
|
131
149
|
return memo;
|
|
132
|
-
|
|
133
|
-
return this.ensureInFlight;
|
|
134
|
-
this.ensureInFlight = this.doEnsureAvailable();
|
|
135
|
-
try {
|
|
136
|
-
return await this.ensureInFlight;
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
this.ensureInFlight = null;
|
|
140
|
-
}
|
|
150
|
+
return this.ensureFlight.run("biome", () => this.doEnsureAvailable());
|
|
141
151
|
}
|
|
142
152
|
/**
|
|
143
153
|
* Probe `biome --version` and classify the failure through the shared
|
|
@@ -177,7 +187,7 @@ export class BiomeClient {
|
|
|
177
187
|
// `unclassifiedFailureOutcome: "missing"` preserves the pre-#1476
|
|
178
188
|
// meaning of a plain non-zero exit (npx reporting no biome package):
|
|
179
189
|
// still "missing", still installable. Only the timeout/abort arm changes.
|
|
180
|
-
const { outcome, cause } = classifyProbeFailure(result, {
|
|
190
|
+
const { outcome, cause, evidence } = classifyProbeFailure(result, {
|
|
181
191
|
hostStallMs,
|
|
182
192
|
unclassifiedFailureOutcome: "missing",
|
|
183
193
|
});
|
|
@@ -187,6 +197,7 @@ export class BiomeClient {
|
|
|
187
197
|
this.lastProbeCause = cause;
|
|
188
198
|
this.lastProbeElapsedMs = elapsedMs;
|
|
189
199
|
this.lastProbeHostStallMs = hostStallMs;
|
|
200
|
+
this.lastProbeEvidence = evidence;
|
|
190
201
|
// The record is emitted by `doEnsureAvailable`, once the latch has said how
|
|
191
202
|
// long this verdict holds.
|
|
192
203
|
return { outcome: failureOutcome };
|
|
@@ -224,6 +235,19 @@ export class BiomeClient {
|
|
|
224
235
|
// session exactly as before.
|
|
225
236
|
const cause = this.lastProbeCause ?? "not-found";
|
|
226
237
|
const retryAfterMs = this.availabilityLatch.noteUnavailable(resolved.outcome, cause);
|
|
238
|
+
// `missing` IS the install-failure arm (#1500 review). `resolveManagedToolClient`
|
|
239
|
+
// only reaches the installer when the probe said `missing`, so every `missing`
|
|
240
|
+
// verdict here has already been through it — declined, suppressed, or tried
|
|
241
|
+
// and failed, which is what `describeInstallAttempt` separates. The earlier
|
|
242
|
+
// marker sat on the `non-installable` arm instead, and that arm cannot happen
|
|
243
|
+
// for biome at all: its `acceptInstalled` always accepts. So the one row that
|
|
244
|
+
// needed the fact was the one shipping without it.
|
|
245
|
+
let installEvidence;
|
|
246
|
+
if (resolved.outcome === "missing") {
|
|
247
|
+
const { getInstallAttempt } = await import("./installer/index.js");
|
|
248
|
+
installEvidence = describeInstallAttempt(getInstallAttempt("biome"));
|
|
249
|
+
}
|
|
250
|
+
const evidence = { ...this.lastProbeEvidence, ...installEvidence };
|
|
227
251
|
logAvailabilityDecision({
|
|
228
252
|
tool: "biome",
|
|
229
253
|
verdict: "unavailable",
|
|
@@ -234,6 +258,13 @@ export class BiomeClient {
|
|
|
234
258
|
hostStallMs: this.lastProbeHostStallMs,
|
|
235
259
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
236
260
|
budgetMs: PROBE_TIMEOUT_MS,
|
|
261
|
+
// Per arm: a probe-derived verdict says `probe`, while a verdict asserted
|
|
262
|
+
// by the install seam — or a cause that fell back because no probe ran —
|
|
263
|
+
// says `caller`.
|
|
264
|
+
classifiedBy: this.lastProbeCause === null || resolved.outcome === "non-installable"
|
|
265
|
+
? "caller"
|
|
266
|
+
: "probe",
|
|
267
|
+
...(Object.keys(evidence).length > 0 && { evidence }),
|
|
237
268
|
});
|
|
238
269
|
if (resolved.outcome === "transient") {
|
|
239
270
|
this.log("biome availability probe timed out; will retry (not installing)");
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn-boundary freshness gate for cached inline blockers (#1631).
|
|
3
|
+
*
|
|
4
|
+
* An inline blocker recorded for a file F is a verdict about F *and everything F
|
|
5
|
+
* imports*. The existing invalidation paths all key on F alone — a later dispatch
|
|
6
|
+
* of the SAME path returning no blockers, the path ceasing to exist (#1245), and a
|
|
7
|
+
* fresh confirmed-clean verdict (#1561/#1573). When only a DEPENDENCY of F changes,
|
|
8
|
+
* none of those events fire, so F's stale verdict re-served at every turn end for
|
|
9
|
+
* the rest of the session. The live instances in #1631 are exactly that shape: the
|
|
10
|
+
* missing export was added to the dependency (one via a bash script, so the
|
|
11
|
+
* dependency was never dispatched at all — even the same-path event was unreachable
|
|
12
|
+
* by construction).
|
|
13
|
+
*
|
|
14
|
+
* The gate here is a READ-time freshness sweep, not the #1561 dependency-axis
|
|
15
|
+
* invalidation (which stays blocked on a maintainer design decision). Before a cached
|
|
16
|
+
* blocking finding is re-served at turn end, stat the file and its forward imports.
|
|
17
|
+
* The file's own import list comes from the parse layer — no reverse-dependency index
|
|
18
|
+
* is needed, sidestepping the tests-free-index blocker that holds #1561's remainder.
|
|
19
|
+
*
|
|
20
|
+
* Drifted entries are DEMOTED, not dropped (#1419 precedent, as applied by sibling
|
|
21
|
+
* issue #1622): a surviving file whose content drifted gets re-served with a
|
|
22
|
+
* `[stale — re-run to confirm]` marker and out of the authoritative blocker channel,
|
|
23
|
+
* rather than being silently deleted or re-asserted at full authority. The gate never
|
|
24
|
+
* re-pulls LSP verdicts on its own — a drifted entry is marked stale and left for an
|
|
25
|
+
* explicit re-run or the next dispatch to resolve. That keeps the sweep bounded and
|
|
26
|
+
* free of the document-resync hazard the issue calls out (re-querying an LSP whose
|
|
27
|
+
* in-memory dependency document is itself stale would regenerate the stale verdict).
|
|
28
|
+
*
|
|
29
|
+
* Scope (#1631 review F4): an inline blocker is not always LSP-only —
|
|
30
|
+
* `dispatcher.ts` builds it from every `semantic === "blocking"` diagnostic across
|
|
31
|
+
* all runners, so an eslint, biome-check, or ast-grep security-rule (hardcoded
|
|
32
|
+
* secret, CVE) finding can land here too. Only a language-server verdict is
|
|
33
|
+
* actually invalidated by an IMPORT changing; an ast-grep secret match doesn't stop
|
|
34
|
+
* being true because a file it imports was edited. The sweep therefore demotes an
|
|
35
|
+
* entry only when its recorded `sources` (`InlineBlockerRecord.sources`) are ALL
|
|
36
|
+
* `"lsp"` — a mixed or non-LSP-sourced blocker is left fully authoritative, the same
|
|
37
|
+
* fail-closed shape `retireInlineBlockerOnConfirmedClean` already uses for
|
|
38
|
+
* provenance it can't yet reason about.
|
|
39
|
+
*
|
|
40
|
+
* Resolution boundary (#1631 review F8): `extractForwardImportPaths` parses static
|
|
41
|
+
* import/require syntax via tree-sitter. It does not, and cannot, resolve a
|
|
42
|
+
* dependency reached only through a dynamic `import()` or `require()` behind a
|
|
43
|
+
* runtime condition — that edge is invisible to this gate, so a blocker whose only
|
|
44
|
+
* stale dependency arrives that way survives a replay. This is an honest boundary,
|
|
45
|
+
* not a silently-closed one.
|
|
46
|
+
*/
|
|
47
|
+
import * as fs from "node:fs";
|
|
48
|
+
import { normalizeEphemeralMapKey } from "./path-utils.js";
|
|
49
|
+
import { resolveImportToFiles } from "./review-graph/import-resolvers.js";
|
|
50
|
+
import { getSharedTreeSitterClient, resolveTreeSitterLanguage, } from "./tree-sitter-shared.js";
|
|
51
|
+
import { TreeSitterSymbolExtractor } from "./tree-sitter-symbol-extractor.js";
|
|
52
|
+
/**
|
|
53
|
+
* Upper bound on the number of forward imports stat-checked for a single blocker.
|
|
54
|
+
* Blocking entries are rare, but one file can import many modules; the sweep must
|
|
55
|
+
* stay bounded so it cannot inflate a turn end.
|
|
56
|
+
*/
|
|
57
|
+
const MAX_DRIFT_CHECK_IMPORTS = 128;
|
|
58
|
+
// Per-language extractor cache, mirroring review-graph/builder.ts and
|
|
59
|
+
// module-report.ts. Memoize failures too: a grammar that fails to load once is not
|
|
60
|
+
// re-probed for every blocker of that language within the process.
|
|
61
|
+
const extractorCache = new Map();
|
|
62
|
+
function getExtractor(languageId) {
|
|
63
|
+
let cached = extractorCache.get(languageId);
|
|
64
|
+
if (!cached) {
|
|
65
|
+
cached = (async () => {
|
|
66
|
+
const client = getSharedTreeSitterClient();
|
|
67
|
+
if (!client)
|
|
68
|
+
return null;
|
|
69
|
+
const extractor = new TreeSitterSymbolExtractor(languageId, client);
|
|
70
|
+
const ok = await extractor.init();
|
|
71
|
+
return ok ? extractor : null;
|
|
72
|
+
})().catch(() => null);
|
|
73
|
+
extractorCache.set(languageId, cached);
|
|
74
|
+
}
|
|
75
|
+
return cached;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* #1631 review F6: memoized forward-import RESOLUTION (the parse step, not the
|
|
79
|
+
* per-dependency mtime stats — those must always be read fresh so drift is never
|
|
80
|
+
* missed). Reviewer measurement: ~450ms/turn at 8 blockers × 300 imports
|
|
81
|
+
* unmemoized, ~435ms per `mode=all` call, because both the turn-end sweep and the
|
|
82
|
+
* `mode=all` gate independently re-parse the SAME consumer files' import lists.
|
|
83
|
+
*
|
|
84
|
+
* Keyed on (path, own mtime) — a cache hit is safe by construction: if the file's
|
|
85
|
+
* mtime hasn't moved, its parsed import LIST cannot have changed (the imports
|
|
86
|
+
* themselves may still have drifted; that's what the mtime stats after this cache
|
|
87
|
+
* check are for). Scoped PER TURN, not process-lifetime (the latch screen this
|
|
88
|
+
* class of state must pass): `noteImportResolutionMemoTurn` clears it whenever the
|
|
89
|
+
* `RuntimeCoordinator`'s `turnIndex` advances, so a resolver injected for one test
|
|
90
|
+
* or a project whose import graph is edited mid-session is never served a result
|
|
91
|
+
* from a stale earlier turn. When no turn index is available (mode=all called
|
|
92
|
+
* outside a tracked turn, or a test with no runtime), the cache is left alone —
|
|
93
|
+
* still correct because of the mtime key, just not turn-bounded in that case.
|
|
94
|
+
*/
|
|
95
|
+
const importResolutionMemo = new Map();
|
|
96
|
+
let importResolutionMemoTurnIndex;
|
|
97
|
+
function noteImportResolutionMemoTurn(turnIndex) {
|
|
98
|
+
if (turnIndex === undefined)
|
|
99
|
+
return;
|
|
100
|
+
if (importResolutionMemoTurnIndex !== turnIndex) {
|
|
101
|
+
importResolutionMemo.clear();
|
|
102
|
+
importResolutionMemoTurnIndex = turnIndex;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function resolveForwardImportsMemoized(cwd, filePath, resolveForwardImports) {
|
|
106
|
+
const sig = await statSignature(filePath);
|
|
107
|
+
if (sig === undefined) {
|
|
108
|
+
// Can't key reliably (deleted/unreadable) — resolve uncached rather than
|
|
109
|
+
// risk serving a memo entry for content that may no longer exist.
|
|
110
|
+
try {
|
|
111
|
+
return await resolveForwardImports(cwd, filePath);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const cached = importResolutionMemo.get(filePath);
|
|
118
|
+
// mtime alone collides for writes within the same timestamp tick (~1ms);
|
|
119
|
+
// size disambiguates the shrink-then-restore case (#1641's measurement).
|
|
120
|
+
if (cached && cached.mtimeMs === sig.mtimeMs && cached.size === sig.size)
|
|
121
|
+
return cached.imports;
|
|
122
|
+
let imports;
|
|
123
|
+
try {
|
|
124
|
+
imports = await resolveForwardImports(cwd, filePath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
imports = [];
|
|
128
|
+
}
|
|
129
|
+
importResolutionMemo.set(filePath, { mtimeMs: sig.mtimeMs, size: sig.size, imports });
|
|
130
|
+
return imports;
|
|
131
|
+
}
|
|
132
|
+
/** Test-only: clear the extractor and import-resolution memos. */
|
|
133
|
+
export function _resetBlockerFreshnessForTests() {
|
|
134
|
+
extractorCache.clear();
|
|
135
|
+
importResolutionMemo.clear();
|
|
136
|
+
importResolutionMemoTurnIndex = undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Production forward-import resolver. Parses `filePath` with its tree-sitter grammar
|
|
140
|
+
* and resolves every extracted import source to in-project files. Degrades to an
|
|
141
|
+
* empty list (never throws) when the grammar, client, or file is unavailable — the
|
|
142
|
+
* sweep then falls back to checking only the blocker file's own drift.
|
|
143
|
+
*/
|
|
144
|
+
export async function extractForwardImportPaths(cwd, filePath) {
|
|
145
|
+
const languageId = resolveTreeSitterLanguage(filePath);
|
|
146
|
+
if (!languageId)
|
|
147
|
+
return [];
|
|
148
|
+
const client = getSharedTreeSitterClient();
|
|
149
|
+
if (!client)
|
|
150
|
+
return [];
|
|
151
|
+
const extractor = await getExtractor(languageId);
|
|
152
|
+
if (!extractor)
|
|
153
|
+
return [];
|
|
154
|
+
let content;
|
|
155
|
+
try {
|
|
156
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const outcome = await client.withParsedTree(filePath, languageId, content, (tree) => extractor.extract(tree, filePath, content).imports);
|
|
162
|
+
if (!outcome.parsed)
|
|
163
|
+
return [];
|
|
164
|
+
const resolved = new Set();
|
|
165
|
+
for (const ref of outcome.value) {
|
|
166
|
+
for (const target of resolveImportToFiles(cwd, filePath, languageId, ref.source)) {
|
|
167
|
+
resolved.add(target);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return [...resolved];
|
|
171
|
+
}
|
|
172
|
+
async function statSignature(filePath) {
|
|
173
|
+
try {
|
|
174
|
+
const stat = await fs.promises.stat(filePath);
|
|
175
|
+
return { mtimeMs: stat.mtimeMs, size: stat.size };
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function statMtimeMs(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
const stat = await fs.promises.stat(filePath);
|
|
184
|
+
return stat.mtimeMs;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Resolve `filePath`'s forward imports and stat each, returning the imports that
|
|
192
|
+
* exist together with their mtime. Shared by the inline-blocker sweep and the
|
|
193
|
+
* widget-store dependency reconcile so both gates weigh drift identically. A path
|
|
194
|
+
* that cannot be stat'ed (deleted/unreadable) is omitted — a deleted dependency is
|
|
195
|
+
* not a content drift this gate reports.
|
|
196
|
+
*/
|
|
197
|
+
export async function collectForwardImportMtimes(cwd, filePath, resolveForwardImports = extractForwardImportPaths, turnIndex) {
|
|
198
|
+
noteImportResolutionMemoTurn(turnIndex);
|
|
199
|
+
let imports = [];
|
|
200
|
+
try {
|
|
201
|
+
imports = await resolveForwardImportsMemoized(cwd, filePath, resolveForwardImports);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
imports = [];
|
|
205
|
+
}
|
|
206
|
+
const truncated = imports.length > MAX_DRIFT_CHECK_IMPORTS;
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const dep of imports.slice(0, MAX_DRIFT_CHECK_IMPORTS)) {
|
|
209
|
+
const mtimeMs = await statMtimeMs(dep);
|
|
210
|
+
if (mtimeMs !== undefined)
|
|
211
|
+
out.push({ path: dep, mtimeMs });
|
|
212
|
+
}
|
|
213
|
+
return { mtimes: out, truncated };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Detect drift for one blocker entry. Returns the set of paths (the file itself plus
|
|
217
|
+
* any forward import) whose mtime is strictly newer than the verdict baseline. A
|
|
218
|
+
* missing/unstat-able path contributes nothing here — a DELETED blocker file is
|
|
219
|
+
* already dropped by `reconcileInlineBlockers`, and a deleted dependency is not a
|
|
220
|
+
* content drift this gate is responsible for.
|
|
221
|
+
*
|
|
222
|
+
* +50ms tolerance. The verdict baseline is `Date.now()`, but on Windows a file's
|
|
223
|
+
* mtime can LEAD `Date.now()` by a measurable margin — not just sub-millisecond
|
|
224
|
+
* rounding. Reviewer measurement across 200 writes on Windows: 184/200 mtimes read
|
|
225
|
+
* ahead of the immediately-following `Date.now()`, by up to ~11.4ms, the same host
|
|
226
|
+
* skew #1491/#1498 hit for install-probe timestamps. A +1ms tolerance (the
|
|
227
|
+
* convention borrowed from `reconcileStaleWidgetFiles` /
|
|
228
|
+
* `reconcileProjectDiagnosticsSnapshot`, both same-process single-writer paths
|
|
229
|
+
* without this skew) demoted a blocker recorded immediately after its own file
|
|
230
|
+
* write with zero real drift — 42 false demotions in 50 runs, and two of this
|
|
231
|
+
* gate's own tests went host-dependent (red 3/6 runs on Windows, green 6/6 at
|
|
232
|
+
* +50ms). +50ms comfortably clears the measured skew while staying far below the
|
|
233
|
+
* gap between genuinely distinct edits.
|
|
234
|
+
*/
|
|
235
|
+
export const MTIME_DRIFT_TOLERANCE_MS = 50;
|
|
236
|
+
async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, turnIndex) {
|
|
237
|
+
const drifted = [];
|
|
238
|
+
const ownMtimeMs = await statMtimeMs(filePath);
|
|
239
|
+
if (ownMtimeMs !== undefined && ownMtimeMs > recordedAtMs + MTIME_DRIFT_TOLERANCE_MS) {
|
|
240
|
+
drifted.push(filePath);
|
|
241
|
+
}
|
|
242
|
+
const { mtimes, truncated } = await collectForwardImportMtimes(cwd, filePath, resolveForwardImports, turnIndex);
|
|
243
|
+
for (const { path, mtimeMs } of mtimes) {
|
|
244
|
+
if (mtimeMs > recordedAtMs + MTIME_DRIFT_TOLERANCE_MS)
|
|
245
|
+
drifted.push(path);
|
|
246
|
+
}
|
|
247
|
+
return { drifted, truncated };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Whether a population entry is eligible for the drift check at all — the same
|
|
251
|
+
* three gates the main sweep loop applies (not already stale, has a timestamp
|
|
252
|
+
* baseline, all-`"lsp"` sources). Factored out so the #1790 review F5 dedup
|
|
253
|
+
* decision below (chain vs. separate row) asks the IDENTICAL question the main
|
|
254
|
+
* loop will ask, rather than a second hand-written approximation of it that
|
|
255
|
+
* could drift from the real gates.
|
|
256
|
+
*/
|
|
257
|
+
function isEligibleForDriftCheck(entry) {
|
|
258
|
+
if (entry.stale)
|
|
259
|
+
return false;
|
|
260
|
+
if (entry.recordedAtMs === undefined)
|
|
261
|
+
return false;
|
|
262
|
+
return (entry.sources !== undefined &&
|
|
263
|
+
entry.sources.length > 0 &&
|
|
264
|
+
entry.sources.every((source) => source === "lsp"));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Freshness sweep over every blocking row the widget currently serves — the cached
|
|
268
|
+
* inline blockers (`RuntimeCoordinator`) AND, since #1790, any widget-store row
|
|
269
|
+
* reached only through a cache-served replay (`options.additionalEntries`, injected
|
|
270
|
+
* by the call site to avoid an import cycle with `widget-state.ts`). Called at turn
|
|
271
|
+
* end before the blockers are re-served. Entries whose own file or forward imports
|
|
272
|
+
* drifted since the verdict are demoted via `demote`; the turn-end renderer then
|
|
273
|
+
* serves them out of the advisory channel with a `[stale — re-run to confirm]`
|
|
274
|
+
* marker instead of as an authoritative blocker.
|
|
275
|
+
*
|
|
276
|
+
* #1790: a file present in BOTH stores — the common case, since a live dispatch
|
|
277
|
+
* writes an inline blocker (`runtime-tool-result.ts`) AND a widget-store record
|
|
278
|
+
* (`pipeline.ts`) for the SAME verdict — is drift-checked ONCE, via its
|
|
279
|
+
* inline-blocker entry, so the sweep's one drift check never runs twice over the
|
|
280
|
+
* same file WHEN that inline entry is itself eligible for the check. But
|
|
281
|
+
* `markInlineBlockerStale` only ever touches `RuntimeCoordinator`'s map, never
|
|
282
|
+
* the widget store; on drift for a duplicated path, the widget demote is
|
|
283
|
+
* CHAINED onto the inline entry's `demote` (both stores write) rather than the
|
|
284
|
+
* widget row being silently dropped — the reviewer's F1 probe caught an earlier
|
|
285
|
+
* revision that discarded it: `revalidated:1` while the widget's own
|
|
286
|
+
* `isBlocking` for the file still read true, the exact ghost #1790 exists to
|
|
287
|
+
* kill.
|
|
288
|
+
*
|
|
289
|
+
* #1790 review F5: chaining is conditional on the inline entry actually being
|
|
290
|
+
* ELIGIBLE for the drift check (`isEligibleForDriftCheck` — not already stale,
|
|
291
|
+
* timestamped, all-LSP sources). An ineligible inline entry never reaches
|
|
292
|
+
* `demote()` in the loop below, so chaining onto one is ALSO a silent drop —
|
|
293
|
+
* just one store removed from F1's. The widget row's eligibility belongs to the
|
|
294
|
+
* WIDGET STORE, not to whatever inline entry happens to share its file path; an
|
|
295
|
+
* ineligible duplicate therefore gets its own separate population row instead
|
|
296
|
+
* of being chained, so a file can legitimately count twice (once per store) when
|
|
297
|
+
* the two stores disagree on eligibility.
|
|
298
|
+
*
|
|
299
|
+
* Never throws: any internal failure leaves the entry untouched (existing re-serve
|
|
300
|
+
* behavior) rather than failing the turn end.
|
|
301
|
+
*/
|
|
302
|
+
export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
|
|
303
|
+
const counts = {
|
|
304
|
+
total: 0,
|
|
305
|
+
kept: 0,
|
|
306
|
+
revalidated: 0,
|
|
307
|
+
alreadyStale: 0,
|
|
308
|
+
truncatedImports: 0,
|
|
309
|
+
};
|
|
310
|
+
const resolveForwardImports = options?.resolveForwardImports ?? extractForwardImportPaths;
|
|
311
|
+
let turnIndex;
|
|
312
|
+
try {
|
|
313
|
+
turnIndex = runtime.turnIndex;
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
turnIndex = undefined;
|
|
317
|
+
}
|
|
318
|
+
let inlineEntries;
|
|
319
|
+
try {
|
|
320
|
+
inlineEntries = runtime.getInlineBlockersSnapshot();
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return counts;
|
|
324
|
+
}
|
|
325
|
+
const population = inlineEntries.map((entry) => ({
|
|
326
|
+
filePath: entry.filePath,
|
|
327
|
+
stale: entry.stale ?? false,
|
|
328
|
+
recordedAtMs: entry.recordedAtMs,
|
|
329
|
+
sources: entry.sources,
|
|
330
|
+
demote: () => runtime.markInlineBlockerStale(entry.filePath, "dependency-drift"),
|
|
331
|
+
}));
|
|
332
|
+
// #1790 review F2: dedup key is `normalizeEphemeralMapKey`, not
|
|
333
|
+
// `normalizeMapKey` — the latter realpaths a live file and walks up the
|
|
334
|
+
// directory tree for a missing one (measured ~313µs per deleted path, exactly
|
|
335
|
+
// the population this sweep processes) on a turn-end hot path. The widget
|
|
336
|
+
// store this population is reconciled against keys on
|
|
337
|
+
// `normalizeEphemeralMapKey` too (see `widget-state.ts`'s module doc on why
|
|
338
|
+
// `normalizeMapKey` is wrong for this in-process, single-walk kind of key),
|
|
339
|
+
// so this also keeps the dedup consistent with the store it is deduping
|
|
340
|
+
// against, not just cheaper.
|
|
341
|
+
const inlineByKey = new Map();
|
|
342
|
+
for (const entry of population) {
|
|
343
|
+
inlineByKey.set(normalizeEphemeralMapKey(entry.filePath), entry);
|
|
344
|
+
}
|
|
345
|
+
for (const extra of options?.additionalEntries ?? []) {
|
|
346
|
+
const key = normalizeEphemeralMapKey(extra.filePath);
|
|
347
|
+
const inlineEntry = inlineByKey.get(key);
|
|
348
|
+
// #1790 review F5: chaining onto an INELIGIBLE inline entry is a silent
|
|
349
|
+
// drop, not a merge. The main loop below short-circuits BEFORE ever
|
|
350
|
+
// calling `demote()` for an already-stale entry (the one-way
|
|
351
|
+
// dependency-drift latch — a forever-ghost once it fires once), an
|
|
352
|
+
// unstamped legacy record, or a non-LSP/mixed-sources entry (e.g. an
|
|
353
|
+
// unrelated ast-grep finding on the same file). Any of those swallows a
|
|
354
|
+
// chained widget demote even though the widget row is pure-LSP by
|
|
355
|
+
// construction (`getWidgetBlockingFilesForSweep` only emits LSP-sourced
|
|
356
|
+
// rows) and carries its OWN baseline. Eligibility belongs to the STORE
|
|
357
|
+
// the row came from, not the file path two stores happen to share — so
|
|
358
|
+
// only chain when the inline entry would itself reach `demote()`;
|
|
359
|
+
// otherwise give the widget row its own population entry so its own
|
|
360
|
+
// gates and its own drift check decide its own fate.
|
|
361
|
+
if (inlineEntry && isEligibleForDriftCheck(inlineEntry)) {
|
|
362
|
+
// #1790 review F1: a duplicated path is counted and drift-checked ONCE
|
|
363
|
+
// (via the inline entry above), but BOTH stores must record the
|
|
364
|
+
// verdict — `markInlineBlockerStale` only ever touches
|
|
365
|
+
// `RuntimeCoordinator`'s map, so without this chain the widget store's
|
|
366
|
+
// own diagnostic for this file stays fully blocking (`isBlocking` true)
|
|
367
|
+
// even after the inline entry demotes. Chain, don't replace: either
|
|
368
|
+
// store's own untouched failure path must not suppress the other's
|
|
369
|
+
// write.
|
|
370
|
+
const demoteInline = inlineEntry.demote;
|
|
371
|
+
const demoteWidget = extra.demote;
|
|
372
|
+
inlineEntry.demote = () => {
|
|
373
|
+
const inlineChanged = demoteInline();
|
|
374
|
+
const widgetChanged = demoteWidget();
|
|
375
|
+
return inlineChanged || widgetChanged;
|
|
376
|
+
};
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
population.push({
|
|
380
|
+
filePath: extra.filePath,
|
|
381
|
+
stale: false,
|
|
382
|
+
recordedAtMs: extra.recordedAtMs,
|
|
383
|
+
sources: ["lsp"],
|
|
384
|
+
demote: extra.demote,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
counts.total = population.length;
|
|
388
|
+
for (const entry of population) {
|
|
389
|
+
try {
|
|
390
|
+
if (entry.stale) {
|
|
391
|
+
counts.alreadyStale += 1;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
// No timestamp baseline (legacy/unstamped record) — we cannot order disk
|
|
395
|
+
// mtimes against the verdict, so leave it untouched (fail toward the
|
|
396
|
+
// existing behavior rather than fabricating a drift signal).
|
|
397
|
+
if (entry.recordedAtMs === undefined) {
|
|
398
|
+
counts.kept += 1;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
// #1631 review F4: fail-closed on non-LSP (or unknown) provenance. Import
|
|
402
|
+
// drift only invalidates a language-server verdict; a mixed or non-`"lsp"`
|
|
403
|
+
// `sources` list is kept at full authority rather than demoted.
|
|
404
|
+
const isLspSourced = entry.sources !== undefined &&
|
|
405
|
+
entry.sources.length > 0 &&
|
|
406
|
+
entry.sources.every((source) => source === "lsp");
|
|
407
|
+
if (!isLspSourced) {
|
|
408
|
+
counts.kept += 1;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const { drifted, truncated } = await detectDrift(cwd, entry.filePath, entry.recordedAtMs, resolveForwardImports, turnIndex);
|
|
412
|
+
if (truncated)
|
|
413
|
+
counts.truncatedImports += 1;
|
|
414
|
+
if (drifted.length > 0) {
|
|
415
|
+
entry.demote();
|
|
416
|
+
counts.revalidated += 1;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
counts.kept += 1;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
// Per-entry failure: keep the entry as-is.
|
|
424
|
+
counts.kept += 1;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return counts;
|
|
428
|
+
}
|