@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,6 +14,7 @@ import * as path from "node:path";
|
|
|
14
14
|
import { Type } from "../clients/deps/typebox.js";
|
|
15
15
|
import { anchorsForDiagnostic, applyDispositions, applyWeakDispositions, getDisposition, } from "../clients/diagnostic-dispositions.js";
|
|
16
16
|
import { applyInlineSuppressions } from "../clients/dispatch/inline-suppressions.js";
|
|
17
|
+
import { gateFindingsByPathFreshness } from "../clients/advisory-provenance.js";
|
|
17
18
|
import { normalizeRuleId } from "../clients/dispatch/rule-id-normalize.js";
|
|
18
19
|
import { applyRulePolicy, rulePolicyMapFromConfig } from "../clients/dispatch/rule-policy.js";
|
|
19
20
|
import { loadPiLensProjectConfig } from "../clients/project-lens-config.js";
|
|
@@ -23,18 +24,23 @@ import { getProjectIgnoreMatcher } from "../clients/file-utils.js";
|
|
|
23
24
|
import { isAtOrAboveHomeDir, normalizeFilePath, } from "../clients/path-utils.js";
|
|
24
25
|
import { getLSPService } from "../clients/lsp/index.js";
|
|
25
26
|
import { primaryServerId } from "../clients/lsp/config.js";
|
|
27
|
+
import { getFullScanWallClockMs } from "../clients/lsp/workspace-sweep-hold.js";
|
|
28
|
+
import { demoteInferredProjectSweepResults } from "../clients/lsp/inferred-project.js";
|
|
26
29
|
import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
|
|
27
30
|
import { loadProjectDiagnosticsDeltaReport, loadProjectDiagnosticsSnapshot, PROJECT_DIAGNOSTICS_CACHE_VERSION, reconcileProjectDiagnosticsSnapshot, } from "../clients/project-diagnostics/cache.js";
|
|
28
|
-
import {
|
|
29
|
-
import { fetchFreshProjectDiagnostics } from "../clients/project-diagnostics/fresh-fetch.js";
|
|
31
|
+
import { formatCacheAgeOld, formatNotRunEntry, } from "../clients/project-diagnostics/extractors.js";
|
|
32
|
+
import { ANALYZER_IDS, fetchFreshProjectDiagnostics, } from "../clients/project-diagnostics/fresh-fetch.js";
|
|
30
33
|
import { loadBootstrapClients } from "../clients/bootstrap.js";
|
|
31
34
|
import { generatedSkipNotice, scanTruncationNotice, } from "../clients/lens-engine.js";
|
|
32
35
|
import { scanProjectDiagnostics } from "../clients/project-diagnostics/scanner.js";
|
|
33
|
-
import { getFileDiagnosticSummaries, reconcileScanDiagnostics, reconcileStaleWidgetFiles, } from "../clients/widget-state.js";
|
|
36
|
+
import { getFileDiagnosticSummaries, reconcileCorrelatedScanDiagnostics, reconcileScanDiagnostics, reconcileStaleWidgetDependencyBlockers, reconcileStaleWidgetFiles, widgetDiagnosticUri, } from "../clients/widget-state.js";
|
|
37
|
+
import { logLatency } from "../clients/latency-logger.js";
|
|
34
38
|
import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
|
|
35
39
|
import { retagAuxiliaryDiagnostics } from "../clients/dispatch/auxiliary-lsp.js";
|
|
36
40
|
import { detectFileRole } from "../clients/file-role.js";
|
|
41
|
+
import { STALE_LINE_MARKER } from "../clients/stale-marker.js";
|
|
37
42
|
import { makeProgressReporter, scanningSummaryLine } from "./scan-progress.js";
|
|
43
|
+
import { demotePastEofDiagnostics, PAST_EOF_STALE_MARKER, resyncDocumentOnPastEof, } from "../clients/diagnostic-line-freshness.js";
|
|
38
44
|
// The widget state exposes the full per-file diagnostic set; this is the tool's
|
|
39
45
|
// own generous display budget per file (independent of the TUI's 12 cap), to
|
|
40
46
|
// keep output bounded on a pathologically broken file.
|
|
@@ -45,15 +51,23 @@ const MAX_DIAGNOSTICS_PER_FILE = 50;
|
|
|
45
51
|
// caller can never believe it checked files it didn't (issue's stated
|
|
46
52
|
// invariant).
|
|
47
53
|
const MAX_PATHS_ENTRIES = 200;
|
|
54
|
+
// #1623: the reason rendered for every heavyweight-analyzer lane (gitleaks,
|
|
55
|
+
// trivy, govulncheck, dead-code, knip, jscpd, madge, opengrep, test-runner)
|
|
56
|
+
// when mode=full is called without refreshRunners=cheap/all/cached — the
|
|
57
|
+
// "quick mode" gate that skips the expensive fresh-fetch entirely (see
|
|
58
|
+
// `formatFullMode`'s `analyzersPromise`). One shared string so it renders
|
|
59
|
+
// identically everywhere it's used.
|
|
60
|
+
const NOT_REQUESTED_REASON = "refreshRunners not requested this call (quick mode) — pass refreshRunners=cheap/all/cached to lens_diagnostics mode=full to run it";
|
|
48
61
|
// Wall-clock ceiling for the whole mode=full scan. Even with per-file budgets
|
|
49
62
|
// and abort-signal honoring, a pathological state (a language server hanging on
|
|
50
63
|
// spawn/initialize across many files) could otherwise stall the tool
|
|
51
64
|
// indefinitely — an unattended session was observed hung for ~8h. This hard cap
|
|
52
65
|
// aborts the scan so it always returns (partial) rather than never. Env-tunable.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
// #1618: single source with `clients/runtime-turn.ts`'s idle-reset base delay
|
|
67
|
+
// (see `getBaseLspIdleResetMs`), which derives itself from this value so the
|
|
68
|
+
// two constants can no longer drift back into the idle-reset-fires-mid-sweep
|
|
69
|
+
// relationship that caused #1618 in the first place.
|
|
70
|
+
const FULL_SCAN_WALL_CLOCK_MS = getFullScanWallClockMs();
|
|
57
71
|
// Binding verification is a fallback for result producers that supplied a
|
|
58
72
|
// content hash without an already-computed disk verdict. Keep it one-file-at-a-
|
|
59
73
|
// time and yield periodically: a full scan can contain thousands of results,
|
|
@@ -174,6 +188,17 @@ captureLspStatusRepaint, getRuntime) {
|
|
|
174
188
|
return `lens_diagnostics delta — clean${coldSuffix}${failedSuffix}`;
|
|
175
189
|
return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}${failedSuffix}`;
|
|
176
190
|
}
|
|
191
|
+
// #1799: `semantic === "blocking"` iff `severity === "error"` holds for
|
|
192
|
+
// RAW classification, but not for the rendered counts — a
|
|
193
|
+
// dependency-drift demotion (#1631, widget-state.ts `isBlocking` vs
|
|
194
|
+
// `countDiagnostics`) revokes an error's blocking authority (stale)
|
|
195
|
+
// while the finding itself stays real evidence, so it lands in
|
|
196
|
+
// `totalErrors` with `totalBlocking` unchanged. That is the one case
|
|
197
|
+
// where the two totals genuinely disagree, and it must still render —
|
|
198
|
+
// otherwise a file with drift-demoted errors reads as "clean". What
|
|
199
|
+
// must NOT happen is printing both terms for the SAME findings (the
|
|
200
|
+
// actual #1799 bug), so `errors` renders only when `blocking === 0`,
|
|
201
|
+
// mirroring the per-file row's own guard below.
|
|
177
202
|
const b = details?.totalBlocking ?? 0;
|
|
178
203
|
const e = details?.totalErrors ?? 0;
|
|
179
204
|
const w = details?.totalWarnings ?? 0;
|
|
@@ -181,7 +206,11 @@ captureLspStatusRepaint, getRuntime) {
|
|
|
181
206
|
if (b + e + w === 0) {
|
|
182
207
|
return `lens_diagnostics ${mode} — clean (${files} files)${coldSuffix}${failedSuffix}`;
|
|
183
208
|
}
|
|
184
|
-
|
|
209
|
+
const parts = [`${b} blocking`];
|
|
210
|
+
if (b === 0 && e > 0)
|
|
211
|
+
parts.push(`${e} errors`);
|
|
212
|
+
parts.push(`${w} warnings`);
|
|
213
|
+
return `lens_diagnostics ${mode} — ${parts.join(" · ")} (${files} files)${coldSuffix}${failedSuffix}`;
|
|
185
214
|
}),
|
|
186
215
|
parameters: Type.Object({
|
|
187
216
|
mode: Type.Optional(Type.String({
|
|
@@ -271,7 +300,25 @@ captureLspStatusRepaint, getRuntime) {
|
|
|
271
300
|
await flushPending();
|
|
272
301
|
const staleDropped = await reconcileStaleWidgetFiles();
|
|
273
302
|
if (mode === "all") {
|
|
274
|
-
|
|
303
|
+
// #1631 dependency-axis gate for the mode=all store. `reconcileStaleWidgetFiles`
|
|
304
|
+
// (above) only sees the diagnosed file's OWN mtime; this demotes blocking
|
|
305
|
+
// findings whose forward imports drifted out-of-band. Paired reconciles, one
|
|
306
|
+
// read site — the same gate that covers the turn-end inline-blocker store.
|
|
307
|
+
const dependencyGateStart = Date.now();
|
|
308
|
+
const { demoted: dependencyDemoted, truncatedImports } = await reconcileStaleWidgetDependencyBlockers(cwd, getRuntime?.()?.turnIndex);
|
|
309
|
+
logLatency({
|
|
310
|
+
type: "phase",
|
|
311
|
+
toolName: "lens_diagnostics",
|
|
312
|
+
filePath: cwd,
|
|
313
|
+
phase: "blocker_freshness_widget_gate",
|
|
314
|
+
durationMs: Date.now() - dependencyGateStart,
|
|
315
|
+
metadata: {
|
|
316
|
+
demoted: dependencyDemoted,
|
|
317
|
+
truncatedImports,
|
|
318
|
+
surface: "mode=all",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
return formatAllMode(cwd, severity, undefined, undefined, staleDropped, pathsScope, dependencyDemoted);
|
|
275
322
|
}
|
|
276
323
|
if (mode === "full") {
|
|
277
324
|
// Fold a hard wall-clock ceiling into the abort signal so the scan
|
|
@@ -301,16 +348,41 @@ captureLspStatusRepaint, getRuntime) {
|
|
|
301
348
|
};
|
|
302
349
|
}
|
|
303
350
|
// ── delta mode ────────────────────────────────────────────────────────────────
|
|
304
|
-
function formatProjectDeltaDiagnostic(diagnostic) {
|
|
351
|
+
function formatProjectDeltaDiagnostic(diagnostic, stale) {
|
|
305
352
|
const marker = diagnostic.semantic === "blocking" || diagnostic.severity === "error"
|
|
306
353
|
? "🔴"
|
|
307
354
|
: "ℹ";
|
|
308
355
|
const rule = diagnostic.rule ?? diagnostic.code ?? diagnostic.runner;
|
|
309
|
-
|
|
356
|
+
const where = stale ? STALE_LINE_MARKER : `L${diagnostic.line ?? "?"}`;
|
|
357
|
+
return ` ${marker} ${where} ${rule} ${diagnostic.message}`;
|
|
310
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* #1634 review round R3: `appendProjectDiagnosticsDeltaLines` re-serves the
|
|
361
|
+
* persisted project-diagnostics DELTA report verbatim — the third of
|
|
362
|
+
* `mode=delta`'s three arms (the other two, actionable/quality warnings, were
|
|
363
|
+
* gated in the F3 pass above), and it carried the identical unfixed shape:
|
|
364
|
+
* cited `file:line` findings from a report with its own `generatedAt`, no
|
|
365
|
+
* freshness check. Same gate, same policy as `applyDeltaFreshnessGate`:
|
|
366
|
+
* missing file -> DROP, edited-since -> DEMOTE (loses its line, keeps
|
|
367
|
+
* rule/message), live -> unchanged. See `clients/finding-delivery-gate.ts`
|
|
368
|
+
* surface `lens-diagnostics:mode-delta`.
|
|
369
|
+
*/
|
|
311
370
|
function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includeFile) {
|
|
312
|
-
const
|
|
313
|
-
matchesSeverity(projectDiagnosticToWidget(diagnostic), severity));
|
|
371
|
+
const scoped = (report?.diagnostics ?? []).filter((diagnostic) => includeFile(diagnostic.filePath) &&
|
|
372
|
+
matchesSeverity(projectDiagnosticToWidget(diagnostic, diagnostic.filePath), severity));
|
|
373
|
+
const gated = gateFindingsByPathFreshness({
|
|
374
|
+
store: "lens-diagnostics-delta-project",
|
|
375
|
+
findings: scoped,
|
|
376
|
+
cwd,
|
|
377
|
+
scannedAt: report?.generatedAt,
|
|
378
|
+
citedPath: (d) => d.filePath,
|
|
379
|
+
onMissing: "drop",
|
|
380
|
+
});
|
|
381
|
+
const staleSet = new Set(gated.stale);
|
|
382
|
+
// Concatenating live-then-stale reorders a file's demoted rows to the end
|
|
383
|
+
// of its bucket below (rather than each diagnostic's original report
|
|
384
|
+
// order) — cosmetic only, findings are neither dropped nor duplicated.
|
|
385
|
+
const diagnostics = [...gated.live, ...gated.stale];
|
|
314
386
|
const byFile = new Map();
|
|
315
387
|
for (const diagnostic of diagnostics) {
|
|
316
388
|
const filePath = path.resolve(diagnostic.filePath);
|
|
@@ -323,7 +395,7 @@ function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includ
|
|
|
323
395
|
if (!lines.includes(rel))
|
|
324
396
|
lines.push(rel);
|
|
325
397
|
for (const diagnostic of fileDiagnostics) {
|
|
326
|
-
lines.push(formatProjectDeltaDiagnostic(diagnostic));
|
|
398
|
+
lines.push(formatProjectDeltaDiagnostic(diagnostic, staleSet.has(diagnostic)));
|
|
327
399
|
}
|
|
328
400
|
}
|
|
329
401
|
return diagnostics.length;
|
|
@@ -342,7 +414,7 @@ function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includ
|
|
|
342
414
|
function filterDeltaReportDispositions(report, cwd, policyMap) {
|
|
343
415
|
if (!report?.diagnostics.length)
|
|
344
416
|
return report;
|
|
345
|
-
const kept = report.diagnostics.filter((d) => applyWeakDispositions([projectDiagnosticToWidget(d)], cwd, d.filePath)
|
|
417
|
+
const kept = report.diagnostics.filter((d) => applyWeakDispositions([projectDiagnosticToWidget(d, d.filePath)], cwd, d.filePath)
|
|
346
418
|
.length === 1);
|
|
347
419
|
const policyKept = applyRulePolicy(kept, policyMap);
|
|
348
420
|
if (policyKept.length === report.diagnostics.length)
|
|
@@ -360,6 +432,58 @@ function filterDeltaReportDispositions(report, cwd, policyMap) {
|
|
|
360
432
|
function loadProjectRulePolicyMap(cwd) {
|
|
361
433
|
return rulePolicyMapFromConfig(loadPiLensProjectConfig(cwd).rules);
|
|
362
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* #1634 review round: `formatDeltaMode` re-serves the `actionable-warnings`/
|
|
437
|
+
* `code-quality-warnings` caches verbatim — each cited `file:line`, no
|
|
438
|
+
* freshness check — the SAME shape #1622 fixed for gitleaks/trivy-secrets,
|
|
439
|
+
* unfixed here because `mode=delta` is the tool's DEFAULT and easy to miss
|
|
440
|
+
* in a per-store sweep. Both reports carry a report-level `generatedAt`; a
|
|
441
|
+
* warning is gated against it exactly like a scanner finding against
|
|
442
|
+
* `scannedAt`: missing file → DROP (nothing to fix in a file that's gone),
|
|
443
|
+
* edited-since → DEMOTE (loses its line, keeps its rule/message, marked
|
|
444
|
+
* `STALE_LINE_MARKER`), live → unchanged. See
|
|
445
|
+
* `clients/finding-delivery-gate.ts` surface `lens-diagnostics:mode-delta`.
|
|
446
|
+
*/
|
|
447
|
+
function applyDeltaFreshnessGate(files, cwd, generatedAt) {
|
|
448
|
+
if (!generatedAt)
|
|
449
|
+
return files;
|
|
450
|
+
const flat = [];
|
|
451
|
+
for (const file of files) {
|
|
452
|
+
for (const warning of file.warnings)
|
|
453
|
+
flat.push({ filePath: file.filePath, warning });
|
|
454
|
+
}
|
|
455
|
+
if (flat.length === 0)
|
|
456
|
+
return files;
|
|
457
|
+
const gated = gateFindingsByPathFreshness({
|
|
458
|
+
store: "lens-diagnostics-delta",
|
|
459
|
+
findings: flat,
|
|
460
|
+
cwd,
|
|
461
|
+
scannedAt: generatedAt,
|
|
462
|
+
citedPath: (f) => f.filePath,
|
|
463
|
+
onMissing: "drop",
|
|
464
|
+
});
|
|
465
|
+
// Two passes (live, then stale) reorder a file's demoted rows to the end
|
|
466
|
+
// of its warnings array rather than the original report order — cosmetic
|
|
467
|
+
// only, nothing is dropped or duplicated.
|
|
468
|
+
const byFile = new Map();
|
|
469
|
+
for (const f of gated.live) {
|
|
470
|
+
const arr = byFile.get(f.filePath) ?? [];
|
|
471
|
+
arr.push(f.warning);
|
|
472
|
+
byFile.set(f.filePath, arr);
|
|
473
|
+
}
|
|
474
|
+
for (const f of gated.stale) {
|
|
475
|
+
const arr = byFile.get(f.filePath) ?? [];
|
|
476
|
+
arr.push({ ...f.warning, stale: true, line: undefined });
|
|
477
|
+
byFile.set(f.filePath, arr);
|
|
478
|
+
}
|
|
479
|
+
return files
|
|
480
|
+
.map((file) => ({
|
|
481
|
+
filePath: file.filePath,
|
|
482
|
+
warnings: byFile.get(file.filePath) ?? [],
|
|
483
|
+
}))
|
|
484
|
+
.filter((file) => file.warnings.length > 0);
|
|
485
|
+
}
|
|
486
|
+
// @delivery-surface: lens-diagnostics:mode-delta
|
|
363
487
|
function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
364
488
|
const actionableEntry = cacheManager.readCache("actionable-warnings", cwd);
|
|
365
489
|
const qualityEntry = cacheManager.readCache("code-quality-warnings", cwd);
|
|
@@ -387,8 +511,8 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
|
387
511
|
warnings: applyRulePolicy(applyWeakDispositions(file.warnings ?? [], cwd, file.filePath), policyMap),
|
|
388
512
|
}))
|
|
389
513
|
.filter((file) => file.warnings.length > 0);
|
|
390
|
-
const actionableFiles = visibleWarningFiles(actionable?.files);
|
|
391
|
-
const qualityFiles = visibleWarningFiles(quality?.files);
|
|
514
|
+
const actionableFiles = applyDeltaFreshnessGate(visibleWarningFiles(actionable?.files), cwd, actionable?.generatedAt);
|
|
515
|
+
const qualityFiles = applyDeltaFreshnessGate(visibleWarningFiles(quality?.files), cwd, quality?.generatedAt);
|
|
392
516
|
const lines = [];
|
|
393
517
|
// Fixable warnings from actionable-warnings
|
|
394
518
|
if (severity !== "error") {
|
|
@@ -396,7 +520,8 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
|
396
520
|
const rel = path.relative(cwd, file.filePath);
|
|
397
521
|
lines.push(`${rel}`);
|
|
398
522
|
for (const w of file.warnings) {
|
|
399
|
-
|
|
523
|
+
const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
|
|
524
|
+
lines.push(` ⚠ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
|
|
400
525
|
}
|
|
401
526
|
}
|
|
402
527
|
}
|
|
@@ -407,7 +532,8 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
|
|
|
407
532
|
if (!lines.includes(rel))
|
|
408
533
|
lines.push(rel);
|
|
409
534
|
for (const w of file.warnings) {
|
|
410
|
-
|
|
535
|
+
const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
|
|
536
|
+
lines.push(` ℹ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
|
|
411
537
|
}
|
|
412
538
|
}
|
|
413
539
|
}
|
|
@@ -568,8 +694,12 @@ function applyProjectRulePolicy(value, policyMap) {
|
|
|
568
694
|
return undefined;
|
|
569
695
|
return { ...value, diagnostics: applyRulePolicy(value.diagnostics, policyMap) };
|
|
570
696
|
}
|
|
571
|
-
/** A diagnostic counts as error-like when it blocks or has error severity.
|
|
697
|
+
/** A diagnostic counts as error-like when it blocks or has error severity.
|
|
698
|
+
* A `stale` (#1641 past-EOF) entry is excluded — its cited line no longer
|
|
699
|
+
* exists in the current file, so it can no longer count toward a hard stop. */
|
|
572
700
|
function isErrorLike(d) {
|
|
701
|
+
if (d.stale)
|
|
702
|
+
return false;
|
|
573
703
|
return d.semantic === "blocking" || d.severity === "error";
|
|
574
704
|
}
|
|
575
705
|
function matchesSeverity(d, severity) {
|
|
@@ -620,7 +750,7 @@ function lspDiagnosticToWidget(diagnostic) {
|
|
|
620
750
|
tool: "lsp",
|
|
621
751
|
};
|
|
622
752
|
}
|
|
623
|
-
function projectDiagnosticToWidget(diagnostic) {
|
|
753
|
+
function projectDiagnosticToWidget(diagnostic, filePath) {
|
|
624
754
|
return {
|
|
625
755
|
severity: diagnostic.severity,
|
|
626
756
|
semantic: diagnostic.semantic,
|
|
@@ -629,6 +759,7 @@ function projectDiagnosticToWidget(diagnostic) {
|
|
|
629
759
|
col: diagnostic.column,
|
|
630
760
|
rule: diagnostic.rule ?? diagnostic.code,
|
|
631
761
|
tool: diagnostic.runner || diagnostic.tool,
|
|
762
|
+
uri: widgetDiagnosticUri(filePath, diagnostic.line, diagnostic.column),
|
|
632
763
|
};
|
|
633
764
|
}
|
|
634
765
|
// The ast-grep LSP keys its diagnostics `rule = "ast-grep:<id>"` (source:code,
|
|
@@ -650,11 +781,23 @@ function summarizeDiagnostics(filePath, diagnostics, hasFinalSnapshot) {
|
|
|
650
781
|
let errors = 0;
|
|
651
782
|
let warnings = 0;
|
|
652
783
|
for (const diagnostic of diagnostics) {
|
|
784
|
+
// #1641: a demoted past-EOF entry keeps its severity for display but
|
|
785
|
+
// drops out of the tallies — same reasoning as widget-state's `isBlocking`.
|
|
786
|
+
if (diagnostic.stale)
|
|
787
|
+
continue;
|
|
653
788
|
if (diagnostic.semantic === "blocking")
|
|
654
789
|
blocking++;
|
|
655
790
|
if (diagnostic.severity === "error")
|
|
656
791
|
errors++;
|
|
657
|
-
|
|
792
|
+
// #1777: `hint` and `info` tally alongside `warning`, matching
|
|
793
|
+
// `countDiagnostics` in `clients/widget-state.ts` — mode=all reads that
|
|
794
|
+
// tally and mode=full reads this one, so the two must agree. An exact
|
|
795
|
+
// `=== "warning"` test scored a hint-only file 0/0/0, the `withIssues`
|
|
796
|
+
// filter below then dropped it, and mode=full rendered "No issues" for a
|
|
797
|
+
// file whose own `details` still carried the diagnostic.
|
|
798
|
+
else if (diagnostic.severity === "warning" ||
|
|
799
|
+
diagnostic.severity === "hint" ||
|
|
800
|
+
diagnostic.severity === "info")
|
|
658
801
|
warnings++;
|
|
659
802
|
}
|
|
660
803
|
return {
|
|
@@ -666,6 +809,85 @@ function summarizeDiagnostics(filePath, diagnostics, hasFinalSnapshot) {
|
|
|
666
809
|
diagnostics,
|
|
667
810
|
};
|
|
668
811
|
}
|
|
812
|
+
// #1618: sentence form used when exactly one reason accounts for every
|
|
813
|
+
// unconfirmed file — mirrors the pre-#1618 two-case ternary's phrasing so
|
|
814
|
+
// existing single-reason callers see unchanged text.
|
|
815
|
+
const UNCONFIRMED_REASON_SENTENCE = {
|
|
816
|
+
budget: "check didn't complete within budget",
|
|
817
|
+
inconclusive: "check was inconclusive (notify-write or diagnostics wait timed out)",
|
|
818
|
+
coverage_gap: "an auxiliary scanner coverage gap",
|
|
819
|
+
service_destroyed: "the LSP service was reset mid-sweep",
|
|
820
|
+
error: "check errored",
|
|
821
|
+
// #1618 review round 2: the LSP layer considered this touch CONFIRMED —
|
|
822
|
+
// the file's content changed under it (stale binding), which is a
|
|
823
|
+
// completely different failure from a timeout. Must never collapse into
|
|
824
|
+
// "within budget", the exact string this PR exists to stop misrendering.
|
|
825
|
+
binding_mismatch: "the file changed on disk since this result was computed",
|
|
826
|
+
};
|
|
827
|
+
// Short per-reason label used only when MORE than one reason is present, so
|
|
828
|
+
// the note can still say "N timed out, M errored" instead of collapsing to
|
|
829
|
+
// one sentence that can't carry two counts.
|
|
830
|
+
const UNCONFIRMED_REASON_COUNT_LABEL = {
|
|
831
|
+
budget: "timed out",
|
|
832
|
+
inconclusive: "inconclusive",
|
|
833
|
+
coverage_gap: "coverage gap",
|
|
834
|
+
service_destroyed: "service reset mid-sweep",
|
|
835
|
+
error: "errored",
|
|
836
|
+
binding_mismatch: "stale binding",
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
839
|
+
* #1618: a result predating the `unconfirmedReason` field (a legacy/test
|
|
840
|
+
* double, or `LSPWorkspaceDiagnosticResult`'s own pre-#1618 shape) is
|
|
841
|
+
* classified from its OTHER fields exactly the way the dead
|
|
842
|
+
* `unconfirmedErrored = length - unconfirmedTimedOut` subtraction meant to,
|
|
843
|
+
* before that subtraction was structurally always 0 (the sweep's catch
|
|
844
|
+
* block sets both `error` and `timedOut`, so `unconfirmedErrored` never had
|
|
845
|
+
* a way to become nonzero). An explicit `result.error` now wins even when
|
|
846
|
+
* `unconfirmedReason` is absent, so an errored file is never rendered as
|
|
847
|
+
* "within budget".
|
|
848
|
+
*
|
|
849
|
+
* #1618 review round 2: `mismatched` MUST be checked first. A binding
|
|
850
|
+
* mismatch is discovered HERE, after the sweep already returned the result
|
|
851
|
+
* as confirmed (`!timedOut`, no `.error`, no `.unconfirmedReason`) — the
|
|
852
|
+
* `?? "budget"` fallback below would otherwise silently claim a
|
|
853
|
+
* stale-binding file as "within budget", the exact string this PR exists to
|
|
854
|
+
* stop misrendering.
|
|
855
|
+
*/
|
|
856
|
+
function classifyUnconfirmedReason(result, mismatched) {
|
|
857
|
+
if (mismatched.has(result))
|
|
858
|
+
return "binding_mismatch";
|
|
859
|
+
return result.unconfirmedReason ?? (result.error ? "error" : "budget");
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* #1618: replaces the `unconfirmedTimedOut`/`unconfirmedErrored` dead
|
|
863
|
+
* subtraction with a real per-reason tally read off each result's own
|
|
864
|
+
* `unconfirmedReason` (falling back to `classifyUnconfirmedReason` for a
|
|
865
|
+
* legacy result that predates the field). `mismatched` is threaded through
|
|
866
|
+
* so a stale-binding file is never counted under a fallback reason it
|
|
867
|
+
* doesn't have (review round 2).
|
|
868
|
+
*/
|
|
869
|
+
function tallyUnconfirmedReasons(results, mismatched) {
|
|
870
|
+
const tally = new Map();
|
|
871
|
+
for (const result of results) {
|
|
872
|
+
const reason = classifyUnconfirmedReason(result, mismatched);
|
|
873
|
+
tally.set(reason, (tally.get(reason) ?? 0) + 1);
|
|
874
|
+
}
|
|
875
|
+
return tally;
|
|
876
|
+
}
|
|
877
|
+
/** Render `tallyUnconfirmedReasons`' output as the clause inside the
|
|
878
|
+
* "unconfirmed (...)" note — a single sentence when one reason accounts for
|
|
879
|
+
* everything, else a comma-joined "N label" breakdown. */
|
|
880
|
+
function formatUnconfirmedReasonClause(tally) {
|
|
881
|
+
const entries = [...tally.entries()].filter(([, count]) => count > 0);
|
|
882
|
+
if (entries.length === 0)
|
|
883
|
+
return "";
|
|
884
|
+
if (entries.length === 1) {
|
|
885
|
+
return UNCONFIRMED_REASON_SENTENCE[entries[0][0]];
|
|
886
|
+
}
|
|
887
|
+
return entries
|
|
888
|
+
.map(([reason, count]) => `${count} ${UNCONFIRMED_REASON_COUNT_LABEL[reason]}`)
|
|
889
|
+
.join(", ");
|
|
890
|
+
}
|
|
669
891
|
/**
|
|
670
892
|
* #646: break the confirmed/unconfirmed LSP-sweep tally down PER primary
|
|
671
893
|
* server id (typescript, pyright, marksman, ...) instead of one flat
|
|
@@ -770,10 +992,10 @@ function mergeDiagnosticsWithWidgetSummaries(widgetSummaries, lspResults, projec
|
|
|
770
992
|
}
|
|
771
993
|
}
|
|
772
994
|
for (const diagnostic of projectSnapshot?.diagnostics ?? []) {
|
|
773
|
-
addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic));
|
|
995
|
+
addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic, diagnostic.filePath));
|
|
774
996
|
}
|
|
775
997
|
for (const diagnostic of projectDelta?.diagnostics ?? []) {
|
|
776
|
-
addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic));
|
|
998
|
+
addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic, diagnostic.filePath));
|
|
777
999
|
}
|
|
778
1000
|
return [...byFile.values()];
|
|
779
1001
|
}
|
|
@@ -896,6 +1118,7 @@ async function getProjectDiagnosticsSnapshotForFullMode(cwd, options) {
|
|
|
896
1118
|
}
|
|
897
1119
|
return undefined;
|
|
898
1120
|
}
|
|
1121
|
+
// @delivery-surface: lens-diagnostics:mode-full
|
|
899
1122
|
async function formatFullMode(cwd, severity, lspService, cacheManager, options = {}) {
|
|
900
1123
|
const runWorkspaceDiagnostics = lspService.runWorkspaceDiagnostics;
|
|
901
1124
|
if (typeof runWorkspaceDiagnostics !== "function") {
|
|
@@ -935,12 +1158,22 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
935
1158
|
// could complete. Building its promise here, alongside the sweep and cheap
|
|
936
1159
|
// scan, makes it genuinely concurrent — all three phases now race the SAME
|
|
937
1160
|
// signal from the same starting point instead of stacking.
|
|
938
|
-
const
|
|
1161
|
+
const projectRunnersRequested = shouldIncludeProjectRunners(options.refreshRunners);
|
|
1162
|
+
const analyzersPromise = projectRunnersRequested
|
|
939
1163
|
? loadBootstrapClients().then((clients) => fetchFreshProjectDiagnostics(cacheManager, cwd, clients, signal, { runtime: options.runtime }))
|
|
940
1164
|
: Promise.resolve({
|
|
941
1165
|
diagnostics: [],
|
|
942
1166
|
runners: [],
|
|
943
|
-
|
|
1167
|
+
// #1623: every heavyweight analyzer is ELIGIBLE for this project but
|
|
1168
|
+
// this call never asked for it (refreshRunners wasn't cheap/all/
|
|
1169
|
+
// cached) — the expensive fetch below deliberately never runs in
|
|
1170
|
+
// that case (see the comment above), but rendering total silence
|
|
1171
|
+
// here reads exactly as "ran clean", the false-empty #1623 exists to
|
|
1172
|
+
// close. `ANALYZER_IDS` (fresh-fetch.ts) is the single source of
|
|
1173
|
+
// truth for which lanes this covers — reuse it rather than
|
|
1174
|
+
// hand-listing ids here.
|
|
1175
|
+
cold: [...ANALYZER_IDS],
|
|
1176
|
+
coldReasons: Object.fromEntries(ANALYZER_IDS.map((id) => [id, NOT_REQUESTED_REASON])),
|
|
944
1177
|
failed: [],
|
|
945
1178
|
timings: {},
|
|
946
1179
|
});
|
|
@@ -960,7 +1193,14 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
960
1193
|
analyzersPromise,
|
|
961
1194
|
]);
|
|
962
1195
|
const aborted = signal?.aborted ?? false;
|
|
963
|
-
|
|
1196
|
+
// #1640: before ANY consumer sees them — the footer reconcile, the widget
|
|
1197
|
+
// merge, the rendered counts — demote TypeScript errors on files tsserver
|
|
1198
|
+
// checked in its INFERRED project. Applied once, here, so a single seam
|
|
1199
|
+
// governs the whole sweep instead of each renderer learning the rule.
|
|
1200
|
+
const lspResults = await demoteInferredProjectSweepResults(rawLspResults.filter((result) => includeFile(result.filePath)), cwd, lspService,
|
|
1201
|
+
// #1645 review F1: the sweep that produced these results is signal-bounded,
|
|
1202
|
+
// so the probe loop that post-processes them must be too.
|
|
1203
|
+
signal);
|
|
964
1204
|
// A result bound to a different document must not replace the current
|
|
965
1205
|
// widget state, even when it contains real diagnostics. Pull results may carry
|
|
966
1206
|
// a content hash without the push binding verdict, so verify that hash against
|
|
@@ -981,13 +1221,15 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
981
1221
|
const confirmedLspResults = lspResults.filter((result) => !result.timedOut &&
|
|
982
1222
|
!result.error &&
|
|
983
1223
|
!mismatchedLspResults.has(result));
|
|
1224
|
+
const fullyCoveredLspResults = confirmedLspResults.filter((result) => (result.unconfirmedServerIds?.length ?? 0) === 0);
|
|
1225
|
+
const partiallyCoveredLspResults = confirmedLspResults.filter((result) => (result.unconfirmedServerIds?.length ?? 0) > 0);
|
|
984
1226
|
const unconfirmedLspResults = lspResults.filter((result) => result.timedOut ||
|
|
985
1227
|
result.error ||
|
|
986
1228
|
mismatchedLspResults.has(result));
|
|
987
1229
|
// #571: reconcile this scan's fresh, CONFIRMED per-file results into the
|
|
988
1230
|
// footer cache. A footer write is never allowed to fail the tool call, so
|
|
989
1231
|
// any unexpected throw is swallowed.
|
|
990
|
-
for (const result of
|
|
1232
|
+
for (const result of fullyCoveredLspResults) {
|
|
991
1233
|
try {
|
|
992
1234
|
// #692: provenance label ONLY — must never affect `rule`/identity (see
|
|
993
1235
|
// `ConvertLspDiagnosticsOptions.scanOrigin`'s doc comment).
|
|
@@ -1053,6 +1295,22 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1053
1295
|
// legitimately show diagnostics from widgetSummaries/project-runner state
|
|
1054
1296
|
// below if those independently have entries for it.
|
|
1055
1297
|
const summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta), cwd, policyMap);
|
|
1298
|
+
// #1888: the full-mode summary above is the first seam where every
|
|
1299
|
+
// producing lane is correlated. The earlier footer loop intentionally writes
|
|
1300
|
+
// only CONFIRMED LSP results, so an ast-grep backpressure failure left
|
|
1301
|
+
// project-scan (`ast-grep-napi`) findings visible to lens_diagnostics but
|
|
1302
|
+
// absent from the widget. Commit this same post-policy, post-suppression set
|
|
1303
|
+
// to the widget so its human-facing count cannot silently become single-lane.
|
|
1304
|
+
const parsedProjectScanObservedAt = projectSnapshot?.scannedAt
|
|
1305
|
+
? Date.parse(projectSnapshot.scannedAt)
|
|
1306
|
+
: undefined;
|
|
1307
|
+
const projectScanObservedAt = parsedProjectScanObservedAt !== undefined &&
|
|
1308
|
+
Number.isFinite(parsedProjectScanObservedAt)
|
|
1309
|
+
? parsedProjectScanObservedAt
|
|
1310
|
+
: undefined;
|
|
1311
|
+
for (const summary of summaries) {
|
|
1312
|
+
reconcileCorrelatedScanDiagnostics(summary.filePath, summary.diagnostics, nextWriteIndex?.(), projectScanObservedAt);
|
|
1313
|
+
}
|
|
1056
1314
|
const result = formatAllMode(cwd, severity, summaries, {
|
|
1057
1315
|
mode: "full",
|
|
1058
1316
|
lspFilesChecked: rawLspResults.length,
|
|
@@ -1083,8 +1341,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1083
1341
|
// as "0 diagnostics" — say explicitly which files the LSP sweep could not
|
|
1084
1342
|
// confirm and why, distinguishing a hard error from a soft timeout the
|
|
1085
1343
|
// same way #570 does upstream.
|
|
1086
|
-
const
|
|
1087
|
-
const unconfirmedErrored = unconfirmedLspResults.length - unconfirmedTimedOut;
|
|
1344
|
+
const unconfirmedByReason = tallyUnconfirmedReasons(unconfirmedLspResults, mismatchedLspResults);
|
|
1088
1345
|
// #646: per-primary-server breakdown of the same confirmed/unconfirmed
|
|
1089
1346
|
// tally — lets an agent see AT A GLANCE which server is responsible for
|
|
1090
1347
|
// any unconfirmed files (e.g. "marksman: 0/34") instead of having to
|
|
@@ -1099,15 +1356,21 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1099
1356
|
: "";
|
|
1100
1357
|
const unconfirmedLspNote = unconfirmedLspResults.length > 0
|
|
1101
1358
|
? `\n\n⚠ LSP sweep: ${confirmedLspResults.length} file(s) confirmed via LSP, ` +
|
|
1102
|
-
`${unconfirmedLspResults.length} unconfirmed (${
|
|
1103
|
-
? `${unconfirmedTimedOut} timed out, ${unconfirmedErrored} errored`
|
|
1104
|
-
: unconfirmedTimedOut > 0
|
|
1105
|
-
? "check didn't complete within budget"
|
|
1106
|
-
: "check errored"})${serverBreakdownClause} — NOT the same as 0 diagnostics for: ${unconfirmedLspResults
|
|
1359
|
+
`${unconfirmedLspResults.length} unconfirmed (${formatUnconfirmedReasonClause(unconfirmedByReason)})${serverBreakdownClause} — NOT the same as 0 diagnostics for: ${unconfirmedLspResults
|
|
1107
1360
|
.slice(0, 20)
|
|
1108
1361
|
.map((result) => result.filePath)
|
|
1109
1362
|
.join(", ")}${unconfirmedLspResults.length > 20 ? ", …" : ""}. These files' LSP contribution is excluded from this result; re-run mode=full to retry them (they may still show findings above from cached/project-runner state).`
|
|
1110
1363
|
: "";
|
|
1364
|
+
// Code-unit comparator (#1883): this list ships as the
|
|
1365
|
+
// `unconfirmedLspServerIds` structured field and as the lane names in the
|
|
1366
|
+
// agent-visible coverage note, so its order must be deterministic across
|
|
1367
|
+
// locales — localeCompare is deliberately avoided.
|
|
1368
|
+
const uncoveredScannerIds = [
|
|
1369
|
+
...new Set(partiallyCoveredLspResults.flatMap((result) => result.unconfirmedServerIds ?? [])),
|
|
1370
|
+
].sort((a, b) => Number(a > b) - Number(a < b));
|
|
1371
|
+
const auxiliaryCoverageNote = uncoveredScannerIds.length > 0
|
|
1372
|
+
? `\n\n⚠ Auxiliary coverage incomplete: ${uncoveredScannerIds.join(", ")} did not answer for ${partiallyCoveredLspResults.length} file(s). Findings from answering servers are included; this is not a clean verdict for the named scanner lane(s).`
|
|
1373
|
+
: "";
|
|
1111
1374
|
// #646: primary-vs-auxiliary split of the raw LSP-sweep findings (before
|
|
1112
1375
|
// they're merged into the widget-state summaries below), mirroring
|
|
1113
1376
|
// `lsp_diagnostics`' "Primary findings"/"Auxiliary findings" separation —
|
|
@@ -1139,13 +1402,31 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1139
1402
|
// analyzers before anything spawns — rendering that as the per-analyzer
|
|
1140
1403
|
// "not applicable / unavailable" list would send the caller chasing seven
|
|
1141
1404
|
// wrong reasons. One note with the real one instead.
|
|
1405
|
+
// #1623: prefer the SPECIFIC reason captured at each gate
|
|
1406
|
+
// (`extracted.coldReasons`) over a generic guess — `formatNotRunEntry`
|
|
1407
|
+
// falls back to `warmTriggerFor` only for an id this call's `coldReasons`
|
|
1408
|
+
// doesn't cover (e.g. a caller-supplied `cold` list from an older cache
|
|
1409
|
+
// shape). Single formatter (extractors.ts) so this note's wording can't
|
|
1410
|
+
// drift from any other caller that renders the same `cold` list.
|
|
1411
|
+
// #1623 fix-round F5: the "not requested" (quick-mode) batch shares ONE
|
|
1412
|
+
// reason (`NOT_REQUESTED_REASON`) across every id — `formatNotRunEntry`
|
|
1413
|
+
// repeating that same ~130-char sentence per id makes the note nearly
|
|
1414
|
+
// unreadable, and the "not applicable / unavailable this run" header
|
|
1415
|
+
// below is a dishonest label for it: these lanes ARE applicable and
|
|
1416
|
+
// available, this call just never asked for them. Render that batch as
|
|
1417
|
+
// its own compact note — bare ids, the shared reason stated once — and
|
|
1418
|
+
// keep the detailed per-id format for genuinely cold lanes, where each
|
|
1419
|
+
// id's reason really does differ (not a git repo vs binary unavailable
|
|
1420
|
+
// vs retry cooldown, ...).
|
|
1142
1421
|
const coldNote = extracted.unsafeRoot
|
|
1143
1422
|
? `\n\nheavyweight analyzers skipped: the working directory resolves at or above the home directory, so a fresh knip/jscpd/madge/gitleaks/govulncheck/trivy/dead-code scan would walk every unrelated tree under it. Re-run from inside a project directory. Absence of their findings is NOT a clean verdict.`
|
|
1144
|
-
: genuinelyColdIds.length > 0
|
|
1145
|
-
? `\n\
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1423
|
+
: !projectRunnersRequested && genuinelyColdIds.length > 0
|
|
1424
|
+
? `\n\nnot run this call (quick mode): ${genuinelyColdIds.join(", ")}. ${NOT_REQUESTED_REASON}. Absence of their findings is NOT a clean verdict.`
|
|
1425
|
+
: genuinelyColdIds.length > 0
|
|
1426
|
+
? `\n\ncold (not applicable / unavailable this run): ${genuinelyColdIds
|
|
1427
|
+
.map((id) => formatNotRunEntry(id, extracted.coldReasons))
|
|
1428
|
+
.join(", ")}. These analyzers have not contributed to this result — absence of their findings is NOT a clean verdict.`
|
|
1429
|
+
: "";
|
|
1149
1430
|
// #1004: unlike every other analyzer above (knip/jscpd/madge/gitleaks/
|
|
1150
1431
|
// govulncheck/opengrep/trivy/dead-code all run a FRESH whole-project scan
|
|
1151
1432
|
// per the fresh-fetch.ts header, so "clean" there really does mean "the
|
|
@@ -1170,6 +1451,21 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1170
1451
|
.map(({ id, summary }) => `${id} — ${summary}`)
|
|
1171
1452
|
.join(", ")}. These analyzers were not cached and will be retried; absence of their findings is NOT a clean verdict.`
|
|
1172
1453
|
: "";
|
|
1454
|
+
// #1617: the #1616 suppressed-bucket rule applied to mode=full — a finding
|
|
1455
|
+
// an agent/user marked false-positive/won't-fix now drops out of
|
|
1456
|
+
// `diagnostics` (fresh-fetch.ts's `record()`), but that drop must stay
|
|
1457
|
+
// visible as a count rather than reading as "nothing was wrong here".
|
|
1458
|
+
// Review-round F4 (#1625): per-lane attribution — "gitleaks 2, knip 1" says
|
|
1459
|
+
// WHICH analyzer's marks are doing the suppressing, not just a bare total.
|
|
1460
|
+
// A lane that's 100% suppressed still won't appear in `runners`/`cold` as
|
|
1461
|
+
// distinct from "ran clean" — that gap is #1623's lane-status territory,
|
|
1462
|
+
// not fixed here.
|
|
1463
|
+
const dispositionSuppressedByLaneText = Object.entries(extracted.dispositionSuppressedByLane ?? {})
|
|
1464
|
+
.map(([id, count]) => `${id} ${count}`)
|
|
1465
|
+
.join(", ");
|
|
1466
|
+
const dispositionSuppressedNote = (extracted.dispositionSuppressed ?? 0) > 0
|
|
1467
|
+
? `\n\nsuppressed by disposition: ${extracted.dispositionSuppressed} finding(s) dropped from this result because they're marked false-positive or won't-fix (${dispositionSuppressedByLaneText}). Not a clean verdict for those locations — they were found, then intentionally hidden.`
|
|
1468
|
+
: "";
|
|
1173
1469
|
// #747/#250: the cheap project-diagnostics scan (scanProjectDiagnostics) and
|
|
1174
1470
|
// the LSP workspace sweep (collectWorkspaceDiagnosticFiles) both refuse to
|
|
1175
1471
|
// WALK from a cwd at/above $HOME — from there, walking would enumerate every
|
|
@@ -1199,6 +1495,28 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1199
1495
|
const generatedSkipNote = generatedSkipNoticeText
|
|
1200
1496
|
? `\n\n${generatedSkipNoticeText}`
|
|
1201
1497
|
: "";
|
|
1498
|
+
// #1623: the cheap in-process project scan (tree-sitter/fact-rules/
|
|
1499
|
+
// ast-grep) is a THIRD lane `coldNote` never covers (it isn't one of
|
|
1500
|
+
// `ANALYZER_IDS` — that list is fetch-fetch.ts's heavyweight analyzers
|
|
1501
|
+
// only). It has three distinct states, all of which must render — the
|
|
1502
|
+
// fix-round F2 finding: with `refreshRunners=cached` and no snapshot ever
|
|
1503
|
+
// written yet, BOTH of the two notes below used to stay empty (one gated
|
|
1504
|
+
// on `scannedAt` being present, the other on the "not requested" branch
|
|
1505
|
+
// this call isn't in), so the original #1623 silence survived in exactly
|
|
1506
|
+
// this mode. `cheapScanScannedAtMs` centralizes the one bit every branch
|
|
1507
|
+
// needs: whether the stored snapshot has a usable timestamp (#1623
|
|
1508
|
+
// fix-round F4 — a corrupt/missing `scannedAt` must not compute a NaN
|
|
1509
|
+
// age, mirroring `./cache.ts`'s `Number.isFinite` guard).
|
|
1510
|
+
const cheapScanScannedAtMs = rawProjectSnapshot?.scannedAt
|
|
1511
|
+
? Date.parse(rawProjectSnapshot.scannedAt)
|
|
1512
|
+
: undefined;
|
|
1513
|
+
const cheapScanStatusNote = !projectRunnersRequested
|
|
1514
|
+
? `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): not run this call (${NOT_REQUESTED_REASON}).`
|
|
1515
|
+
: options.refreshRunners === "cached"
|
|
1516
|
+
? cheapScanScannedAtMs !== undefined && Number.isFinite(cheapScanScannedAtMs)
|
|
1517
|
+
? `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): served from cache, ${formatCacheAgeOld(Date.now() - cheapScanScannedAtMs)} — not re-run this call.`
|
|
1518
|
+
: `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): not run (no cached scan; refresh with refreshRunners=cheap/all to populate).`
|
|
1519
|
+
: "";
|
|
1202
1520
|
const abortedNote = abortedIds.size > 0
|
|
1203
1521
|
? `\n\nstopped mid-scan (still running in the background, not reflected in this result): ${[
|
|
1204
1522
|
...abortedIds,
|
|
@@ -1208,12 +1526,25 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1208
1526
|
// possibly-stale session_start cache — say so honestly, with per-analyzer
|
|
1209
1527
|
// elapsed time, since this can legitimately take a while (trivy's own
|
|
1210
1528
|
// ~180s ceiling).
|
|
1211
|
-
|
|
1529
|
+
// #1623: `extracted.cachedAgeMs` marks ids that are a cache-read BY DESIGN
|
|
1530
|
+
// (today, only test-runner — see fresh-fetch.ts) rather than a fresh
|
|
1531
|
+
// execution this call. Exclude those from "fetched fresh" below — folding
|
|
1532
|
+
// a cache replay into the same list as a genuine run is exactly the
|
|
1533
|
+
// misread the second #1623 dogfood pass reported (a cached result read as
|
|
1534
|
+
// "just ran"). They get their own note with an actual age instead.
|
|
1535
|
+
const cachedAgeMs = extracted.cachedAgeMs ?? {};
|
|
1536
|
+
const timingEntries = Object.entries(extracted.timings ?? {}).filter(([id]) => !(id in cachedAgeMs));
|
|
1212
1537
|
const freshNote = timingEntries.length > 0
|
|
1213
1538
|
? `\n\nfetched fresh this call: ${timingEntries
|
|
1214
1539
|
.map(([id, ms]) => `${id} (${Math.round(ms)}ms)`)
|
|
1215
1540
|
.join(", ")}.`
|
|
1216
1541
|
: "";
|
|
1542
|
+
const cachedAgeEntries = Object.entries(cachedAgeMs);
|
|
1543
|
+
const cachedAgeNote = cachedAgeEntries.length > 0
|
|
1544
|
+
? `\n\nserved from cache this call (not re-run): ${cachedAgeEntries
|
|
1545
|
+
.map(([id, ms]) => `${id} (${formatCacheAgeOld(ms)})`)
|
|
1546
|
+
.join(", ")}.`
|
|
1547
|
+
: "";
|
|
1217
1548
|
// coldRunners always lands in details (even when empty) so a caller can
|
|
1218
1549
|
// reliably check "were any extractors cold" without a presence check.
|
|
1219
1550
|
// analyzerTimingsMs surfaces the #585 fresh-fetch elapsed time per
|
|
@@ -1224,8 +1555,19 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1224
1555
|
details: {
|
|
1225
1556
|
...result.details,
|
|
1226
1557
|
coldRunners: extracted.cold,
|
|
1558
|
+
// #1623: the specific reason each cold id was skipped (single source
|
|
1559
|
+
// of truth: extractors.ts's `formatNotRunEntry` renders the same map
|
|
1560
|
+
// into the text note above) — lets a caller check WHY without parsing
|
|
1561
|
+
// text.
|
|
1562
|
+
coldReasons: extracted.coldReasons ?? {},
|
|
1227
1563
|
failedAnalyzers,
|
|
1228
1564
|
analyzerTimingsMs: extracted.timings,
|
|
1565
|
+
dispositionSuppressed: extracted.dispositionSuppressed ?? 0,
|
|
1566
|
+
dispositionSuppressedByLane: extracted.dispositionSuppressedByLane ?? {},
|
|
1567
|
+
// #1623: ms-old each cache-read-by-design lane's data was (today, only
|
|
1568
|
+
// test-runner) — lets a caller distinguish "just ran" from "served
|
|
1569
|
+
// from cache" without parsing the text note.
|
|
1570
|
+
analyzersCachedAgeMs: cachedAgeMs,
|
|
1229
1571
|
analyzersAborted: extracted.aborted ?? false,
|
|
1230
1572
|
analyzersAbortedIds: extracted.abortedIds ?? [],
|
|
1231
1573
|
// #747: true when the fresh fetch refused an at-or-above-$HOME root —
|
|
@@ -1241,6 +1583,8 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1241
1583
|
// any files unconfirmed" without re-deriving it from the text.
|
|
1242
1584
|
lspFilesConfirmed: confirmedLspResults.length,
|
|
1243
1585
|
lspFilesUnconfirmed: unconfirmedLspResults.length,
|
|
1586
|
+
lspFilesPartiallyCovered: partiallyCoveredLspResults.length,
|
|
1587
|
+
unconfirmedLspServerIds: uncoveredScannerIds,
|
|
1244
1588
|
unconfirmedLspFiles: unconfirmedLspResults.map((result) => result.filePath),
|
|
1245
1589
|
// #646: per-primary-server breakdown of the same tally, plus the
|
|
1246
1590
|
// primary/auxiliary findings split — mirrors `lsp_diagnostics`'
|
|
@@ -1287,15 +1631,19 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1287
1631
|
text: result.content[0].text +
|
|
1288
1632
|
note +
|
|
1289
1633
|
unconfirmedLspNote +
|
|
1634
|
+
auxiliaryCoverageNote +
|
|
1290
1635
|
lspPrimaryVsAuxiliaryNote +
|
|
1291
1636
|
coldNote +
|
|
1292
1637
|
testRunnerEditScopedNote +
|
|
1293
1638
|
failedNote +
|
|
1639
|
+
dispositionSuppressedNote +
|
|
1294
1640
|
walkUnsafeRootNote +
|
|
1295
1641
|
scanTruncatedNote +
|
|
1296
1642
|
generatedSkipNote +
|
|
1297
1643
|
abortedNote +
|
|
1298
1644
|
freshNote +
|
|
1645
|
+
cachedAgeNote +
|
|
1646
|
+
cheapScanStatusNote +
|
|
1299
1647
|
missingNote,
|
|
1300
1648
|
},
|
|
1301
1649
|
],
|
|
@@ -1306,12 +1654,16 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1306
1654
|
coldNote ||
|
|
1307
1655
|
testRunnerEditScopedNote ||
|
|
1308
1656
|
failedNote ||
|
|
1657
|
+
dispositionSuppressedNote ||
|
|
1309
1658
|
walkUnsafeRootNote ||
|
|
1310
1659
|
scanTruncatedNote ||
|
|
1311
1660
|
generatedSkipNote ||
|
|
1312
1661
|
abortedNote ||
|
|
1313
1662
|
freshNote ||
|
|
1663
|
+
cachedAgeNote ||
|
|
1664
|
+
cheapScanStatusNote ||
|
|
1314
1665
|
unconfirmedLspNote ||
|
|
1666
|
+
auxiliaryCoverageNote ||
|
|
1315
1667
|
lspPrimaryVsAuxiliaryNote) {
|
|
1316
1668
|
return {
|
|
1317
1669
|
content: [
|
|
@@ -1319,15 +1671,19 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1319
1671
|
type: "text",
|
|
1320
1672
|
text: result.content[0].text +
|
|
1321
1673
|
unconfirmedLspNote +
|
|
1674
|
+
auxiliaryCoverageNote +
|
|
1322
1675
|
lspPrimaryVsAuxiliaryNote +
|
|
1323
1676
|
coldNote +
|
|
1324
1677
|
testRunnerEditScopedNote +
|
|
1325
1678
|
failedNote +
|
|
1679
|
+
dispositionSuppressedNote +
|
|
1326
1680
|
walkUnsafeRootNote +
|
|
1327
1681
|
scanTruncatedNote +
|
|
1328
1682
|
generatedSkipNote +
|
|
1329
1683
|
abortedNote +
|
|
1330
1684
|
freshNote +
|
|
1685
|
+
cachedAgeNote +
|
|
1686
|
+
cheapScanStatusNote +
|
|
1331
1687
|
missingNote,
|
|
1332
1688
|
},
|
|
1333
1689
|
],
|
|
@@ -1336,13 +1692,17 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
1336
1692
|
}
|
|
1337
1693
|
return resultWithCold;
|
|
1338
1694
|
}
|
|
1339
|
-
|
|
1695
|
+
// @delivery-surface: lens-diagnostics:mode-all
|
|
1696
|
+
function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(), detailOverrides = { mode: "all" }, staleDropped = 0, pathsScope, dependencyDemoted = 0) {
|
|
1340
1697
|
// Files changed/deleted since their diagnostics were recorded have already
|
|
1341
1698
|
// been dropped by reconcileStaleWidgetFiles; note them so the agent knows
|
|
1342
1699
|
// those aren't "clean", just un-rescanned (use mode=full to refresh).
|
|
1343
|
-
const staleNote = staleDropped > 0
|
|
1700
|
+
const staleNote = (staleDropped > 0
|
|
1344
1701
|
? ` (${staleDropped} changed file${staleDropped === 1 ? "" : "s"} omitted as stale — use mode=full to rescan)`
|
|
1345
|
-
: ""
|
|
1702
|
+
: "") +
|
|
1703
|
+
(dependencyDemoted > 0
|
|
1704
|
+
? ` (${dependencyDemoted} blocking finding${dependencyDemoted === 1 ? "" : "s"} demoted to [stale] — an imported file changed since; re-run to confirm)`
|
|
1705
|
+
: "");
|
|
1346
1706
|
// mode=full already actively scanned exactly the requested paths, so a zero
|
|
1347
1707
|
// result there IS a legitimate clean read — the cache-only note only applies
|
|
1348
1708
|
// to delta/all, which is why this is gated on detailOverrides.mode !== "full".
|
|
@@ -1370,14 +1730,43 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
|
|
|
1370
1730
|
return s;
|
|
1371
1731
|
return summarizeDiagnostics(s.filePath, policyKept, s.hasFinalSnapshot);
|
|
1372
1732
|
});
|
|
1373
|
-
|
|
1374
|
-
//
|
|
1733
|
+
// #1641: the single chokepoint both mode=all (cache-only `summaries`) and
|
|
1734
|
+
// mode=full (widget cache merged with a FRESH LSP sweep, above) converge
|
|
1735
|
+
// through — a fresh sweep's server can still be citing its own stale
|
|
1736
|
+
// in-memory document, so gating only the cached half would miss exactly
|
|
1737
|
+
// the live incident this issue reports. Runs AFTER dispositions/rule
|
|
1738
|
+
// policy (not before): a finding a mark or a `.pi-lens.json` rule policy
|
|
1739
|
+
// already dropped is not being served, so it must never trigger a resync
|
|
1740
|
+
// or a `diagnostic_past_eof` telemetry record on this call.
|
|
1741
|
+
const eofGated = dispositioned.map((s) => {
|
|
1742
|
+
const { diagnostics, demotedCount } = demotePastEofDiagnostics({
|
|
1743
|
+
store: "lens_diagnostics",
|
|
1744
|
+
cwd,
|
|
1745
|
+
filePath: s.filePath,
|
|
1746
|
+
diagnostics: s.diagnostics ?? [],
|
|
1747
|
+
resync: resyncDocumentOnPastEof,
|
|
1748
|
+
});
|
|
1749
|
+
if (demotedCount === 0)
|
|
1750
|
+
return s;
|
|
1751
|
+
return summarizeDiagnostics(s.filePath, diagnostics, s.hasFinalSnapshot);
|
|
1752
|
+
});
|
|
1753
|
+
const visibleSummaries = eofGated.filter((s) => includeFile(s.filePath));
|
|
1754
|
+
// Filter to files with actual issues. A file whose only findings were
|
|
1755
|
+
// demoted by the #1641 past-EOF gate has blocking/errors/warnings at 0 —
|
|
1756
|
+
// counting it as "issue-free" would tell the agent the file is CLEAN when
|
|
1757
|
+
// it actually has an unconfirmed finding waiting on a rescan. Surface it
|
|
1758
|
+
// under `severity: "all"` (the gate's own advisory tier) so it stays
|
|
1759
|
+
// visible; a narrower `error`/`warning` filter still excludes it, matching
|
|
1760
|
+
// how those filters already treat any other non-matching severity.
|
|
1375
1761
|
const withIssues = visibleSummaries.filter((s) => {
|
|
1376
1762
|
if (severity === "error")
|
|
1377
1763
|
return s.blocking > 0 || s.errors > 0;
|
|
1378
1764
|
if (severity === "warning")
|
|
1379
1765
|
return s.warnings > 0;
|
|
1380
|
-
return s.blocking > 0 ||
|
|
1766
|
+
return (s.blocking > 0 ||
|
|
1767
|
+
s.errors > 0 ||
|
|
1768
|
+
s.warnings > 0 ||
|
|
1769
|
+
(s.diagnostics ?? []).some((d) => d.stale));
|
|
1381
1770
|
});
|
|
1382
1771
|
const pathsNote = isFullMode ? "" : pathsScopeCacheOnlyNote(pathsScope);
|
|
1383
1772
|
if (withIssues.length === 0) {
|
|
@@ -1412,6 +1801,10 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
|
|
|
1412
1801
|
parts.push(`${s.warnings}W`);
|
|
1413
1802
|
if (!s.hasFinalSnapshot)
|
|
1414
1803
|
parts.push(`(pending)`);
|
|
1804
|
+
const staleCount = (s.diagnostics ?? []).filter((d) => d.stale).length;
|
|
1805
|
+
if (staleCount > 0) {
|
|
1806
|
+
parts.push(`${staleCount} stale — re-run to confirm`);
|
|
1807
|
+
}
|
|
1415
1808
|
lines.push(`${rel} ${parts.join(" ")}`);
|
|
1416
1809
|
// List the actual diagnostics (not just counts) so the agent can act on
|
|
1417
1810
|
// them without re-running anything — same "L<line>: <message>" shape as the
|
|
@@ -1423,16 +1816,27 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
|
|
|
1423
1816
|
.sort(bySeverityThenLine);
|
|
1424
1817
|
const shown = matching.slice(0, MAX_DIAGNOSTICS_PER_FILE);
|
|
1425
1818
|
for (const d of shown) {
|
|
1426
|
-
const marker =
|
|
1427
|
-
?
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1819
|
+
const marker = d.stale
|
|
1820
|
+
? ""
|
|
1821
|
+
: isErrorLike(d)
|
|
1822
|
+
? d.semantic === "blocking"
|
|
1823
|
+
? "🔴 "
|
|
1824
|
+
: ""
|
|
1825
|
+
: "";
|
|
1431
1826
|
const label = d.rule ?? d.tool;
|
|
1432
1827
|
const tag = label ? ` [${label}]` : "";
|
|
1433
1828
|
const flaggedTag = d.flagged ? " 📌 flagged-to-fix" : "";
|
|
1434
1829
|
const msg = d.message.replace(/\s+/g, " ").trim();
|
|
1435
|
-
|
|
1830
|
+
// #1641: a demoted past-EOF entry no longer has a trustworthy line —
|
|
1831
|
+
// show the marker instead of a coordinate that doesn't exist in the
|
|
1832
|
+
// current file, matching the #1622/#1627 stale-line render convention.
|
|
1833
|
+
// Past-EOF demotions replace the untrustworthy coordinate; other
|
|
1834
|
+
// demotions (#1631 drift) keep their in-bounds line and append the
|
|
1835
|
+
// shared stale marker instead.
|
|
1836
|
+
const pastEof = d.stale && (d.staleReason ?? "past-eof") === "past-eof";
|
|
1837
|
+
const loc = pastEof ? PAST_EOF_STALE_MARKER : `L${d.line ?? "?"}`;
|
|
1838
|
+
const staleTag = d.stale && !pastEof ? ` ${STALE_LINE_MARKER}` : "";
|
|
1839
|
+
lines.push(` ${marker}${loc}: ${msg}${tag}${flaggedTag}${staleTag}`);
|
|
1436
1840
|
}
|
|
1437
1841
|
if (matching.length > shown.length) {
|
|
1438
1842
|
lines.push(` … ${matching.length - shown.length} more in this file (showing ${shown.length} of ${matching.length})`);
|
|
@@ -1441,12 +1845,20 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
|
|
|
1441
1845
|
totalErrors += s.errors;
|
|
1442
1846
|
totalWarnings += s.warnings;
|
|
1443
1847
|
}
|
|
1848
|
+
// #1799: `totalBlocking` and `totalErrors` count the same findings UNLESS a
|
|
1849
|
+
// dependency-drift demotion (#1631) has revoked blocking authority from an
|
|
1850
|
+
// error while leaving it in the error tally (widget-state.ts `isBlocking`
|
|
1851
|
+
// vs `countDiagnostics`) — that's a real, live disagreement between the two
|
|
1852
|
+
// totals, not double-counting. So `errors` renders only when
|
|
1853
|
+
// `blocking === 0`, same guard as the per-file row above: it shows the
|
|
1854
|
+
// drift-demoted findings the blocking line can't, without ever printing the
|
|
1855
|
+
// same findings twice under two labels when blocking > 0.
|
|
1444
1856
|
const summary = [
|
|
1445
1857
|
`\nSummary (${visibleSummaries.length} files diagnosed this session):`,
|
|
1446
1858
|
totalBlocking > 0
|
|
1447
1859
|
? ` 🔴 ${totalBlocking} blocking error${totalBlocking === 1 ? "" : "s"}`
|
|
1448
1860
|
: null,
|
|
1449
|
-
totalErrors > 0
|
|
1861
|
+
totalBlocking === 0 && totalErrors > 0
|
|
1450
1862
|
? ` ${totalErrors} error${totalErrors === 1 ? "" : "s"}`
|
|
1451
1863
|
: null,
|
|
1452
1864
|
totalWarnings > 0
|