@herbertgao/pi-extensions 2026.8.4 → 2026.8.6
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 +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.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 +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -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 +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- 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/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- 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/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- 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-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- 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-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- 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-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -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 +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- 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 +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- package/node_modules/pi-lens/dist/clients/types.js +0 -11
|
@@ -28,6 +28,11 @@ pi-lens gives AI coding agents fast, language-aware feedback while they write/ed
|
|
|
28
28
|
- `/lens-map` — interactive HTML dependency map of the project
|
|
29
29
|
- Read-guard and edit-autopatch support to reduce bad edits
|
|
30
30
|
- Background security/dependency scans for opted-in projects
|
|
31
|
+
- Runtime health telemetry (`/lens-health`) including a bounded degradation
|
|
32
|
+
ledger for silently-degraded behavior (LSP breakers, formatter
|
|
33
|
+
skips/failures, idle evictions, timeouts)
|
|
34
|
+
- MCP server (experimental) so Claude Code or any MCP client can drive the
|
|
35
|
+
same diagnostics/read-substitute tools pi-lens exposes to pi
|
|
31
36
|
|
|
32
37
|
## Install
|
|
33
38
|
|
|
@@ -70,6 +75,8 @@ pi install git:github.com/apmantza/pi-lens
|
|
|
70
75
|
and formatters
|
|
71
76
|
- [Dependencies](docs/dependencies.md) — auto-install policy and external tools
|
|
72
77
|
- [Custom rules](docs/custom-rules.md) — project ast-grep and tree-sitter rules
|
|
78
|
+
- [MCP server](docs/mcp.md) — experimental MCP server for Claude Code and
|
|
79
|
+
other MCP clients
|
|
73
80
|
|
|
74
81
|
## Contributing
|
|
75
82
|
|
|
@@ -206,6 +213,12 @@ Thanks goes to these wonderful people:
|
|
|
206
213
|
</tr>
|
|
207
214
|
<tr>
|
|
208
215
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pppobear"><img src="https://avatars.githubusercontent.com/u/21952175?v=4" width="100px;" alt=""/><br /><sub><b>pppobear</b></sub></a><br /><a href="#bug-pppobear" title="Bug reports">🐛</a></td>
|
|
216
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/3choBoomer"><img src="https://avatars.githubusercontent.com/u/521828?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Cooke</b></sub></a><br /><a href="#code-3choBoomer" title="Code">💻</a></td>
|
|
217
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/white-hat"><img src="https://avatars.githubusercontent.com/u/922988?v=4" width="100px;" alt=""/><br /><sub><b>Eli Stark</b></sub></a><br /><a href="#code-white-hat" title="Code">💻</a></td>
|
|
218
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marvtub"><img src="https://avatars.githubusercontent.com/u/33159023?v=4" width="100px;" alt=""/><br /><sub><b>Marvin Aziz</b></sub></a><br /><a href="#code-marvtub" title="Code">💻</a></td>
|
|
219
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark Faga</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
|
|
220
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aeturnal"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
|
|
221
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/floatGray"><img src="https://avatars.githubusercontent.com/u/44295302?v=4" width="100px;" alt=""/><br /><sub><b>floatGray</b></sub></a><br /><a href="#code-floatGray" title="Code">💻</a></td>
|
|
209
222
|
</tr>
|
|
210
223
|
</tbody>
|
|
211
224
|
</table>
|
|
@@ -4,9 +4,16 @@ import * as path from "node:path";
|
|
|
4
4
|
import { applyWorkspaceEdit } from "./lsp/edits.js";
|
|
5
5
|
import { getLSPService } from "./lsp/index.js";
|
|
6
6
|
import { normalizeMapKey } from "./path-utils.js";
|
|
7
|
+
import { recordLspMutationBatch, } from "./lsp-mutation.js";
|
|
7
8
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
8
9
|
import { logActionableWarningsEvent } from "./actionable-warnings-logger.js";
|
|
9
10
|
import { getProjectDataDir } from "./file-utils.js";
|
|
11
|
+
import { commitDurableStore } from "./durable-store.js";
|
|
12
|
+
let beforeWarningStateLockForTests = null;
|
|
13
|
+
/** Test seam for a sibling process commit immediately before lock acquisition. */
|
|
14
|
+
export function _setBeforeWarningStateLockForTests(hook) {
|
|
15
|
+
beforeWarningStateLockForTests = hook;
|
|
16
|
+
}
|
|
10
17
|
function normalizeMessage(message) {
|
|
11
18
|
return message.replace(/\s+/g, " ").trim().toLowerCase();
|
|
12
19
|
}
|
|
@@ -53,10 +60,9 @@ function serializeAction(action) {
|
|
|
53
60
|
skipReason: safety.reason,
|
|
54
61
|
};
|
|
55
62
|
}
|
|
56
|
-
function
|
|
57
|
-
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
63
|
+
function deserializeSuppressionState(contents) {
|
|
58
64
|
try {
|
|
59
|
-
const parsed = JSON.parse(
|
|
65
|
+
const parsed = JSON.parse(contents ?? "");
|
|
60
66
|
return parsed && typeof parsed === "object"
|
|
61
67
|
? parsed
|
|
62
68
|
: {};
|
|
@@ -65,23 +71,49 @@ function readSuppressionState(cwd) {
|
|
|
65
71
|
return {};
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
|
-
function
|
|
74
|
+
function readSuppressionState(cwd) {
|
|
69
75
|
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
state.warnings[warning.id] = {
|
|
76
|
-
...existing,
|
|
77
|
-
status: existing.status ?? "active",
|
|
78
|
-
firstSeenAt: existing.firstSeenAt ?? now,
|
|
79
|
-
lastSeenAt: now,
|
|
80
|
-
seenCount: (existing.seenCount ?? 0) + 1,
|
|
81
|
-
};
|
|
76
|
+
try {
|
|
77
|
+
return deserializeSuppressionState(fs.readFileSync(statePath, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return {};
|
|
82
81
|
}
|
|
82
|
+
}
|
|
83
|
+
function updateWarningState(cwd, warnings) {
|
|
84
|
+
const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
|
|
83
85
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
84
|
-
|
|
86
|
+
const hook = beforeWarningStateLockForTests;
|
|
87
|
+
beforeWarningStateLockForTests = null;
|
|
88
|
+
hook?.();
|
|
89
|
+
commitDurableStore({
|
|
90
|
+
path: statePath,
|
|
91
|
+
deserialize: deserializeSuppressionState,
|
|
92
|
+
merge: (state) => {
|
|
93
|
+
const now = new Date().toISOString();
|
|
94
|
+
state.warnings ??= {};
|
|
95
|
+
for (const warning of warnings) {
|
|
96
|
+
const existing = state.warnings[warning.id] ?? {};
|
|
97
|
+
state.warnings[warning.id] = {
|
|
98
|
+
...existing,
|
|
99
|
+
status: existing.status ?? "active",
|
|
100
|
+
firstSeenAt: existing.firstSeenAt ?? now,
|
|
101
|
+
lastSeenAt: now,
|
|
102
|
+
seenCount: (existing.seenCount ?? 0) + 1,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return state;
|
|
106
|
+
},
|
|
107
|
+
serialize: (state) => JSON.stringify(state, null, 2),
|
|
108
|
+
waitMs: 2_000,
|
|
109
|
+
retryMs: 10,
|
|
110
|
+
timeoutMessage: "timed out acquiring actionable warning store lock",
|
|
111
|
+
onContention: "skip-log",
|
|
112
|
+
logContention: () => logActionableWarningsEvent({
|
|
113
|
+
event: "warning_state_write_dropped",
|
|
114
|
+
metadata: { reason: "lock_contention" },
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
85
117
|
}
|
|
86
118
|
function suppressionFor(cwd, id) {
|
|
87
119
|
const entry = readSuppressionState(cwd).warnings?.[id];
|
|
@@ -452,6 +484,8 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
452
484
|
skipped: [],
|
|
453
485
|
};
|
|
454
486
|
const changedFiles = new Set();
|
|
487
|
+
const appliedResults = [];
|
|
488
|
+
let failedCount = 0;
|
|
455
489
|
const lspService = getLSPService();
|
|
456
490
|
const maxFixes = Math.max(0, args.maxFixes ?? 5);
|
|
457
491
|
for (const file of args.report.files) {
|
|
@@ -504,12 +538,23 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
504
538
|
continue;
|
|
505
539
|
}
|
|
506
540
|
const edit = selected.edit;
|
|
507
|
-
const applied = await applyWorkspaceEdit(edit, args.cwd
|
|
541
|
+
const applied = await applyWorkspaceEdit(edit, args.cwd, args.mutationContext
|
|
542
|
+
? { mutationContext: { ...args.mutationContext, emitSummary: false } }
|
|
543
|
+
: undefined);
|
|
544
|
+
appliedResults.push(applied);
|
|
508
545
|
for (const changedFile of applied.files)
|
|
509
546
|
changedFiles.add(changedFile);
|
|
510
547
|
summary.applied++;
|
|
511
548
|
}
|
|
512
549
|
catch (err) {
|
|
550
|
+
failedCount++;
|
|
551
|
+
const partial = err
|
|
552
|
+
.appliedWorkspaceEdit;
|
|
553
|
+
if (partial) {
|
|
554
|
+
appliedResults.push(partial);
|
|
555
|
+
for (const changedFile of partial.files)
|
|
556
|
+
changedFiles.add(changedFile);
|
|
557
|
+
}
|
|
513
558
|
const message = err instanceof Error ? err.message : String(err);
|
|
514
559
|
args.dbg?.(`actionable_warnings_autofix failed for ${warning.id}: ${message}`);
|
|
515
560
|
summary.skipped.push({ id: warning.id, reason: "apply_failed" });
|
|
@@ -517,6 +562,16 @@ export async function applyConservativeActionableWarningFixes(args) {
|
|
|
517
562
|
}
|
|
518
563
|
}
|
|
519
564
|
summary.changedFiles = [...changedFiles];
|
|
565
|
+
if (args.mutationContext && (summary.considered > 0 || appliedResults.length > 0)) {
|
|
566
|
+
recordLspMutationBatch(args.mutationContext, {
|
|
567
|
+
results: appliedResults,
|
|
568
|
+
considered: summary.considered,
|
|
569
|
+
completed: summary.applied,
|
|
570
|
+
failedCount,
|
|
571
|
+
status: failedCount > 0 ? "failed" : appliedResults.length > 0 ? "success" : "skipped",
|
|
572
|
+
bookkeep: false,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
520
575
|
return summary;
|
|
521
576
|
}
|
|
522
577
|
export function formatActionableWarningsAdvisory(report) {
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { logLatency } from "./latency-logger.js";
|
|
5
|
+
import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
|
|
6
|
+
import { resolveRunnerPath } from "./dispatch/runner-context.js";
|
|
7
|
+
export const MAX_ADVISORY_AFFECTED_FILES = 256;
|
|
8
|
+
export function advisoryPathKey(filePath, cwd) {
|
|
9
|
+
return normalizeMapKey(path.resolve(cwd, filePath));
|
|
10
|
+
}
|
|
11
|
+
/** Shared with git guard: there is one SHA-256 implementation for advisories. */
|
|
12
|
+
export function advisoryFileHash(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const code = error.code ?? "unknown";
|
|
18
|
+
return code === "ENOENT" ? "missing" : `unreadable:${code}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function snapshotOne(filePath, cwd, role) {
|
|
22
|
+
const resolved = path.resolve(cwd, filePath);
|
|
23
|
+
try {
|
|
24
|
+
const stat = fs.statSync(resolved);
|
|
25
|
+
return {
|
|
26
|
+
path: resolved,
|
|
27
|
+
role,
|
|
28
|
+
mtimeMs: stat.mtimeMs,
|
|
29
|
+
size: stat.size,
|
|
30
|
+
sha256: advisoryFileHash(resolved),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const code = error.code ?? "unknown";
|
|
35
|
+
return {
|
|
36
|
+
path: resolved,
|
|
37
|
+
role,
|
|
38
|
+
mtimeMs: -1,
|
|
39
|
+
size: -1,
|
|
40
|
+
sha256: code === "ENOENT" ? "missing" : `unreadable:${code}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function snapshotAdvisoryProvenance(args) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const file of args.files) {
|
|
48
|
+
const key = advisoryPathKey(file.path, args.cwd);
|
|
49
|
+
if (seen.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(key);
|
|
52
|
+
files.push(snapshotOne(file.path, args.cwd, file.role));
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
revision: {
|
|
56
|
+
sessionId: args.runtime.telemetrySessionId,
|
|
57
|
+
projectSeq: args.runtime.projectSeq,
|
|
58
|
+
turnIndex: args.runtime.turnIndex,
|
|
59
|
+
generation: args.generation,
|
|
60
|
+
capturedAt: args.capturedAt ?? Date.now(),
|
|
61
|
+
},
|
|
62
|
+
files,
|
|
63
|
+
...(args.truncated ? { truncated: true } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function isFiniteNumber(value) {
|
|
67
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
68
|
+
}
|
|
69
|
+
function isCapturedHash(value) {
|
|
70
|
+
return typeof value === "string" &&
|
|
71
|
+
(/^[a-f0-9]{64}$/.test(value) || value === "missing" || value.startsWith("unreadable:"));
|
|
72
|
+
}
|
|
73
|
+
function isWellFormed(value) {
|
|
74
|
+
if (!value || typeof value !== "object")
|
|
75
|
+
return false;
|
|
76
|
+
const record = value;
|
|
77
|
+
const revision = record.revision;
|
|
78
|
+
return !!revision && typeof revision.sessionId === "string" &&
|
|
79
|
+
isFiniteNumber(revision.projectSeq) && isFiniteNumber(revision.turnIndex) &&
|
|
80
|
+
isFiniteNumber(revision.generation) && isFiniteNumber(revision.capturedAt) &&
|
|
81
|
+
Array.isArray(record.files) && record.files.length > 0 && record.files.every((file) => !!file && typeof file.path === "string" &&
|
|
82
|
+
(file.role === "source" || file.role === "test" || file.role === "affected") &&
|
|
83
|
+
isFiniteNumber(file.mtimeMs) && isFiniteNumber(file.size) &&
|
|
84
|
+
isCapturedHash(file.sha256));
|
|
85
|
+
}
|
|
86
|
+
export function validateAdvisoryProvenance(record, cwd, runtime) {
|
|
87
|
+
if (!isWellFormed(record.provenance)) {
|
|
88
|
+
return { status: "unknown", reasons: ["malformed-or-legacy-provenance"], allFilesDeleted: false, changedPathCount: 0 };
|
|
89
|
+
}
|
|
90
|
+
const provenance = record.provenance;
|
|
91
|
+
const reasons = [];
|
|
92
|
+
let unknown = provenance.truncated === true;
|
|
93
|
+
if (unknown)
|
|
94
|
+
reasons.push("truncated-provenance");
|
|
95
|
+
if (runtime) {
|
|
96
|
+
if (provenance.revision.sessionId !== runtime.telemetrySessionId)
|
|
97
|
+
reasons.push("session-mismatch");
|
|
98
|
+
}
|
|
99
|
+
let deletedFiles = 0;
|
|
100
|
+
const changedPaths = new Set();
|
|
101
|
+
for (const captured of provenance.files) {
|
|
102
|
+
const resolved = path.resolve(cwd, captured.path);
|
|
103
|
+
const reasonsBefore = reasons.length;
|
|
104
|
+
let stat;
|
|
105
|
+
try {
|
|
106
|
+
stat = fs.statSync(resolved);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const code = error.code ?? "unknown";
|
|
110
|
+
if (code === "ENOENT") {
|
|
111
|
+
if (captured.sha256 !== "missing") {
|
|
112
|
+
deletedFiles += 1;
|
|
113
|
+
reasons.push(`missing:${advisoryPathKey(resolved, cwd)}`);
|
|
114
|
+
changedPaths.add(advisoryPathKey(resolved, cwd));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
unknown = true;
|
|
119
|
+
reasons.push(`unreadable:${advisoryPathKey(resolved, cwd)}:${code}`);
|
|
120
|
+
changedPaths.add(advisoryPathKey(resolved, cwd));
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (captured.sha256.startsWith("unreadable:")) {
|
|
125
|
+
unknown = true;
|
|
126
|
+
reasons.push(`capture-unreadable:${advisoryPathKey(resolved, cwd)}`);
|
|
127
|
+
changedPaths.add(advisoryPathKey(resolved, cwd));
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (captured.sha256 === "missing") {
|
|
131
|
+
reasons.push(`created:${advisoryPathKey(resolved, cwd)}`);
|
|
132
|
+
changedPaths.add(advisoryPathKey(resolved, cwd));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (stat.mtimeMs !== captured.mtimeMs || stat.size !== captured.size) {
|
|
136
|
+
reasons.push(`metadata-changed:${advisoryPathKey(resolved, cwd)}`);
|
|
137
|
+
}
|
|
138
|
+
const currentHash = advisoryFileHash(resolved);
|
|
139
|
+
if (currentHash.startsWith("unreadable:")) {
|
|
140
|
+
unknown = true;
|
|
141
|
+
reasons.push(`${currentHash}:${advisoryPathKey(resolved, cwd)}`);
|
|
142
|
+
}
|
|
143
|
+
else if (currentHash !== captured.sha256) {
|
|
144
|
+
reasons.push(`content-changed:${advisoryPathKey(resolved, cwd)}`);
|
|
145
|
+
}
|
|
146
|
+
if (reasons.length > reasonsBefore)
|
|
147
|
+
changedPaths.add(advisoryPathKey(resolved, cwd));
|
|
148
|
+
}
|
|
149
|
+
const allFilesDeleted = deletedFiles === provenance.files.length;
|
|
150
|
+
if (unknown)
|
|
151
|
+
return { status: "unknown", reasons, allFilesDeleted, changedPathCount: changedPaths.size };
|
|
152
|
+
return reasons.length > 0
|
|
153
|
+
? { status: "superseded", reasons, allFilesDeleted, changedPathCount: changedPaths.size }
|
|
154
|
+
: { status: "current", reasons: [], allFilesDeleted, changedPathCount: 0 };
|
|
155
|
+
}
|
|
156
|
+
// ── Finding-cited path existence (#1461 slice 1) ──────────────────────────────
|
|
157
|
+
//
|
|
158
|
+
// `validateAdvisoryProvenance` above answers "did the files the agent EDITED
|
|
159
|
+
// change since capture?". A cached scanner finding asks a different question:
|
|
160
|
+
// "does the file this finding NAMES still exist?". #1460's live case is the gap
|
|
161
|
+
// between them — a gitleaks blocker for a directory deleted eleven minutes
|
|
162
|
+
// earlier shipped as `current` seven consecutive times, because the edited
|
|
163
|
+
// files were all intact and the cited path was never in the envelope.
|
|
164
|
+
//
|
|
165
|
+
// CONTRACT (the five remaining #1461 slices reuse this verbatim):
|
|
166
|
+
// - Input is any finding shape; the caller supplies `citedPath`, so nothing
|
|
167
|
+
// here knows about gitleaks, trivy, govulncheck, vulture, or delta mode.
|
|
168
|
+
// - A finding whose cited path is absent (ENOENT/ENOTDIR) is DROPPED, not
|
|
169
|
+
// demoted. There is no remediation for a file that is gone — the agent
|
|
170
|
+
// cannot rotate a credential in it or delete a line from it. Content drift
|
|
171
|
+
// on a SURVIVING file stays `validateAdvisoryProvenance`'s job, and that
|
|
172
|
+
// one demotes.
|
|
173
|
+
// - Fail open, never closed: a finding with no cited path, an unreadable
|
|
174
|
+
// path (EACCES/EPERM/EBUSY), or a path past the stat budget is DELIVERED.
|
|
175
|
+
// Unreadable is not absent; a missed drop is noise, a wrong drop is a lost
|
|
176
|
+
// secret.
|
|
177
|
+
// - Bounded cost: findings are deduped by their RAW cited string first —
|
|
178
|
+
// zero filesystem work — before anything pays for `resolveRunnerPath`'s
|
|
179
|
+
// ancestor walk or `normalizeMapKey`'s realpath. A first cut that deduped
|
|
180
|
+
// by the resolved/canonical key instead still ran that expensive step
|
|
181
|
+
// once per FINDING, not once per unique path, because the dedup lookup
|
|
182
|
+
// came after the cost it was meant to avoid: #1461's live 126-finding
|
|
183
|
+
// record measured 72.7ms in that shape, all of it before the first
|
|
184
|
+
// `statSync`. Deduping the raw string first drops the same record to a
|
|
185
|
+
// ~1.6ms median (this repo, Windows, 10-sample bench) — the remaining
|
|
186
|
+
// cost is one canonicalization + one stat per distinct cited string,
|
|
187
|
+
// capped at `MAX_FINDING_PATH_STATS`.
|
|
188
|
+
// - Path identity uses the guard normalizer (`resolveRunnerPath` →
|
|
189
|
+
// `normalizeMapKey`) for the canonical-key dedup and the shape-aware
|
|
190
|
+
// `toProjectRelativePath` for display, so Windows spellings of one path
|
|
191
|
+
// collapse to one stat and one log entry (defect shapes 1 and 2). This
|
|
192
|
+
// helper has no zero-I/O contract of its own to protect — it stats.
|
|
193
|
+
// Slice 3 (delta mode) must find and name its OWN seam before reusing
|
|
194
|
+
// this shape; `formatDeltaMode` (tools/lens-diagnostics.ts) reads only
|
|
195
|
+
// actionable-warnings, code-quality-warnings, and the delta report, none
|
|
196
|
+
// of which any #1461 slice writes yet, so nothing here currently touches
|
|
197
|
+
// it.
|
|
198
|
+
/** Stat budget: one per unique cited path, sharing the envelope's own cap. */
|
|
199
|
+
export const MAX_FINDING_PATH_STATS = MAX_ADVISORY_AFFECTED_FILES;
|
|
200
|
+
/** How many dead paths a single drop record names before it stops. */
|
|
201
|
+
const MAX_LOGGED_DEAD_PATHS = 3;
|
|
202
|
+
export function findingPathExistence(resolvedPath) {
|
|
203
|
+
try {
|
|
204
|
+
fs.statSync(resolvedPath);
|
|
205
|
+
return "live";
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const code = error.code ?? "unknown";
|
|
209
|
+
// ENOTDIR: an ancestor component is no longer a directory — the cited
|
|
210
|
+
// path cannot exist either, same as ENOENT.
|
|
211
|
+
return code === "ENOENT" || code === "ENOTDIR" ? "missing" : "unknown";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Partition findings into `{live, dropped}` by whether the path each one names
|
|
216
|
+
* still exists. Pure apart from the `fs.statSync` probe, which is injectable so
|
|
217
|
+
* the partition rules are unit-testable without a filesystem.
|
|
218
|
+
*/
|
|
219
|
+
export function partitionFindingsByCitedPath(args) {
|
|
220
|
+
const limit = args.maxUniquePaths ?? MAX_FINDING_PATH_STATS;
|
|
221
|
+
const probe = args.existence ?? findingPathExistence;
|
|
222
|
+
// Two dedup layers, cheapest first. `rawVerdicts` collapses findings that
|
|
223
|
+
// cite the IDENTICAL string with zero filesystem work — the dominant case
|
|
224
|
+
// (#1460's live record: 126 findings over a handful of distinct `file`
|
|
225
|
+
// strings). Only a raw string not seen before pays for
|
|
226
|
+
// `resolveRunnerPath`/`advisoryPathKey`, which folds FS-confirmed spelling
|
|
227
|
+
// variants (case, separators, ancestor walk-up) into `verdicts`, the
|
|
228
|
+
// canonical-key map that `statCount` reports. Keying the expensive work by
|
|
229
|
+
// the RESOLVED path instead of the raw one (the pre-#1461-HIGH-2 shape)
|
|
230
|
+
// still called it once per finding, since the dedup lookup came after the
|
|
231
|
+
// cost it was meant to dedupe.
|
|
232
|
+
const rawVerdicts = new Map();
|
|
233
|
+
const verdicts = new Map();
|
|
234
|
+
const live = [];
|
|
235
|
+
const dropped = [];
|
|
236
|
+
const deadPaths = [];
|
|
237
|
+
let truncated = false;
|
|
238
|
+
for (const finding of args.findings) {
|
|
239
|
+
const cited = args.citedPath(finding);
|
|
240
|
+
if (!cited) {
|
|
241
|
+
live.push(finding);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
let verdict = rawVerdicts.get(cited);
|
|
245
|
+
if (verdict === undefined) {
|
|
246
|
+
// Ancestor-tolerant, same as `toRunnerDisplayPath`'s resolution in
|
|
247
|
+
// runtime-turn.ts — a bare `path.resolve` here would decide the drop
|
|
248
|
+
// against a different root than the one used to render the survivor,
|
|
249
|
+
// dropping findings the display path would have shown correctly
|
|
250
|
+
// (#1461 HIGH-1). `resolveRunnerPath` already runs its result through
|
|
251
|
+
// `normalizeMapKey`, so the resolved path IS the canonical key — a
|
|
252
|
+
// second `advisoryPathKey` pass would re-pay the same realpath cost.
|
|
253
|
+
const resolved = resolveRunnerPath(args.cwd, cited);
|
|
254
|
+
const key = resolved;
|
|
255
|
+
verdict = verdicts.get(key);
|
|
256
|
+
if (verdict === undefined) {
|
|
257
|
+
if (verdicts.size >= limit) {
|
|
258
|
+
// Budget spent on paths we have not seen before — deliver rather
|
|
259
|
+
// than guess. Already-probed paths keep their cached verdict.
|
|
260
|
+
truncated = true;
|
|
261
|
+
verdict = "live";
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
verdict = probe(resolved);
|
|
265
|
+
verdicts.set(key, verdict);
|
|
266
|
+
if (verdict === "missing")
|
|
267
|
+
deadPaths.push(resolved);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
rawVerdicts.set(cited, verdict);
|
|
271
|
+
}
|
|
272
|
+
if (verdict === "missing")
|
|
273
|
+
dropped.push(finding);
|
|
274
|
+
else
|
|
275
|
+
live.push(finding);
|
|
276
|
+
}
|
|
277
|
+
return { live, dropped, deadPaths, statCount: verdicts.size, truncated };
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Delivery-seam wrapper: partition, then emit one bounded `finding_dead_path_drop`
|
|
281
|
+
* record when anything was dropped, and return only what is safe to deliver.
|
|
282
|
+
*
|
|
283
|
+
* The record is the #1432 Gap 1 principle applied here — an eviction that logs
|
|
284
|
+
* nothing is only confirmable by the absence of complaints. One record per
|
|
285
|
+
* store per delivery, with a capped path sample; never one per finding.
|
|
286
|
+
*/
|
|
287
|
+
export function dropFindingsForMissingPaths(args) {
|
|
288
|
+
const partition = partitionFindingsByCitedPath(args);
|
|
289
|
+
if (partition.dropped.length === 0)
|
|
290
|
+
return partition.live;
|
|
291
|
+
logLatency({
|
|
292
|
+
type: "phase",
|
|
293
|
+
phase: "finding_dead_path_drop",
|
|
294
|
+
filePath: args.cwd,
|
|
295
|
+
durationMs: 0,
|
|
296
|
+
metadata: {
|
|
297
|
+
store: args.store,
|
|
298
|
+
droppedDeadPaths: partition.dropped.length,
|
|
299
|
+
deadPathCount: partition.deadPaths.length,
|
|
300
|
+
deliveredCount: partition.live.length,
|
|
301
|
+
statCount: partition.statCount,
|
|
302
|
+
samplePaths: partition.deadPaths
|
|
303
|
+
.slice(0, MAX_LOGGED_DEAD_PATHS)
|
|
304
|
+
.map((deadPath) => toProjectRelativePath(deadPath, args.cwd)),
|
|
305
|
+
...(partition.truncated ? { truncated: true } : {}),
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
return partition.live;
|
|
309
|
+
}
|
|
310
|
+
export function provenanceStamp(provenance) {
|
|
311
|
+
if (!isWellFormed(provenance))
|
|
312
|
+
return "session unknown / turn unknown / generation unknown";
|
|
313
|
+
return `session ${provenance.revision.sessionId} / turn ${provenance.revision.turnIndex} / generation ${provenance.revision.generation}`;
|
|
314
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* AgentBehaviorClient for pi-lens
|
|
3
3
|
*
|
|
4
4
|
* Tracks tool call sequences and flags anti-patterns in real-time:
|
|
5
|
-
* - Blind writes: editing or writing without reading first
|
|
5
|
+
* - Blind writes: editing or writing without reading first (a same-session
|
|
6
|
+
* write/edit of the same path counts as file knowledge)
|
|
6
7
|
* - Thrashing: repeated identical tool calls with no progress
|
|
7
8
|
*
|
|
8
9
|
* No external dependencies — purely tracks tool call history.
|
|
@@ -23,6 +24,7 @@ export class AgentBehaviorClient {
|
|
|
23
24
|
lastToolTimestamp = 0;
|
|
24
25
|
// Per-file tracking
|
|
25
26
|
fileEditCount = new Map();
|
|
27
|
+
static FILE_EDIT_IDLE_MS = 30 * 60_000;
|
|
26
28
|
/**
|
|
27
29
|
* Record a tool call and return any warnings triggered.
|
|
28
30
|
* Called from tool_result handler.
|
|
@@ -63,7 +65,14 @@ export class AgentBehaviorClient {
|
|
|
63
65
|
// Check for blind writes
|
|
64
66
|
if (WRITE_OPS.has(toolName)) {
|
|
65
67
|
const recentWindow = this.toolHistory.slice(-BLIND_WRITE_WINDOW);
|
|
66
|
-
|
|
68
|
+
// A read in the window proves file knowledge; so does a same-session
|
|
69
|
+
// write/edit of THIS path — the agent authored the content, so the
|
|
70
|
+
// write-then-edit loop on a self-authored file is not a blind write.
|
|
71
|
+
const hasRecentRead = recentWindow.some((r) => READ_OPS.has(r.toolName) ||
|
|
72
|
+
(WRITE_OPS.has(r.toolName) &&
|
|
73
|
+
r.filePath !== undefined &&
|
|
74
|
+
normalizedPath !== null &&
|
|
75
|
+
normalizeMapKey(r.filePath) === normalizedPath));
|
|
67
76
|
if (!hasRecentRead && recentWindow.length > 0) {
|
|
68
77
|
// Count how many writes in the window without reads
|
|
69
78
|
const writesWithoutRead = recentWindow.filter((r) => WRITE_OPS.has(r.toolName)).length;
|
|
@@ -82,7 +91,15 @@ export class AgentBehaviorClient {
|
|
|
82
91
|
// Track edits per file
|
|
83
92
|
if (filePath) {
|
|
84
93
|
const key = normalizeMapKey(filePath);
|
|
85
|
-
this.fileEditCount.set(key,
|
|
94
|
+
this.fileEditCount.set(key, {
|
|
95
|
+
count: (this.fileEditCount.get(key)?.count ?? 0) + 1,
|
|
96
|
+
lastUsedAt: now,
|
|
97
|
+
});
|
|
98
|
+
for (const [pathKey, entry] of this.fileEditCount) {
|
|
99
|
+
if (now - entry.lastUsedAt > AgentBehaviorClient.FILE_EDIT_IDLE_MS) {
|
|
100
|
+
this.fileEditCount.delete(pathKey);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
// Add to history (keep last 50 entries)
|
|
@@ -104,7 +121,7 @@ export class AgentBehaviorClient {
|
|
|
104
121
|
* Get edit count for a file in this session.
|
|
105
122
|
*/
|
|
106
123
|
getEditCount(filePath) {
|
|
107
|
-
return this.fileEditCount.get(filePath) ?? 0;
|
|
124
|
+
return this.fileEditCount.get(normalizeMapKey(filePath))?.count ?? 0;
|
|
108
125
|
}
|
|
109
126
|
/**
|
|
110
127
|
* Reset state (e.g., on session start).
|
|
@@ -169,12 +169,19 @@ export function wireAgentNudgeSubscriber(args) {
|
|
|
169
169
|
* - all local → "after your last turn" (the original #485 wording,
|
|
170
170
|
* unchanged — verified by the pre-existing #485
|
|
171
171
|
* tests);
|
|
172
|
-
* - all cross-process → "by
|
|
173
|
-
* - mixed → "after your last turn (N of them by
|
|
174
|
-
*
|
|
172
|
+
* - all cross-process → "by an automatic run outside your turn";
|
|
173
|
+
* - mixed → "after your last turn (N of them by an automatic
|
|
174
|
+
* run outside it)" — the base framing stays local
|
|
175
175
|
* and the cross-process portion is counted out
|
|
176
|
-
* precisely, so no local file is ever misattributed
|
|
177
|
-
*
|
|
176
|
+
* precisely, so no local file is ever misattributed.
|
|
177
|
+
*
|
|
178
|
+
* The cross-process wording names the ACTION, not the process identity. It
|
|
179
|
+
* used to read "by another pi-lens instance (e.g. a subagent's)", which sent
|
|
180
|
+
* agents investigating what other instance was running — the exact behavior
|
|
181
|
+
* this nudge exists to prevent. Which process formatted the file is an
|
|
182
|
+
* implementation detail the reading agent cannot act on; that it was an
|
|
183
|
+
* automatic run outside their turn is the whole actionable content. The
|
|
184
|
+
* origin split stays exact in the telemetry for human debugging.
|
|
178
185
|
*
|
|
179
186
|
* Clears the accumulator ONLY here, on actual injection — never on
|
|
180
187
|
* agent_end/agent_settled/turn_start. Files formatted at the last turn_end of
|
|
@@ -222,10 +229,10 @@ export function consumeAgentNudge(dbg) {
|
|
|
222
229
|
// file to another instance — a mixed batch keeps the local base framing
|
|
223
230
|
// and calls out the cross-process portion by exact count.
|
|
224
231
|
const attribution = localCount === 0
|
|
225
|
-
? "by
|
|
232
|
+
? "by an automatic run outside your turn"
|
|
226
233
|
: crossProcessCount === 0
|
|
227
234
|
? "after your last turn"
|
|
228
|
-
: `after your last turn (${crossProcessCount} of them by
|
|
235
|
+
: `after your last turn (${crossProcessCount} of them by an automatic run outside it)`;
|
|
229
236
|
const message = `pi-lens: ${filesTotal} file(s) were ${verbLabel} ${attribution}: ${nameList} — working-tree changes to these are expected; re-read before editing.`;
|
|
230
237
|
logLatency({
|
|
231
238
|
type: "phase",
|