@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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host project-trust gate (#1334 S5).
|
|
3
|
+
*
|
|
4
|
+
* pi decides project trust itself: an extension may *answer* the question by
|
|
5
|
+
* registering `pi.on("project_trust", handler)` (returning
|
|
6
|
+
* `{ trusted: "yes" | "no" | "undecided" }`), but every other extension simply
|
|
7
|
+
* *consumes* the outcome through `ExtensionContext.isProjectTrusted()`. pi-lens
|
|
8
|
+
* is a consumer — it must never register the handler and answer the trust
|
|
9
|
+
* question on the user's behalf.
|
|
10
|
+
*
|
|
11
|
+
* Verified against the pinned host types
|
|
12
|
+
* (`@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts`):
|
|
13
|
+
* - `ExtensionContext.isProjectTrusted(): boolean` (line 234)
|
|
14
|
+
* - `ProjectTrustEventDecision = "yes" | "no" | "undecided"` (line 390)
|
|
15
|
+
*
|
|
16
|
+
* Note the asymmetry: the *event* has a three-valued decision, but the *ctx*
|
|
17
|
+
* accessor collapses it to a boolean. So the only distinctions pi-lens can make
|
|
18
|
+
* are "host says trusted", "host says NOT trusted", and "host gave no signal at
|
|
19
|
+
* all" (older hosts with no `isProjectTrusted` on the ctx).
|
|
20
|
+
*
|
|
21
|
+
* Policy:
|
|
22
|
+
* - `untrusted` — the host said no or its accessor failed. Block every
|
|
23
|
+
* install/materialization path (including npx and grammar WASM downloads)
|
|
24
|
+
* and LSP child process spawn. Discovery and cached/in-process analysis
|
|
25
|
+
* continue; a missing grammar follows its unavailable path.
|
|
26
|
+
* - `trusted` / `unknown` — current behavior, unchanged. Fail-open is
|
|
27
|
+
* deliberate ONLY for `unknown`: a host that never exposed the accessor
|
|
28
|
+
* never had a trust decision to honor, and degrading it would break every
|
|
29
|
+
* older pi.
|
|
30
|
+
*
|
|
31
|
+
* Process-wide singleton on purpose: `ensureTool()` and the LSP service sit
|
|
32
|
+
* many layers below any `ctx`, and threading trust through every call site
|
|
33
|
+
* would be a far larger and more fragile change than a single latched state
|
|
34
|
+
* refreshed on each `session_start` and `turn_start`.
|
|
35
|
+
*/
|
|
36
|
+
import { logExtension } from "./extension-log.js";
|
|
37
|
+
import { recordDegradation } from "./degradation-ledger.js";
|
|
38
|
+
let trustState = "unknown";
|
|
39
|
+
const installRefusalWarnings = new Set();
|
|
40
|
+
// Bounded (#1363 review): dynamic contexts (per-formatter/per-server strings)
|
|
41
|
+
// must not grow without limit across a long untrusted session. Clearing on
|
|
42
|
+
// overflow trades an occasional repeat warning for bounded memory.
|
|
43
|
+
const INSTALL_REFUSAL_WARNING_CAP = 200;
|
|
44
|
+
// Monotonic transition counter (#1363 review): consumers needing
|
|
45
|
+
// clear-on-transition semantics compare this lazily at use time instead of
|
|
46
|
+
// registering callbacks -- a per-instance listener with no teardown path
|
|
47
|
+
// strongly retains every instance ever constructed.
|
|
48
|
+
let trustGeneration = 0;
|
|
49
|
+
export function getProjectTrustGeneration() {
|
|
50
|
+
return trustGeneration;
|
|
51
|
+
}
|
|
52
|
+
function latchProjectTrustState(next) {
|
|
53
|
+
if (next === trustState)
|
|
54
|
+
return;
|
|
55
|
+
trustState = next;
|
|
56
|
+
installRefusalWarnings.clear();
|
|
57
|
+
trustGeneration += 1;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Feature-detected read of the host trust decision off an event ctx.
|
|
61
|
+
*
|
|
62
|
+
* Returns `"unknown"` when the host predates the accessor, when the accessor
|
|
63
|
+
* throws, or when it returns a non-boolean — never guesses "untrusted" from a
|
|
64
|
+
* missing surface, because that would silently disable pi-lens on every older
|
|
65
|
+
* host.
|
|
66
|
+
*/
|
|
67
|
+
export function readProjectTrustFromContext(ctx) {
|
|
68
|
+
try {
|
|
69
|
+
const accessor = ctx
|
|
70
|
+
?.isProjectTrusted;
|
|
71
|
+
if (typeof accessor !== "function")
|
|
72
|
+
return "unknown";
|
|
73
|
+
const trusted = accessor.call(ctx);
|
|
74
|
+
if (typeof trusted !== "boolean")
|
|
75
|
+
return "unknown";
|
|
76
|
+
return trusted ? "trusted" : "untrusted";
|
|
77
|
+
}
|
|
78
|
+
catch (accessorErr) {
|
|
79
|
+
// The accessor exists, so this is not the absent older-host signal. If it
|
|
80
|
+
// fails, executable content must remain gated until a later adoption works.
|
|
81
|
+
// Log it (#1350 delta review): a host whose accessor throws on every call
|
|
82
|
+
// permanently gates installs/LSP, and without this line the only trace is
|
|
83
|
+
// indirect refusal logs.
|
|
84
|
+
logExtension({
|
|
85
|
+
subsystem: "project-trust",
|
|
86
|
+
level: "warn",
|
|
87
|
+
message: "isProjectTrusted() threw -- latching untrusted (fail-closed)",
|
|
88
|
+
metadata: { error: String(accessorErr) },
|
|
89
|
+
});
|
|
90
|
+
return "untrusted";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Latch a trust state directly (tests, and the ctx adoption path below). */
|
|
94
|
+
export function setProjectTrustState(next) {
|
|
95
|
+
const previous = trustState;
|
|
96
|
+
latchProjectTrustState(next);
|
|
97
|
+
if (previous !== next) {
|
|
98
|
+
logExtension({
|
|
99
|
+
subsystem: "project-trust",
|
|
100
|
+
level: "debug",
|
|
101
|
+
message: `project trust state transitioned: ${previous} -> ${next}`,
|
|
102
|
+
metadata: { previous, next },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Read the host decision off `ctx` and latch it. Called from `session_start`,
|
|
108
|
+
* which fires again on fork/reload/resume — a re-read per session start is
|
|
109
|
+
* exactly right, since the cwd (and therefore the trust decision) can change.
|
|
110
|
+
*/
|
|
111
|
+
export function adoptProjectTrustFromContext(ctx) {
|
|
112
|
+
const next = readProjectTrustFromContext(ctx);
|
|
113
|
+
const previous = trustState;
|
|
114
|
+
latchProjectTrustState(next);
|
|
115
|
+
if (previous !== next) {
|
|
116
|
+
logExtension({
|
|
117
|
+
subsystem: "project-trust",
|
|
118
|
+
level: "debug",
|
|
119
|
+
message: `project trust state transitioned: ${previous} -> ${next}`,
|
|
120
|
+
metadata: { previous, next },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
/** Adopt through the canonical host capability boundary. */
|
|
126
|
+
export function adoptProjectTrustFromPorts(ports) {
|
|
127
|
+
const next = ports.trust.isProjectTrusted();
|
|
128
|
+
setProjectTrustState(next);
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
131
|
+
export function getProjectTrustState() {
|
|
132
|
+
return trustState;
|
|
133
|
+
}
|
|
134
|
+
/** Test/teardown-only: back to the fail-open default. */
|
|
135
|
+
export function resetProjectTrust() {
|
|
136
|
+
latchProjectTrustState("unknown");
|
|
137
|
+
installRefusalWarnings.clear();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* False only when the host actively denied trust. Gates tool auto-install
|
|
141
|
+
* (download + execute), never plain discovery of an already-present binary.
|
|
142
|
+
*/
|
|
143
|
+
export function isToolInstallAllowedByTrust() {
|
|
144
|
+
return trustState !== "untrusted";
|
|
145
|
+
}
|
|
146
|
+
/** Central gate for operations that may download or install executable content. */
|
|
147
|
+
export function assertInstallAllowed(context) {
|
|
148
|
+
if (isToolInstallAllowedByTrust())
|
|
149
|
+
return true;
|
|
150
|
+
recordDegradation({
|
|
151
|
+
kind: "trust-refusal",
|
|
152
|
+
subject: context,
|
|
153
|
+
reason: projectTrustDenialReason() ?? "project trust denied",
|
|
154
|
+
});
|
|
155
|
+
if (!installRefusalWarnings.has(context)) {
|
|
156
|
+
if (installRefusalWarnings.size >= INSTALL_REFUSAL_WARNING_CAP) {
|
|
157
|
+
installRefusalWarnings.clear();
|
|
158
|
+
}
|
|
159
|
+
installRefusalWarnings.add(context);
|
|
160
|
+
logExtension({
|
|
161
|
+
subsystem: "project-trust",
|
|
162
|
+
level: "warn",
|
|
163
|
+
message: `install/materialization blocked: ${context}`,
|
|
164
|
+
metadata: { context, trustState },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* False only when the host actively denied trust. Gates LSP server child
|
|
171
|
+
* process spawns.
|
|
172
|
+
*/
|
|
173
|
+
export function isLspSpawnAllowedByTrust() {
|
|
174
|
+
return trustState !== "untrusted";
|
|
175
|
+
}
|
|
176
|
+
/** Human/log-readable reason, or undefined when nothing is being blocked. */
|
|
177
|
+
export function projectTrustDenialReason() {
|
|
178
|
+
return trustState === "untrusted"
|
|
179
|
+
? "project is not trusted (host isProjectTrusted() === false)"
|
|
180
|
+
: undefined;
|
|
181
|
+
}
|
|
@@ -185,10 +185,24 @@ const HEARTBEAT_SAMPLE_TIMEOUT_MS = 2000;
|
|
|
185
185
|
* a timeout resolves to `{}` (same "leave everything untouched" semantics as
|
|
186
186
|
* any other sampling failure — see the module docstring below). */
|
|
187
187
|
async function buildHeartbeatResourcePatchBounded() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
// #1097: clear the timeout once the race settles. An uncleared, REF'D
|
|
189
|
+
// setTimeout would keep the event loop alive for the full
|
|
190
|
+
// HEARTBEAT_SAMPLE_TIMEOUT_MS after `agent_settled` fires the quiet window —
|
|
191
|
+
// a same-class sibling of the LSP client-wait leak: harmless in a long-lived
|
|
192
|
+
// session, but a keep-alive tail in a one-shot `pi --print` process.
|
|
193
|
+
let timer;
|
|
194
|
+
try {
|
|
195
|
+
return await Promise.race([
|
|
196
|
+
buildHeartbeatResourcePatch(),
|
|
197
|
+
new Promise((resolve) => {
|
|
198
|
+
timer = setTimeout(() => resolve({}), HEARTBEAT_SAMPLE_TIMEOUT_MS);
|
|
199
|
+
}),
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
if (timer)
|
|
204
|
+
clearTimeout(timer);
|
|
205
|
+
}
|
|
192
206
|
}
|
|
193
207
|
/**
|
|
194
208
|
* #620: sample this process's host CPU% plus every currently-recorded LSP
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic read-recording bridge for pi-lens.
|
|
3
|
+
*
|
|
4
|
+
* ## Trust model — explicitly advisory
|
|
5
|
+
*
|
|
6
|
+
* This bridge is an **advisory, trust-based protocol** for same-process Pi
|
|
7
|
+
* extensions. Any code sharing the same Node.js process already has full
|
|
8
|
+
* access to pi-lens's internal state, so the bridge cannot and does not
|
|
9
|
+
* provide a meaningful security boundary. What it *does* provide is:
|
|
10
|
+
*
|
|
11
|
+
* - A stable API surface so extensions do not need to import pi-lens
|
|
12
|
+
* internals directly.
|
|
13
|
+
* - A single place for flag/scope checks (no-read-guard, ignored paths).
|
|
14
|
+
* - Basic defensive validation to catch integration bugs early (malformed
|
|
15
|
+
* payloads).
|
|
16
|
+
*
|
|
17
|
+
* Mounts a `ReadBridge` object at `globalThis[READ_BRIDGE_KEY]` that any
|
|
18
|
+
* co-process extension can call to register a file read against pi-lens's
|
|
19
|
+
* read-guard — without either party needing to know about the other.
|
|
20
|
+
*
|
|
21
|
+
* Protocol (producer side)
|
|
22
|
+
* ────────────────────────
|
|
23
|
+
* A co-process Pi extension that performs file reads outside pi-lens's
|
|
24
|
+
* awareness (e.g. via a custom registered tool) can forward those reads so
|
|
25
|
+
* that a subsequent `edit` call on the same file is not blocked by the
|
|
26
|
+
* read-before-edit guard:
|
|
27
|
+
*
|
|
28
|
+
* const bridge = (globalThis as any)[Symbol.for("pi-lens:read-bridge")];
|
|
29
|
+
* bridge?.recordRead({
|
|
30
|
+
* filePath, // absolute path
|
|
31
|
+
* requestedOffset, // 1-indexed first line (default 1)
|
|
32
|
+
* requestedLimit, // line count, or undefined for the whole file
|
|
33
|
+
* consumer, // optional identifier, e.g. "my-extension" (appears in read-guard.log)
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* Check `bridge.version` before calling to guard against future incompatible
|
|
37
|
+
* changes — a bridge whose version you don't recognise should be treated as
|
|
38
|
+
* unsupported.
|
|
39
|
+
*
|
|
40
|
+
* The timestamp is stamped by the bridge itself (Date.now()) to match
|
|
41
|
+
* exactly how the internal read path works.
|
|
42
|
+
*
|
|
43
|
+
* Calling before pi-lens is loaded, or when the guard is disabled via
|
|
44
|
+
* `PI_LENS_NO_READ_GUARD`, is safe — the bridge is absent or the call is
|
|
45
|
+
* silently dropped.
|
|
46
|
+
*
|
|
47
|
+
* Protocol (registration side, internal to pi-lens)
|
|
48
|
+
* ──────────────────────────────────────────────────
|
|
49
|
+
* `registerReadBridge` is called once from inside the extension factory
|
|
50
|
+
* (after `getLensFlag` is available) via the `_readBridgeRegistered`
|
|
51
|
+
* singleton guard so that factory re-activations in the same process do not
|
|
52
|
+
* mount a second bridge.
|
|
53
|
+
*
|
|
54
|
+
* The `isRecordable` predicate is re-evaluated on every `recordRead` call
|
|
55
|
+
* using the *current* activation's flag getter (stored in a module-level
|
|
56
|
+
* holder refreshed on every factory activation — same pattern as
|
|
57
|
+
* `_turnSummaryEmitCtx`), so flag changes take effect immediately.
|
|
58
|
+
*/
|
|
59
|
+
/** Stable Symbol key — identical across module reloads in the same process. */
|
|
60
|
+
import { captureReadContentBinding, } from "./read-guard.js";
|
|
61
|
+
export const READ_BRIDGE_KEY = Symbol.for("pi-lens:read-bridge");
|
|
62
|
+
/**
|
|
63
|
+
* Validate a raw entry from an untrusted caller.
|
|
64
|
+
* Returns `true` when the entry is structurally sound and safe to forward.
|
|
65
|
+
*
|
|
66
|
+
* Validation is deliberately lightweight — this is an advisory protocol
|
|
67
|
+
* between same-process extensions (see the module-level trust-model note).
|
|
68
|
+
* The goal is to catch integration bugs (typo'd fields, bad numbers) early
|
|
69
|
+
* rather than to enforce a security boundary.
|
|
70
|
+
*/
|
|
71
|
+
function isValidEntry(entry) {
|
|
72
|
+
if (typeof entry !== "object" || entry === null)
|
|
73
|
+
return false;
|
|
74
|
+
const e = entry;
|
|
75
|
+
// filePath must be a non-empty string (absolute paths are expected but
|
|
76
|
+
// we don't re-resolve here — `isRecordable` handles scope checks).
|
|
77
|
+
if (typeof e["filePath"] !== "string" || e["filePath"] === "")
|
|
78
|
+
return false;
|
|
79
|
+
// requestedOffset must be a finite integer ≥ 1.
|
|
80
|
+
const offset = e["requestedOffset"];
|
|
81
|
+
if (typeof offset !== "number" ||
|
|
82
|
+
!Number.isFinite(offset) ||
|
|
83
|
+
offset < 1 ||
|
|
84
|
+
!Number.isInteger(offset))
|
|
85
|
+
return false;
|
|
86
|
+
// requestedLimit must be undefined or a finite positive integer.
|
|
87
|
+
const limit = e["requestedLimit"];
|
|
88
|
+
if (limit !== undefined) {
|
|
89
|
+
if (typeof limit !== "number" ||
|
|
90
|
+
!Number.isFinite(limit) ||
|
|
91
|
+
limit < 1 ||
|
|
92
|
+
!Number.isInteger(limit))
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Mount the bridge singleton. Call once from inside the extension factory
|
|
99
|
+
* (protected by the `_readBridgeRegistered` module-level flag).
|
|
100
|
+
* Subsequent calls are no-ops.
|
|
101
|
+
*/
|
|
102
|
+
export function registerReadBridge(deps) {
|
|
103
|
+
// Use `in` check so the frozen non-configurable property doesn't throw
|
|
104
|
+
// on a redundant defineProperty attempt.
|
|
105
|
+
if (READ_BRIDGE_KEY in globalThis)
|
|
106
|
+
return;
|
|
107
|
+
const bridge = Object.freeze({
|
|
108
|
+
version: 1,
|
|
109
|
+
recordRead(entry) {
|
|
110
|
+
// Validate the payload before doing anything else — this catches
|
|
111
|
+
// integration bugs in callers (malformed fields, bad numbers).
|
|
112
|
+
if (!isValidEntry(entry))
|
|
113
|
+
return;
|
|
114
|
+
if (!deps.isRecordable(entry.filePath))
|
|
115
|
+
return;
|
|
116
|
+
const offset = entry.requestedOffset;
|
|
117
|
+
// When no limit is given treat the whole file as covered — the guard
|
|
118
|
+
// clips to the actual line count via its own file-length probe.
|
|
119
|
+
const limit = entry.requestedLimit ?? Number.MAX_SAFE_INTEGER;
|
|
120
|
+
const contentBinding = captureReadContentBinding(entry.filePath, offset, limit);
|
|
121
|
+
deps.getReadGuard().recordRead({
|
|
122
|
+
filePath: entry.filePath,
|
|
123
|
+
requestedOffset: offset,
|
|
124
|
+
requestedLimit: limit,
|
|
125
|
+
effectiveOffset: offset,
|
|
126
|
+
effectiveLimit: limit,
|
|
127
|
+
expandedByLsp: false,
|
|
128
|
+
turnIndex: deps.getTurnIndex(),
|
|
129
|
+
writeIndex: deps.peekWriteIndex(),
|
|
130
|
+
// Stamp the timestamp here, matching exactly how the internal read
|
|
131
|
+
// path works (runtime-tool-call.ts always uses Date.now()).
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
// Provenance: identifies this record as bridge-sourced in read-guard.log.
|
|
134
|
+
source: `bridge:${entry.consumer ?? "unknown"}`,
|
|
135
|
+
...(contentBinding !== undefined && { contentBinding }),
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
// Register as non-writable, non-configurable so no subsequent code can
|
|
140
|
+
// silently overwrite the bridge (first-wins is the contract).
|
|
141
|
+
Object.defineProperty(globalThis, READ_BRIDGE_KEY, {
|
|
142
|
+
value: bridge,
|
|
143
|
+
writable: false,
|
|
144
|
+
configurable: false,
|
|
145
|
+
enumerable: false,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -17,6 +17,101 @@ const VERBOSE_READ_GUARD_LOG = process.env.PI_LENS_READ_GUARD_VERBOSE === "1" ||
|
|
|
17
17
|
const LOG_ALLOWED_EDITS = process.env.PI_LENS_READ_GUARD_LOG_ALLOWS === "1";
|
|
18
18
|
const SNAPSHOT_LOG_SETTING = (process.env.PI_LENS_READ_GUARD_LOG_SNAPSHOTS ?? "1").toLowerCase();
|
|
19
19
|
const LOG_SNAPSHOT_VALIDATION = !["0", "false", "off"].includes(SNAPSHOT_LOG_SETTING);
|
|
20
|
+
export const MAX_EDIT_BATCH_ITEMS = 100;
|
|
21
|
+
let generatedCorrelationCounter = 0;
|
|
22
|
+
function sanitizeCorrelationId(value) {
|
|
23
|
+
if (typeof value !== "string" && typeof value !== "number")
|
|
24
|
+
return undefined;
|
|
25
|
+
const sanitized = String(value)
|
|
26
|
+
.trim()
|
|
27
|
+
.replace(/[^a-zA-Z0-9._:-]/g, "_")
|
|
28
|
+
.slice(0, 64);
|
|
29
|
+
return sanitized.length > 0 ? sanitized : undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Prefer the host call token; otherwise create a bounded per-process token. */
|
|
32
|
+
export function getReadGuardCorrelationId(event) {
|
|
33
|
+
const value = (event ?? {});
|
|
34
|
+
const details = (value.details ?? {});
|
|
35
|
+
for (const candidate of [
|
|
36
|
+
details.readGuardCorrelationId,
|
|
37
|
+
details.toolCallId,
|
|
38
|
+
value.toolCallId,
|
|
39
|
+
value.callId,
|
|
40
|
+
value.requestId,
|
|
41
|
+
value.id,
|
|
42
|
+
]) {
|
|
43
|
+
const sanitized = sanitizeCorrelationId(candidate);
|
|
44
|
+
if (sanitized)
|
|
45
|
+
return sanitized;
|
|
46
|
+
}
|
|
47
|
+
generatedCorrelationCounter =
|
|
48
|
+
(generatedCorrelationCounter + 1) % 1_000_000;
|
|
49
|
+
return `rg-${Date.now().toString(36)}-${generatedCorrelationCounter}`;
|
|
50
|
+
}
|
|
51
|
+
function validIndexes(indexes) {
|
|
52
|
+
return indexes.filter((index) => Number.isInteger(index) && index >= 0);
|
|
53
|
+
}
|
|
54
|
+
export function boundedEditIndexes(indexes) {
|
|
55
|
+
return [...new Set(validIndexes(indexes))].slice(0, MAX_EDIT_BATCH_ITEMS);
|
|
56
|
+
}
|
|
57
|
+
export function boundedIndexesForCount(count) {
|
|
58
|
+
const boundedCount = Math.max(0, Math.floor(count));
|
|
59
|
+
return Array.from({ length: Math.min(boundedCount, MAX_EDIT_BATCH_ITEMS) }, (_, index) => index);
|
|
60
|
+
}
|
|
61
|
+
export function formatBoundedEditIndexes(indexes) {
|
|
62
|
+
return boundedEditIndexes(indexes)
|
|
63
|
+
.map((index) => `edits[${index}]`)
|
|
64
|
+
.join(", ");
|
|
65
|
+
}
|
|
66
|
+
export function createReadGuardEditBatchSummary(args) {
|
|
67
|
+
const requestedSource = validIndexes(args.requestedIndexes);
|
|
68
|
+
const resolvedSource = validIndexes(args.resolvedIndexes ?? []);
|
|
69
|
+
const rejectedSource = (args.rejectedReasons ?? []).filter((entry) => Number.isInteger(entry.index) && entry.index >= 0);
|
|
70
|
+
const appliedSource = validIndexes(args.appliedIndexes ?? []);
|
|
71
|
+
const requestedIndexes = boundedEditIndexes(requestedSource);
|
|
72
|
+
const resolvedIndexes = boundedEditIndexes(resolvedSource);
|
|
73
|
+
const rejectedReasons = rejectedSource.slice(0, MAX_EDIT_BATCH_ITEMS);
|
|
74
|
+
const rejectedIndexes = boundedEditIndexes(rejectedSource.map((entry) => entry.index));
|
|
75
|
+
const appliedIndexes = boundedEditIndexes(appliedSource);
|
|
76
|
+
const participantSource = (args.participantIds ?? []).filter((id) => typeof id === "string" && id.length > 0);
|
|
77
|
+
const participantIds = [...new Set(participantSource)].slice(0, MAX_EDIT_BATCH_ITEMS);
|
|
78
|
+
const commitStatus = args.commitStatus ?? "not_attempted";
|
|
79
|
+
const postEditStatus = args.postEditStatus ?? "not_run";
|
|
80
|
+
const terminalStatus = args.terminalStatus ??
|
|
81
|
+
(postEditStatus === "failed" || commitStatus === "failed"
|
|
82
|
+
? "failed"
|
|
83
|
+
: commitStatus === "not_attempted" && rejectedSource.length > 0
|
|
84
|
+
? "blocked"
|
|
85
|
+
: commitStatus === "no_changes"
|
|
86
|
+
? "skipped"
|
|
87
|
+
: "success");
|
|
88
|
+
return {
|
|
89
|
+
requestedCount: requestedSource.length,
|
|
90
|
+
requestedTotal: args.requestedTotal ?? requestedSource.length,
|
|
91
|
+
requestedIndexes,
|
|
92
|
+
resolvedCount: resolvedSource.length,
|
|
93
|
+
resolvedTotal: args.resolvedTotal ?? resolvedSource.length,
|
|
94
|
+
resolvedIndexes,
|
|
95
|
+
rejectedCount: rejectedSource.length,
|
|
96
|
+
rejectedTotal: args.rejectedTotal ?? rejectedSource.length,
|
|
97
|
+
rejectedIndexes,
|
|
98
|
+
rejectedReasons,
|
|
99
|
+
appliedCount: appliedSource.length,
|
|
100
|
+
appliedTotal: args.appliedTotal ?? appliedSource.length,
|
|
101
|
+
appliedIndexes,
|
|
102
|
+
participantIds,
|
|
103
|
+
participantTotal: args.participantTotal ?? participantSource.length,
|
|
104
|
+
participantIdsTruncated: (args.participantTotal ?? participantSource.length) > participantIds.length,
|
|
105
|
+
indexesTruncated: (args.requestedTotal ?? requestedSource.length) > requestedIndexes.length ||
|
|
106
|
+
(args.resolvedTotal ?? resolvedSource.length) > resolvedIndexes.length ||
|
|
107
|
+
(args.rejectedTotal ?? rejectedSource.length) > rejectedIndexes.length ||
|
|
108
|
+
(args.appliedTotal ?? appliedSource.length) > appliedIndexes.length,
|
|
109
|
+
commitStatus,
|
|
110
|
+
postEditStatus,
|
|
111
|
+
terminalStatus,
|
|
112
|
+
durationMs: Math.max(0, Math.min(args.durationMs ?? 0, 86_400_000)),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
20
115
|
function shouldLogEvent(event) {
|
|
21
116
|
if (VERBOSE_READ_GUARD_LOG)
|
|
22
117
|
return true;
|
|
@@ -35,13 +130,76 @@ function shouldLogEvent(event) {
|
|
|
35
130
|
event === "edit_range_relocated" ||
|
|
36
131
|
event === "edit_preflight_blocked" ||
|
|
37
132
|
event === "edit_partial_apply" ||
|
|
133
|
+
event === "edit_partial_apply_skipped" ||
|
|
134
|
+
event === "edit_post_edit_pipeline_failed" ||
|
|
135
|
+
event === "edit_batch_summary" ||
|
|
136
|
+
event === "edit_batch_summary_overflow" ||
|
|
38
137
|
event === "touched_lines_missing");
|
|
39
138
|
}
|
|
139
|
+
const MAX_TELEMETRY_STRING = 512;
|
|
140
|
+
const MAX_TELEMETRY_KEYS = 64;
|
|
141
|
+
function boundTelemetryValue(value, pathName, depth) {
|
|
142
|
+
if (typeof value === "string") {
|
|
143
|
+
return value.length > MAX_TELEMETRY_STRING
|
|
144
|
+
? {
|
|
145
|
+
value: `${value.slice(0, MAX_TELEMETRY_STRING)}…`,
|
|
146
|
+
truncated: true,
|
|
147
|
+
arrayTotals: {},
|
|
148
|
+
}
|
|
149
|
+
: { value, truncated: false, arrayTotals: {} };
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(value)) {
|
|
152
|
+
const sampled = value.slice(0, MAX_EDIT_BATCH_ITEMS);
|
|
153
|
+
const bounded = sampled.map((item, index) => boundTelemetryValue(item, `${pathName}[${index}]`, depth + 1));
|
|
154
|
+
const arrayTotals = value.length > MAX_EDIT_BATCH_ITEMS
|
|
155
|
+
? { [pathName || "array"]: value.length }
|
|
156
|
+
: {};
|
|
157
|
+
let truncated = value.length > MAX_EDIT_BATCH_ITEMS;
|
|
158
|
+
for (const item of bounded) {
|
|
159
|
+
truncated ||= item.truncated;
|
|
160
|
+
Object.assign(arrayTotals, item.arrayTotals);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
value: bounded.map((item) => item.value),
|
|
164
|
+
truncated,
|
|
165
|
+
arrayTotals,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (!value || typeof value !== "object" || depth >= 4) {
|
|
169
|
+
return { value, truncated: false, arrayTotals: {} };
|
|
170
|
+
}
|
|
171
|
+
const output = {};
|
|
172
|
+
let truncated = false;
|
|
173
|
+
const arrayTotals = {};
|
|
174
|
+
for (const [key, child] of Object.entries(value).slice(0, MAX_TELEMETRY_KEYS)) {
|
|
175
|
+
const bounded = boundTelemetryValue(child, pathName ? `${pathName}.${key}` : key, depth + 1);
|
|
176
|
+
output[key] = bounded.value;
|
|
177
|
+
truncated ||= bounded.truncated;
|
|
178
|
+
Object.assign(arrayTotals, bounded.arrayTotals);
|
|
179
|
+
}
|
|
180
|
+
if (Object.keys(value).length > MAX_TELEMETRY_KEYS)
|
|
181
|
+
truncated = true;
|
|
182
|
+
return { value: output, truncated, arrayTotals };
|
|
183
|
+
}
|
|
40
184
|
export function logReadGuardEvent(entry) {
|
|
41
185
|
if (isTestMode() || !shouldLogEvent(entry.event)) {
|
|
42
186
|
return;
|
|
43
187
|
}
|
|
44
|
-
|
|
188
|
+
const rawMetadata = entry.correlationId
|
|
189
|
+
? { ...(entry.metadata ?? {}), correlationId: entry.correlationId }
|
|
190
|
+
: entry.metadata;
|
|
191
|
+
const bounded = boundTelemetryValue(rawMetadata, "metadata", 0);
|
|
192
|
+
const metadata = bounded.truncated && bounded.value && typeof bounded.value === "object"
|
|
193
|
+
? {
|
|
194
|
+
...bounded.value,
|
|
195
|
+
telemetryTruncated: true,
|
|
196
|
+
telemetryArrayTotals: bounded.arrayTotals,
|
|
197
|
+
}
|
|
198
|
+
: bounded.value && typeof bounded.value === "object"
|
|
199
|
+
? bounded.value
|
|
200
|
+
: undefined;
|
|
201
|
+
const { correlationId: _correlationId, ...logEntry } = entry;
|
|
202
|
+
writer.log({ ts: new Date().toISOString(), ...logEntry, metadata });
|
|
45
203
|
}
|
|
46
204
|
export function getReadGuardLogPath() {
|
|
47
205
|
return READ_GUARD_LOG_FILE;
|