@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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides utilities for integrating the declarative dispatch system
|
|
5
5
|
* with the existing index.ts tool_result handler.
|
|
6
6
|
*/
|
|
7
|
+
import { CASCADE_DIAGNOSTICS_TTL_MS } from "../cascade-types.js";
|
|
7
8
|
import { getDiagnosticLogger } from "../diagnostic-logger.js";
|
|
8
9
|
import { detectFileKind } from "../file-kinds.js";
|
|
9
10
|
import { getLspCapableKinds, getPrimaryDispatchGroup, } from "../language-policy.js";
|
|
@@ -16,17 +17,18 @@ export { clearLatencyReports, formatLatencyReport, getLatencyReports };
|
|
|
16
17
|
import * as nodeFs from "node:fs";
|
|
17
18
|
import * as nodePath from "node:path";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { CASCADE_NEIGHBOUR_BUDGET, deriveCascadeNeighbourBudget, } from "../cascade-budget.js";
|
|
19
21
|
import { formatCascadeNeighborDiagnostics } from "../cascade-format.js";
|
|
20
22
|
import { logCascade } from "../cascade-logger.js";
|
|
21
23
|
import { getDiagnosticTracker } from "../diagnostic-tracker.js";
|
|
22
|
-
import { isTierAwareCascadeEnabled, recordOutstandingCascadeTouch, } from "../lsp/cascade-tier.js";
|
|
23
|
-
import {
|
|
24
|
-
import { bindingStateLabel, } from "../lsp/diagnostic-binding.js";
|
|
24
|
+
import { classifyCascadeWaitTier, isTierAwareCascadeEnabled, recordOutstandingCascadeTouch, } from "../lsp/cascade-tier.js";
|
|
25
|
+
import { bindingStateLabel, touchCoverageGap, } from "../lsp/diagnostic-binding.js";
|
|
25
26
|
import { getServersForFileWithConfig } from "../lsp/config.js";
|
|
26
27
|
import { getLSPService } from "../lsp/index.js";
|
|
27
28
|
import { isExternalOrVendorFile, normalizeMapKey } from "../path-utils.js";
|
|
28
29
|
import { getProjectIgnoreMatcher } from "../file-utils.js";
|
|
29
|
-
import { resetAstGrepUnsupportedLanguageLog } from "./runners/ast-grep-napi.js";
|
|
30
|
+
import { resetAstGrepNapiLoadState, resetAstGrepUnsupportedLanguageLog, } from "./runners/ast-grep-napi.js";
|
|
31
|
+
import { resetTreeSitterClientLoadState } from "../tree-sitter-shared.js";
|
|
30
32
|
import { isTestRoleCollateral } from "../collateral-test-role.js";
|
|
31
33
|
import { clearReviewGraphWorkspaceCache, getGraphBuildInfoForGraph, getGraphImportChanges, graphBuildInfoIsTrustworthy, } from "../review-graph/builder.js";
|
|
32
34
|
import { buildReverseDependencyIndexFromGraph, getAffectedFilesFromIndex, patchReverseDependencyIndex, writeReverseDependencyIndexToSnapshot, } from "../reverse-deps.js";
|
|
@@ -299,13 +301,18 @@ export function resetDispatchBaselines(cwd) {
|
|
|
299
301
|
if (cwd)
|
|
300
302
|
applyProjectLensConfig(cwd);
|
|
301
303
|
resetAstGrepUnsupportedLanguageLog();
|
|
304
|
+
// #1567: a genuine-failure verdict or a live transient cooldown on the
|
|
305
|
+
// napi load latch is session-scoped state, not process-lifetime state.
|
|
306
|
+
resetAstGrepNapiLoadState();
|
|
307
|
+
// #1592: same shape for the web-tree-sitter load latch — an evaluation-
|
|
308
|
+
// shaped rejection is session-scoped state, not process-lifetime state.
|
|
309
|
+
resetTreeSitterClientLoadState();
|
|
302
310
|
sessionFacts.clearAll();
|
|
303
311
|
resetSessionSlopScore();
|
|
304
312
|
clearCoverageNoticeState();
|
|
305
313
|
clearReviewGraphWorkspaceCache();
|
|
306
314
|
clearReverseDepsIndexCache();
|
|
307
315
|
clearModuleGraphCache();
|
|
308
|
-
neighborTouchCache.clear();
|
|
309
316
|
recentlyCleanNeighborCache.clear();
|
|
310
317
|
primaryFilesThisTurn.clear();
|
|
311
318
|
cascadeDiagnosticBaselines.clear();
|
|
@@ -326,13 +333,11 @@ let cascadeSessionStats = {
|
|
|
326
333
|
export function getCascadeSessionStats() {
|
|
327
334
|
return { ...cascadeSessionStats };
|
|
328
335
|
}
|
|
329
|
-
const neighborTouchCache = new Map();
|
|
330
336
|
const recentlyCleanNeighborCache = new Map();
|
|
331
|
-
/** O(1) entry
|
|
332
|
-
* memory attribution) —
|
|
337
|
+
/** O(1) entry count of this module's turn-bounded cache (#1123 item 2
|
|
338
|
+
* memory attribution) — a `Map.size` read, never iterated. */
|
|
333
339
|
export function getDispatchCascadeCacheStats() {
|
|
334
340
|
return {
|
|
335
|
-
neighborTouchCacheSize: neighborTouchCache.size,
|
|
336
341
|
recentlyCleanNeighborCacheSize: recentlyCleanNeighborCache.size,
|
|
337
342
|
};
|
|
338
343
|
}
|
|
@@ -347,14 +352,12 @@ function ensureCascadeTurnScope(turnSeq) {
|
|
|
347
352
|
return;
|
|
348
353
|
cascadeTurnScope = turnSeq;
|
|
349
354
|
primaryFilesThisTurn.clear();
|
|
350
|
-
neighborTouchCache.clear();
|
|
351
355
|
for (const [key, entry] of recentlyCleanNeighborCache) {
|
|
352
356
|
if (turnSeq - entry.turnSeq > RECENTLY_CLEAN_TTL_TURNS) {
|
|
353
357
|
recentlyCleanNeighborCache.delete(key);
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
|
-
const CASCADE_TTL_MS = 240_000;
|
|
358
361
|
const MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
|
|
359
362
|
const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
|
|
360
363
|
/**
|
|
@@ -412,6 +415,17 @@ function readBoundToCurrentDisk(rawDiags) {
|
|
|
412
415
|
* a future flag cannot be silently missed at just one of the several gate sites:
|
|
413
416
|
* - `inconclusive` (#1093/#571): the notify/diagnostics wait lapsed its deadline,
|
|
414
417
|
* so a resolved `[]` is NOT a confirmed clean (the #533 false-clean trap).
|
|
418
|
+
* #1549: this now means a PRIMARY's deadline lapsed. A slow auxiliary lands in
|
|
419
|
+
* the coverage-gap bullet below instead of collapsing the whole touch, so this
|
|
420
|
+
* predicate still fails closed for it while the primary's findings survive —
|
|
421
|
+
* which is the point: the two disqualifiers were doing one job for two causes.
|
|
422
|
+
* - a COVERAGE GAP (#1470/#1493): an auxiliary never reported — its push wait was
|
|
423
|
+
* cut off by the aux grace timer (#1470), or it stayed silent with no stored
|
|
424
|
+
* publication for this content (#1493) — so the merged result is missing whatever
|
|
425
|
+
* that scanner would have said. Such a touch is deliberately NOT `inconclusive`
|
|
426
|
+
* (the primary answered), which is exactly why it needs naming here: reading
|
|
427
|
+
* `!inconclusive` alone would let a hung or silent opengrep wipe a live footer
|
|
428
|
+
* finding and seed the recently-clean cache.
|
|
415
429
|
* - `binding.boundToCurrentDisk === false` (#1095): the diagnostics were computed
|
|
416
430
|
* against a DIFFERENT disk state than what is on disk now (the server's view
|
|
417
431
|
* diverged / a pre-fix buffer) — not an observation of current disk. `true` and
|
|
@@ -425,15 +439,38 @@ function readBoundToCurrentDisk(rawDiags) {
|
|
|
425
439
|
function readInconclusive(rawDiags) {
|
|
426
440
|
return rawDiags?.inconclusive === true;
|
|
427
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* #1549: an inconclusive `neighbor_touch` row used to carry NO attribution, so a
|
|
444
|
+
* forensic sweep could only infer the cause from duration histograms. `touchFile`
|
|
445
|
+
* now names the PRIMARY server(s) whose deadline produced the verdict and which
|
|
446
|
+
* deadline it was (`notify-write` vs `diagnostics-wait`, or `mixed`) — carry both
|
|
447
|
+
* into cascade.log so the record stands on its own. Read off the wrapper, like
|
|
448
|
+
* every other flag here (#1179).
|
|
449
|
+
*/
|
|
450
|
+
function readInconclusiveAttribution(rawDiags) {
|
|
451
|
+
const wrapper = rawDiags;
|
|
452
|
+
return {
|
|
453
|
+
...(wrapper?.inconclusiveServerIds?.length && {
|
|
454
|
+
inconclusiveServerIds: [...wrapper.inconclusiveServerIds],
|
|
455
|
+
}),
|
|
456
|
+
...(wrapper?.inconclusiveReason && {
|
|
457
|
+
inconclusiveReason: wrapper.inconclusiveReason,
|
|
458
|
+
}),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
428
461
|
function isConfirmedTouch(rawDiags) {
|
|
429
|
-
return !readInconclusive(rawDiags) &&
|
|
462
|
+
return (!readInconclusive(rawDiags) &&
|
|
463
|
+
touchCoverageGap(rawDiags).length === 0 &&
|
|
464
|
+
readBoundToCurrentDisk(rawDiags) !== false);
|
|
430
465
|
}
|
|
431
466
|
const reverseDepsIndexCache = new Map();
|
|
432
467
|
const REVERSE_DEPS_MAX_WARM_ROOTS = 8;
|
|
433
468
|
const REVERSE_DEPS_IDLE_EVICT_MS_DEFAULT = 20 * 60_000;
|
|
434
469
|
function reverseDepsIdleEvictMs() {
|
|
435
470
|
const value = Number.parseInt(process.env.PI_LENS_REVERSE_DEPS_IDLE_EVICT_MS ?? "", 10);
|
|
436
|
-
return Number.isSafeInteger(value) && value > 0
|
|
471
|
+
return Number.isSafeInteger(value) && value > 0
|
|
472
|
+
? value
|
|
473
|
+
: REVERSE_DEPS_IDLE_EVICT_MS_DEFAULT;
|
|
437
474
|
}
|
|
438
475
|
function deleteReverseDepsEntry(key) {
|
|
439
476
|
const entry = reverseDepsIndexCache.get(key);
|
|
@@ -494,8 +531,9 @@ export function _seedReverseDepsIndexCacheForTests(key, index, generation) {
|
|
|
494
531
|
// one-hop set is always the floor (it sorts first, before depth-2). Both
|
|
495
532
|
// env-tunable; set depth to 1 to restore the old one-hop-only behaviour.
|
|
496
533
|
const CASCADE_TRANSITIVE_DEPTH = Math.max(1, Number.parseInt(process.env.PI_LENS_CASCADE_TRANSITIVE_DEPTH ?? "2", 10) || 2);
|
|
497
|
-
|
|
498
|
-
|
|
534
|
+
// #1462: the cap itself, and the derivation that narrows it when this run has
|
|
535
|
+
// already spent the settle window, live in clients/cascade-budget.ts alongside
|
|
536
|
+
// `cascadeSettleWaitMs` — the two knobs only make sense read together.
|
|
499
537
|
// Exported (not just module-local) so the MCP warm-analyze seam
|
|
500
538
|
// (clients/mcp/analyze.ts, #536) can gate its own buildOrUpdateGraph call on
|
|
501
539
|
// the SAME file-kind eligibility this cascade path uses — one source of truth
|
|
@@ -599,8 +637,12 @@ function updateWordIndexForCascade(args) {
|
|
|
599
637
|
export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
600
638
|
const reverseDepsTimersToRelease = new Set();
|
|
601
639
|
const reverseDepsEntriesAtStart = new Set(reverseDepsIndexCache.values());
|
|
640
|
+
// #1462: legacy callers use this run's age when sizing the neighbour walk.
|
|
641
|
+
// The pipeline supplies a live turn_end clock so deferred work does not charge
|
|
642
|
+
// time that elapsed before turn_end began waiting.
|
|
643
|
+
const cascadeStart = Date.now();
|
|
602
644
|
try {
|
|
603
|
-
const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, seqState, fileContent, wordIndex, onWordIndexUpdated, } = options;
|
|
645
|
+
const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, seqState, turnEndCascadeSettleStart, fileContent, wordIndex, onWordIndexUpdated, } = options;
|
|
604
646
|
ensureCascadeTurnScope(turnSeq);
|
|
605
647
|
if (hasBlockers) {
|
|
606
648
|
logCascade({
|
|
@@ -666,6 +708,24 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
666
708
|
let importerSet = new Set();
|
|
667
709
|
let callerSet = new Set();
|
|
668
710
|
let referenceCount = 0;
|
|
711
|
+
let cascadeCandidateCount = 0;
|
|
712
|
+
let cascadeEligibleCount = 0;
|
|
713
|
+
let cascadeFilterDroppedCount = 0;
|
|
714
|
+
// #1446 item 4: how many eligible neighbors the flat CASCADE_NEIGHBOUR_BUDGET
|
|
715
|
+
// cut off, distinct from candidates dropped by the filters above it
|
|
716
|
+
// (missing on disk, vendor, ignored, already-primary-this-turn) — those are
|
|
717
|
+
// never actionable regardless of budget, so counting them as "truncated"
|
|
718
|
+
// would overstate what a larger budget could actually recover.
|
|
719
|
+
let cascadeBudgetTruncated = 0;
|
|
720
|
+
let transitiveTruncated = false;
|
|
721
|
+
// #1462: the budget actually in force for THIS run and the settle time left
|
|
722
|
+
// when it was sized. Both are overwritten inside the graph branch below,
|
|
723
|
+
// which is the only path that reaches `cascade_result` — the other branch
|
|
724
|
+
// returns a `non_code` skip before any of this is logged.
|
|
725
|
+
let cascadeNeighbourBudget = CASCADE_NEIGHBOUR_BUDGET;
|
|
726
|
+
let cascadeBudgetRemainingMs = 0;
|
|
727
|
+
let cascadeBudgetZone = "fits";
|
|
728
|
+
let cascadeDeliveryWindowMs = 0;
|
|
669
729
|
if (CASCADE_GRAPH_KINDS.has(fileKind)) {
|
|
670
730
|
const graphStart = Date.now();
|
|
671
731
|
const graph = await buildOrUpdateGraph(cwd, [normalizedFile], sessionFacts, seqState);
|
|
@@ -947,6 +1007,29 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
947
1007
|
});
|
|
948
1008
|
}
|
|
949
1009
|
}
|
|
1010
|
+
// #1462: size the rest of this run against the on-time window it has NOT
|
|
1011
|
+
// already spent. Derived HERE — after the graph build, the reverse-deps
|
|
1012
|
+
// refresh and the LSP-reference expansion, which is where the measured
|
|
1013
|
+
// overruns were bought — and used for both the transitive BFS cap and the
|
|
1014
|
+
// final slice, so a rescued run expands less as well as walking less.
|
|
1015
|
+
// Narrowing happens ONLY inside the rescue band (see cascade-budget.ts):
|
|
1016
|
+
// a run that is merely late keeps the whole set and is delivered complete
|
|
1017
|
+
// by #1443's carry-over, because a dropped neighbour is lost for good
|
|
1018
|
+
// while a late one is not. A wired callback that cannot observe an active
|
|
1019
|
+
// turn_end wait uses zero elapsed time rather than charging pre-turn age.
|
|
1020
|
+
{
|
|
1021
|
+
const settleStart = turnEndCascadeSettleStart?.();
|
|
1022
|
+
const elapsedMs = turnEndCascadeSettleStart
|
|
1023
|
+
? settleStart !== undefined && Number.isFinite(settleStart)
|
|
1024
|
+
? Math.max(0, Date.now() - settleStart)
|
|
1025
|
+
: 0
|
|
1026
|
+
: Date.now() - cascadeStart;
|
|
1027
|
+
const decision = deriveCascadeNeighbourBudget({ elapsedMs });
|
|
1028
|
+
cascadeNeighbourBudget = decision.budget;
|
|
1029
|
+
cascadeBudgetRemainingMs = decision.remainingMs;
|
|
1030
|
+
cascadeBudgetZone = decision.zone;
|
|
1031
|
+
cascadeDeliveryWindowMs = decision.deliveryWindowMs;
|
|
1032
|
+
}
|
|
950
1033
|
// Bounded transitive expansion: add depth>1 dependents (indirect
|
|
951
1034
|
// importers/callers/referencers) so the blast radius isn't limited to one
|
|
952
1035
|
// hop. The one-hop sets above remain the floor (they sort first); these
|
|
@@ -954,8 +1037,9 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
954
1037
|
if (CASCADE_TRANSITIVE_DEPTH > 1) {
|
|
955
1038
|
const transitive = computeTransitiveImpact(graph, normalizedFile, {
|
|
956
1039
|
maxDepth: CASCADE_TRANSITIVE_DEPTH,
|
|
957
|
-
maxHits:
|
|
1040
|
+
maxHits: cascadeNeighbourBudget,
|
|
958
1041
|
});
|
|
1042
|
+
transitiveTruncated = transitive.truncated;
|
|
959
1043
|
const added = [
|
|
960
1044
|
...new Set(transitive.hits
|
|
961
1045
|
.map((hit) => hit.file)
|
|
@@ -991,6 +1075,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
991
1075
|
// touched solely for cascade diagnostics. Composes the shared `detectFileRole`
|
|
992
1076
|
// seam; a classifier failure RETAINS the candidate (honest — never a false
|
|
993
1077
|
// clean). The project ignore filter below is separate and unchanged (#297).
|
|
1078
|
+
cascadeCandidateCount = impact.neighborFiles.length;
|
|
994
1079
|
impact.directImporters = impact.directImporters.filter((f) => !isTestRoleCollateral(f));
|
|
995
1080
|
impact.directCallers = impact.directCallers.filter((f) => !isTestRoleCollateral(f));
|
|
996
1081
|
impact.neighborFiles = impact.neighborFiles.filter((f) => !isTestRoleCollateral(f));
|
|
@@ -1004,7 +1089,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1004
1089
|
...impact.directCallers,
|
|
1005
1090
|
]);
|
|
1006
1091
|
referenceCount = impact.neighborFiles.filter((n) => !importerOrCallerSet.has(n)).length;
|
|
1007
|
-
|
|
1092
|
+
const eligibleNeighbors = [...impact.neighborFiles]
|
|
1008
1093
|
.filter((n) => nodeFs.existsSync(n))
|
|
1009
1094
|
.filter((n) => !isExternalOrVendorFile(n, cwd))
|
|
1010
1095
|
// Honour the project's ignore config: a user-ignored neighbour (e.g.
|
|
@@ -1016,8 +1101,43 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1016
1101
|
.sort((a, b) => {
|
|
1017
1102
|
const rank = (p) => importerSet.has(p) ? 0 : callerSet.has(p) ? 1 : 2;
|
|
1018
1103
|
return rank(a) - rank(b);
|
|
1019
|
-
})
|
|
1020
|
-
|
|
1104
|
+
});
|
|
1105
|
+
cascadeEligibleCount = eligibleNeighbors.length;
|
|
1106
|
+
cascadeFilterDroppedCount = Math.max(0, cascadeCandidateCount - cascadeEligibleCount);
|
|
1107
|
+
cascadeBudgetTruncated = Math.max(0, eligibleNeighbors.length - cascadeNeighbourBudget);
|
|
1108
|
+
sortedNeighbors = eligibleNeighbors.slice(0, cascadeNeighbourBudget);
|
|
1109
|
+
if (cascadeBudgetTruncated > 0 || transitiveTruncated) {
|
|
1110
|
+
const budget = {
|
|
1111
|
+
candidateCount: cascadeCandidateCount,
|
|
1112
|
+
eligibleCount: cascadeEligibleCount,
|
|
1113
|
+
selectedCount: sortedNeighbors.length,
|
|
1114
|
+
truncatedCount: cascadeBudgetTruncated,
|
|
1115
|
+
...(transitiveTruncated && { transitiveTruncated: true }),
|
|
1116
|
+
};
|
|
1117
|
+
const detailParts = [
|
|
1118
|
+
...(cascadeBudgetTruncated > 0
|
|
1119
|
+
? [
|
|
1120
|
+
`cascade neighbor budget checked ${budget.selectedCount} of ${budget.eligibleCount} eligible dependents ` +
|
|
1121
|
+
`(${budget.truncatedCount} omitted)`,
|
|
1122
|
+
]
|
|
1123
|
+
: []),
|
|
1124
|
+
...(transitiveTruncated
|
|
1125
|
+
? [
|
|
1126
|
+
"transitive expansion was capped before all eligible dependents were enumerated",
|
|
1127
|
+
]
|
|
1128
|
+
: []),
|
|
1129
|
+
];
|
|
1130
|
+
const detail = detailParts.join("; ");
|
|
1131
|
+
impact.indeterminate = impact.indeterminate
|
|
1132
|
+
? {
|
|
1133
|
+
...impact.indeterminate,
|
|
1134
|
+
detail: impact.indeterminate.detail
|
|
1135
|
+
? `${impact.indeterminate.detail}; ${detail}`
|
|
1136
|
+
: detail,
|
|
1137
|
+
budget,
|
|
1138
|
+
}
|
|
1139
|
+
: { reason: "budget_truncated", detail, budget };
|
|
1140
|
+
}
|
|
1021
1141
|
}
|
|
1022
1142
|
else {
|
|
1023
1143
|
logCascade({
|
|
@@ -1043,7 +1163,13 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1043
1163
|
callerCount: impact.directCallers.length,
|
|
1044
1164
|
referenceCount: Math.max(0, referenceCount),
|
|
1045
1165
|
riskFlags: impact.riskFlags,
|
|
1046
|
-
metadata: {
|
|
1166
|
+
metadata: {
|
|
1167
|
+
neighbors: sortedNeighbors.slice(0, 10),
|
|
1168
|
+
candidateNeighborCount: cascadeCandidateCount,
|
|
1169
|
+
eligibleNeighborCount: cascadeEligibleCount,
|
|
1170
|
+
filterDroppedCount: cascadeFilterDroppedCount,
|
|
1171
|
+
budgetTrimmedCount: cascadeBudgetTruncated,
|
|
1172
|
+
},
|
|
1047
1173
|
});
|
|
1048
1174
|
const lspService = getLSPService();
|
|
1049
1175
|
// Hoist passive snapshot once — used for auto-propagating LSPs and fallback path.
|
|
@@ -1066,6 +1192,33 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1066
1192
|
// the latter must not collapse into a clean-looking result (#1104 honesty
|
|
1067
1193
|
// rule, same doctrine as #1023's graph-degraded indeterminate marker).
|
|
1068
1194
|
let fallbackBindingRejected = false;
|
|
1195
|
+
const noLspCandidatePaths = new Set();
|
|
1196
|
+
// #1444: neighbours whose in-lane wait was skipped for the quiet-window
|
|
1197
|
+
// reconcile to answer later. Logged on `cascade_result` so a cascade that
|
|
1198
|
+
// deferred EVERY neighbour is distinguishable from a genuine leaf (both are
|
|
1199
|
+
// `neighborCount: 0` with no output otherwise).
|
|
1200
|
+
let collectLaterSkipped = 0;
|
|
1201
|
+
// #1446 item 5: `recentlyCleanNeighborCache` hits are the whole point of the
|
|
1202
|
+
// cache, but they were never counted — the only visible signal was 267s/day
|
|
1203
|
+
// of touch wall time with no way to tell whether the cache was absorbing
|
|
1204
|
+
// repeat work or every touch was cold. (#1899 removed the sibling
|
|
1205
|
+
// `cacheHits` counter along with the dead `neighborTouchCache`.)
|
|
1206
|
+
let recentlyCleanHits = 0;
|
|
1207
|
+
// F1 (#1446 follow-up): `coldTouches` must be counted at the point each
|
|
1208
|
+
// neighbour's OUTCOME is actually known, not derived from `coldSnapshotPaths`
|
|
1209
|
+
// (finalized earlier, before the cache-hit checks below run against it). Using
|
|
1210
|
+
// the pre-outcome list let a neighbour double-count (cold-snapshot AND cache/
|
|
1211
|
+
// recently-clean hit) or vanish from every bucket (an `activePaths` neighbour —
|
|
1212
|
+
// e.g. Python/Go — that misses both caches). These counters partition the
|
|
1213
|
+
// selected-neighbour outcomes; passive snapshots and no-server selections are
|
|
1214
|
+
// tracked separately instead of silently disappearing from the denominator.
|
|
1215
|
+
// `coldTouches` includes every genuine active touch attempt, while
|
|
1216
|
+
// `touchFailures` is its separate bounded failure subtype.
|
|
1217
|
+
let deferredTouches = 0;
|
|
1218
|
+
let coldTouches = 0;
|
|
1219
|
+
let passiveSnapshotHits = 0;
|
|
1220
|
+
let noLspConfigured = 0;
|
|
1221
|
+
let touchFailures = 0;
|
|
1069
1222
|
if (sortedNeighbors.length > 0) {
|
|
1070
1223
|
const snapshotPaths = sortedNeighbors.filter(shouldReadCascadeFromSnapshot);
|
|
1071
1224
|
const activePaths = sortedNeighbors.filter((n) => !shouldReadCascadeFromSnapshot(n));
|
|
@@ -1080,7 +1233,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1080
1233
|
const snapshotAgeSec = entry
|
|
1081
1234
|
? Math.round((Date.now() - entry.ts) / 1000)
|
|
1082
1235
|
: undefined;
|
|
1083
|
-
const ttlFresh = entry != null && Date.now() - entry.ts <
|
|
1236
|
+
const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_DIAGNOSTICS_TTL_MS;
|
|
1084
1237
|
// #1095: content binding is the INNER gate; TTL stays the outer bound.
|
|
1085
1238
|
// false → the server's diagnostics were computed against a DIFFERENT
|
|
1086
1239
|
// disk state (e.g. the PRE-fix content) — don't trust or
|
|
@@ -1128,6 +1281,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1128
1281
|
// had no remaining purpose and is simply dropped rather than migrated.
|
|
1129
1282
|
const diags = convertLspDiagnostics(entry.diags.filter((d) => d.severity === 1).slice(0, MAX_PER_FILE), neighborPath);
|
|
1130
1283
|
producedLspData = true;
|
|
1284
|
+
passiveSnapshotHits++;
|
|
1131
1285
|
const durationMs = Date.now() - neighborStart;
|
|
1132
1286
|
logCascade({
|
|
1133
1287
|
phase: "neighbor_snapshot",
|
|
@@ -1149,7 +1303,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1149
1303
|
// LSP warning on the neighbor is preserved. Keyed by the primary edit's
|
|
1150
1304
|
// `writeSeq` so a genuinely newer per-edit write still wins the
|
|
1151
1305
|
// WriteOrderingGuard. `observedAt = entry.ts` (the snapshot's own publish
|
|
1152
|
-
// time, up to
|
|
1306
|
+
// time, up to CASCADE_DIAGNOSTICS_TTL_MS old) — NOT now() — so replaying an aging
|
|
1153
1307
|
// snapshot never re-arms the mtime-staleness gate (the same #1092
|
|
1154
1308
|
// re-arming defect this PR fixes for cache hits).
|
|
1155
1309
|
//
|
|
@@ -1179,13 +1333,14 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1179
1333
|
const cacheKey = normalizeMapKey(neighborPath);
|
|
1180
1334
|
const passiveEntry = allDiags.get(cacheKey);
|
|
1181
1335
|
const hasFreshPassiveErrors = passiveEntry != null &&
|
|
1182
|
-
Date.now() - passiveEntry.ts <
|
|
1336
|
+
Date.now() - passiveEntry.ts < CASCADE_DIAGNOSTICS_TTL_MS &&
|
|
1183
1337
|
passiveEntry.diags.some((d) => d.severity === 1);
|
|
1184
1338
|
const recentlyClean = recentlyCleanNeighborCache.get(cacheKey);
|
|
1185
1339
|
if (recentlyClean &&
|
|
1186
1340
|
turnSeq - recentlyClean.turnSeq <= RECENTLY_CLEAN_TTL_TURNS &&
|
|
1187
1341
|
!hasFreshPassiveErrors) {
|
|
1188
1342
|
producedLspData = true;
|
|
1343
|
+
recentlyCleanHits++;
|
|
1189
1344
|
const durationMs = Date.now() - neighborStart;
|
|
1190
1345
|
logCascade({
|
|
1191
1346
|
phase: "neighbor_snapshot",
|
|
@@ -1208,32 +1363,13 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1208
1363
|
durationMs,
|
|
1209
1364
|
};
|
|
1210
1365
|
}
|
|
1211
|
-
//
|
|
1212
|
-
//
|
|
1213
|
-
|
|
1214
|
-
if (cached?.turnSeq === turnSeq && cached?.writeSeq === writeSeq) {
|
|
1215
|
-
producedLspData = true;
|
|
1216
|
-
const durationMs = Date.now() - neighborStart;
|
|
1217
|
-
logCascade({
|
|
1218
|
-
phase: "neighbor_snapshot",
|
|
1219
|
-
filePath,
|
|
1220
|
-
neighborFile: neighborPath,
|
|
1221
|
-
diagnosticCount: cached.diagnostics.length,
|
|
1222
|
-
durationMs,
|
|
1223
|
-
autoPropagate: false,
|
|
1224
|
-
snapshotMissing: false,
|
|
1225
|
-
metadata: { cachedWriteSeq: writeSeq },
|
|
1226
|
-
});
|
|
1227
|
-
return {
|
|
1228
|
-
filePath: neighborPath,
|
|
1229
|
-
reason: neighborReason(importerSet, callerSet, neighborPath),
|
|
1230
|
-
diagnostics: cached.diagnostics,
|
|
1231
|
-
lspTouched: false,
|
|
1232
|
-
durationMs,
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1366
|
+
// #1899: the A5 same-write cache read stood here. See the removal note
|
|
1367
|
+
// at the module's cache declarations — the gate could only pass inside
|
|
1368
|
+
// a single write, which never runs two cascades.
|
|
1235
1369
|
const configuredServerCount = getServersForFileWithConfig(neighborPath).length;
|
|
1236
1370
|
if (configuredServerCount === 0) {
|
|
1371
|
+
noLspConfigured++;
|
|
1372
|
+
noLspCandidatePaths.add(cacheKey);
|
|
1237
1373
|
logCascade({
|
|
1238
1374
|
phase: "neighbor_fallback",
|
|
1239
1375
|
filePath,
|
|
@@ -1245,10 +1381,10 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1245
1381
|
}
|
|
1246
1382
|
// A6: async read to avoid blocking event loop on network-mounted drives
|
|
1247
1383
|
const content = await nodeFs.promises.readFile(neighborPath, "utf8");
|
|
1248
|
-
// #458: tier-aware cascade-lane wait. A Tier-3
|
|
1249
|
-
//
|
|
1250
|
-
//
|
|
1251
|
-
//
|
|
1384
|
+
// #458/#1444: tier-aware cascade-lane wait. A Tier-3 silent server
|
|
1385
|
+
// cannot give this wait an affirmative clean signal. Native TS7 does
|
|
1386
|
+
// publish, but not inside the cold-snapshot budget. In both cases the
|
|
1387
|
+
// in-lane budget is pure cost. Fire the touch (didOpen/
|
|
1252
1388
|
// didChange still happens — the server starts real work) and record
|
|
1253
1389
|
// it as outstanding for the agent_settled quiet window to reconcile
|
|
1254
1390
|
// instead of waiting here. Ambiguous/missing capability data always
|
|
@@ -1261,7 +1397,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1261
1397
|
try {
|
|
1262
1398
|
const snapshots = (await lspService.getCapabilitySnapshots?.(neighborPath)) ?? [];
|
|
1263
1399
|
const tier = classifyCascadeWaitTier(lspService, neighborPath, snapshots);
|
|
1264
|
-
if (tier === "tier3-silent") {
|
|
1400
|
+
if (tier === "tier3-silent" || tier === "collect-later") {
|
|
1265
1401
|
const spawnedForTouch = await lspService.getClientForFile(neighborPath);
|
|
1266
1402
|
if (spawnedForTouch) {
|
|
1267
1403
|
// Sampled BEFORE the touchFile notify: a publish landing
|
|
@@ -1283,6 +1419,14 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1283
1419
|
touchedAt,
|
|
1284
1420
|
});
|
|
1285
1421
|
const durationMs = Date.now() - neighborStart;
|
|
1422
|
+
if (tier === "collect-later")
|
|
1423
|
+
collectLaterSkipped++;
|
|
1424
|
+
// F1: both tier3-silent and collect-later skip the in-lane
|
|
1425
|
+
// wait and record an outstanding touch for the quiet-window
|
|
1426
|
+
// reconcile — neither a cache hit nor a genuine completed
|
|
1427
|
+
// cold touch, so both share this explicit "deferred" bucket
|
|
1428
|
+
// instead of falling out of the partition uncounted.
|
|
1429
|
+
deferredTouches++;
|
|
1286
1430
|
logCascade({
|
|
1287
1431
|
phase: "cascade_tier3_skip",
|
|
1288
1432
|
filePath,
|
|
@@ -1290,11 +1434,14 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1290
1434
|
durationMs,
|
|
1291
1435
|
lspServerCount: configuredServerCount,
|
|
1292
1436
|
coldSnapshot: isColdSnapshot,
|
|
1293
|
-
metadata: {
|
|
1437
|
+
metadata: {
|
|
1438
|
+
serverId: spawnedForTouch.client.serverId,
|
|
1439
|
+
waitTier: tier,
|
|
1440
|
+
},
|
|
1294
1441
|
});
|
|
1295
1442
|
// Deliberately NOT cached as clean/diagnosed — the wait was
|
|
1296
|
-
// skipped, not resolved, so
|
|
1297
|
-
//
|
|
1443
|
+
// skipped, not resolved, so recentlyCleanNeighborCache must
|
|
1444
|
+
// not treat this as a real answer
|
|
1298
1445
|
// (#240 doctrine). Return undefined: the degraded-fallback
|
|
1299
1446
|
// path below still has a chance to surface a passive/stale
|
|
1300
1447
|
// snapshot, same as any other "no fresh data this touch" case.
|
|
@@ -1311,13 +1458,35 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1311
1458
|
// Cold-snapshot neighbors (autoPropagate LSP, server warm) use a tighter
|
|
1312
1459
|
// 1000ms budget — they should respond quickly; we'd rather return zero
|
|
1313
1460
|
// than block cascade for 2s on a slow open.
|
|
1461
|
+
// F1: this is the ONE remaining outcome after cache hit, recently-clean
|
|
1462
|
+
// hit, and tier-aware deferral have all been ruled out — a genuine
|
|
1463
|
+
// active LSP touch is being issued right now. Count it here (an
|
|
1464
|
+
// attempt, whether it resolves, times out, or the promise rejects
|
|
1465
|
+
// below in the allSettled catch) rather than from `coldSnapshotPaths`,
|
|
1466
|
+
// which is finalized before any of the above checks run and includes
|
|
1467
|
+
// neighbours that resolve via cache/recently-clean instead.
|
|
1468
|
+
coldTouches++;
|
|
1469
|
+
// #1720: language-server scope only, matching the tier-aware touch
|
|
1470
|
+
// above (`clientScope: "primary"`, integration.ts:1826). A neighbor's
|
|
1471
|
+
// content did not change — only its import target did — so an
|
|
1472
|
+
// auxiliary scanner's (ast-grep/opengrep/typos) file-local verdict for
|
|
1473
|
+
// it cannot have changed; only cross-file type semantics (the
|
|
1474
|
+
// language server) can. `reconcileCascadeNeighborLspErrors` already
|
|
1475
|
+
// merges errors-only and preserves the neighbor's existing aux
|
|
1476
|
+
// findings by construction (see its doc comment and
|
|
1477
|
+
// `cascadeReconcilableLspErrors` above), so an `"all"`-scope touch
|
|
1478
|
+
// paid full aux notify traffic and aux confirmation-wait latency for
|
|
1479
|
+
// a re-derivation nothing downstream ever reads. `"primary"` resolves
|
|
1480
|
+
// per `neighborPath` (`getClientForFile` → `getServersForFileWithConfig`),
|
|
1481
|
+
// so a multi-language neighbor still gets its OWN language server,
|
|
1482
|
+
// not the primary edit's.
|
|
1314
1483
|
const rawDiags = await lspService.touchFile(neighborPath, content, {
|
|
1315
1484
|
diagnostics: "document",
|
|
1316
1485
|
collectDiagnostics: true,
|
|
1317
1486
|
maxClientWaitMs: isColdSnapshot ? 1000 : 2000,
|
|
1318
1487
|
silent: true,
|
|
1319
1488
|
source: "cascade",
|
|
1320
|
-
clientScope: "
|
|
1489
|
+
clientScope: "primary",
|
|
1321
1490
|
});
|
|
1322
1491
|
if (!rawDiags)
|
|
1323
1492
|
return undefined;
|
|
@@ -1336,6 +1505,11 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1336
1505
|
const confirmed = isConfirmedTouch(rawDiags);
|
|
1337
1506
|
const bindingRejected = readBoundToCurrentDisk(rawDiags) === false;
|
|
1338
1507
|
const inconclusive = readInconclusive(rawDiags);
|
|
1508
|
+
// #1470/#1493: the third, independent reason a touch is unconfirmed — an
|
|
1509
|
+
// auxiliary that never reported, cut off by our grace timer or silent
|
|
1510
|
+
// with nothing published for this content. Logged alongside the other two
|
|
1511
|
+
// so cascade.log alone still tells the three apart.
|
|
1512
|
+
const unconfirmedServerIds = touchCoverageGap(rawDiags);
|
|
1339
1513
|
// #692: `source: "cascade"` no longer overrides `rule` (see the
|
|
1340
1514
|
// doc comment on the sibling call above) — dropped rather than
|
|
1341
1515
|
// migrated to `scanOrigin` since cascade output never touches
|
|
@@ -1345,14 +1519,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1345
1519
|
// wrapper and are unaffected by this copy (the shape-5 fix).
|
|
1346
1520
|
const diags = convertLspDiagnostics(rawDiags.diags.filter((d) => d.severity === 1).slice(0, MAX_PER_FILE), neighborPath);
|
|
1347
1521
|
const durationMs = Date.now() - neighborStart;
|
|
1348
|
-
//
|
|
1349
|
-
if (writeSeq != null) {
|
|
1350
|
-
neighborTouchCache.set(cacheKey, {
|
|
1351
|
-
turnSeq,
|
|
1352
|
-
writeSeq,
|
|
1353
|
-
diagnostics: diags,
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1522
|
+
// #1899: the A5 same-write cache write stood here.
|
|
1356
1523
|
if (diags.length === 0) {
|
|
1357
1524
|
// Only a CONFIRMED clean touch may seed the recently-clean cache
|
|
1358
1525
|
// (#1095: a bound-false touch is unconfirmed, exactly like inconclusive).
|
|
@@ -1385,7 +1552,15 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1385
1552
|
// stays conditional since "bound" carries no extra signal.
|
|
1386
1553
|
metadata: {
|
|
1387
1554
|
inconclusive,
|
|
1388
|
-
|
|
1555
|
+
// #1549: WHICH primary and WHICH deadline, present only when the
|
|
1556
|
+
// touch actually reported itself inconclusive.
|
|
1557
|
+
...(inconclusive && readInconclusiveAttribution(rawDiags)),
|
|
1558
|
+
...(bindingRejected && {
|
|
1559
|
+
bindingState: bindingStateLabel(false),
|
|
1560
|
+
}),
|
|
1561
|
+
...(unconfirmedServerIds.length > 0 && {
|
|
1562
|
+
unconfirmedServerIds: [...unconfirmedServerIds],
|
|
1563
|
+
}),
|
|
1389
1564
|
},
|
|
1390
1565
|
});
|
|
1391
1566
|
// #1093/#1095: a completed, CONFIRMED active touch is a confirmed
|
|
@@ -1411,6 +1586,16 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1411
1586
|
reason: neighborReason(importerSet, callerSet, neighborPath),
|
|
1412
1587
|
diagnostics: diags,
|
|
1413
1588
|
lspTouched: true,
|
|
1589
|
+
...(inconclusive && { inconclusive: true }),
|
|
1590
|
+
// #1459: carry the coverage gap to the agent-facing surface. A
|
|
1591
|
+
// scanner whose breaker was open, whose resync the fan-out gate
|
|
1592
|
+
// deferred, or whom the aux grace timer cut off never looked at this
|
|
1593
|
+
// file — without this the neighbour reaches the agent as
|
|
1594
|
+
// `{ diagnostics: [] }` with no marker, which is the false-clean the
|
|
1595
|
+
// touch already refuses to claim for itself.
|
|
1596
|
+
...(unconfirmedServerIds.length > 0 && {
|
|
1597
|
+
unconfirmedServerIds: [...unconfirmedServerIds],
|
|
1598
|
+
}),
|
|
1414
1599
|
durationMs,
|
|
1415
1600
|
};
|
|
1416
1601
|
}));
|
|
@@ -1423,10 +1608,11 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1423
1608
|
neighbors.push(result.value);
|
|
1424
1609
|
}
|
|
1425
1610
|
else {
|
|
1611
|
+
touchFailures++;
|
|
1426
1612
|
// A3: one failed LSP doesn't kill the rest — fall back to passive snapshot
|
|
1427
1613
|
dbg?.(`cascade neighbor touch error for ${neighborPath}: ${result.reason}`);
|
|
1428
1614
|
const entry = allDiags.get(normalizeMapKey(neighborPath));
|
|
1429
|
-
const ttlFresh = entry != null && Date.now() - entry.ts <
|
|
1615
|
+
const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_DIAGNOSTICS_TTL_MS;
|
|
1430
1616
|
// #1104: consult binding before trusting a TTL-fresh fallback snapshot —
|
|
1431
1617
|
// MATCH #1100/#1095 semantics (false → skip, "unknown" → keep the
|
|
1432
1618
|
// pre-#1104 TTL-only behavior, true → use). Without this, a failed
|
|
@@ -1473,8 +1659,15 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1473
1659
|
}
|
|
1474
1660
|
}
|
|
1475
1661
|
// CR-3/A2: degraded fallback when no neighbor produced trustworthy LSP data —
|
|
1476
|
-
// not merely when the graph returned zero neighbors.
|
|
1477
|
-
|
|
1662
|
+
// not merely when the graph returned zero neighbors. In a mixed run, only
|
|
1663
|
+
// candidates that had no configured LSP may use passive fallback; broadening
|
|
1664
|
+
// to unrelated cache entries would make the partial active result misleading.
|
|
1665
|
+
if (producedLspData && noLspCandidatePaths.size > 0) {
|
|
1666
|
+
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, noLspCandidatePaths);
|
|
1667
|
+
if (bindingRejected)
|
|
1668
|
+
fallbackBindingRejected = true;
|
|
1669
|
+
}
|
|
1670
|
+
else if (!producedLspData) {
|
|
1478
1671
|
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath);
|
|
1479
1672
|
if (bindingRejected)
|
|
1480
1673
|
fallbackBindingRejected = true;
|
|
@@ -1505,6 +1698,12 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1505
1698
|
};
|
|
1506
1699
|
}
|
|
1507
1700
|
const filesWithErrors = visibleNeighbors.filter((n) => n.diagnostics.length > 0).length;
|
|
1701
|
+
const selectedOutcomeCount = passiveSnapshotHits +
|
|
1702
|
+
recentlyCleanHits +
|
|
1703
|
+
deferredTouches +
|
|
1704
|
+
coldTouches +
|
|
1705
|
+
noLspConfigured;
|
|
1706
|
+
const selectedOutcomeGap = Math.max(0, sortedNeighbors.length - selectedOutcomeCount);
|
|
1508
1707
|
logCascade({
|
|
1509
1708
|
phase: "cascade_result",
|
|
1510
1709
|
filePath,
|
|
@@ -1513,9 +1712,60 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1513
1712
|
metadata: {
|
|
1514
1713
|
filesWithErrors,
|
|
1515
1714
|
hasOutput: formatted.length > 0,
|
|
1715
|
+
indeterminateReason: impact.indeterminate?.reason,
|
|
1716
|
+
candidateNeighborCount: cascadeCandidateCount,
|
|
1717
|
+
eligibleNeighborCount: cascadeEligibleCount,
|
|
1718
|
+
filterDroppedCount: cascadeFilterDroppedCount,
|
|
1719
|
+
selectedNeighborCount: sortedNeighbors.length,
|
|
1720
|
+
selectedOutcomeCount,
|
|
1721
|
+
selectedOutcomeGap,
|
|
1722
|
+
passiveSnapshotHits,
|
|
1723
|
+
noLspConfigured,
|
|
1724
|
+
touchFailures,
|
|
1725
|
+
// #1444: >0 means "answers are still outstanding", not "nothing found".
|
|
1726
|
+
collectLaterSkipped,
|
|
1516
1727
|
// Log when cascade ran but found nothing — distinguishes "clean" from "no signal"
|
|
1517
1728
|
noNeighbors: visibleNeighbors.length === 0,
|
|
1518
1729
|
noErrors: visibleNeighbors.length > 0 && filesWithErrors === 0,
|
|
1730
|
+
// #1446 item 5: cache effectiveness as a number instead of an inference
|
|
1731
|
+
// from `coldSnapshot`/`snapshotMissing` flags scattered across
|
|
1732
|
+
// per-neighbor `neighbor_touch`/`neighbor_snapshot` rows.
|
|
1733
|
+
// F1: recentlyCleanHits + deferredTouches + coldTouches
|
|
1734
|
+
// retain their outcome semantics. Passive snapshots and no-server selections
|
|
1735
|
+
// complete the selected-neighbor denominator; touchFailures is a bounded
|
|
1736
|
+
// subtype of coldTouches, not an additional outcome.
|
|
1737
|
+
recentlyCleanHits,
|
|
1738
|
+
deferredTouches,
|
|
1739
|
+
coldTouches,
|
|
1740
|
+
// #1446 item 4: the budget in force and how many eligible candidates
|
|
1741
|
+
// it cut off this run — the correctness half (a truncated run being
|
|
1742
|
+
// silently discarded) is #1443.
|
|
1743
|
+
// #1462: `neighborBudget` is now the DERIVED budget (still "the one in
|
|
1744
|
+
// force"); `neighborBudgetCeiling` is the flat cap, `budgetRemainingMs`
|
|
1745
|
+
// the on-time window left when it was sized, and `budgetZone` the rule
|
|
1746
|
+
// that decided it. Only `budgetZone: "narrowed"` shortens a walk — the
|
|
1747
|
+
// other three keep the flat cap, so a row where budget < ceiling is a
|
|
1748
|
+
// deliberate rescue, never a cold-start stub. `budgetDeliveryWindowMs`
|
|
1749
|
+
// records how long the pipeline (turn_end settle + quiet-window drain)
|
|
1750
|
+
// keeps a slow run alive, which is why the other zones can afford to
|
|
1751
|
+
// stay wide.
|
|
1752
|
+
//
|
|
1753
|
+
// NOTE for whoever reads these rows: `budgetTruncated` INVERTS under a
|
|
1754
|
+
// narrowed budget — it is `eligible - budget`, so a smaller budget
|
|
1755
|
+
// mechanically raises it. Compare truncation WITHIN a zone, never
|
|
1756
|
+
// across one.
|
|
1757
|
+
neighborBudget: cascadeNeighbourBudget,
|
|
1758
|
+
neighborBudgetCeiling: CASCADE_NEIGHBOUR_BUDGET,
|
|
1759
|
+
budgetRemainingMs: cascadeBudgetRemainingMs,
|
|
1760
|
+
budgetZone: cascadeBudgetZone,
|
|
1761
|
+
budgetDeliveryWindowMs: cascadeDeliveryWindowMs,
|
|
1762
|
+
budgetTruncated: cascadeBudgetTruncated,
|
|
1763
|
+
transitiveTruncated,
|
|
1764
|
+
budgetTrimmedCount: cascadeBudgetTruncated,
|
|
1765
|
+
budgetDerivationDisabled: cascadeBudgetZone === "no-rescue-window",
|
|
1766
|
+
budgetDerivationDisabledReason: cascadeBudgetZone === "no-rescue-window"
|
|
1767
|
+
? "no-rescue-window"
|
|
1768
|
+
: undefined,
|
|
1519
1769
|
neighbors: visibleNeighbors.slice(0, 10).map((n) => ({
|
|
1520
1770
|
file: n.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/"),
|
|
1521
1771
|
diagnostics: n.diagnostics.length,
|
|
@@ -1539,6 +1789,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1539
1789
|
diagnosticCount: diagCount,
|
|
1540
1790
|
skipReason: "indeterminate",
|
|
1541
1791
|
indeterminate: impact.indeterminate,
|
|
1792
|
+
selectedNeighborPaths: sortedNeighbors.slice(),
|
|
1542
1793
|
};
|
|
1543
1794
|
}
|
|
1544
1795
|
const skipReason = visibleNeighbors.length === 0 ? "no_neighbors" : "clean";
|
|
@@ -1616,7 +1867,7 @@ function applyCascadeDeltaBaselines(neighbors) {
|
|
|
1616
1867
|
* because its content binding was rejected (#1104) — the caller folds this
|
|
1617
1868
|
* into the run-level `fallbackBindingRejected` flag for the honesty check.
|
|
1618
1869
|
*/
|
|
1619
|
-
function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath) {
|
|
1870
|
+
function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, allowedPaths) {
|
|
1620
1871
|
const now = Date.now();
|
|
1621
1872
|
const seen = new Set(neighbors.map((n) => normalizeMapKey(n.filePath)));
|
|
1622
1873
|
let bindingRejected = false;
|
|
@@ -1625,6 +1876,8 @@ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, fi
|
|
|
1625
1876
|
const diagKey = normalizeMapKey(diagPath);
|
|
1626
1877
|
if (diagKey === normalizedFileKey || seen.has(diagKey))
|
|
1627
1878
|
continue;
|
|
1879
|
+
if (allowedPaths && !allowedPaths.has(diagKey))
|
|
1880
|
+
continue;
|
|
1628
1881
|
if (primaryFilesThisTurn.has(diagKey))
|
|
1629
1882
|
continue;
|
|
1630
1883
|
if (isExternalOrVendorFile(diagPath, cwd))
|
|
@@ -1638,7 +1891,7 @@ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, fi
|
|
|
1638
1891
|
continue;
|
|
1639
1892
|
if (!nodeFs.existsSync(diagPath))
|
|
1640
1893
|
continue;
|
|
1641
|
-
if (now - ts >
|
|
1894
|
+
if (now - ts > CASCADE_DIAGNOSTICS_TTL_MS)
|
|
1642
1895
|
continue;
|
|
1643
1896
|
// #1104: a TTL-fresh entry is not automatically trustworthy — consult
|
|
1644
1897
|
// binding the same way the reconcile path (#1100) and the touch-error
|
|
@@ -1742,7 +1995,7 @@ export async function dispatchLintWithResult(filePath, cwd, pi, modifiedRanges,
|
|
|
1742
1995
|
// Default true preserves the per-edit fast path (errors only). Callers that
|
|
1743
1996
|
// want the full picture (warnings + structural smells), e.g. the MCP review
|
|
1744
1997
|
// facade, pass blockingOnly=false to run every runner.
|
|
1745
|
-
const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? true, modifiedRanges, options?.projectRoot, options?.writeIndex);
|
|
1998
|
+
const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? true, modifiedRanges, options?.projectRoot, options?.writeIndex, options?.telemetryModel, options?.telemetryProvider);
|
|
1746
1999
|
sessionFacts.clearFileFactsFor(ctx.filePath);
|
|
1747
2000
|
const kind = ctx.kind;
|
|
1748
2001
|
if (!kind) {
|