@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
|
@@ -15,7 +15,7 @@ import { findNodeToolBinary } from "./package-manager.js";
|
|
|
15
15
|
import { isFullyQualified } from "./path-utils.js";
|
|
16
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
17
17
|
import { createAvailabilityChecker, discoverManagedTool, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
18
|
-
import { createAvailabilityLatch } from "./dispatch/runners/utils/availability-policy.js";
|
|
18
|
+
import { createAvailabilityLatch, } from "./dispatch/runners/utils/availability-policy.js";
|
|
19
19
|
/**
|
|
20
20
|
* Build madge's argv for a circular-dependency scan.
|
|
21
21
|
*
|
|
@@ -136,8 +136,15 @@ async function resolvedCommandIsStale(resolved, spawnableCache) {
|
|
|
136
136
|
spawnableCache.set(resolved.cmd, spawnable);
|
|
137
137
|
return !spawnable;
|
|
138
138
|
}
|
|
139
|
-
/**
|
|
140
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Run `mapper` over `items` with at most `concurrency` in flight at once.
|
|
141
|
+
* Exported (#1810 review F6) so `clients/dispatch/runners/biome-check.ts`'s
|
|
142
|
+
* `resolveBiomeFixKinds` can reuse this exact worker-pool shape for its
|
|
143
|
+
* `biome explain` fan-out instead of carrying a second copy — this file
|
|
144
|
+
* predates that need but is otherwise unrelated to it; a shared home for
|
|
145
|
+
* this one helper wasn't worth a whole new module for two call sites.
|
|
146
|
+
*/
|
|
147
|
+
export async function mapWithConcurrency(items, concurrency, mapper) {
|
|
141
148
|
if (items.length === 0)
|
|
142
149
|
return;
|
|
143
150
|
let nextIndex = 0;
|
|
@@ -353,6 +360,21 @@ export class DependencyChecker {
|
|
|
353
360
|
this.availabilityLatch.noteUnavailable(verdict.outcome ?? "missing", verdict.cause ?? "not-found");
|
|
354
361
|
return false;
|
|
355
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* #1623: public availability verdict for callers outside the dispatch
|
|
365
|
+
* graph (mode=full's fresh-fetch) that need to say WHY `ensureAvailable()`
|
|
366
|
+
* most recently returned false, using the SAME outcome/cause this class
|
|
367
|
+
* already latched above — not a re-guessed "madge binary unavailable"
|
|
368
|
+
* that can't tell a transient retry-cooldown probe from a durable
|
|
369
|
+
* absence.
|
|
370
|
+
*/
|
|
371
|
+
getAvailabilityVerdict() {
|
|
372
|
+
return {
|
|
373
|
+
outcome: this.availabilityLatch.getOutcome(),
|
|
374
|
+
cause: this.availabilityLatch.getCause(),
|
|
375
|
+
retryAtMs: this.availabilityLatch.getRetryAtMs(),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
356
378
|
/**
|
|
357
379
|
* Check if a file is part of a circular dependency (from cache)
|
|
358
380
|
*/
|
|
@@ -53,26 +53,40 @@
|
|
|
53
53
|
* assumption proves wrong in practice, a surrounding-window hash can be
|
|
54
54
|
* layered on later without changing the store shape.
|
|
55
55
|
*/
|
|
56
|
-
import { createHash } from "node:crypto";
|
|
57
56
|
import * as fs from "node:fs";
|
|
58
57
|
import * as path from "node:path";
|
|
59
58
|
import { commitDurableStore } from "./durable-store.js";
|
|
60
59
|
import { logDispositionEvent } from "./disposition-logger.js";
|
|
61
60
|
import { publishDisposition } from "./disposition-publish.js";
|
|
62
61
|
import { getProjectDataDir } from "./file-utils.js";
|
|
62
|
+
import { normalizeMessage as sharedNormalizeMessage, relativeFile as sharedRelativeFile, stableFindingId, } from "./finding-identity.js";
|
|
63
63
|
import { normalizeMapKey } from "./path-utils.js";
|
|
64
64
|
import { lineContentHash } from "./read-guard.js";
|
|
65
65
|
// "defer" is session-ephemeral by design (#690) — held only in memory so it
|
|
66
66
|
// resurfaces for free on the next process run, with no expiry/pruning logic
|
|
67
67
|
// needed. Stores WEAK anchors (see module doc) so a deferred finding stays
|
|
68
68
|
// hidden all session even if the flagged line itself is edited.
|
|
69
|
+
//
|
|
70
|
+
// Review-round F3 (#1625): this Set is process-GLOBAL, but a weak anchor
|
|
71
|
+
// itself only encodes a RELATIVE path (`relativeFile` derives it from
|
|
72
|
+
// `path.relative(cwd, filePath)`, never the cwd's own identity) plus
|
|
73
|
+
// tool/rule/normalizedMessage — two unrelated projects sharing the same
|
|
74
|
+
// relative path/tool/rule/message (e.g. "src/auth.ts" flagged by the same
|
|
75
|
+
// eslint rule in project A and project B) collapse onto the IDENTICAL weak
|
|
76
|
+
// anchor string. A defer in project A therefore silently suppressed the same
|
|
77
|
+
// finding in project B for the rest of the process's life. Every read/write
|
|
78
|
+
// below goes through `deferredKey`, which folds the resolved cwd into the Set
|
|
79
|
+
// key, so the two projects can no longer collide.
|
|
69
80
|
const deferredThisSession = new Set();
|
|
81
|
+
function deferredKey(cwd, anchor) {
|
|
82
|
+
return `${normalizeMapKey(cwd)}::${anchor}`;
|
|
83
|
+
}
|
|
70
84
|
/** Exported (#802) so lens-diagnostic-mark's cross-check against live widget
|
|
71
85
|
* diagnostics matches a message the same way anchor derivation does — a
|
|
72
|
-
* second, slightly different normalizer would make a real match invisible.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
* second, slightly different normalizer would make a real match invisible.
|
|
87
|
+
* Re-exports the shared `finding-identity.js` normalizer (#1816) so this
|
|
88
|
+
* module keeps its existing public surface. */
|
|
89
|
+
export const normalizeMessage = sharedNormalizeMessage;
|
|
76
90
|
// Anchor derivation chokepoint (#1024, #210 class): the `dd:`/`ddw:` id builders
|
|
77
91
|
// (computeStrictAnchor/computeWeakAnchor) both derive their path component here,
|
|
78
92
|
// so a mark and its later lookup diverge whenever the two callers pass different
|
|
@@ -91,34 +105,35 @@ export function normalizeMessage(message) {
|
|
|
91
105
|
// widget hot path, and the read side already pays exactly this cost. Semantics
|
|
92
106
|
// are unchanged: the `..`-escape fallback still returns the canonical filePath,
|
|
93
107
|
// only now in the same canonical form the non-escape branch uses.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
//
|
|
109
|
+
// #1816: this used to be a local copy; it is now `finding-identity.js`'s
|
|
110
|
+
// `relativeFile` (identical body — canonicalize both inputs through
|
|
111
|
+
// `normalizeMapKey` before relativizing), re-exported so the rest of this
|
|
112
|
+
// file, and the module doc/comments above referencing `relativeFile`, keep
|
|
113
|
+
// working unchanged.
|
|
114
|
+
const relativeFile = sharedRelativeFile;
|
|
100
115
|
/** Site-specific anchor — see module doc. Used only for false-positive. */
|
|
101
116
|
export function computeStrictAnchor(args) {
|
|
102
117
|
const lines = args.content?.split(/\r?\n/);
|
|
103
118
|
const lineText = args.line !== undefined && lines ? (lines[args.line - 1] ?? "") : "";
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
args.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
return stableFindingId("dd:", {
|
|
120
|
+
cwd: args.cwd,
|
|
121
|
+
filePath: args.filePath,
|
|
122
|
+
parts: [
|
|
123
|
+
args.tool ?? "",
|
|
124
|
+
args.rule ?? "",
|
|
125
|
+
normalizeMessage(args.message),
|
|
126
|
+
lineContentHash(lineText),
|
|
127
|
+
],
|
|
128
|
+
});
|
|
112
129
|
}
|
|
113
130
|
/** Intent-level anchor — see module doc. Used for defer/flagged/suppress. */
|
|
114
131
|
export function computeWeakAnchor(args) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
args.
|
|
118
|
-
args.rule ?? "",
|
|
119
|
-
|
|
120
|
-
];
|
|
121
|
-
return `ddw:${createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12)}`;
|
|
132
|
+
return stableFindingId("ddw:", {
|
|
133
|
+
cwd: args.cwd,
|
|
134
|
+
filePath: args.filePath,
|
|
135
|
+
parts: [args.tool ?? "", args.rule ?? "", normalizeMessage(args.message)],
|
|
136
|
+
});
|
|
122
137
|
}
|
|
123
138
|
/** Both anchors a stored/filtered diagnostic would compute — the one shared
|
|
124
139
|
* derivation both the dispatch-pipeline filter and lens-diagnostics' flagged
|
|
@@ -156,6 +171,15 @@ export function _setBeforeDispositionCacheRefreshForTests(hook) {
|
|
|
156
171
|
export function _setDispositionStatForTests(statSync) {
|
|
157
172
|
dispositionStatSync = statSync ?? fs.statSync;
|
|
158
173
|
}
|
|
174
|
+
// Test seam for `applyDispositionsMultiFile`'s per-group content read (#1625
|
|
175
|
+
// F2) — Vitest can't `vi.spyOn` a bare ESM `node:fs` named export directly
|
|
176
|
+
// ("Module namespace is not configurable in ESM"), so the F2 regression test
|
|
177
|
+
// (proving the empty-store hoist means zero reads, not just fast ones)
|
|
178
|
+
// swaps this indirection instead, mirroring `dispositionStatSync` above.
|
|
179
|
+
let multiFileReadFileSync = fs.readFileSync;
|
|
180
|
+
export function _setMultiFileReadForTests(readFileSync) {
|
|
181
|
+
multiFileReadFileSync = readFileSync ?? fs.readFileSync;
|
|
182
|
+
}
|
|
159
183
|
function readState(cwd) {
|
|
160
184
|
const p = statePath(cwd);
|
|
161
185
|
let stat;
|
|
@@ -328,7 +352,7 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
|
|
|
328
352
|
// mark) — the log should record what this mark shadowed either way.
|
|
329
353
|
const existing = readState(cwd).dispositions?.[anchor];
|
|
330
354
|
if (disposition === "defer") {
|
|
331
|
-
deferredThisSession.add(anchor);
|
|
355
|
+
deferredThisSession.add(deferredKey(cwd, anchor));
|
|
332
356
|
emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
|
|
333
357
|
return anchor;
|
|
334
358
|
}
|
|
@@ -352,8 +376,12 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
|
|
|
352
376
|
export function getDisposition(cwd, anchor) {
|
|
353
377
|
return readState(cwd).dispositions?.[anchor];
|
|
354
378
|
}
|
|
355
|
-
|
|
356
|
-
|
|
379
|
+
/** #1625 F3: `anchor` alone is ambiguous across projects — a weak anchor
|
|
380
|
+
* encodes only a RELATIVE path plus tool/rule/message, never the project's
|
|
381
|
+
* own identity, so `cwd` must be supplied to disambiguate which project's
|
|
382
|
+
* defer is being checked. */
|
|
383
|
+
export function isDeferredThisSession(cwd, anchor) {
|
|
384
|
+
return deferredThisSession.has(deferredKey(cwd, anchor));
|
|
357
385
|
}
|
|
358
386
|
/** Test-only escape hatch — defer state is module-level (one process = one
|
|
359
387
|
* session), so tests need to reset it between cases. */
|
|
@@ -369,6 +397,14 @@ export function _resetDeferredForTests() {
|
|
|
369
397
|
* Computes both anchors per diagnostic (cheap — same hash primitive, twice)
|
|
370
398
|
* since false-positive is keyed strict while defer/suppress are keyed weak;
|
|
371
399
|
* see module doc for why each disposition binds the way it does.
|
|
400
|
+
*
|
|
401
|
+
* Review-round F1 (#1625): a `d.semantic === "blocking"` diagnostic can be
|
|
402
|
+
* dropped ONLY by the STRICT (false-positive) branch — never by a WEAK-
|
|
403
|
+
* anchored suppress or defer. Two distinct secrets sharing the same
|
|
404
|
+
* rule/message (e.g. two different AWS keys on different lines) collapse
|
|
405
|
+
* onto the SAME weak anchor, so a weak suppress/defer on one would silently
|
|
406
|
+
* silence the other too; unacceptable for a STOP-tier finding. `flagged` is
|
|
407
|
+
* unaffected either way — it never drops anything, blocking or not.
|
|
372
408
|
*/
|
|
373
409
|
export function applyDispositions(diagnostics, cwd, filePath, content) {
|
|
374
410
|
if (!diagnostics.length)
|
|
@@ -378,16 +414,21 @@ export function applyDispositions(diagnostics, cwd, filePath, content) {
|
|
|
378
414
|
return diagnostics;
|
|
379
415
|
return diagnostics.filter((d) => {
|
|
380
416
|
const { strict, weak } = anchorsForDiagnostic(cwd, filePath, d, content);
|
|
381
|
-
|
|
417
|
+
const isBlocking = d.semantic === "blocking";
|
|
418
|
+
if (!isBlocking && deferredThisSession.has(deferredKey(cwd, weak))) {
|
|
382
419
|
return false;
|
|
420
|
+
}
|
|
383
421
|
if (dispositions?.[strict]?.disposition === "false-positive")
|
|
384
422
|
return false;
|
|
385
423
|
// Belt-and-braces: the inline `pi-lens-ignore` comment is the real
|
|
386
424
|
// suppress enforcement (see suppress-writer.ts) and normally already
|
|
387
425
|
// dropped this finding upstream via applyInlineSuppressions. This is a
|
|
388
|
-
// harmless second cover for the store-only audit trail case
|
|
389
|
-
|
|
426
|
+
// harmless second cover for the store-only audit trail case — gated
|
|
427
|
+
// off blocking findings per F1 above (the inline comment, unaffected
|
|
428
|
+
// by this gate, remains the real suppress mechanism for those too).
|
|
429
|
+
if (!isBlocking && dispositions?.[weak]?.disposition === "suppress") {
|
|
390
430
|
return false;
|
|
431
|
+
}
|
|
391
432
|
return true;
|
|
392
433
|
});
|
|
393
434
|
}
|
|
@@ -407,6 +448,13 @@ export function applyDispositions(diagnostics, cwd, filePath, content) {
|
|
|
407
448
|
* content-based filter). suppress/defer, being intent-level and weak-anchored,
|
|
408
449
|
* are the marks that must apply the instant a query re-serves cached findings,
|
|
409
450
|
* and they do so here without any read.
|
|
451
|
+
*
|
|
452
|
+
* Review-round F1 (#1625): this filter is ENTIRELY weak-anchored — it has no
|
|
453
|
+
* strict-anchor branch to fall back to — so a `d.semantic === "blocking"`
|
|
454
|
+
* diagnostic is never dropped here at all, regardless of any suppress/defer
|
|
455
|
+
* mark. The full `applyDispositions` (content in hand, at the next per-edit
|
|
456
|
+
* dispatch or mode=full merge) is the only place a blocking finding can ever
|
|
457
|
+
* be suppressed, and only via its STRICT branch.
|
|
410
458
|
*/
|
|
411
459
|
export function applyWeakDispositions(diagnostics, cwd, filePath) {
|
|
412
460
|
if (!diagnostics.length)
|
|
@@ -415,6 +463,8 @@ export function applyWeakDispositions(diagnostics, cwd, filePath) {
|
|
|
415
463
|
if (!dispositions && deferredThisSession.size === 0)
|
|
416
464
|
return diagnostics;
|
|
417
465
|
return diagnostics.filter((d) => {
|
|
466
|
+
if (d.semantic === "blocking")
|
|
467
|
+
return true;
|
|
418
468
|
const weak = computeWeakAnchor({
|
|
419
469
|
cwd,
|
|
420
470
|
filePath,
|
|
@@ -423,10 +473,74 @@ export function applyWeakDispositions(diagnostics, cwd, filePath) {
|
|
|
423
473
|
message: d.message,
|
|
424
474
|
line: d.line,
|
|
425
475
|
});
|
|
426
|
-
if (deferredThisSession.has(weak))
|
|
476
|
+
if (deferredThisSession.has(deferredKey(cwd, weak)))
|
|
427
477
|
return false;
|
|
428
478
|
if (dispositions?.[weak]?.disposition === "suppress")
|
|
429
479
|
return false;
|
|
430
480
|
return true;
|
|
431
481
|
});
|
|
432
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Multi-file variant of {@link applyDispositions} for lanes whose findings
|
|
485
|
+
* span many files in ONE report — gitleaks/trivy/govulncheck/opengrep
|
|
486
|
+
* project-wide scans (#1617), unlike the dispatch path's one-`ctx.filePath`-
|
|
487
|
+
* at-a-time shape. Groups by `filePathOf(diagnostic)`, reads each file's
|
|
488
|
+
* CURRENT content once, then delegates to `applyDispositions` per group —
|
|
489
|
+
* same anchor derivation, same strict/weak split, no cloned logic (#1617's
|
|
490
|
+
* single-source-of-truth requirement).
|
|
491
|
+
*
|
|
492
|
+
* Content read is best-effort: a file that no longer exists, or that this
|
|
493
|
+
* process can't read, degrades to `content: ""` for that group rather than
|
|
494
|
+
* dropping its diagnostics outright. `applyDispositions` already treats a
|
|
495
|
+
* missing/empty content argument safely — the STRICT (false-positive) anchor
|
|
496
|
+
* hashes the diagnostic's own line, so an empty content can't collide with a
|
|
497
|
+
* mark made against the file's real content and the finding simply isn't
|
|
498
|
+
* matched (fails OPEN — still reported); the WEAK anchor (suppress/defer)
|
|
499
|
+
* never looks at content at all, so those marks keep applying regardless.
|
|
500
|
+
* This is a deliberate choice, not an oversight: a security finding must
|
|
501
|
+
* never silently vanish just because its file went unreadable at filter
|
|
502
|
+
* time (a security finding staying VISIBLE on a read failure is the safe
|
|
503
|
+
* default; a stale mark quietly reappearing is recoverable, a leaked secret
|
|
504
|
+
* quietly disappearing is not).
|
|
505
|
+
*
|
|
506
|
+
* Review-round F2 (#1625): the empty-store early return is checked HERE,
|
|
507
|
+
* before any per-group file read — not left to `applyDispositions` to notice
|
|
508
|
+
* once already inside the per-group loop. Every mode=full analyzer call
|
|
509
|
+
* (9 lanes) runs through this on every scan, and the overwhelmingly common
|
|
510
|
+
* case is zero marks in the project; measured ~270ms of synchronous
|
|
511
|
+
* `fs.readFileSync` per analyzer per run for that common case before this
|
|
512
|
+
* hoist (reading every finding's file just to discover there was nothing to
|
|
513
|
+
* filter), ~0-1ms after.
|
|
514
|
+
*/
|
|
515
|
+
export function applyDispositionsMultiFile(diagnostics, cwd, filePathOf) {
|
|
516
|
+
if (!diagnostics.length)
|
|
517
|
+
return diagnostics;
|
|
518
|
+
const dispositions = readState(cwd).dispositions;
|
|
519
|
+
if (!dispositions && deferredThisSession.size === 0)
|
|
520
|
+
return diagnostics;
|
|
521
|
+
const groups = new Map();
|
|
522
|
+
for (const d of diagnostics) {
|
|
523
|
+
const filePath = filePathOf(d);
|
|
524
|
+
const group = groups.get(filePath);
|
|
525
|
+
if (group)
|
|
526
|
+
group.push(d);
|
|
527
|
+
else
|
|
528
|
+
groups.set(filePath, [d]);
|
|
529
|
+
}
|
|
530
|
+
const kept = new Set();
|
|
531
|
+
for (const [filePath, group] of groups) {
|
|
532
|
+
let content = "";
|
|
533
|
+
try {
|
|
534
|
+
content = multiFileReadFileSync(filePath, "utf-8");
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Unreadable/missing — fail open, see doc above.
|
|
538
|
+
}
|
|
539
|
+
for (const d of applyDispositions(group, cwd, filePath, content)) {
|
|
540
|
+
kept.add(d);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Preserve the caller's original order/dedup by reference rather than the
|
|
544
|
+
// per-group insertion order above.
|
|
545
|
+
return diagnostics.filter((d) => kept.has(d));
|
|
546
|
+
}
|
|
@@ -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
|
+
}
|