@herbertgao/pi-extensions 2026.8.5 → 2026.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +119 -103
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +51 -23
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -452,3 +452,163 @@ export function direntsHaveMarkerGlobMatch(entries, pattern) {
|
|
|
452
452
|
return entries.some((entry) => (entry.isFile() || entry.isSymbolicLink()) &&
|
|
453
453
|
nameMatchesMarkerGlob(entry.name, pattern));
|
|
454
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Narrow no-break space, U+202F. macOS writes it before AM/PM in screenshot
|
|
457
|
+
* file names; users type an ordinary space.
|
|
458
|
+
*/
|
|
459
|
+
const NARROW_NO_BREAK_SPACE = "\u202F";
|
|
460
|
+
/** Right single quotation mark, U+2019. macOS writes it; users type U+0027. */
|
|
461
|
+
const RIGHT_SINGLE_QUOTE = "\u2019";
|
|
462
|
+
/**
|
|
463
|
+
* The unicode space class pi folds to U+0020, copied character-for-character
|
|
464
|
+
* from `@earendil-works/pi-coding-agent/dist/utils/paths.js:6`
|
|
465
|
+
* (`UNICODE_SPACES`, source `src/utils/paths.ts`). Widening or narrowing this
|
|
466
|
+
* set makes pi-lens resolve a different file than pi does.
|
|
467
|
+
*/
|
|
468
|
+
const HOST_UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
469
|
+
/**
|
|
470
|
+
* Mirror pi's `normalizeWindowsShellPath`
|
|
471
|
+
* (`@earendil-works/pi-coding-agent/dist/utils/paths.js:47-56`): convert Git
|
|
472
|
+
* Bash, MSYS, Cygwin, and WSL drive paths to a form native Windows APIs
|
|
473
|
+
* accept.
|
|
474
|
+
*/
|
|
475
|
+
function hostNormalizeWindowsShellPath(filePath) {
|
|
476
|
+
if (!filePath.startsWith("/") ||
|
|
477
|
+
filePath.startsWith("//") ||
|
|
478
|
+
filePath.includes("\\")) {
|
|
479
|
+
return filePath;
|
|
480
|
+
}
|
|
481
|
+
const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
|
|
482
|
+
if (!match)
|
|
483
|
+
return filePath;
|
|
484
|
+
const suffix = match[2]?.replaceAll("/", "\\");
|
|
485
|
+
return `${match[1].toUpperCase()}:\\${suffix ?? ""}`;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Mirror pi's `normalizePath`
|
|
489
|
+
* (`@earendil-works/pi-coding-agent/dist/utils/paths.js:57-79`, source
|
|
490
|
+
* `src/utils/paths.ts`), in pi's order, which is load-bearing: the unicode
|
|
491
|
+
* fold runs BEFORE the `@` strip, and the tilde expansion before the
|
|
492
|
+
* `file://` conversion.
|
|
493
|
+
*
|
|
494
|
+
* The `win32` step is gated on `process.platform`, exactly as pi gates it.
|
|
495
|
+
* This is a deliberate exception to the usual probe-the-filesystem rule
|
|
496
|
+
* (AGENTS.md shape 2): pi-lens runs in pi's own process, so mirroring the
|
|
497
|
+
* host's own platform branch is what keeps the two resolvers in agreement.
|
|
498
|
+
* Shape-based parsing here would DIVERGE from the host, not protect against
|
|
499
|
+
* it.
|
|
500
|
+
*/
|
|
501
|
+
export function normalizeHostToolPath(input, options = {}) {
|
|
502
|
+
let normalized = input;
|
|
503
|
+
if (options.normalizeUnicodeSpaces) {
|
|
504
|
+
normalized = normalized.replace(HOST_UNICODE_SPACES, " ");
|
|
505
|
+
}
|
|
506
|
+
if (options.stripAtPrefix && normalized.startsWith("@")) {
|
|
507
|
+
normalized = normalized.slice(1);
|
|
508
|
+
}
|
|
509
|
+
if (process.platform === "win32") {
|
|
510
|
+
normalized = hostNormalizeWindowsShellPath(normalized);
|
|
511
|
+
}
|
|
512
|
+
// `homedir()` is resolved inside the branch, as pi does. This runs on every
|
|
513
|
+
// tool_call and a `~` path is the rare case.
|
|
514
|
+
if (normalized === "~")
|
|
515
|
+
return os.homedir();
|
|
516
|
+
if (normalized.startsWith("~/") ||
|
|
517
|
+
(process.platform === "win32" && normalized.startsWith("~\\"))) {
|
|
518
|
+
return path.join(os.homedir(), normalized.slice(2));
|
|
519
|
+
}
|
|
520
|
+
if (/^file:\/\//.test(normalized)) {
|
|
521
|
+
try {
|
|
522
|
+
return fileURLToPath(normalized);
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// pi lets a malformed file: URL throw out of normalizePath, but pi-lens
|
|
526
|
+
// is advisory instrumentation on the same event: a URL pi rejects must
|
|
527
|
+
// degrade to "no path" here, never take down the tool_call handler.
|
|
528
|
+
return normalized;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return normalized;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Mirror pi's `resolveToCwd`
|
|
535
|
+
* (`@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js:42-44`,
|
|
536
|
+
* source `src/core/tools/path-utils.ts:~44-46`) \u2014 the BASE resolution every
|
|
537
|
+
* read/edit/write path goes through before the variant ladder below ever
|
|
538
|
+
* runs.
|
|
539
|
+
*
|
|
540
|
+
* Two details are pi's, not ours, and both matter:
|
|
541
|
+
* - the input is normalized with `normalizeUnicodeSpaces` + `stripAtPrefix`;
|
|
542
|
+
* the BASE DIR is normalized with neither (`resolvePath`,
|
|
543
|
+
* `dist/utils/paths.js:80-84`).
|
|
544
|
+
* - an already-absolute input is re-resolved on its own, ignoring the cwd.
|
|
545
|
+
*/
|
|
546
|
+
export function resolveHostToolPath(input, baseDir) {
|
|
547
|
+
const normalized = normalizeHostToolPath(input, {
|
|
548
|
+
normalizeUnicodeSpaces: true,
|
|
549
|
+
stripAtPrefix: true,
|
|
550
|
+
});
|
|
551
|
+
const normalizedBaseDir = normalizeHostToolPath(baseDir);
|
|
552
|
+
return path.isAbsolute(normalized)
|
|
553
|
+
? path.resolve(normalized)
|
|
554
|
+
: path.resolve(normalizedBaseDir, normalized);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Mirror pi's read-path fallback ladder (#1655 item 5).
|
|
558
|
+
*
|
|
559
|
+
* pi does NOT open `resolve(cwd, input.path)`. `resolveReadPath`
|
|
560
|
+
* (`@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js:45-70`,
|
|
561
|
+
* source `src/core/tools/path-utils.ts:52-83`) resolves, and when that path
|
|
562
|
+
* does not exist it silently retries four unicode/spacing variants in this
|
|
563
|
+
* exact order:
|
|
564
|
+
*
|
|
565
|
+
* 1. narrow no-break space before `AM.`/`PM.` (`tryMacOSScreenshotPath`)
|
|
566
|
+
* 2. NFD normalization (`tryNFDVariant`) — macOS stores names decomposed
|
|
567
|
+
* 3. U+0027 → U+2019 (`tryCurlyQuoteVariant`)
|
|
568
|
+
* 4. NFD + curly quote combined
|
|
569
|
+
*
|
|
570
|
+
* Each candidate is used only when it DIFFERS from the resolved path and the
|
|
571
|
+
* file exists; otherwise pi falls back to the resolved path. So the file pi
|
|
572
|
+
* actually read can differ from what a naive `path.resolve` produces, and
|
|
573
|
+
* pi-lens keyed its read guard, LSP touch, and dispatch off the naive form —
|
|
574
|
+
* silently doing nothing for exactly those files.
|
|
575
|
+
*
|
|
576
|
+
* Order matters: it is pi's, so pi-lens picks the same file pi did when more
|
|
577
|
+
* than one variant happens to exist.
|
|
578
|
+
*
|
|
579
|
+
* @param resolvedPath an already-resolved absolute path (the naive form)
|
|
580
|
+
* @param fileExists injectable existence probe; defaults to `existsSync`
|
|
581
|
+
*/
|
|
582
|
+
export function resolveHostPathVariants(resolvedPath, fileExists = existsSync) {
|
|
583
|
+
if (fileExists(resolvedPath)) {
|
|
584
|
+
return { path: resolvedPath, unresolved: false, triedVariants: [] };
|
|
585
|
+
}
|
|
586
|
+
const nfd = resolvedPath.normalize("NFD");
|
|
587
|
+
const candidates = [
|
|
588
|
+
{
|
|
589
|
+
variant: "narrow-nbsp",
|
|
590
|
+
candidate: resolvedPath.replace(/ (AM|PM)\./gi, `${NARROW_NO_BREAK_SPACE}$1.`),
|
|
591
|
+
},
|
|
592
|
+
{ variant: "nfd", candidate: nfd },
|
|
593
|
+
{
|
|
594
|
+
variant: "curly-quote",
|
|
595
|
+
candidate: resolvedPath.replaceAll("'", RIGHT_SINGLE_QUOTE),
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
variant: "nfd-curly-quote",
|
|
599
|
+
candidate: nfd.replaceAll("'", RIGHT_SINGLE_QUOTE),
|
|
600
|
+
},
|
|
601
|
+
];
|
|
602
|
+
const triedVariants = [];
|
|
603
|
+
for (const { variant, candidate } of candidates) {
|
|
604
|
+
// pi skips a candidate identical to the resolved path, so pi-lens does
|
|
605
|
+
// too — otherwise a no-op "variant" would be reported as a match.
|
|
606
|
+
if (candidate === resolvedPath)
|
|
607
|
+
continue;
|
|
608
|
+
triedVariants.push(variant);
|
|
609
|
+
if (fileExists(candidate)) {
|
|
610
|
+
return { path: candidate, variant, unresolved: false, triedVariants };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return { path: resolvedPath, unresolved: true, triedVariants };
|
|
614
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as nodeFs from "node:fs";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
-
import { findNearestContaining } from "./path-utils.js";
|
|
16
|
+
import { findNearestContaining, normalizeEphemeralMapKey } from "./path-utils.js";
|
|
17
17
|
import { recordFromDispatchDiagnostic, } from "./actionable-warnings.js";
|
|
18
18
|
import { recordFromCodeQualityDiagnostic, } from "./code-quality-warnings.js";
|
|
19
19
|
import { recordDiagnostics } from "./widget-state.js";
|
|
@@ -21,7 +21,7 @@ import { getDiagnosticLogger } from "./diagnostic-logger.js";
|
|
|
21
21
|
import { getDiagnosticTracker } from "./diagnostic-tracker.js";
|
|
22
22
|
import { loadDispatchIntegration } from "./dispatch/lazy.js";
|
|
23
23
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
24
|
-
import { createAvailabilityChecker, resolveAvailableOrInstall, resolveCommandArgsWithInstallFallback, resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
24
|
+
import { classifyProbeFailure, createAvailabilityChecker, createAvailabilityLatch, describeProbeEvidence, logAvailabilityDecision, startHostStallSampler, resolveAvailableOrInstall, resolveCommandArgsWithInstallFallback, resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
25
25
|
import { findDetektConfig } from "./dispatch/runners/detekt.js";
|
|
26
26
|
import { detectFileKind, getFileKindLabel } from "./file-kinds.js";
|
|
27
27
|
import { detectFileChangedAfterCommand, getProjectIgnoreMatcher, isExcludedDirName, } from "./file-utils.js";
|
|
@@ -178,6 +178,13 @@ function createPhaseTracker(toolName, filePath) {
|
|
|
178
178
|
}
|
|
179
179
|
// --- ESLint autofix helpers ---
|
|
180
180
|
export { hasEslintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, };
|
|
181
|
+
/**
|
|
182
|
+
* eslint autofix availability, per cwd + PATH. The verdict is owned by the
|
|
183
|
+
* shared availability latch (#1494): a `missing` / `non-installable` outcome
|
|
184
|
+
* sticks for the session, a timeout expires on a cooldown and is re-probed, so
|
|
185
|
+
* one stalled probe cannot silently stop autofixing a project's JS/TS.
|
|
186
|
+
*/
|
|
187
|
+
const ESLINT_FIX_PROBE_BUDGET_MS = 5000;
|
|
181
188
|
const _eslintCache = new BoundedLruCache(32);
|
|
182
189
|
/**
|
|
183
190
|
* Run eslint --fix on a file. Runs a single spawn and diffs the file before/after,
|
|
@@ -196,18 +203,70 @@ async function tryEslintFix(filePath, cwd) {
|
|
|
196
203
|
const cacheKey = `${path.resolve(cwd)}|${process.env.PATH ?? ""}`;
|
|
197
204
|
let cached = _eslintCache.get(cacheKey);
|
|
198
205
|
if (!cached) {
|
|
199
|
-
|
|
200
|
-
const check = await safeSpawnAsync(candidate, ["--version"], {
|
|
201
|
-
timeout: 5000,
|
|
202
|
-
cwd,
|
|
203
|
-
});
|
|
204
|
-
cached = {
|
|
205
|
-
available: !check.error && check.status === 0,
|
|
206
|
-
bin: !check.error && check.status === 0 ? candidate : null,
|
|
207
|
-
};
|
|
206
|
+
cached = { latch: createAvailabilityLatch(), bin: null };
|
|
208
207
|
_eslintCache.set(cacheKey, cached);
|
|
209
208
|
}
|
|
210
|
-
if (
|
|
209
|
+
if (cached.latch.read() === null) {
|
|
210
|
+
const candidate = resolveToolCommand(cwd, "eslint") ?? "eslint";
|
|
211
|
+
// The budget is enforced by a HOST-side timer, so a stalled event loop
|
|
212
|
+
// expires it while eslint is still healthy. Measure the overlapping stall
|
|
213
|
+
// and hand it to the classifier, exactly like the runner path: without it a
|
|
214
|
+
// host-stall timeout is re-caused as `probe-timeout` and gets the escalating
|
|
215
|
+
// 30s→300s cooldown instead of the 5s host-stall one (#1467).
|
|
216
|
+
const stallSampler = startHostStallSampler();
|
|
217
|
+
const startedAt = Date.now();
|
|
218
|
+
let check;
|
|
219
|
+
let hostStallMs;
|
|
220
|
+
try {
|
|
221
|
+
check = await safeSpawnAsync(candidate, ["--version"], {
|
|
222
|
+
timeout: ESLINT_FIX_PROBE_BUDGET_MS,
|
|
223
|
+
cwd,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
hostStallMs = stallSampler.stop();
|
|
228
|
+
}
|
|
229
|
+
const elapsedMs = Date.now() - startedAt;
|
|
230
|
+
const decisionCwd = path.resolve(cwd);
|
|
231
|
+
if (!check.error && check.status === 0) {
|
|
232
|
+
cached.bin = candidate;
|
|
233
|
+
cached.latch.noteAvailable();
|
|
234
|
+
logAvailabilityDecision({
|
|
235
|
+
tool: "eslint",
|
|
236
|
+
verdict: "available",
|
|
237
|
+
outcome: "success",
|
|
238
|
+
cause: "ok",
|
|
239
|
+
elapsedMs,
|
|
240
|
+
latched: true,
|
|
241
|
+
hostStallMs,
|
|
242
|
+
budgetMs: ESLINT_FIX_PROBE_BUDGET_MS,
|
|
243
|
+
classifiedBy: "probe",
|
|
244
|
+
evidence: describeProbeEvidence(check, "eslint"),
|
|
245
|
+
}, decisionCwd);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
cached.bin = null;
|
|
249
|
+
const { outcome, cause, evidence } = classifyProbeFailure(check, {
|
|
250
|
+
hostStallMs,
|
|
251
|
+
command: "eslint",
|
|
252
|
+
});
|
|
253
|
+
const retryAfterMs = cached.latch.noteUnavailable(outcome, cause);
|
|
254
|
+
logAvailabilityDecision({
|
|
255
|
+
tool: "eslint",
|
|
256
|
+
verdict: "unavailable",
|
|
257
|
+
outcome,
|
|
258
|
+
cause,
|
|
259
|
+
elapsedMs,
|
|
260
|
+
latched: retryAfterMs === 0,
|
|
261
|
+
hostStallMs,
|
|
262
|
+
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
263
|
+
budgetMs: ESLINT_FIX_PROBE_BUDGET_MS,
|
|
264
|
+
classifiedBy: "probe",
|
|
265
|
+
evidence,
|
|
266
|
+
}, decisionCwd);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (cached.latch.read() !== true || !cached.bin)
|
|
211
270
|
return 0;
|
|
212
271
|
const cmd = cached.bin;
|
|
213
272
|
return detectFileChangedAfterCommand(filePath, cmd, ["--fix", "--no-error-on-unmatched-pattern", filePath], cwd, [1]);
|
|
@@ -650,6 +709,24 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
650
709
|
if (outcome === "bailed") {
|
|
651
710
|
// Abandon the still-pending write; the edit continues. Log it so this
|
|
652
711
|
// stall — previously an invisible hang — is queryable in latency.log.
|
|
712
|
+
//
|
|
713
|
+
// #1766: a resync deadline can expire while the target server's FIRST
|
|
714
|
+
// spawn is still in flight (cold spawn > budget). That is not the same
|
|
715
|
+
// state as a running server that stalled on a write — the server the
|
|
716
|
+
// old wording blamed as "slow/wedged" did not exist yet. Distinguish
|
|
717
|
+
// the two via a fresh, synchronous inFlight lookup so the record keeps
|
|
718
|
+
// the discriminating identity (which server, which lifecycle state).
|
|
719
|
+
// Guarded: a test double or future service shape lacking the method
|
|
720
|
+
// must degrade to the old "timeout"/slow-wedged wording, not throw
|
|
721
|
+
// into the catch below and suppress this record entirely (#1766 F3).
|
|
722
|
+
const spawnInFlight = !abort?.aborted &&
|
|
723
|
+
typeof lspService.isSpawnInFlight === "function" &&
|
|
724
|
+
lspService.isSpawnInFlight(filePath);
|
|
725
|
+
const reason = abort?.aborted
|
|
726
|
+
? "aborted"
|
|
727
|
+
: spawnInFlight
|
|
728
|
+
? "spawn-in-flight"
|
|
729
|
+
: "timeout";
|
|
653
730
|
logLatency({
|
|
654
731
|
type: "phase",
|
|
655
732
|
phase: "lsp_sync_abandoned",
|
|
@@ -657,11 +734,16 @@ export async function resyncLspFile(filePath, fileContent, needsContentRefresh,
|
|
|
657
734
|
durationMs: Date.now() - startedAt,
|
|
658
735
|
metadata: {
|
|
659
736
|
source: "lsp_sync",
|
|
660
|
-
reason
|
|
737
|
+
reason,
|
|
661
738
|
budgetMs,
|
|
662
739
|
},
|
|
663
740
|
});
|
|
664
|
-
|
|
741
|
+
const cause = abort?.aborted
|
|
742
|
+
? "aborted (Escape)"
|
|
743
|
+
: spawnInFlight
|
|
744
|
+
? `timed out after ${budgetMs}ms; reason: spawn-in-flight (server still cold-spawning)`
|
|
745
|
+
: `timed out after ${budgetMs}ms; server slow/wedged`;
|
|
746
|
+
dbg(`LSP resync ${cause} for ${filePath}`);
|
|
665
747
|
}
|
|
666
748
|
}
|
|
667
749
|
}
|
|
@@ -843,7 +925,25 @@ export async function runPipeline(ctx, deps) {
|
|
|
843
925
|
});
|
|
844
926
|
// --- 3. Auto-fix ---
|
|
845
927
|
phase.start("autofix");
|
|
846
|
-
|
|
928
|
+
let fixedCount = 0;
|
|
929
|
+
let autofixTools = [];
|
|
930
|
+
let attemptedTools = [];
|
|
931
|
+
let autofixChangedFiles = [];
|
|
932
|
+
let fixRefresh = false;
|
|
933
|
+
let autofixSkipReason;
|
|
934
|
+
if (ctx.autofixMode === "deferred") {
|
|
935
|
+
autofixSkipReason = "deferred_to_agent_end";
|
|
936
|
+
dbg(`autofix: deferred until agent_end for ${filePath}`);
|
|
937
|
+
}
|
|
938
|
+
else
|
|
939
|
+
({
|
|
940
|
+
fixedCount,
|
|
941
|
+
autofixTools,
|
|
942
|
+
attemptedTools,
|
|
943
|
+
changedFiles: autofixChangedFiles,
|
|
944
|
+
needsContentRefresh: fixRefresh,
|
|
945
|
+
skipReason: autofixSkipReason,
|
|
946
|
+
} = await runAutofix(filePath, cwd, getFlag, dbg, deps, getFlagSource));
|
|
847
947
|
for (const changedFile of autofixChangedFiles) {
|
|
848
948
|
piChangedFiles.add(path.resolve(changedFile));
|
|
849
949
|
}
|
|
@@ -903,6 +1003,8 @@ export async function runPipeline(ctx, deps) {
|
|
|
903
1003
|
}, {
|
|
904
1004
|
projectRoot: ctx.projectRoot,
|
|
905
1005
|
writeIndex: ctx.telemetry?.writeIndex,
|
|
1006
|
+
telemetryModel: ctx.telemetry?.modelId,
|
|
1007
|
+
telemetryProvider: ctx.telemetry?.provider,
|
|
906
1008
|
});
|
|
907
1009
|
recordDiagnostics(filePath, dispatchResult.diagnostics, ctx.telemetry?.writeIndex);
|
|
908
1010
|
// #502: emit the write batch's FINAL diagnostic state immediately after
|
|
@@ -987,20 +1089,16 @@ export async function runPipeline(ctx, deps) {
|
|
|
987
1089
|
: "";
|
|
988
1090
|
output += `\n\n⚠️ Auto-format failed: ${details}${suffix}`;
|
|
989
1091
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
? "\nModified files:\n" + topFiles + overflow
|
|
1001
|
-
: "";
|
|
1002
|
-
output += `\n\n⚠️ **File was modified by auto-format/fix. You MUST re-read modified file(s) before making any further edits — the content on disk has changed (whitespace, indentation, quotes, or code). Editing from memory will produce mismatches.**${fileList}`;
|
|
1003
|
-
}
|
|
1092
|
+
// #1590: the notice sentence itself is NOT rendered here. This layer cannot
|
|
1093
|
+
// see the attachment cap or the aggregate budget, so it hands the display
|
|
1094
|
+
// paths to `handleToolResult`, which owns the decision and renders the one
|
|
1095
|
+
// sentence with `renderPostAutofixNotice`.
|
|
1096
|
+
const postAutofixNotice = formatChanged || fixedCount > 0
|
|
1097
|
+
? {
|
|
1098
|
+
targetPath: toRunnerDisplayPath(cwd, filePath),
|
|
1099
|
+
changedFiles: [...piChangedFiles].map((changedFile) => toRunnerDisplayPath(cwd, changedFile)),
|
|
1100
|
+
}
|
|
1101
|
+
: undefined;
|
|
1004
1102
|
phase.end("dispatch_lint", {
|
|
1005
1103
|
hasOutput: !!dispatchResult.output,
|
|
1006
1104
|
diagnosticCount: dispatchResult.diagnostics.length,
|
|
@@ -1024,6 +1122,7 @@ export async function runPipeline(ctx, deps) {
|
|
|
1024
1122
|
turnSeq: ctx.telemetry?.turnIndex,
|
|
1025
1123
|
writeSeq: ctx.telemetry?.writeIndex,
|
|
1026
1124
|
seqState: ctx.seqState,
|
|
1125
|
+
turnEndCascadeSettleStart: ctx.turnEndCascadeSettleStart,
|
|
1027
1126
|
fileContent,
|
|
1028
1127
|
wordIndex: ctx.wordIndex,
|
|
1029
1128
|
onWordIndexUpdated: ctx.onWordIndexUpdated,
|
|
@@ -1048,7 +1147,10 @@ export async function runPipeline(ctx, deps) {
|
|
|
1048
1147
|
});
|
|
1049
1148
|
// --- Final timing + all-clear ---
|
|
1050
1149
|
const elapsed = Date.now() - pipelineStart;
|
|
1051
|
-
|
|
1150
|
+
// #1590: `postAutofixNotice` is output this run produces, just rendered a
|
|
1151
|
+
// layer up. Treat it as output here, or a format-only change would newly
|
|
1152
|
+
// gain an "all clear" line it never had while the sentence lived inline.
|
|
1153
|
+
if (!output && !postAutofixNotice) {
|
|
1052
1154
|
output = buildAllClearOutput(dispatchResult, elapsed, filePath);
|
|
1053
1155
|
}
|
|
1054
1156
|
phase.end("total", { hasOutput: !!output });
|
|
@@ -1082,11 +1184,57 @@ export async function runPipeline(ctx, deps) {
|
|
|
1082
1184
|
inlineBlockerSummary: dispatchResult.hasBlockers
|
|
1083
1185
|
? dispatchResult.blockerOutput.trim() || undefined
|
|
1084
1186
|
: undefined,
|
|
1187
|
+
// #1561 F1: taken from the very diagnostics `blockerOutput` was rendered
|
|
1188
|
+
// from, so the provenance can never disagree with the text it guards. An
|
|
1189
|
+
// untagged diagnostic contributes the literal "unknown", which no verdict
|
|
1190
|
+
// claims coverage for — it pins the entry rather than silently widening
|
|
1191
|
+
// what an LSP check is allowed to clear.
|
|
1192
|
+
inlineBlockerSources: dispatchResult.hasBlockers
|
|
1193
|
+
? [
|
|
1194
|
+
...new Set(dispatchResult.blockers.map((d) => d.tool?.trim() || "unknown")),
|
|
1195
|
+
]
|
|
1196
|
+
: undefined,
|
|
1197
|
+
inlineBlockerLines: dispatchResult.hasBlockers
|
|
1198
|
+
? dispatchResult.blockers
|
|
1199
|
+
// #1641 review F2: `dispatchResult.blockers` is NOT guaranteed to be
|
|
1200
|
+
// scoped to THIS file — a chart-wide runner (helm-lint, helm-render)
|
|
1201
|
+
// reports blocking diagnostics against other files in the chart
|
|
1202
|
+
// (e.g. `values.yaml`) alongside `ctx.filePath`. The precedent every
|
|
1203
|
+
// per-file runner already follows (dotnet-build.ts, javac.ts) is to
|
|
1204
|
+
// drop cross-file rows before they reach a per-file record; this is
|
|
1205
|
+
// that same filter applied at the aggregation point instead, since
|
|
1206
|
+
// `blockers` is pooled across every runner dispatched for this file.
|
|
1207
|
+
// Without it, a cross-file line count gets attributed to THIS
|
|
1208
|
+
// file's past-EOF check and can demote an in-bounds, fully valid
|
|
1209
|
+
// blocker for content the diagnostic never described.
|
|
1210
|
+
//
|
|
1211
|
+
// #1641 review round 2 (LOW): `path.resolve` equality doesn't fold
|
|
1212
|
+
// case, and an LSP-sourced diagnostic's `filePath` is stamped with
|
|
1213
|
+
// realpath canonical casing (dispatch/runners/lsp.ts ->
|
|
1214
|
+
// normalizeMapKey) while `ctx.filePath` can arrive lowercase-drive
|
|
1215
|
+
// on Windows — the drive-letter class from #1139/#1150. A bare
|
|
1216
|
+
// `path.resolve` equality then drops EVERY LSP blocker line and
|
|
1217
|
+
// this record silently skips the past-EOF gate (fail-open, but
|
|
1218
|
+
// exactly the pre-fix behavior on the surface #1641 targets).
|
|
1219
|
+
// `normalizeEphemeralMapKey` slash-folds and (on win32)
|
|
1220
|
+
// lowercase-folds both sides with no filesystem I/O — cheap enough
|
|
1221
|
+
// for this per-blocker hot-path filter. `pathsEqual` was
|
|
1222
|
+
// deliberately NOT used here: it calls `realpathSync` per
|
|
1223
|
+
// comparison, which this filter cannot afford per blocker.
|
|
1224
|
+
.filter((d) => normalizeEphemeralMapKey(d.filePath) ===
|
|
1225
|
+
normalizeEphemeralMapKey(filePath))
|
|
1226
|
+
.map((d) => d.line)
|
|
1227
|
+
.filter((line) => typeof line === "number")
|
|
1228
|
+
: undefined,
|
|
1085
1229
|
actionableWarnings,
|
|
1086
1230
|
codeQualityWarnings,
|
|
1087
1231
|
diagnostics: dispatchResult.diagnostics,
|
|
1088
1232
|
formattersUsed,
|
|
1089
1233
|
fixedCount,
|
|
1090
1234
|
autofixTools,
|
|
1235
|
+
postAutofixNotice,
|
|
1236
|
+
postMutation: ctx.autofixMode !== "deferred" && autofixChangedFiles.some((f) => path.resolve(f) === path.resolve(filePath)) && fileContent !== undefined
|
|
1237
|
+
? { filePath: path.resolve(filePath), content: fileContent, source: "autofix" }
|
|
1238
|
+
: undefined,
|
|
1091
1239
|
};
|
|
1092
1240
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single post-autofix instruction about a written file (#1590).
|
|
3
|
+
*
|
|
4
|
+
* Two layers used to phrase this. `clients/pipeline.ts` claimed "the attached
|
|
5
|
+
* full content is authoritative" from changed-file membership alone, and
|
|
6
|
+
* `clients/runtime-tool-result.ts` — the only layer that sees the per-file
|
|
7
|
+
* attachment cap and the per-command aggregate budget — appended "too large to
|
|
8
|
+
* attach" for the same file. On a size-capped write the agent got both.
|
|
9
|
+
*
|
|
10
|
+
* The wording now lives here, and `handleToolResult` is the only caller: the
|
|
11
|
+
* pipeline supplies the data (display paths it already resolved) and the layer
|
|
12
|
+
* that made the attachment decision picks the sentence. Keeping the renderer
|
|
13
|
+
* out of `pipeline.ts` also keeps it real in tests that mock the pipeline away.
|
|
14
|
+
*/
|
|
15
|
+
/** Render the ONE post-autofix instruction for `decision`. */
|
|
16
|
+
export function renderPostAutofixNotice(notice, decision) {
|
|
17
|
+
const topFiles = notice.changedFiles
|
|
18
|
+
.slice(0, 8)
|
|
19
|
+
.map((f) => " - " + f)
|
|
20
|
+
.join("\n");
|
|
21
|
+
const overflow = notice.changedFiles.length > 8
|
|
22
|
+
? "\n - ... and " + (notice.changedFiles.length - 8) + " more"
|
|
23
|
+
: "";
|
|
24
|
+
const fileList = notice.changedFiles.length
|
|
25
|
+
? "\nModified files:\n" + topFiles + overflow
|
|
26
|
+
: "";
|
|
27
|
+
switch (decision) {
|
|
28
|
+
case "attached":
|
|
29
|
+
return `⚠️ **The attached full content for ${notice.targetPath} is authoritative after autofix. You MUST re-read any other modified side-effect files before editing them.**${fileList}`;
|
|
30
|
+
case "size-capped":
|
|
31
|
+
return `⚠️ **File was modified by auto-format/fix. You MUST re-read ${notice.targetPath} before making any further edits — the authoritative content is too large to attach.**${fileList}`;
|
|
32
|
+
case "aggregate-budget-degraded":
|
|
33
|
+
return `⚠️ **File was modified by auto-format/fix. You MUST re-read ${notice.targetPath} before making any further edits — the aggregate authoritative content for this command is too large to attach.**${fileList}`;
|
|
34
|
+
default:
|
|
35
|
+
return `⚠️ **File was modified by auto-format/fix. You MUST re-read modified file(s) before making any further edits — the content on disk has changed (whitespace, indentation, quotes, or code). Editing from memory will produce mismatches.**${fileList}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { getProjectDataDir } from "../file-utils.js";
|
|
4
4
|
import { writeFileAtomic } from "../atomic-write.js";
|
|
5
5
|
import { readJsonCache } from "../json-cache-read.js";
|
|
6
|
+
import { MTIME_DRIFT_TOLERANCE_MS } from "../blocker-freshness.js";
|
|
6
7
|
// v2: cheap-tier scan now also runs ast-grep-napi (#308); invalidate older
|
|
7
8
|
// snapshots so a pre-ast-grep cache isn't served as complete via refreshRunners=cached.
|
|
8
9
|
export const PROJECT_DIAGNOSTICS_CACHE_VERSION = 2;
|
|
@@ -47,7 +48,8 @@ export function writeProjectDiagnosticsDeltaReport(cwd, report) {
|
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Drop diagnostics whose underlying file changed on disk after the snapshot was
|
|
50
|
-
* taken (`mtimeMs > scannedAt`) or no longer
|
|
51
|
+
* taken (`mtimeMs > scannedAt + MTIME_DRIFT_TOLERANCE_MS`) or no longer
|
|
52
|
+
* exists. The persisted snapshot is a
|
|
51
53
|
* cross-session cache served by `lens_diagnostics mode=full refreshRunners=cached`;
|
|
52
54
|
* without this it replays diagnostics the agent has since fixed or for files that
|
|
53
55
|
* were deleted (#298 — "the cache needs to be cleaned before running diagnostics
|
|
@@ -56,6 +58,15 @@ export function writeProjectDiagnosticsDeltaReport(cwd, report) {
|
|
|
56
58
|
* stays a pure reader. Synchronous (a `statSync` per *distinct* file, memoised),
|
|
57
59
|
* since the cached full-mode path is already off the typing hot loop.
|
|
58
60
|
*
|
|
61
|
+
* The boundary is `mtime > scannedAt + MTIME_DRIFT_TOLERANCE_MS`
|
|
62
|
+
* (`blocker-freshness.ts`), not a bare `mtime > scannedAt`: #1711 found this
|
|
63
|
+
* consumer's old +1ms slack did not cover the measured Windows host skew
|
|
64
|
+
* between a file's mtime and the `Date.now()` read that produces `scannedAt`
|
|
65
|
+
* (up to ~11.4ms, #1491/#1498) — a same-tick write-then-scan silently
|
|
66
|
+
* dropped a live finding here, worse than `findingPathFreshness`'s sibling
|
|
67
|
+
* gate (#1708) since this arm DROPS rather than demotes. Reusing the shared
|
|
68
|
+
* constant keeps one source of truth for the measured skew.
|
|
69
|
+
*
|
|
59
70
|
* Fail-safe on an unparseable `scannedAt`: return the snapshot untouched rather
|
|
60
71
|
* than risk dropping live findings on a clock/format anomaly.
|
|
61
72
|
*/
|
|
@@ -70,8 +81,11 @@ export function reconcileProjectDiagnosticsSnapshot(snapshot) {
|
|
|
70
81
|
return cached;
|
|
71
82
|
let stale;
|
|
72
83
|
try {
|
|
73
|
-
//
|
|
74
|
-
|
|
84
|
+
// MTIME_DRIFT_TOLERANCE_MS: a file scanned at scannedAt, or written
|
|
85
|
+
// within the tolerance window of the scan timestamp being captured,
|
|
86
|
+
// has mtime <= scannedAt + MTIME_DRIFT_TOLERANCE_MS.
|
|
87
|
+
stale =
|
|
88
|
+
fs.statSync(filePath).mtimeMs > scannedAtMs + MTIME_DRIFT_TOLERANCE_MS;
|
|
75
89
|
}
|
|
76
90
|
catch {
|
|
77
91
|
stale = true; // deleted / unreadable → drop
|
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Provenance metadata for the heavyweight project analyzers surfaced in
|
|
3
|
-
* `lens_diagnostics mode=full`.
|
|
4
|
-
*
|
|
5
|
-
* NOTE (#585 regression cleanup): this module USED to also own
|
|
6
|
-
* `extractCachedProjectDiagnostics` — a CACHE-ONLY reader that adapted each
|
|
7
|
-
* analyzer's cached result to `ProjectDiagnostic[]` via a parallel `EXTRACTORS`
|
|
8
|
-
* registry. #585 replaced that reader in production with
|
|
9
|
-
* `fetchFreshProjectDiagnostics` (./fresh-fetch.ts), which triggers-or-joins a
|
|
10
|
-
* fresh run instead of settling for a possibly-hours-stale session_start
|
|
11
|
-
* snapshot. The old reader lingered here wired only to a test — a SECOND,
|
|
12
|
-
* dead surfacing path whose registry could (and did) silently diverge from
|
|
13
|
-
* fresh-fetch's `ANALYZER_IDS`: opengrep was registered here but missing there,
|
|
14
|
-
* so opengrep scanned+cached yet never reached the agent (the exact #585-class
|
|
15
|
-
* regression this cleanup removes). The parallel registry is gone; fresh-fetch
|
|
16
|
-
* is the single source of truth (#883).
|
|
17
|
-
*
|
|
18
|
-
* What survives is the honesty metadata (#533): given an analyzer id that
|
|
19
|
-
* fresh-fetch reported `cold` (not applicable / unavailable this run),
|
|
20
|
-
* `warmTriggerFor` names what WOULD warm it, so the rendered note is actionable
|
|
21
|
-
* rather than a bare "not run".
|
|
22
|
-
*/
|
|
23
1
|
/**
|
|
24
2
|
* #533: which trigger warms each analyzer, surfaced in the "cold" honesty note
|
|
25
3
|
* so the note is actionable (names what to do), matching the #511/#514 house
|
|
@@ -40,3 +18,92 @@ const WARM_TRIGGER = {
|
|
|
40
18
|
export function warmTriggerFor(analyzerId) {
|
|
41
19
|
return WARM_TRIGGER[analyzerId] ?? "runs at session-start";
|
|
42
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* #1623: single formatter for a "not run" lane entry — reused by every
|
|
23
|
+
* caller that renders `fetchFreshProjectDiagnostics`'s `cold` list (today,
|
|
24
|
+
* `tools/lens-diagnostics.ts`'s mode=full) so the wording can't drift
|
|
25
|
+
* per-caller. Prefers the SPECIFIC reason captured at the decision point
|
|
26
|
+
* (`coldReasons`, ./fresh-fetch.ts) over the generic `warmTriggerFor` guess —
|
|
27
|
+
* the latter only remains a fallback for a `cold` id this map doesn't cover.
|
|
28
|
+
*/
|
|
29
|
+
export function formatNotRunEntry(analyzerId, coldReasons) {
|
|
30
|
+
const reason = coldReasons?.[analyzerId];
|
|
31
|
+
return reason
|
|
32
|
+
? `${analyzerId} — not run (${reason})`
|
|
33
|
+
: `${analyzerId} — not run (${warmTriggerFor(analyzerId)})`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* #1623: human-readable age, for a lane whose result was served from cache
|
|
37
|
+
* rather than executed fresh this call (see `cachedAgeMs`, ./fresh-fetch.ts).
|
|
38
|
+
* Mirrors the granularity an agent actually needs to judge staleness — exact
|
|
39
|
+
* seconds don't matter, "12m" vs "3h" does.
|
|
40
|
+
*/
|
|
41
|
+
export function formatCacheAge(ms) {
|
|
42
|
+
// #1623 fix-round F4: `CacheManager.readCache` accepts a missing/corrupt
|
|
43
|
+
// `meta.timestamp` as a HIT (the timestamp only gates staleness, not
|
|
44
|
+
// validity), so `Date.now() - new Date(badTimestamp).getTime()` reaches
|
|
45
|
+
// here as NaN. Rendering "NaNh old" is a worse honesty gap than the one
|
|
46
|
+
// this whole module exists to close — say the age is unknown instead of
|
|
47
|
+
// fabricating one. Mirrors the sibling `Number.isFinite` guard in
|
|
48
|
+
// `./cache.ts`'s `reconcileProjectDiagnosticsSnapshot` (fail-safe on an
|
|
49
|
+
// unparseable timestamp rather than propagate NaN).
|
|
50
|
+
if (!Number.isFinite(ms))
|
|
51
|
+
return "age unknown";
|
|
52
|
+
if (ms < 0)
|
|
53
|
+
return "0m";
|
|
54
|
+
const totalMinutes = Math.round(ms / 60_000);
|
|
55
|
+
if (totalMinutes < 1)
|
|
56
|
+
return "under 1m";
|
|
57
|
+
if (totalMinutes < 60)
|
|
58
|
+
return `${totalMinutes}m`;
|
|
59
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
60
|
+
const minutes = totalMinutes % 60;
|
|
61
|
+
return minutes > 0 ? `${hours}h${minutes}m` : `${hours}h`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* #1623 fix-round F5: the full "<age> old" phrase for a cache-read lane.
|
|
65
|
+
* Every render call site used to append the literal word " old" after
|
|
66
|
+
* `formatCacheAge` itself, so a corrupt/missing timestamp's "age unknown"
|
|
67
|
+
* came out as the ungrammatical "age unknown old" — the exact residual the
|
|
68
|
+
* issue's tracking comment named. "Old" only makes sense once an age is
|
|
69
|
+
* actually known, so this is the single place that decides whether to say
|
|
70
|
+
* it, instead of every caller repeating (and occasionally forgetting) the
|
|
71
|
+
* same guard.
|
|
72
|
+
*/
|
|
73
|
+
export function formatCacheAgeOld(ms) {
|
|
74
|
+
const age = formatCacheAge(ms);
|
|
75
|
+
return Number.isFinite(ms) ? `${age} old` : age;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* #1623 fix-round F1: a "cold" reason for an analyzer whose `ensureAvailable()`
|
|
79
|
+
* returned false, built from the SAME `AvailabilityOutcome`/`AvailabilityCause`
|
|
80
|
+
* taxonomy every dispatch-side unavailability message already uses
|
|
81
|
+
* (`describeUnavailability`, availability-policy.ts) — not a re-guessed
|
|
82
|
+
* "binary unavailable" for every failure shape.
|
|
83
|
+
*
|
|
84
|
+
* `ensureAvailable() === false` collapses at least three distinct causes: a
|
|
85
|
+
* durable absence (`missing`), a transient probe under an active retry
|
|
86
|
+
* cooldown (`transient`), and — for govulncheck specifically — a project-trust
|
|
87
|
+
* install denial the latch never even records (`assertInstallAllowed` returns
|
|
88
|
+
* before touching it, project-trust.ts). fresh-fetch.ts's govulncheck task
|
|
89
|
+
* checks trust denial itself, via `projectTrustDenialReason()`, before
|
|
90
|
+
* falling back to this function. Wording a cooldown as "unavailable" would
|
|
91
|
+
* repeat, one layer up, exactly the dispatch-layer defect #1467 fixed.
|
|
92
|
+
*/
|
|
93
|
+
export function reasonFromAvailabilityVerdict(tool, verdict) {
|
|
94
|
+
if (!verdict || verdict.outcome === null)
|
|
95
|
+
return `${tool} binary unavailable`;
|
|
96
|
+
const { outcome, cause } = verdict;
|
|
97
|
+
if (outcome === "transient") {
|
|
98
|
+
if (cause === "install-retry-exhausted") {
|
|
99
|
+
return `${tool} install kept timing out and hit its retry ceiling this session (not reported missing)`;
|
|
100
|
+
}
|
|
101
|
+
const remainingMs = verdict.retryAtMs - Date.now();
|
|
102
|
+
const remainingS = remainingMs > 0 ? Math.round(remainingMs / 1000) : 0;
|
|
103
|
+
return `${tool} availability probe — retry cooldown (${remainingS}s), not a missing install`;
|
|
104
|
+
}
|
|
105
|
+
if (outcome === "non-installable") {
|
|
106
|
+
return `${tool} unavailable on this host (non-installable)`;
|
|
107
|
+
}
|
|
108
|
+
return `${tool} binary unavailable`;
|
|
109
|
+
}
|