@herbertgao/pi-extensions 2026.8.6 → 2026.8.8
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 +8 -6
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -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 +79 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -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 +5 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
- 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 +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -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 +140 -97
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
- 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-plugins/fast-mode/dist/index.mjs +44 -1856
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
- package/node_modules/pi-lens/CHANGELOG.md +999 -0
- package/node_modules/pi-lens/README.md +117 -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 +381 -13
- 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 +129 -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 +206 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
- 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 +84 -14
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -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/detekt.js +9 -6
- 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/hadolint.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
- 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 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
- 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 +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
- 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 +188 -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 +21 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
- 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 +316 -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 +87 -8
- 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 +9 -2
- 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 +128 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
- 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 +2423 -151
- 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 +22 -1
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
- 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 +23 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
- 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 +8 -3
- package/node_modules/pi-lens/dist/clients/read-guard.js +253 -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/builder.js +194 -31
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
- 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 +352 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -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 +620 -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/session-event-guard.js +184 -0
- 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/source-filter.js +6 -2
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
- 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-logger.js +1 -0
- 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 +61407 -48577
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
- 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 +27 -10
- 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/scripts/install-selftest.mjs +99 -11
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -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 +37 -0
- package/node_modules/pi-web-access/README.md +32 -13
- 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/chrome-cookies.ts +86 -15
- 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/data-uri-sanitize.ts +406 -0
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +40 -5
- package/node_modules/pi-web-access/firecrawl.ts +18 -2
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/github-extract.ts +65 -36
- package/node_modules/pi-web-access/index.ts +60 -61
- package/node_modules/pi-web-access/openai-search.ts +17 -6
- 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/pdf-extract.ts +18 -5
- 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/ssrf-protection.ts +12 -2
- 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 +14 -14
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { demotePastEofDiagnostics, } from "./diagnostic-line-freshness.js";
|
|
4
5
|
import { visibleWidth } from "./deps/pi-tui.js";
|
|
5
6
|
import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
|
|
6
7
|
import { fitLine } from "./tui-fit.js";
|
|
7
8
|
import { WriteOrderingGuard } from "./write-ordering-guard.js";
|
|
9
|
+
import { collectForwardImportMtimes, MTIME_DRIFT_TOLERANCE_MS, } from "./blocker-freshness.js";
|
|
10
|
+
import { PAST_EOF_STALE_MARKER } from "./diagnostic-line-freshness.js";
|
|
11
|
+
import { STALE_LINE_MARKER } from "./stale-marker.js";
|
|
8
12
|
/**
|
|
9
13
|
* Canonical key for the `files` map (and `diagnosticsWriteGuard`) — #1020.
|
|
10
14
|
*
|
|
@@ -34,8 +38,17 @@ function fileMapKey(filePath) {
|
|
|
34
38
|
* (`semantic === "blocking"`). Falls back to severity for sources that
|
|
35
39
|
* don't set `semantic` (raw tsc/eslint diagnostics) so the red dot still
|
|
36
40
|
* fires on traditional compile errors.
|
|
41
|
+
*
|
|
42
|
+
* #1561: exported so the blocker-retire decision in `tools/lsp-diagnostics.ts`
|
|
43
|
+
* asks THIS predicate rather than growing a second severity rule that can drift
|
|
44
|
+
* from what the footer counts.
|
|
37
45
|
*/
|
|
38
|
-
function isBlocking(d) {
|
|
46
|
+
export function isBlocking(d) {
|
|
47
|
+
// #1631: a dependency-drift-demoted finding is no longer a hard stop. The gate
|
|
48
|
+
// sets `stale` rather than dropping the entry (#1419 demote-not-drop), so every
|
|
49
|
+
// tally and render that asks "is this blocking?" must answer no once demoted.
|
|
50
|
+
if (d.stale)
|
|
51
|
+
return false;
|
|
39
52
|
if (d.semantic === "blocking")
|
|
40
53
|
return true;
|
|
41
54
|
if (d.semantic == null && d.severity === "error")
|
|
@@ -111,7 +124,17 @@ export function clearWidgetState() {
|
|
|
111
124
|
// migrates a v1 record by inheriting each entry's `observedAt` from the record's
|
|
112
125
|
// `touchedAt`. A v1 file must never be rejected (that would silently drop resume
|
|
113
126
|
// diagnostics) nor crash.
|
|
114
|
-
|
|
127
|
+
//
|
|
128
|
+
// v2 → v3 (#1631 review F3): a dependency-drift `stale` demotion is a verdict
|
|
129
|
+
// about THIS session's disk state — the next session starts with a fresh (and
|
|
130
|
+
// possibly different) dependency graph, so a demotion from the last session
|
|
131
|
+
// must not outlive it. `exportWidgetState` had been serializing `stale: true`
|
|
132
|
+
// verbatim; on restore, a genuine type error stayed non-blocking across a
|
|
133
|
+
// resume with the dependency pinned back into the past. Same shape as the
|
|
134
|
+
// #1348 failed-formatter precedent one field over: `importWidgetState` strips
|
|
135
|
+
// `stale` on every restored entry so a resumed session re-evaluates from
|
|
136
|
+
// scratch instead of inheriting a possibly-stale-itself verdict.
|
|
137
|
+
export const WIDGET_STATE_VERSION = 3;
|
|
115
138
|
/**
|
|
116
139
|
* Snapshot the per-file widget diagnostics for persistence (#190). Excludes
|
|
117
140
|
* `lspServers` — those are process-bound (servers re-spawn fresh on the next
|
|
@@ -143,9 +166,19 @@ export function exportWidgetState() {
|
|
|
143
166
|
* with the record's `touchedAt` (the single stamp the whole record shared under
|
|
144
167
|
* v1). Non-mutating; entries that already carry a stamp (a v2 record) pass
|
|
145
168
|
* through untouched.
|
|
169
|
+
*
|
|
170
|
+
* #1631 review F3 (v2→v3): also strips `stale` from every entry. A dependency
|
|
171
|
+
* drift demotion is scoped to the session that observed the drift; restoring it
|
|
172
|
+
* verbatim would let a real blocking finding resume as permanently non-blocking
|
|
173
|
+
* (#1348 precedent — see `WIDGET_STATE_VERSION`'s doc comment).
|
|
146
174
|
*/
|
|
147
175
|
function migrateEntryStamps(entries, recordTouchedAt) {
|
|
148
|
-
return (entries ?? []).map((d) =>
|
|
176
|
+
return (entries ?? []).map((d) => {
|
|
177
|
+
const { stale: _stale, ...rest } = d;
|
|
178
|
+
return rest.observedAt == null
|
|
179
|
+
? { ...rest, observedAt: recordTouchedAt }
|
|
180
|
+
: rest;
|
|
181
|
+
});
|
|
149
182
|
}
|
|
150
183
|
export function importWidgetState(state) {
|
|
151
184
|
// Accept any known-or-older version and migrate (#1186): reject a missing
|
|
@@ -180,6 +213,19 @@ export function importWidgetState(state) {
|
|
|
180
213
|
// gate has a concrete observation time and never treats `undefined` as
|
|
181
214
|
// epoch-0 (which would drop every migrated entry on the first sweep).
|
|
182
215
|
const recordTouchedAt = f.touchedAt ?? Date.now();
|
|
216
|
+
// #1631 review V1: `diagnosticCounts` is DERIVED from `allDiagnostics`
|
|
217
|
+
// everywhere else in this module (see the `countDiagnostics(rec.allDiagnostics)`
|
|
218
|
+
// call sites) — it must be recomputed here too, from the just-migrated
|
|
219
|
+
// (stale-stripped) entries, not restored verbatim from the snapshot. The
|
|
220
|
+
// persisted counts were computed while a finding was still demoted
|
|
221
|
+
// (`blocking: 0`); once F3 strips `stale` so `isBlocking` reports true
|
|
222
|
+
// again, a verbatim count would still say `blocking: 0` for a record
|
|
223
|
+
// whose entries ARE blocking — inverting the one-predicate invariant
|
|
224
|
+
// `isBlocking` exists to hold. Every consumer (`getFileDiagnosticSummaries`,
|
|
225
|
+
// the record-tier classifier, the footer) trusts `diagnosticCounts`, not a
|
|
226
|
+
// live re-scan of the entries, so a derived count out of sync with its own
|
|
227
|
+
// entries is silently wrong everywhere at once.
|
|
228
|
+
const migratedAllDiagnostics = migrateEntryStamps(f.allDiagnostics, recordTouchedAt);
|
|
183
229
|
files.set(fileMapKey(f.filePath), {
|
|
184
230
|
filePath: f.filePath,
|
|
185
231
|
runners: new Map(f.runners ?? []),
|
|
@@ -190,12 +236,8 @@ export function importWidgetState(state) {
|
|
|
190
236
|
// clear it). Successes rehydrate as before.
|
|
191
237
|
formatters: new Map((f.formatters ?? []).filter(([, outcome]) => outcome?.success !== false)),
|
|
192
238
|
diagnostics: migrateEntryStamps(f.diagnostics, recordTouchedAt),
|
|
193
|
-
allDiagnostics:
|
|
194
|
-
diagnosticCounts:
|
|
195
|
-
blocking: 0,
|
|
196
|
-
errors: 0,
|
|
197
|
-
warnings: 0,
|
|
198
|
-
},
|
|
239
|
+
allDiagnostics: migratedAllDiagnostics,
|
|
240
|
+
diagnosticCounts: countDiagnostics(migratedAllDiagnostics),
|
|
199
241
|
hasFinalDiagnosticsSnapshot: f.hasFinalDiagnosticsSnapshot ?? false,
|
|
200
242
|
touchedAt: recordTouchedAt,
|
|
201
243
|
});
|
|
@@ -262,6 +304,13 @@ export function recordRunner(filePath, runnerId, status, diagnosticCount, durati
|
|
|
262
304
|
function toSingleLineMessage(message) {
|
|
263
305
|
return (message ?? "").replace(/\s+/g, " ").trim();
|
|
264
306
|
}
|
|
307
|
+
/** Build the OSC-8 target used by every stored diagnostic row. */
|
|
308
|
+
export function widgetDiagnosticUri(filePath, line, column) {
|
|
309
|
+
const base = pathToFileURL(filePath).href;
|
|
310
|
+
return line != null
|
|
311
|
+
? `${base}#L${line}${column != null ? `:${column}` : ""}`
|
|
312
|
+
: base;
|
|
313
|
+
}
|
|
265
314
|
export function recordDiagnostics(filePath, diagnostics, writeIndex,
|
|
266
315
|
// #1093: when the truth was OBSERVED, not when it's being written. Defaults
|
|
267
316
|
// to `Date.now()` for the per-edit/live path (observed now). A reconcile
|
|
@@ -298,12 +347,8 @@ observedAt) {
|
|
|
298
347
|
* of diagnostics was observed — so the per-entry stale gate can later drop just
|
|
299
348
|
* the entries older than the file's mtime rather than the whole record. */
|
|
300
349
|
function normalizeDiagnostics(filePath, diagnostics, observedTs) {
|
|
301
|
-
const base = pathToFileURL(filePath).href;
|
|
302
350
|
return diagnostics.map((d) => {
|
|
303
351
|
const rule = d.rule ?? d.id;
|
|
304
|
-
const uri = d.line != null
|
|
305
|
-
? `${base}#L${d.line}${d.column != null ? `:${d.column}` : ""}`
|
|
306
|
-
: base;
|
|
307
352
|
return {
|
|
308
353
|
severity: d.severity ?? "info",
|
|
309
354
|
semantic: d.semantic,
|
|
@@ -312,7 +357,7 @@ function normalizeDiagnostics(filePath, diagnostics, observedTs) {
|
|
|
312
357
|
col: d.column,
|
|
313
358
|
rule,
|
|
314
359
|
tool: d.tool,
|
|
315
|
-
uri,
|
|
360
|
+
uri: widgetDiagnosticUri(filePath, d.line, d.column),
|
|
316
361
|
observedAt: observedTs,
|
|
317
362
|
};
|
|
318
363
|
});
|
|
@@ -344,13 +389,69 @@ function countDiagnostics(diags) {
|
|
|
344
389
|
for (const diagnostic of diags) {
|
|
345
390
|
if (isBlocking(diagnostic))
|
|
346
391
|
blocking++;
|
|
392
|
+
// A past-EOF stale entry keeps its severity for display purposes but is
|
|
393
|
+
// excluded from the error/warning tallies alongside blocking — its cited
|
|
394
|
+
// coordinate is no longer trustworthy, same reasoning as `isBlocking`.
|
|
395
|
+
// A dependency-drift demotion (#1631 criterion 3) is different: the
|
|
396
|
+
// finding itself is still real evidence, only its BLOCKING authority is
|
|
397
|
+
// revoked until re-confirmed, so it stays in the error/warning tally.
|
|
398
|
+
if (diagnostic.stale && (diagnostic.staleReason ?? "past-eof") === "past-eof")
|
|
399
|
+
continue;
|
|
347
400
|
if (diagnostic.severity === "error")
|
|
348
401
|
errors++;
|
|
349
|
-
|
|
402
|
+
// #1777: `hint` and `info` tally alongside `warning`. The dispatch path
|
|
403
|
+
// now preserves all four ast-grep tiers; before, the runner collapsed
|
|
404
|
+
// them to "warning" here. An exact `=== "warning"` test would drop those
|
|
405
|
+
// findings out of the footer entirely, so a file with real findings would
|
|
406
|
+
// render clean. The footer stays a blocking/error/warning summary on
|
|
407
|
+
// purpose — the tier distinction is rendered by the code-quality-warnings
|
|
408
|
+
// advisory, not by these counters.
|
|
409
|
+
else if (diagnostic.severity === "warning" ||
|
|
410
|
+
diagnostic.severity === "hint" ||
|
|
411
|
+
diagnostic.severity === "info")
|
|
350
412
|
warnings++;
|
|
351
413
|
}
|
|
352
414
|
return { blocking, errors, warnings };
|
|
353
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Apply the #1641 past-EOF gate to `rec` in place: demote (never drop) any
|
|
418
|
+
* stored diagnostic whose cited line exceeds the file's CURRENT on-disk line
|
|
419
|
+
* count, then recompute the capped/full diagnostic lists and the counts so
|
|
420
|
+
* every reader (the TUI render loop, `getFileDiagnosticSummaries`,
|
|
421
|
+
* `getFileDiagnostics`) sees one consistent, already-gated record instead of
|
|
422
|
+
* each re-deriving its own verdict. Cheap on the common case: one memoized
|
|
423
|
+
* stat per record (see `getCachedLineCount`), a full recount only when the
|
|
424
|
+
* file's mtime moved since the last check.
|
|
425
|
+
*
|
|
426
|
+
* No `resync` callback here deliberately: this gate runs on the TUI's render
|
|
427
|
+
* path and the bus-publish/mark-tool read accessors, all of which fire far
|
|
428
|
+
* more often than an agent's own edit/tool cadence. Triggering a document
|
|
429
|
+
* resync from every one of those reads would storm the LSP with didOpen calls
|
|
430
|
+
* for a file whose drift hasn't yet been fixed. `tools/lens-diagnostics.ts`'s
|
|
431
|
+
* `formatAllMode` — an explicit, agent-invoked tool call — is the resync
|
|
432
|
+
* trigger point (#1641 criterion 2); this gate still demotes/logs on its own.
|
|
433
|
+
*
|
|
434
|
+
* The gate RE-DERIVES every entry's `stale` flag on every call — it is never
|
|
435
|
+
* a one-way latch. A transient shrink that later restores (truncate-then-
|
|
436
|
+
* write, a formatter pass, a checkout) un-demotes on its own next read, so
|
|
437
|
+
* this always persists the freshly-derived array back into the record, not
|
|
438
|
+
* only when `demotedCount` (which counts RISING edges only, for telemetry)
|
|
439
|
+
* is nonzero.
|
|
440
|
+
*/
|
|
441
|
+
function applyPastEofGate(rec, lineCountCache) {
|
|
442
|
+
if (rec.allDiagnostics.length === 0)
|
|
443
|
+
return;
|
|
444
|
+
const { diagnostics } = demotePastEofDiagnostics({
|
|
445
|
+
store: "widget-state",
|
|
446
|
+
cwd: process.cwd(),
|
|
447
|
+
filePath: rec.filePath,
|
|
448
|
+
diagnostics: rec.allDiagnostics,
|
|
449
|
+
lineCountCache,
|
|
450
|
+
});
|
|
451
|
+
rec.allDiagnostics = diagnostics;
|
|
452
|
+
rec.diagnostics = capStoredDiagnostics(diagnostics);
|
|
453
|
+
rec.diagnosticCounts = countDiagnostics(diagnostics);
|
|
454
|
+
}
|
|
354
455
|
/** The newest per-entry `observedAt` in `diags`, or `fallback` when empty (or no
|
|
355
456
|
* entry carries a stamp). Used as the record-level `touchedAt` (#1186). */
|
|
356
457
|
function freshestObservation(diags, fallback) {
|
|
@@ -453,6 +554,29 @@ export function reconcileScanDiagnostics(filePath, diagnostics, confirmed, write
|
|
|
453
554
|
return;
|
|
454
555
|
recordDiagnostics(filePath, diagnostics, writeIndex, observedAt);
|
|
455
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Commit the already-correlated result of a full diagnostics scan.
|
|
559
|
+
*
|
|
560
|
+
* Unlike {@link reconcileScanDiagnostics}, this seam accepts widget diagnostics
|
|
561
|
+
* that have already been merged across producing lanes. `lens_diagnostics`
|
|
562
|
+
* uses it after correlating the confirmed LSP sweep, the cheap project scan,
|
|
563
|
+
* and retained widget rows. This prevents a broken LSP lane from hiding
|
|
564
|
+
* independently-produced `ast-grep-napi` findings in the widget count (#1888).
|
|
565
|
+
* Existing per-entry observation times survive the merge; newly-added project
|
|
566
|
+
* rows inherit the scan's observation time.
|
|
567
|
+
*/
|
|
568
|
+
export function reconcileCorrelatedScanDiagnostics(filePath, diagnostics, writeIndex, observedAt) {
|
|
569
|
+
const key = fileMapKey(filePath);
|
|
570
|
+
if (!diagnosticsWriteGuard.shouldWrite(key, writeIndex))
|
|
571
|
+
return;
|
|
572
|
+
runnerWriteGuard.shouldWrite(key, writeIndex);
|
|
573
|
+
const observedTs = observedAt ?? Date.now();
|
|
574
|
+
const correlated = diagnostics.map((diagnostic) => ({
|
|
575
|
+
...diagnostic,
|
|
576
|
+
observedAt: diagnostic.observedAt ?? observedTs,
|
|
577
|
+
}));
|
|
578
|
+
commitDiagnostics(getOrCreate(filePath, key), filePath, correlated, observedTs, key);
|
|
579
|
+
}
|
|
456
580
|
/**
|
|
457
581
|
* Drop widget entries whose file changed on disk after pi-lens last recorded
|
|
458
582
|
* them (`mtimeMs > touchedAt` → the recorded diagnostics predate the current
|
|
@@ -479,8 +603,18 @@ export async function reconcileStaleWidgetFiles() {
|
|
|
479
603
|
// A clean record (no findings) has no per-entry stamps to consult —
|
|
480
604
|
// gate it on the record's own `touchedAt` exactly as before, so a ✓
|
|
481
605
|
// entry for a file that changed on disk still drops.
|
|
606
|
+
//
|
|
607
|
+
// #1631 review F11 follow-up: a THIRD site hitting the same Windows
|
|
608
|
+
// mtime-vs-`Date.now()` skew F2 named for `blocker-freshness.ts` and
|
|
609
|
+
// `reconcileStaleWidgetDependencyBlockers` — a file's mtime can lead
|
|
610
|
+
// `Date.now()` by up to ~11.4ms measured (#1491/#1498 precedent), so a
|
|
611
|
+
// record touched immediately after its own write dropped here at +1ms
|
|
612
|
+
// with zero real drift. Found investigating F11: `lens-diagnostics-
|
|
613
|
+
// mode-all-freshness.test.ts`'s "control" case (and its sibling) failed
|
|
614
|
+
// under this gate, not from cross-test state. Same tolerance, same
|
|
615
|
+
// constant, for the same reason.
|
|
482
616
|
if (rec.allDiagnostics.length === 0) {
|
|
483
|
-
return mtimeMs > rec.touchedAt +
|
|
617
|
+
return mtimeMs > rec.touchedAt + MTIME_DRIFT_TOLERANCE_MS
|
|
484
618
|
? { mapKey, action: "drop" }
|
|
485
619
|
: { mapKey, action: "keep" };
|
|
486
620
|
}
|
|
@@ -489,9 +623,9 @@ export async function reconcileStaleWidgetFiles() {
|
|
|
489
623
|
// preserved entry beside an entry replayed from an aging snapshot, so a
|
|
490
624
|
// per-RECORD gate over-cleared the whole record (the residual documented
|
|
491
625
|
// at dispatch/integration.ts). A missing per-entry stamp (a migrated
|
|
492
|
-
// pre-#1186 record) inherits the record's `touchedAt`.
|
|
493
|
-
//
|
|
494
|
-
const survivors = rec.allDiagnostics.filter((d) => !(mtimeMs > (d.observedAt ?? rec.touchedAt) +
|
|
626
|
+
// pre-#1186 record) inherits the record's `touchedAt`. Tolerance matches
|
|
627
|
+
// the Windows host-clock skew rationale above.
|
|
628
|
+
const survivors = rec.allDiagnostics.filter((d) => !(mtimeMs > (d.observedAt ?? rec.touchedAt) + MTIME_DRIFT_TOLERANCE_MS));
|
|
495
629
|
if (survivors.length === rec.allDiagnostics.length) {
|
|
496
630
|
return { mapKey, action: "keep" }; // nothing stale
|
|
497
631
|
}
|
|
@@ -524,6 +658,140 @@ export async function reconcileStaleWidgetFiles() {
|
|
|
524
658
|
requestRenderFn?.();
|
|
525
659
|
return dropped;
|
|
526
660
|
}
|
|
661
|
+
export async function reconcileStaleWidgetDependencyBlockers(cwd, turnIndex) {
|
|
662
|
+
let demoted = 0;
|
|
663
|
+
let truncatedImports = 0;
|
|
664
|
+
for (const [, rec] of files.entries()) {
|
|
665
|
+
// #1631 review F4: narrow demotion to LSP-sourced findings. The import-closure
|
|
666
|
+
// drift check only knows how to reason about IMPORT graphs — an ast-grep
|
|
667
|
+
// hardcoded-secret or a govulncheck CVE finding doesn't stop being true because
|
|
668
|
+
// a file this diagnostic's file imports changed; only a language-server verdict
|
|
669
|
+
// (`tool === "lsp"`, the same predicate `isLspErrorEntry` above uses) is actually
|
|
670
|
+
// invalidated by that shape of drift. Documented trade: a non-LSP blocking
|
|
671
|
+
// finding on a file with drifted imports stays fully authoritative until its own
|
|
672
|
+
// content changes or it is explicitly re-run.
|
|
673
|
+
if (!rec.allDiagnostics.some((d) => isBlocking(d) && d.tool === "lsp"))
|
|
674
|
+
continue;
|
|
675
|
+
let importMtimes = [];
|
|
676
|
+
try {
|
|
677
|
+
const result = await collectForwardImportMtimes(cwd, rec.filePath, undefined, turnIndex);
|
|
678
|
+
importMtimes = result.mtimes;
|
|
679
|
+
if (result.truncated)
|
|
680
|
+
truncatedImports += 1;
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
importMtimes = [];
|
|
684
|
+
}
|
|
685
|
+
if (importMtimes.length === 0)
|
|
686
|
+
continue;
|
|
687
|
+
let changed = false;
|
|
688
|
+
for (const d of rec.allDiagnostics) {
|
|
689
|
+
if (!isBlocking(d) || d.tool !== "lsp")
|
|
690
|
+
continue;
|
|
691
|
+
const baseline = d.observedAt ?? rec.touchedAt;
|
|
692
|
+
// +50ms tolerance (#1631 review F2): a whole-millisecond `Date.now()`
|
|
693
|
+
// baseline vs. sub-millisecond mtime precision only needs +1ms, but on
|
|
694
|
+
// Windows a file's mtime can LEAD `Date.now()` by up to ~11.4ms (measured
|
|
695
|
+
// across 200 writes; #1491/#1498 precedent for the same host-clock skew).
|
|
696
|
+
// +1ms produced 42 false demotions in 50 runs on Windows; +50ms clears the
|
|
697
|
+
// measured skew while staying far below the gap between real edits.
|
|
698
|
+
if (importMtimes.some((im) => im.mtimeMs > baseline + MTIME_DRIFT_TOLERANCE_MS)) {
|
|
699
|
+
d.stale = true;
|
|
700
|
+
d.staleReason = "dependency-drift";
|
|
701
|
+
changed = true;
|
|
702
|
+
demoted += 1;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (changed) {
|
|
706
|
+
rec.diagnosticCounts = countDiagnostics(rec.allDiagnostics);
|
|
707
|
+
rec.diagnostics = capStoredDiagnostics(rec.allDiagnostics);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (demoted > 0)
|
|
711
|
+
requestRenderFn?.();
|
|
712
|
+
return { demoted, truncatedImports };
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* #1790: sweep-population feed for `sweepInlineBlockerFreshness` (the turn-end
|
|
716
|
+
* gate in `clients/blocker-freshness.ts`). That sweep's population was built
|
|
717
|
+
* solely from `RuntimeCoordinator`'s inline-blocker map — a live-dispatch-only
|
|
718
|
+
* store. A workspace-diagnostics CACHE HIT never touches that map; it calls
|
|
719
|
+
* `reconcileScanDiagnostics` straight into `files` here (the cache-serve branch
|
|
720
|
+
* in `tools/lsp-diagnostics.ts`), so a cache-served blocking row could render in
|
|
721
|
+
* the widget while the turn-end sweep's `total` count never saw it (the live
|
|
722
|
+
* 2026-08-20 dogfood: `total:1 kept:1` against five stale cache-served blocking
|
|
723
|
+
* rows on screen).
|
|
724
|
+
*
|
|
725
|
+
* One entry per file with at least one CURRENTLY blocking (non-stale),
|
|
726
|
+
* LSP-sourced diagnostic — the same provenance narrowing
|
|
727
|
+
* `reconcileStaleWidgetDependencyBlockers` already applies, so every row this
|
|
728
|
+
* feeds into the sweep is one its `isLspSourced` gate would keep anyway.
|
|
729
|
+
* `recordedAtMs` is the EARLIEST `observedAt` among them: the conservative
|
|
730
|
+
* baseline, since using the latest could hide drift that predates an earlier
|
|
731
|
+
* diagnostic's own observation.
|
|
732
|
+
*/
|
|
733
|
+
export function getWidgetBlockingFilesForSweep() {
|
|
734
|
+
const out = [];
|
|
735
|
+
for (const rec of files.values()) {
|
|
736
|
+
let earliest;
|
|
737
|
+
for (const d of rec.allDiagnostics) {
|
|
738
|
+
if (!isBlocking(d) || d.tool !== "lsp")
|
|
739
|
+
continue;
|
|
740
|
+
const observedAt = d.observedAt ?? rec.touchedAt;
|
|
741
|
+
if (earliest === undefined || observedAt < earliest)
|
|
742
|
+
earliest = observedAt;
|
|
743
|
+
}
|
|
744
|
+
if (earliest !== undefined)
|
|
745
|
+
out.push({ filePath: rec.filePath, recordedAtMs: earliest });
|
|
746
|
+
}
|
|
747
|
+
return out;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* #1790: demote every currently-blocking, LSP-sourced diagnostic for `filePath`
|
|
751
|
+
* to stale (#1419 demote-not-drop). Called by the turn-end sweep once its own
|
|
752
|
+
* drift check (`detectDrift`/`collectForwardImportMtimes`, shared with the
|
|
753
|
+
* inline-blocker branch) independently confirms drift for a file that reached
|
|
754
|
+
* the sweep's population through `getWidgetBlockingFilesForSweep` rather than an
|
|
755
|
+
* inline blocker. Same store and same write `reconcileStaleWidgetDependencyBlockers`
|
|
756
|
+
* already performs per-diagnostic — this is the single-file entry point the
|
|
757
|
+
* sweep drives once ITS check (not a second implementation of the check) says
|
|
758
|
+
* to. Returns true iff something changed.
|
|
759
|
+
*
|
|
760
|
+
* #1790 review F4 (latent): unlike `reconcileStaleWidgetDependencyBlockers`, which
|
|
761
|
+
* checks drift per-diagnostic against EACH diagnostic's own `observedAt`, this
|
|
762
|
+
* demotes every currently-blocking LSP diagnostic for the file against the ONE
|
|
763
|
+
* file-level baseline `getWidgetBlockingFilesForSweep` computed (the earliest
|
|
764
|
+
* `observedAt` among them) — because the sweep's `detectDrift` contract is
|
|
765
|
+
* file-level, not per-diagnostic. That is safe ONLY as long as every LSP
|
|
766
|
+
* diagnostic on a `FileRecord` shares (or is more conservative than) that
|
|
767
|
+
* earliest stamp, which holds today because every writer that populates
|
|
768
|
+
* `allDiagnostics` (`recordDiagnostics`, `reconcileCascadeNeighborLspErrors`)
|
|
769
|
+
* replaces the file's LSP errors wholesale with one `observedAt` per write. If a
|
|
770
|
+
* future writer ever merges LSP diagnostics with genuinely MIXED per-entry
|
|
771
|
+
* stamps on one record, this function would over-demote entries observed after
|
|
772
|
+
* the earliest one drifted — re-derive per-diagnostic (mirroring
|
|
773
|
+
* `reconcileStaleWidgetDependencyBlockers`) rather than assume the single
|
|
774
|
+
* baseline still holds.
|
|
775
|
+
*/
|
|
776
|
+
export function markWidgetFileBlockersStale(filePath, reason) {
|
|
777
|
+
const rec = files.get(fileMapKey(filePath));
|
|
778
|
+
if (!rec)
|
|
779
|
+
return false;
|
|
780
|
+
let changed = false;
|
|
781
|
+
for (const d of rec.allDiagnostics) {
|
|
782
|
+
if (!isBlocking(d) || d.tool !== "lsp")
|
|
783
|
+
continue;
|
|
784
|
+
d.stale = true;
|
|
785
|
+
d.staleReason = reason;
|
|
786
|
+
changed = true;
|
|
787
|
+
}
|
|
788
|
+
if (changed) {
|
|
789
|
+
rec.diagnosticCounts = countDiagnostics(rec.allDiagnostics);
|
|
790
|
+
rec.diagnostics = capStoredDiagnostics(rec.allDiagnostics);
|
|
791
|
+
requestRenderFn?.();
|
|
792
|
+
}
|
|
793
|
+
return changed;
|
|
794
|
+
}
|
|
527
795
|
/**
|
|
528
796
|
* Keep the TUI honest (#298 follow-up). `reconcileStaleWidgetFiles` drops
|
|
529
797
|
* widget entries whose file changed on disk after they were last recorded
|
|
@@ -553,14 +821,17 @@ export function scheduleStaleReconcile() {
|
|
|
553
821
|
* everything, not just the 12 the TUI keeps for rendering.
|
|
554
822
|
*/
|
|
555
823
|
export function getFileDiagnosticSummaries() {
|
|
556
|
-
return [...files.values()].map((rec) =>
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
824
|
+
return [...files.values()].map((rec) => {
|
|
825
|
+
applyPastEofGate(rec);
|
|
826
|
+
return {
|
|
827
|
+
filePath: rec.filePath,
|
|
828
|
+
blocking: rec.diagnosticCounts.blocking,
|
|
829
|
+
errors: rec.diagnosticCounts.errors,
|
|
830
|
+
warnings: rec.diagnosticCounts.warnings,
|
|
831
|
+
hasFinalSnapshot: rec.hasFinalDiagnosticsSnapshot,
|
|
832
|
+
diagnostics: rec.allDiagnostics.map((d) => ({ ...d })),
|
|
833
|
+
};
|
|
834
|
+
});
|
|
564
835
|
}
|
|
565
836
|
/**
|
|
566
837
|
* Return the current FULL (uncapped) diagnostic set for a single file, as
|
|
@@ -584,6 +855,7 @@ export function getFileDiagnostics(filePath) {
|
|
|
584
855
|
const rec = files.get(fileMapKey(filePath));
|
|
585
856
|
if (!rec)
|
|
586
857
|
return undefined;
|
|
858
|
+
applyPastEofGate(rec);
|
|
587
859
|
return rec.allDiagnostics.map((d) => ({ ...d }));
|
|
588
860
|
}
|
|
589
861
|
/** @internal Test-only helpers. Do not use in production code. */
|
|
@@ -666,24 +938,52 @@ export function renderWidget(width, theme) {
|
|
|
666
938
|
lines.push(fitLine(formatFileRowVertical(rec, theme), w));
|
|
667
939
|
}
|
|
668
940
|
}
|
|
669
|
-
// Diagnostics — blocking only, from the most
|
|
670
|
-
// Vertical mode keeps the divider/filename
|
|
671
|
-
// filename on the packed row above, so
|
|
672
|
-
|
|
941
|
+
// Diagnostics — blocking (or dependency-drift-demoted) only, from the most
|
|
942
|
+
// recently touched file that has them. Vertical mode keeps the divider/filename
|
|
943
|
+
// context; horizontal already shows the filename on the packed row above, so
|
|
944
|
+
// we drop the extra header noise there.
|
|
945
|
+
//
|
|
946
|
+
// #1631 review F10: `isBlocking` answers false for a demoted (`stale`) finding
|
|
947
|
+
// by design (#1419 demote-not-drop) — but this render loop used that same
|
|
948
|
+
// predicate to pick which findings to SHOW, so a demoted finding vanished from
|
|
949
|
+
// the footer entirely instead of demoting visibly. `isBlockingOrDemoted` keeps
|
|
950
|
+
// it in the list; the per-entry render below distinguishes the two with a
|
|
951
|
+
// dimmed marker instead of the red dot.
|
|
952
|
+
const isBlockingOrDemoted = (d) => isBlocking(d) || d.stale === true;
|
|
953
|
+
const withBlocking = recencySorted.filter((r) => r.diagnostics.some(isBlockingOrDemoted));
|
|
673
954
|
if (withBlocking.length > 0) {
|
|
674
955
|
const rec = withBlocking[0];
|
|
956
|
+
// #1641: gate only the ONE record whose line numbers are about to be
|
|
957
|
+
// rendered, not the whole (potentially session-long) `deduped` list the
|
|
958
|
+
// header counts read — one memoized stat per redraw, never a per-file
|
|
959
|
+
// scan of every file pi-lens has touched this session. A past-EOF
|
|
960
|
+
// citation demoted here can leave the header's aggregate blocking count
|
|
961
|
+
// one turn stale; `reconcileStaleWidgetFiles`'s existing debounced sweep
|
|
962
|
+
// (scheduleStaleReconcile) already re-derives that aggregate on its own
|
|
963
|
+
// cadence, so this is a bounded, self-correcting gap, not a silent one.
|
|
964
|
+
applyPastEofGate(rec);
|
|
675
965
|
if (!useHorizontal) {
|
|
676
966
|
lines.push(fitLine(dim("─".repeat(Math.min(w, 60))), w));
|
|
677
967
|
lines.push(fitLine(` ${dim(path.basename(rec.filePath))}`, w));
|
|
678
968
|
}
|
|
679
|
-
const blockers = rec.diagnostics.filter(
|
|
969
|
+
const blockers = rec.diagnostics.filter(isBlockingOrDemoted).slice(0, 5);
|
|
680
970
|
for (const d of blockers) {
|
|
681
|
-
|
|
971
|
+
// A past-EOF demotion's coordinate is untrustworthy — render the
|
|
972
|
+
// marker instead of the line (#1641); drift demotions keep theirs.
|
|
973
|
+
const pastEof = d.stale && (d.staleReason ?? "past-eof") === "past-eof";
|
|
974
|
+
// No link for a past-EOF demotion: the anchor would carry the same
|
|
975
|
+
// untrustworthy line the marker exists to replace.
|
|
976
|
+
const loc = pastEof
|
|
977
|
+
? PAST_EOF_STALE_MARKER
|
|
978
|
+
: d.line != null
|
|
979
|
+
? osc8(d.uri ?? "", `L${d.line}`)
|
|
980
|
+
: "";
|
|
682
981
|
const rule = d.rule ? dim(` ${d.rule}`) : "";
|
|
683
|
-
const
|
|
684
|
-
const
|
|
982
|
+
const staleTag = d.stale ? dim(` ${STALE_LINE_MARKER}`) : "";
|
|
983
|
+
const prefix = ` ${d.stale ? dim("○") : red("●")} ${loc}${rule} `;
|
|
984
|
+
const msgWidth = Math.max(1, w - visibleWidth(prefix) - visibleWidth(staleTag));
|
|
685
985
|
const msg = fitLine(d.message, msgWidth, "…");
|
|
686
|
-
lines.push(fitLine(`${prefix}${msg}`, w));
|
|
986
|
+
lines.push(fitLine(`${prefix}${msg}${staleTag}`, w));
|
|
687
987
|
}
|
|
688
988
|
}
|
|
689
989
|
// LSP status tail — only in vertical mode; horizontal folds into header
|