@herbertgao/pi-extensions 2026.8.6 → 2026.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
- package/node_modules/@narumitw/pi-btw/README.md +120 -111
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +10 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
- package/node_modules/pi-lens/CHANGELOG.md +999 -0
- package/node_modules/pi-lens/README.md +117 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
- package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
- package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
- package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
- package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
- package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
- package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +61407 -48577
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/docs/agent-guide.md +6 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +19 -5
- package/node_modules/pi-lens/docs/globalconfig.md +3 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +19 -1
- package/node_modules/pi-lens/docs/usage.md +1 -0
- package/node_modules/pi-lens/package.json +27 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
- package/node_modules/pi-mcp-adapter/README.md +47 -5
- package/node_modules/pi-mcp-adapter/cli.js +163 -1
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +25 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +3 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +37 -0
- package/node_modules/pi-web-access/README.md +32 -13
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
- package/node_modules/pi-web-access/curator-page.ts +6 -3
- package/node_modules/pi-web-access/curator-server.ts +4 -1
- package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +40 -5
- package/node_modules/pi-web-access/firecrawl.ts +18 -2
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/github-extract.ts +65 -36
- package/node_modules/pi-web-access/index.ts +60 -61
- package/node_modules/pi-web-access/openai-search.ts +17 -6
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/pdf-extract.ts +18 -5
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +44 -22
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/package.json +14 -14
|
@@ -27,7 +27,9 @@ import { flushReviewGraphLogSync, logReviewGraph, makeReviewGraphBuildMetadata,
|
|
|
27
27
|
import { getSharedTreeSitterClient } from "../tree-sitter-shared.js";
|
|
28
28
|
import { TreeSitterSymbolExtractor, } from "../tree-sitter-symbol-extractor.js";
|
|
29
29
|
import { withTreeSitterRoot } from "../tree-sitter-shared.js";
|
|
30
|
+
import { incrementDegradationCount } from "../degradation-ledger.js";
|
|
30
31
|
import { resolveGitIdentity } from "./git-identity.js";
|
|
32
|
+
import { formatReviewGraphRevisionDriftNote, } from "./revision-drift.js";
|
|
31
33
|
import { buildSymbolId } from "./symbol-id.js";
|
|
32
34
|
import { clearReviewGraphFileIr, getFreshReviewGraphFileIr, reviewGraphIrContentHash, } from "./shared-extraction-ir.js";
|
|
33
35
|
// v3 (#260): test files are no longer indexed. Bumping the version makes
|
|
@@ -92,10 +94,21 @@ const MAIN_KIND_EXTENSIONS = Array.from(MAIN_KINDS).flatMap((kind) => KIND_EXTEN
|
|
|
92
94
|
export const REVIEW_GRAPH_SOURCE_EXTENSIONS = MAIN_KIND_EXTENSIONS;
|
|
93
95
|
const CHANGED_SYMBOLS_PREFIX = "session.reviewGraph.changedSymbols:";
|
|
94
96
|
const extractorCache = new Map();
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
97
|
+
// IN-FLIGHT Promise cache: deduplicates CONCURRENT buildOrUpdateGraph calls for
|
|
98
|
+
// the same (cwd, changedFiles). A separate workspace cache below preserves the
|
|
99
|
+
// expensive parsed graph across invocations when source file mtimes/sizes have
|
|
100
|
+
// not changed.
|
|
101
|
+
//
|
|
102
|
+
// #1962: an entry lives only while its build is PENDING — `buildOrUpdateGraph`
|
|
103
|
+
// deletes it on settle, success or failure alike. It used to delete only on
|
|
104
|
+
// rejection, so a settled promise for a SKIPPED or COMPLETED build answered
|
|
105
|
+
// every later call for the same key forever. The pipeline's `clearGraphCache()`
|
|
106
|
+
// (pipeline.ts) was the only thing that ever removed it, and the background
|
|
107
|
+
// build project_report kicks off never goes through the pipeline: four
|
|
108
|
+
// project_report calls over 37s produced ONE build_started, while the tool told
|
|
109
|
+
// the agent a retry had been started each time. That is the process-lifetime
|
|
110
|
+
// latch shape from AGENTS.md — dedupe state whose lifetime must be the
|
|
111
|
+
// operation's, not the process's.
|
|
99
112
|
const _buildCache = new Map();
|
|
100
113
|
const _workspaceGraphCache = new Map();
|
|
101
114
|
const REVIEW_GRAPH_MAX_WARM_WORKSPACES = 8;
|
|
@@ -117,13 +130,37 @@ function clearWorkspaceGraphTimer(entry) {
|
|
|
117
130
|
clearTimeout(entry.idleTimer);
|
|
118
131
|
entry.idleTimer = undefined;
|
|
119
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The canonical workspace identity for a `_buildCache` key (#1962 review F2).
|
|
135
|
+
*
|
|
136
|
+
* `path.resolve` collapses `.`/`..` segments and anchors a relative path on
|
|
137
|
+
* every OS; `normalizeMapKey` folds separator and casing, and it does that by
|
|
138
|
+
* probing the filesystem (`realpathSync.native`) rather than branching on
|
|
139
|
+
* `process.platform`. Both steps are idempotent, so feeding this an
|
|
140
|
+
* already-canonical key — which the two read sites below do — returns it
|
|
141
|
+
* unchanged.
|
|
142
|
+
*/
|
|
143
|
+
function buildCacheWorkspaceKey(cwd) {
|
|
144
|
+
return normalizeMapKey(path.resolve(cwd));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The workspace half of an existing `_buildCache` key. `buildCacheKey`
|
|
148
|
+
* canonicalizes that half at construction, so this is a plain split.
|
|
149
|
+
*/
|
|
150
|
+
function buildCacheKeyWorkspace(buildKey) {
|
|
151
|
+
const separator = buildKey.indexOf("|");
|
|
152
|
+
return separator >= 0 ? buildKey.slice(0, separator) : undefined;
|
|
153
|
+
}
|
|
120
154
|
function evictWorkspaceGraph(key, entry) {
|
|
121
155
|
if (_workspaceGraphCache.get(key) !== entry)
|
|
122
156
|
return;
|
|
123
157
|
clearWorkspaceGraphTimer(entry);
|
|
158
|
+
// `key` is a workspace-cache key; run it through the build-key derivation so
|
|
159
|
+
// both sides of this comparison are canonical the same way. Idempotent, so
|
|
160
|
+
// an already-canonical key passes through untouched (#1962 review F2).
|
|
161
|
+
const buildWorkspace = buildCacheWorkspaceKey(key);
|
|
124
162
|
for (const buildKey of _buildCache.keys()) {
|
|
125
|
-
|
|
126
|
-
if (separator >= 0 && normalizeMapKey(buildKey.slice(0, separator)) === key) {
|
|
163
|
+
if (buildCacheKeyWorkspace(buildKey) === buildWorkspace) {
|
|
127
164
|
_buildCache.delete(buildKey);
|
|
128
165
|
}
|
|
129
166
|
}
|
|
@@ -254,8 +291,12 @@ export function clearReviewGraphWorkspaceCache(cwd) {
|
|
|
254
291
|
}
|
|
255
292
|
else {
|
|
256
293
|
const normalized = normalizeMapKey(cwd);
|
|
294
|
+
// Compare the key's WORKSPACE half only, both sides canonicalized the same
|
|
295
|
+
// way. The old form normalized the whole key, changed-file list included,
|
|
296
|
+
// which is neither meaningful nor cheap (#1962 review F2).
|
|
297
|
+
const buildWorkspace = buildCacheWorkspaceKey(cwd);
|
|
257
298
|
for (const key of _buildCache.keys()) {
|
|
258
|
-
if (
|
|
299
|
+
if (buildCacheKeyWorkspace(key) === buildWorkspace) {
|
|
259
300
|
_buildCache.delete(key);
|
|
260
301
|
}
|
|
261
302
|
}
|
|
@@ -440,8 +481,11 @@ export function getCachedReviewGraph(cwd) {
|
|
|
440
481
|
// skip the disk read. loadPersistedGraph already rebuilt the indexes.
|
|
441
482
|
// #300: this read is BLIND — nothing downstream content-verifies it, so a
|
|
442
483
|
// stamped snapshot from a different HEAD/worktree must be dropped here.
|
|
484
|
+
// #1961: a snapshot stamped for a DIFFERENT WORKTREE is dropped here; one
|
|
485
|
+
// stamped at a different HEAD is served and marked drifted. See
|
|
486
|
+
// loadPersistedGraph for the one-policy rationale.
|
|
443
487
|
const disk = loadPersistedGraph(cwd, {
|
|
444
|
-
|
|
488
|
+
verifyWorktreeIdentity: true,
|
|
445
489
|
allowPartial: true,
|
|
446
490
|
});
|
|
447
491
|
if (!disk)
|
|
@@ -451,9 +495,39 @@ export function getCachedReviewGraph(cwd) {
|
|
|
451
495
|
fileSignatures: disk.fileSignatures,
|
|
452
496
|
fileHashes: disk.fileHashes,
|
|
453
497
|
graph: disk.graph,
|
|
498
|
+
// Durable half of the drift fact. The current-HEAD half is never cached
|
|
499
|
+
// beside it — see getReviewGraphRevisionDrift (#1961 review F3).
|
|
500
|
+
...(disk.stampedHead ? { snapshotStampedHead: disk.stampedHead } : {}),
|
|
454
501
|
});
|
|
455
502
|
return disk.graph;
|
|
456
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* Revision drift for `cwd`'s currently cached graph, computed NOW (#1961).
|
|
506
|
+
*
|
|
507
|
+
* Returns the stamped/current commit pair when the warm entry came from a disk
|
|
508
|
+
* snapshot whose stamp names a commit other than the worktree's HEAD at this
|
|
509
|
+
* instant, and `undefined` otherwise — including the case where HEAD has moved
|
|
510
|
+
* BACK to the stamped commit, which resolves the drift and must clear the note.
|
|
511
|
+
*
|
|
512
|
+
* Derived, never stored: caching the pair is what made the first version of
|
|
513
|
+
* this feature report a commit that had since stopped being HEAD, and keep
|
|
514
|
+
* reporting drift after it was resolved. Callers ask on every render.
|
|
515
|
+
*/
|
|
516
|
+
export function getReviewGraphRevisionDrift(cwd) {
|
|
517
|
+
const entry = _workspaceGraphCache.get(normalizeMapKey(cwd));
|
|
518
|
+
const stampedHead = entry?.snapshotStampedHead;
|
|
519
|
+
if (!stampedHead)
|
|
520
|
+
return undefined;
|
|
521
|
+
const current = resolveGitIdentity(cwd);
|
|
522
|
+
// Unresolvable identity is "can't tell", never "drifted" — the same
|
|
523
|
+
// fail-open rule loadPersistedGraph applies to the worktree check.
|
|
524
|
+
if (!current || current.headCommit === stampedHead)
|
|
525
|
+
return undefined;
|
|
526
|
+
return { stampedHead, currentHead: current.headCommit };
|
|
527
|
+
}
|
|
528
|
+
// Re-exported so a consumer that already imports the builder dynamically
|
|
529
|
+
// (module_report) gets the accessor and its renderer from one place (#1961).
|
|
530
|
+
export { formatReviewGraphRevisionDriftNote };
|
|
457
531
|
function makeCtx(filePath, cwd, facts) {
|
|
458
532
|
return {
|
|
459
533
|
filePath,
|
|
@@ -888,6 +962,29 @@ const LEGACY_GRAPH_CACHE_FILENAME = "review-graph.json";
|
|
|
888
962
|
// laundered to a consumer as a complete graph. It is read back exclusively by
|
|
889
963
|
// the full-build resume path (`loadReviewGraphCheckpoint`).
|
|
890
964
|
const GRAPH_CHECKPOINT_FILENAME = "review-graph.checkpoint.json.gz";
|
|
965
|
+
/**
|
|
966
|
+
* Record what the blind read decided about a stamped snapshot (#1961).
|
|
967
|
+
*
|
|
968
|
+
* Bounded the way AGENTS.md requires and `bounded-telemetry.ts` documents: the
|
|
969
|
+
* ledger counts EVERY occurrence exactly, and only the rising edge per
|
|
970
|
+
* (verdict, cwd) also writes the detailed `review-graph.log` record. The
|
|
971
|
+
* accessor runs on every module_report / lens-engine / project_report call, so
|
|
972
|
+
* an unbounded record here would flood the log during a single navigation
|
|
973
|
+
* session. No second latch: the rising edge comes from the ledger's own tally.
|
|
974
|
+
*/
|
|
975
|
+
function logSnapshotReadVerdict(cwd, phase, reason) {
|
|
976
|
+
const verdict = phase === "snapshot_read_dropped" ? "dropped" : "drifted";
|
|
977
|
+
const isRisingEdge = incrementDegradationCount({
|
|
978
|
+
kind: "review-graph-snapshot-read",
|
|
979
|
+
// Subject keeps BOTH discriminators, so aggregation still answers which
|
|
980
|
+
// workspace and which verdict after the detailed records stop.
|
|
981
|
+
subject: `${verdict}:${normalizeMapKey(cwd)}`,
|
|
982
|
+
reason,
|
|
983
|
+
});
|
|
984
|
+
if (!isRisingEdge)
|
|
985
|
+
return;
|
|
986
|
+
logReviewGraph({ cwd, phase, reason });
|
|
987
|
+
}
|
|
891
988
|
function loadPersistedGraph(cwd, opts) {
|
|
892
989
|
const cacheDir = path.join(getProjectDataDir(cwd), "cache");
|
|
893
990
|
const cachePath = path.join(cacheDir, GRAPH_CACHE_FILENAME);
|
|
@@ -907,24 +1004,33 @@ function loadPersistedGraph(cwd, opts) {
|
|
|
907
1004
|
return null;
|
|
908
1005
|
if (data.coverage?.partial && !opts?.allowPartial)
|
|
909
1006
|
return null;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1007
|
+
// #1961: ONE verification policy across both load paths — verify tree
|
|
1008
|
+
// IDENTITY, never revision. A HEAD move says nothing about file contents,
|
|
1009
|
+
// and the build path's tier-2 load has said so since #300 (see the comment
|
|
1010
|
+
// above its `loadPersistedGraph(cwd)` call): dropping on every HEAD move
|
|
1011
|
+
// forces a full whole-repo rebuild after each plain `git commit`. The BLIND
|
|
1012
|
+
// read path used to do exactly that, which is why a snapshot survived a
|
|
1013
|
+
// median of ~12 minutes before every reader saw "graph: cold".
|
|
1014
|
+
//
|
|
1015
|
+
// What stays: `worktreeRoot`. A snapshot stamped for a DIFFERENT worktree
|
|
1016
|
+
// reached this data dir through slug reuse, so it describes another tree
|
|
1017
|
+
// and nothing downstream would catch it — this read is blind. What goes:
|
|
1018
|
+
// the `headCommit` equality drop. A revision difference is now REPORTED
|
|
1019
|
+
// (`stampedHead` below → `getReviewGraphRevisionDrift` → computeTrust and
|
|
1020
|
+
// module_report) instead of hiding the graph. Any resolution failure
|
|
1021
|
+
// (non-git, unreadable HEAD) yields undefined from resolveGitIdentity —
|
|
1022
|
+
// "can't verify," not a mismatch, so it does not drop the snapshot.
|
|
1023
|
+
let stampedHead;
|
|
1024
|
+
if (opts?.verifyWorktreeIdentity && data.gitStamp) {
|
|
922
1025
|
const current = resolveGitIdentity(cwd);
|
|
923
|
-
if (current &&
|
|
924
|
-
(
|
|
925
|
-
current.worktreeRoot !== data.gitStamp.worktreeRoot)) {
|
|
1026
|
+
if (current && current.worktreeRoot !== data.gitStamp.worktreeRoot) {
|
|
1027
|
+
logSnapshotReadVerdict(cwd, "snapshot_read_dropped", "worktree_mismatch");
|
|
926
1028
|
return null;
|
|
927
1029
|
}
|
|
1030
|
+
if (current && current.headCommit !== data.gitStamp.headCommit) {
|
|
1031
|
+
stampedHead = data.gitStamp.headCommit;
|
|
1032
|
+
logSnapshotReadVerdict(cwd, "snapshot_read_drifted", "head_moved");
|
|
1033
|
+
}
|
|
928
1034
|
}
|
|
929
1035
|
const graph = {
|
|
930
1036
|
version: data.version,
|
|
@@ -944,6 +1050,7 @@ function loadPersistedGraph(cwd, opts) {
|
|
|
944
1050
|
fileSignatures: new Map(data.fileSignatures ?? []),
|
|
945
1051
|
fileHashes: new Map(data.fileHashes ?? []),
|
|
946
1052
|
graph,
|
|
1053
|
+
...(stampedHead ? { stampedHead } : {}),
|
|
947
1054
|
};
|
|
948
1055
|
}
|
|
949
1056
|
catch {
|
|
@@ -1951,8 +2058,13 @@ function deleteReviewGraphCheckpoint(cwd) {
|
|
|
1951
2058
|
/**
|
|
1952
2059
|
* Read back a checkpoint for `cwd`, gated on the graph version (single source of
|
|
1953
2060
|
* truth: {@link REVIEW_GRAPH_VERSION}) and, when both stamps resolve, the git
|
|
1954
|
-
* identity — the same
|
|
1955
|
-
*
|
|
2061
|
+
* WORKTREE identity — the same one-policy guard `loadPersistedGraph` uses, and
|
|
2062
|
+
* for the same reason (#1961): the resume path content-verifies every processed
|
|
2063
|
+
* file by hash below (`contentHashEntry` vs `processedHashes`) and evicts the
|
|
2064
|
+
* stale ones, so revision equality proves nothing the hashes do not already
|
|
2065
|
+
* prove. Dropping on a HEAD move threw away a whole resumable partial build
|
|
2066
|
+
* after each plain `git commit`. Returns null (and best-effort deletes an
|
|
2067
|
+
* unusable file) when absent/stale/corrupt.
|
|
1956
2068
|
* The returned graph carries `persistCoverage.inProgress` so it can never be
|
|
1957
2069
|
* mistaken for a complete graph if it escapes the resume path.
|
|
1958
2070
|
*/
|
|
@@ -1984,13 +2096,11 @@ function loadReviewGraphCheckpoint(cwd) {
|
|
|
1984
2096
|
}
|
|
1985
2097
|
if (data.gitStamp) {
|
|
1986
2098
|
const current = resolveGitIdentity(cwd);
|
|
1987
|
-
if (current &&
|
|
1988
|
-
(current.headCommit !== data.gitStamp.headCommit ||
|
|
1989
|
-
current.worktreeRoot !== data.gitStamp.worktreeRoot)) {
|
|
2099
|
+
if (current && current.worktreeRoot !== data.gitStamp.worktreeRoot) {
|
|
1990
2100
|
logReviewGraph({
|
|
1991
2101
|
cwd,
|
|
1992
2102
|
phase: "checkpoint_discarded",
|
|
1993
|
-
reason: "
|
|
2103
|
+
reason: "worktree_mismatch",
|
|
1994
2104
|
});
|
|
1995
2105
|
deleteReviewGraphCheckpoint(cwd);
|
|
1996
2106
|
return null;
|
|
@@ -3904,6 +4014,15 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
3904
4014
|
const graph = resumed?.graph ?? createEmptyGraph();
|
|
3905
4015
|
const filesToExtract = resumed?.remaining ?? filesToBuild;
|
|
3906
4016
|
const treeSitterClient = getSharedTreeSitterClient();
|
|
4017
|
+
// #1941: grow the tree cache to span THIS pass's actual per-parse working
|
|
4018
|
+
// set before parsing starts — filesToExtract, not filesToBuild. On a cold
|
|
4019
|
+
// build they're the same array; on a resumed build filesToExtract is only
|
|
4020
|
+
// the checkpoint's remaining (unprocessed) files, since resumed files are
|
|
4021
|
+
// reused from the checkpoint graph and never re-parsed here. Sizing to
|
|
4022
|
+
// filesToBuild on a resume would over-grow the cache for files this pass
|
|
4023
|
+
// never touches. Same #1715 pattern as scanner.ts:147 — monotonic and
|
|
4024
|
+
// ceiling-bounded inside ensureTreeCacheCapacity itself.
|
|
4025
|
+
treeSitterClient?.ensureTreeCacheCapacity(filesToExtract.length);
|
|
3907
4026
|
const extractionStartedAt = Date.now();
|
|
3908
4027
|
// Seeded with the reused files' hashes on resume so the completed snapshot
|
|
3909
4028
|
// still records a hash for every file (needed by #202 incremental next time).
|
|
@@ -4045,8 +4164,42 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4045
4164
|
facts.setSessionFact("session.reviewGraph", graph);
|
|
4046
4165
|
return graph;
|
|
4047
4166
|
}
|
|
4167
|
+
/**
|
|
4168
|
+
* The one place the in-flight dedupe key is derived — and the one place its
|
|
4169
|
+
* workspace half is normalized (#1962 review F2).
|
|
4170
|
+
*
|
|
4171
|
+
* The key used to interpolate the caller's RAW `cwd`. Callers hand in whatever
|
|
4172
|
+
* path they hold: `runtime-session.ts:1562`, `lens-map.ts:1243`, and
|
|
4173
|
+
* `mcp/cli.ts:59` pass an unnormalized root, while project_report's trigger
|
|
4174
|
+
* passes `normalizeMapKey(path.resolve(cwd))`. On Windows those differ by
|
|
4175
|
+
* separator and casing alone, so ONE workspace produced two live `_buildCache`
|
|
4176
|
+
* entries: two concurrent full builds of the same repo (the #256 two-build OOM
|
|
4177
|
+
* shape), and an `isGraphBuildInFlight` probe that answered about a key nobody
|
|
4178
|
+
* else used. Folding here makes every caller land on one key without any of
|
|
4179
|
+
* them having to know that.
|
|
4180
|
+
*
|
|
4181
|
+
* The derivation lives in {@link buildCacheWorkspaceKey}, which the two sites
|
|
4182
|
+
* that read build keys BACK also use, so write and read agree by construction
|
|
4183
|
+
* rather than by coincidence.
|
|
4184
|
+
*/
|
|
4185
|
+
function buildCacheKey(cwd, changedFiles) {
|
|
4186
|
+
return `${buildCacheWorkspaceKey(cwd)}|${[...changedFiles].sort((a, b) => a.localeCompare(b)).join(",")}`;
|
|
4187
|
+
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Whether a build for this exact (cwd, changedFiles) key is PENDING right now
|
|
4190
|
+
* (#1962). Callers that report to a user — project_report's cold-path trigger —
|
|
4191
|
+
* read this BEFORE calling `buildOrUpdateGraph`, so "a retry was started" is
|
|
4192
|
+
* only said when a build actually started rather than when an in-flight one
|
|
4193
|
+
* absorbed the call. Check and call must happen in the same synchronous block;
|
|
4194
|
+
* anything awaited between them reopens the race this exists to close.
|
|
4195
|
+
*/
|
|
4196
|
+
export function isGraphBuildInFlight(cwd, changedFiles = []) {
|
|
4197
|
+
return _buildCache.has(buildCacheKey(cwd, changedFiles));
|
|
4198
|
+
}
|
|
4048
4199
|
export function buildOrUpdateGraph(cwd, changedFiles, facts, seqHint) {
|
|
4049
|
-
const cacheKey =
|
|
4200
|
+
const cacheKey = buildCacheKey(cwd, changedFiles);
|
|
4201
|
+
// Only a PENDING build is here (see `_buildCache`), so this dedupes genuine
|
|
4202
|
+
// concurrency and nothing else.
|
|
4050
4203
|
const cached = _buildCache.get(cacheKey);
|
|
4051
4204
|
if (cached)
|
|
4052
4205
|
return cached;
|
|
@@ -4117,7 +4270,6 @@ export function buildOrUpdateGraph(cwd, changedFiles, facts, seqHint) {
|
|
|
4117
4270
|
return graph;
|
|
4118
4271
|
})
|
|
4119
4272
|
.catch((err) => {
|
|
4120
|
-
_buildCache.delete(cacheKey);
|
|
4121
4273
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4122
4274
|
recordBuildAttempt(cwd, "failed", reason, buildId);
|
|
4123
4275
|
logReviewGraph({
|
|
@@ -4138,6 +4290,17 @@ export function buildOrUpdateGraph(cwd, changedFiles, facts, seqHint) {
|
|
|
4138
4290
|
throw err;
|
|
4139
4291
|
});
|
|
4140
4292
|
_buildCache.set(cacheKey, promise);
|
|
4293
|
+
// #1962: the entry's lifetime is the BUILD's, not the process's. Settling —
|
|
4294
|
+
// fulfilled, skipped, or rejected — releases the key so the next caller
|
|
4295
|
+
// really builds. The identity guard means a newer build that already claimed
|
|
4296
|
+
// the key survives this older build's cleanup. `then(fn, fn)` rather than
|
|
4297
|
+
// `finally` so the derived promise handles the rejection instead of raising
|
|
4298
|
+
// an unhandled one; the returned `promise` is unchanged either way.
|
|
4299
|
+
const release = () => {
|
|
4300
|
+
if (_buildCache.get(cacheKey) === promise)
|
|
4301
|
+
_buildCache.delete(cacheKey);
|
|
4302
|
+
};
|
|
4303
|
+
promise.then(release, release);
|
|
4141
4304
|
return promise;
|
|
4142
4305
|
}
|
|
4143
4306
|
/**
|
|
@@ -53,6 +53,14 @@ export function computeTransitiveImpact(graph, seedFile, options) {
|
|
|
53
53
|
continue;
|
|
54
54
|
if (visited.has(edge.from))
|
|
55
55
|
continue;
|
|
56
|
+
if (hits.length >= maxHits) {
|
|
57
|
+
return {
|
|
58
|
+
seedFile: normalized,
|
|
59
|
+
hits,
|
|
60
|
+
truncated: true,
|
|
61
|
+
maxDepthReached,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
56
64
|
visited.add(edge.from);
|
|
57
65
|
const dependent = graph.nodes.get(edge.from);
|
|
58
66
|
const hitDepth = node.depth + 1;
|
|
@@ -63,9 +71,6 @@ export function computeTransitiveImpact(graph, seedFile, options) {
|
|
|
63
71
|
depth: hitDepth,
|
|
64
72
|
relation: edge.kind,
|
|
65
73
|
});
|
|
66
|
-
if (hits.length >= maxHits) {
|
|
67
|
-
return { seedFile: normalized, hits, truncated: true, maxDepthReached };
|
|
68
|
-
}
|
|
69
74
|
next.push({ id: edge.from, depth: hitDepth });
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -73,6 +78,44 @@ export function computeTransitiveImpact(graph, seedFile, options) {
|
|
|
73
78
|
}
|
|
74
79
|
return { seedFile: normalized, hits, truncated: false, maxDepthReached };
|
|
75
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* #1550: evidence for a `missing_node` verdict — the node id sought and the
|
|
83
|
+
* nearest thing the graph actually held. Runs ONLY on the miss path (one pass
|
|
84
|
+
* over `fileNodes`, which the same call already consulted), so the hot path is
|
|
85
|
+
* untouched. Keys are compared as the graph keys them (`normalizeMapKey`), so a
|
|
86
|
+
* key-shape divergence shows up as "nearest is the same path" rather than
|
|
87
|
+
* hiding behind a bare "missing".
|
|
88
|
+
*/
|
|
89
|
+
function describeMissingNode(graph, normalizedFile) {
|
|
90
|
+
const lastSlash = normalizedFile.lastIndexOf("/");
|
|
91
|
+
const dir = lastSlash === -1 ? "" : normalizedFile.slice(0, lastSlash);
|
|
92
|
+
const base = normalizedFile.slice(lastSlash + 1);
|
|
93
|
+
const stem = base.replace(/\.[^./]*$/, "");
|
|
94
|
+
let sameDirFileCount = 0;
|
|
95
|
+
let sameDirSibling;
|
|
96
|
+
let sameStemTwin;
|
|
97
|
+
for (const known of graph.fileNodes.keys()) {
|
|
98
|
+
const knownLastSlash = known.lastIndexOf("/");
|
|
99
|
+
const knownDir = knownLastSlash === -1 ? "" : known.slice(0, knownLastSlash);
|
|
100
|
+
const knownBase = known.slice(knownLastSlash + 1);
|
|
101
|
+
if (knownDir === dir) {
|
|
102
|
+
sameDirFileCount += 1;
|
|
103
|
+
sameDirSibling ??= known;
|
|
104
|
+
// A same-stem twin in the same directory is the strongest hint —
|
|
105
|
+
// an extension/compiled-twin resolution miss rather than an absent file.
|
|
106
|
+
if (sameStemTwin === undefined && knownBase.replace(/\.[^./]*$/, "") === stem) {
|
|
107
|
+
sameStemTwin = known;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
soughtNodeId: `file:${normalizedFile}`,
|
|
113
|
+
graphFileCount: graph.fileNodes.size,
|
|
114
|
+
symbolNodeCount: graph.symbolNodesByFile.get(normalizedFile)?.length ?? 0,
|
|
115
|
+
sameDirFileCount,
|
|
116
|
+
nearestFile: sameStemTwin ?? sameDirSibling,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
76
119
|
export function computeImpactCascade(graph, changedFile, moduleGraph) {
|
|
77
120
|
const normalizedFile = normalizeMapKey(changedFile);
|
|
78
121
|
const fileNodeId = graph.fileNodes.get(normalizedFile);
|
|
@@ -111,7 +154,12 @@ export function computeImpactCascade(graph, changedFile, moduleGraph) {
|
|
|
111
154
|
directCallers: [],
|
|
112
155
|
neighborFiles: [],
|
|
113
156
|
riskFlags: [],
|
|
114
|
-
indeterminate: {
|
|
157
|
+
indeterminate: {
|
|
158
|
+
reason: "missing_node",
|
|
159
|
+
// #1550: the reason alone forced a manual re-derivation on every
|
|
160
|
+
// occurrence. Attach the evidence (log-only — see the type doc).
|
|
161
|
+
diagnostic: describeMissingNode(graph, normalizedFile),
|
|
162
|
+
},
|
|
115
163
|
};
|
|
116
164
|
}
|
|
117
165
|
const changedSymbols = graph.changedSymbolsByFile.get(normalizedFile) ?? [];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revision-drift evidence for a served review-graph snapshot, and the one
|
|
3
|
+
* wording every consumer renders it with (#1961).
|
|
4
|
+
*
|
|
5
|
+
* A separate module on purpose. `project-report.ts` and `module-report.ts`
|
|
6
|
+
* import the builder DYNAMICALLY, to keep that large module (and its
|
|
7
|
+
* tree-sitter dependencies) off their eager load path. Both still need this
|
|
8
|
+
* caveat's wording synchronously, and two hand-written copies of a warning
|
|
9
|
+
* sentence drift apart. This file imports nothing, so depending on it costs
|
|
10
|
+
* nothing.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The one wording for the drift caveat, so `project_report`'s trust note and
|
|
14
|
+
* `module_report`'s warning cannot say different things about the same fact.
|
|
15
|
+
* Short commit ids only.
|
|
16
|
+
*/
|
|
17
|
+
export function formatReviewGraphRevisionDriftNote(drift) {
|
|
18
|
+
return (`Graph was built at commit ${drift.stampedHead.slice(0, 8)}; HEAD is now ` +
|
|
19
|
+
`${drift.currentHead.slice(0, 8)}. Results below reflect the earlier ` +
|
|
20
|
+
"revision — run pilens_rebuild or re-analyze to refresh.");
|
|
21
|
+
}
|
|
@@ -490,11 +490,14 @@ export function getModuleSourceFiles(moduleRoot, maxFiles = 20) {
|
|
|
490
490
|
}
|
|
491
491
|
if (!entry.isFile())
|
|
492
492
|
continue;
|
|
493
|
+
// #1974: extension gate before isIgnored — isIgnored recompiles
|
|
494
|
+
// minimatch patterns per ancestor dir per call, so it should only run
|
|
495
|
+
// for files the cheap extension check already flags as source.
|
|
496
|
+
if (!SOURCE_EXTS.has(path.extname(entry.name).toLowerCase()))
|
|
497
|
+
continue;
|
|
493
498
|
if (ignoreMatcher.isIgnored(fullPath, false))
|
|
494
499
|
continue;
|
|
495
|
-
|
|
496
|
-
files.push(normalizeMapKey(fullPath));
|
|
497
|
-
}
|
|
500
|
+
files.push(normalizeMapKey(fullPath));
|
|
498
501
|
}
|
|
499
502
|
};
|
|
500
503
|
visit(root, 0);
|
|
@@ -5,6 +5,7 @@ import { publishFilesTouched } from "./bus-publish.js";
|
|
|
5
5
|
import { publishAutofixStart, publishFormatStart, } from "./format-events-publish.js";
|
|
6
6
|
import { logLatency } from "./latency-logger.js";
|
|
7
7
|
import { isPathIgnoredByProject } from "./file-utils.js";
|
|
8
|
+
import { admitBounded, emitBounded } from "./bounded-telemetry.js";
|
|
8
9
|
import { newLspMutationCorrelationId } from "./lsp-mutation.js";
|
|
9
10
|
import { getGlobalActionableWarningMaxFixes, } from "./lens-config.js";
|
|
10
11
|
import { resyncLspFile, runAutofix, runFormatPhase } from "./pipeline.js";
|
|
@@ -46,7 +47,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
46
47
|
// session (e.g. a concurrent in-process secondary/subagent) stay queued
|
|
47
48
|
// for their owner's own agent_end, unless they've been stale long enough
|
|
48
49
|
// to fall back to "claim as orphaned" (see claimDeferredFormatFiles).
|
|
49
|
-
const { claimed, staleClaimed, deferredToOwner } = runtime.claimDeferredMutations(currentSessionId, Date.now(), staleAfterMs);
|
|
50
|
+
const { claimed, staleClaimed, deferredToOwner, droppedOrphans } = runtime.claimDeferredMutations(currentSessionId, Date.now(), staleAfterMs, ctxCwd ?? runtime.projectRoot);
|
|
50
51
|
const records = [...claimed, ...staleClaimed];
|
|
51
52
|
const requeuedKinds = new Set();
|
|
52
53
|
// S2d (gap 4, #1432 review): the aggregate `agent_end_deferred_mutation_drain`
|
|
@@ -85,13 +86,84 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
85
86
|
runtime.requeueDeferredMutations(pending);
|
|
86
87
|
};
|
|
87
88
|
const rootActionableAutofixEnabled = !!getFlag("lens-actionable-warning-autofix");
|
|
89
|
+
// The actionable-warnings cache is only ever written when the
|
|
90
|
+
// "lens-actionable-warnings" writer flag is on (clients/runtime-turn.ts).
|
|
91
|
+
// A reader that ignores this gate always misses in a host where the
|
|
92
|
+
// writer is off and logs "cache missing or expired" at every agent_end,
|
|
93
|
+
// misdirecting the debug log toward a bogus cache problem instead of the
|
|
94
|
+
// real cause: the feature that populates the cache is disabled (#1607).
|
|
95
|
+
const actionableWarningsWriterEnabled = !!getFlag("lens-actionable-warnings");
|
|
88
96
|
// A path-aware source resolver signals that nested configs may re-enable
|
|
89
97
|
// actionable autofix even when the root default is off. Legacy/test hosts
|
|
90
98
|
// without that resolver keep the old fast exit and avoid a cache read.
|
|
91
|
-
const inspectActionableReport =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
const inspectActionableReport = actionableWarningsWriterEnabled &&
|
|
100
|
+
typeof cacheManager.readCache === "function" &&
|
|
101
|
+
(rootActionableAutofixEnabled || getFlagSource !== undefined);
|
|
102
|
+
if (droppedOrphans.length > 0) {
|
|
103
|
+
// #1642 F3: a stale/foreign record whose origin (the cwd/worktree it
|
|
104
|
+
// was actually queued under) does NOT match this flush's own
|
|
105
|
+
// workspace/worktree is left queued and NEVER formatted by this
|
|
106
|
+
// flush — claiming it would be the same worktree→parent
|
|
107
|
+
// re-attribution the reported incident hit, just via the orphan
|
|
108
|
+
// fallback instead of tool_result's path resolution. It stays in
|
|
109
|
+
// `_pendingDeferredMutations` (not deleted) so a flush whose origin
|
|
110
|
+
// actually matches can still claim it later; a genuinely abandoned
|
|
111
|
+
// origin will keep surfacing this record on every subsequent
|
|
112
|
+
// agent_end, which is the deliberately safer trade-off over silently
|
|
113
|
+
// losing it forever.
|
|
114
|
+
dbg(`agent_end deferred_format: left ${droppedOrphans.length} orphaned file(s) queued due to a mismatched origin (unclaimed >${staleAfterMs}ms, not formatted by this flush): ${droppedOrphans
|
|
115
|
+
.map((r) => `${r.filePath} origin=${r.originCwd}`)
|
|
116
|
+
.join(", ")}`);
|
|
117
|
+
// #1678 item 1: a genuinely abandoned origin re-surfaces the SAME
|
|
118
|
+
// record on every subsequent agent_end for as long as the queued
|
|
119
|
+
// entry lives — by design (see the comment above), but the repo
|
|
120
|
+
// invariant is that a repeated degradation goes through the ledger's
|
|
121
|
+
// counting path instead of accumulating as unbounded raw log lines.
|
|
122
|
+
// The ledger is the single source of truth for whether THIS is a
|
|
123
|
+
// record's first appearance (its return value): the detailed forensic
|
|
124
|
+
// record below names only the orphans on that rising edge, with file
|
|
125
|
+
// identity/origin/age. Every later repeat of
|
|
126
|
+
// the same record is counted ONLY by the ledger's running count —
|
|
127
|
+
// no second raw event, no parallel "already logged" latch to
|
|
128
|
+
// maintain here.
|
|
129
|
+
// #1743: `admitBounded` IS that ledger call, plus the registry
|
|
130
|
+
// membership the bounded-telemetry sweep checks. This site admits PER
|
|
131
|
+
// ORPHAN and then writes ONE batched record naming the admitted ones,
|
|
132
|
+
// which is why it uses `admitBounded` directly instead of a per-record
|
|
133
|
+
// `emitBounded` — the per-call form would turn one log line into N.
|
|
134
|
+
const firstSeenOrphans = [];
|
|
135
|
+
for (const record of droppedOrphans) {
|
|
136
|
+
const isFirstOccurrence = admitBounded("agent_end_deferred_format_orphan_origin_mismatch", record.filePath, {
|
|
137
|
+
ledgerKind: "path-attribution-orphan-unresolved",
|
|
138
|
+
risingEdgePer: "identity",
|
|
139
|
+
reason: `origin=${record.originCwd} unclaimed >${staleAfterMs}ms`,
|
|
140
|
+
});
|
|
141
|
+
if (isFirstOccurrence)
|
|
142
|
+
firstSeenOrphans.push(record);
|
|
143
|
+
}
|
|
144
|
+
if (firstSeenOrphans.length > 0) {
|
|
145
|
+
// Already admitted above, one orphan at a time — hence no bounding
|
|
146
|
+
// options here. The batch's own identity is the origin this flush ran
|
|
147
|
+
// under; per-file identity rides in `metadata.files`.
|
|
148
|
+
emitBounded("agent_end_deferred_format_orphan_origin_mismatch", ctxCwd ?? runtime.projectRoot, {
|
|
149
|
+
type: "phase",
|
|
150
|
+
toolName: "agent_end",
|
|
151
|
+
durationMs: 0,
|
|
152
|
+
metadata: {
|
|
153
|
+
fileCount: firstSeenOrphans.length,
|
|
154
|
+
staleAfterMs,
|
|
155
|
+
currentOriginCwd: ctxCwd ?? runtime.projectRoot,
|
|
156
|
+
files: firstSeenOrphans.map((r) => ({
|
|
157
|
+
filePath: r.filePath,
|
|
158
|
+
originCwd: r.originCwd,
|
|
159
|
+
ownerSessionId: r.ownerSessionId,
|
|
160
|
+
queuedTurnIndex: r.queuedTurnIndex,
|
|
161
|
+
ageMs: Date.now() - r.lastTouchedAt,
|
|
162
|
+
})),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
95
167
|
if (records.length === 0 && !inspectActionableReport)
|
|
96
168
|
return undefined;
|
|
97
169
|
if (deferredToOwner.length > 0) {
|
|
@@ -402,9 +474,26 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
|
|
|
402
474
|
await resyncLspFile(changedPath, content, true, false, getFlag, dbg);
|
|
403
475
|
}
|
|
404
476
|
if (inspectActionableReport) {
|
|
405
|
-
const
|
|
477
|
+
const actionCwd = ctxCwd ?? runtime.projectRoot;
|
|
478
|
+
const actionableWarningsMaxAgeMs = 10 * 60_000;
|
|
479
|
+
const actionReport = cacheManager.readCache("actionable-warnings", actionCwd, actionableWarningsMaxAgeMs);
|
|
406
480
|
if (!actionReport?.data) {
|
|
407
|
-
|
|
481
|
+
// Distinguish "no cache file was ever written" from "a cache file
|
|
482
|
+
// exists but its TTL elapsed" (#1607) — both used to collapse into
|
|
483
|
+
// one "cache missing or expired" line, which doesn't tell a reader
|
|
484
|
+
// whether the writer ran and produced a stale file, or never ran.
|
|
485
|
+
const inspection = typeof cacheManager.inspectCache === "function"
|
|
486
|
+
? cacheManager.inspectCache("actionable-warnings", actionCwd, actionableWarningsMaxAgeMs)
|
|
487
|
+
: undefined;
|
|
488
|
+
if (inspection === "missing") {
|
|
489
|
+
dbg("agent_end actionable_warnings_autofix: cache absent, skipping fixes");
|
|
490
|
+
}
|
|
491
|
+
else if (inspection === "stale") {
|
|
492
|
+
dbg("agent_end actionable_warnings_autofix: cache expired, skipping fixes");
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
dbg(`agent_end actionable_warnings_autofix: cache missing or expired${inspection ? ` (${inspection})` : ""}, skipping fixes`);
|
|
496
|
+
}
|
|
408
497
|
}
|
|
409
498
|
else {
|
|
410
499
|
const enabledFiles = actionReport.data.files.filter((file) => {
|
|
@@ -147,7 +147,14 @@ export function consumeSessionStartGuidance(cacheManager, cwd) {
|
|
|
147
147
|
const guidance = cacheManager.readCache("session-start-guidance", cwd);
|
|
148
148
|
if (!guidance?.data?.content)
|
|
149
149
|
return;
|
|
150
|
-
|
|
150
|
+
// Consume by writing empty guidance, not by casting `null` into the entry's
|
|
151
|
+
// own type. `writeCache` infers its type parameter from `data`, so the cast
|
|
152
|
+
// bought nothing and claimed the file held a `{ content: string }` when it
|
|
153
|
+
// held JSON `null`. The producer (`runtime-session.ts`) always writes
|
|
154
|
+
// `{ content }`, and this reader gates on `data?.content` being non-empty,
|
|
155
|
+
// so an empty string is the same "already consumed" signal with the type
|
|
156
|
+
// the entry actually has. Same idiom as `acknowledgeTestFindings` above.
|
|
157
|
+
cacheManager.writeCache("session-start-guidance", { content: "" }, cwd);
|
|
151
158
|
return {
|
|
152
159
|
messages: [
|
|
153
160
|
{
|