@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,192 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const waitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
6
|
+
function ownerPidIsLive(pid) {
|
|
7
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
8
|
+
return false;
|
|
9
|
+
try {
|
|
10
|
+
process.kill(pid, 0);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return error.code === "EPERM";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function quarantinePath(lockPath, token) {
|
|
18
|
+
return `${lockPath}.quarantine-${process.pid}-${token}`;
|
|
19
|
+
}
|
|
20
|
+
async function restoreQuarantinedLock(lockPath, quarantined) {
|
|
21
|
+
try {
|
|
22
|
+
await fsp.rename(quarantined, lockPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// A replacement owner may already hold the canonical name. Never overwrite it.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function releaseQuarantineLock(lockPath, token) {
|
|
29
|
+
const quarantined = quarantinePath(lockPath, `release-${token}`);
|
|
30
|
+
let renamed = false;
|
|
31
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
32
|
+
try {
|
|
33
|
+
await fsp.rename(lockPath, quarantined);
|
|
34
|
+
renamed = true;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code !== "ENOENT" ||
|
|
39
|
+
attempt === 2)
|
|
40
|
+
return;
|
|
41
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!renamed)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
|
|
48
|
+
if (owner.token === token) {
|
|
49
|
+
await fsp.rm(quarantined, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function quarantineOwnerIsStale(owner, staleMs) {
|
|
60
|
+
return Number.isInteger(owner.pid) && owner.pid > 0 &&
|
|
61
|
+
Number.isFinite(owner.createdAt) &&
|
|
62
|
+
(!ownerPidIsLive(owner.pid) || Date.now() - owner.createdAt > staleMs);
|
|
63
|
+
}
|
|
64
|
+
async function reclaimQuarantineLock(lockPath, staleMs) {
|
|
65
|
+
const quarantined = quarantinePath(lockPath, `reclaim-${Date.now()}-${randomUUID()}`);
|
|
66
|
+
try {
|
|
67
|
+
await fsp.rename(lockPath, quarantined);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
let stale = false;
|
|
73
|
+
try {
|
|
74
|
+
const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
|
|
75
|
+
stale = quarantineOwnerIsStale(owner, staleMs);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
try {
|
|
79
|
+
stale = Date.now() - (await fsp.stat(quarantined)).mtimeMs > staleMs;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
stale = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (stale) {
|
|
86
|
+
await fsp.rm(quarantined, { recursive: true, force: true });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
await restoreQuarantinedLock(lockPath, quarantined);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
async function tryAcquireQuarantineLock(lockPath, staleMs) {
|
|
93
|
+
const owner = {
|
|
94
|
+
pid: process.pid,
|
|
95
|
+
createdAt: Date.now(),
|
|
96
|
+
token: `${process.pid}-${Date.now()}-${randomUUID()}`,
|
|
97
|
+
};
|
|
98
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
99
|
+
try {
|
|
100
|
+
await fsp.mkdir(lockPath);
|
|
101
|
+
try {
|
|
102
|
+
await fsp.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), "utf8");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return () => releaseQuarantineLock(lockPath, owner.token);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error.code !== "EEXIST")
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
let stale;
|
|
115
|
+
try {
|
|
116
|
+
const existing = JSON.parse(await fsp.readFile(path.join(lockPath, "owner.json"), "utf8"));
|
|
117
|
+
stale = quarantineOwnerIsStale(existing, staleMs);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
try {
|
|
121
|
+
stale = Date.now() - (await fsp.stat(lockPath)).mtimeMs > staleMs;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
stale = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!stale || !(await reclaimQuarantineLock(lockPath, staleMs)))
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
export async function acquireQuarantinePidFileLock(lockPath, options) {
|
|
133
|
+
const deadline = Date.now() + options.waitMs;
|
|
134
|
+
for (;;) {
|
|
135
|
+
const release = await tryAcquireQuarantineLock(lockPath, options.staleMs);
|
|
136
|
+
if (release)
|
|
137
|
+
return release;
|
|
138
|
+
const remaining = deadline - Date.now();
|
|
139
|
+
if (remaining <= 0) {
|
|
140
|
+
if (options.onContention === "skip-log") {
|
|
141
|
+
options.logContention();
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(options.timeoutMessage);
|
|
145
|
+
}
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(options.retryMs, remaining)));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function acquireBoundedPidFileLock(lockPath, options) {
|
|
150
|
+
const token = `${process.pid}:${Date.now()}:${randomUUID()}`;
|
|
151
|
+
const deadline = Date.now() + options.waitMs;
|
|
152
|
+
for (;;) {
|
|
153
|
+
try {
|
|
154
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
155
|
+
fs.writeFileSync(fd, token, "utf8");
|
|
156
|
+
fs.closeSync(fd);
|
|
157
|
+
return () => {
|
|
158
|
+
try {
|
|
159
|
+
if (fs.readFileSync(lockPath, "utf8") === token) {
|
|
160
|
+
fs.unlinkSync(lockPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Protected write completed; cleanup is best-effort.
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error.code !== "EEXIST")
|
|
170
|
+
throw error;
|
|
171
|
+
try {
|
|
172
|
+
const [pidText] = fs.readFileSync(lockPath, "utf8").split(":", 1);
|
|
173
|
+
if (!ownerPidIsLive(Number.parseInt(pidText ?? "", 10))) {
|
|
174
|
+
fs.unlinkSync(lockPath);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (lockError) {
|
|
179
|
+
if (lockError.code === "ENOENT")
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (Date.now() >= deadline) {
|
|
183
|
+
if (options.onContention === "skip-log") {
|
|
184
|
+
options.logContention();
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
throw new Error(options.timeoutMessage);
|
|
188
|
+
}
|
|
189
|
+
Atomics.wait(waitArray, 0, 0, options.retryMs);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persistent NDJSON trace of `pi.events` bus publish attempts
|
|
3
3
|
* (`pilens:files:touched` #482 / `pilens:diagnostics` #502 /
|
|
4
|
-
* `pilens:
|
|
4
|
+
* `pilens:diagnostic:disposition` / `pilens:format:queued` +
|
|
5
|
+
* `pilens:format:start` + `pilens:autofix:start` #673/#684 /
|
|
6
|
+
* `pi-lens/analysis-complete` + `pi-lens/findings` + `pi-lens/turn-findings`
|
|
7
|
+
* #1415) — nine event names across five producers.
|
|
5
8
|
*
|
|
6
|
-
* All
|
|
7
|
-
* clients/format-events-publish.ts
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* All five producers (clients/bus-publish.ts, clients/diagnostics-publish.ts,
|
|
10
|
+
* clients/disposition-publish.ts, clients/format-events-publish.ts,
|
|
11
|
+
* clients/lens-events.ts) are fire-and-forget: on failure or on a structural
|
|
12
|
+
* no-op (never wired, kill switch off) the four #482/#502/#673/#684
|
|
13
|
+
* producers only invoke an optional `dbg` callback, which varies by host and
|
|
14
|
+
* is a documented no-op in the MCP host (clients/mcp/session.ts's
|
|
15
|
+
* `dbg: noop`); `lens-events.ts` (#1415) has no `dbg` param at all — its
|
|
16
|
+
* events are purely observational inter-extension telemetry, so this NDJSON
|
|
17
|
+
* trace is its ONLY failure-visible surface. Either way, that leaves
|
|
11
18
|
* bus-publish outcomes invisible in exactly the context where they matter
|
|
12
19
|
* most — same failure shape as the #544 MCP session_start incident this repo
|
|
13
20
|
* already fixed once.
|
|
@@ -26,6 +33,9 @@
|
|
|
26
33
|
* spam one identical line per write batch for an entire session with zero
|
|
27
34
|
* new information after the first. Both are gated log-once-per-process, the
|
|
28
35
|
* same `hasLoggedFailure` shape the producers already use for emit_failed.
|
|
36
|
+
* `skipped_stale_session` is an info-level per-occurrence outcome: the emit
|
|
37
|
+
* seam intentionally declined a target whose associated ctx was confirmed
|
|
38
|
+
* stale, so it is neither a bus failure nor input for the smells rollup.
|
|
29
39
|
* The empty-batch branch (`paths.length === 0` / `files.length === 0`) is
|
|
30
40
|
* NOT logged at all: every call site already guards against invoking these
|
|
31
41
|
* functions with nothing to report (see clients/pipeline.ts,
|
|
@@ -37,17 +47,63 @@ import { isTestMode } from "./env-utils.js";
|
|
|
37
47
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
38
48
|
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
39
49
|
import { getMaxLogSizeMB } from "./log-cleanup.js";
|
|
50
|
+
import { logLatency } from "./latency-logger.js";
|
|
40
51
|
const BUS_EVENTS_LOG_FILE = path.join(getGlobalPiLensDir(), "bus-events.log");
|
|
41
52
|
const writer = createNdjsonLogger({
|
|
42
53
|
filePath: BUS_EVENTS_LOG_FILE,
|
|
43
54
|
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
44
55
|
});
|
|
56
|
+
const eventRollupCounts = new Map();
|
|
57
|
+
function bumpRollupCount(event, outcome) {
|
|
58
|
+
const existing = eventRollupCounts.get(event) ?? {
|
|
59
|
+
emitted: 0,
|
|
60
|
+
skipped_stale_session: 0,
|
|
61
|
+
emit_failed: 0,
|
|
62
|
+
};
|
|
63
|
+
existing[outcome] += 1;
|
|
64
|
+
eventRollupCounts.set(event, existing);
|
|
65
|
+
}
|
|
45
66
|
export function logBusEvent(entry) {
|
|
67
|
+
if (entry.outcome === "emitted" ||
|
|
68
|
+
entry.outcome === "skipped_stale_session" ||
|
|
69
|
+
entry.outcome === "emit_failed") {
|
|
70
|
+
bumpRollupCount(entry.event, entry.outcome);
|
|
71
|
+
}
|
|
46
72
|
if (isTestMode()) {
|
|
47
73
|
return;
|
|
48
74
|
}
|
|
49
75
|
writer.log({ ts: new Date().toISOString(), ...entry });
|
|
50
76
|
}
|
|
77
|
+
/** Snapshot the current session's rollup, keyed by event name. Non-mutating —
|
|
78
|
+
* pair with {@link resetBusEventRollupCounts} at session end. */
|
|
79
|
+
export function getBusEventRollupCounts() {
|
|
80
|
+
return Object.fromEntries(eventRollupCounts);
|
|
81
|
+
}
|
|
82
|
+
/** Clear the rollup — call once the session-end snapshot has been logged
|
|
83
|
+
* (or from tests) so a new session starts from zero. */
|
|
84
|
+
export function resetBusEventRollupCounts() {
|
|
85
|
+
eventRollupCounts.clear();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Log one `session_end_bus_rollup` latency row per event NAME that had any
|
|
89
|
+
* activity this session, then clear the rollup. Called from index.ts's
|
|
90
|
+
* `session_shutdown` handler (the session-end hook this repo's other
|
|
91
|
+
* teardown work — LSP fleet shutdown, cache-prefix eviction — already runs
|
|
92
|
+
* from). A no-op (logs nothing) when nothing was ever published, matching
|
|
93
|
+
* `formatSmellsSessionStartLine`'s "no noise on an ordinary session" shape.
|
|
94
|
+
*/
|
|
95
|
+
export function emitBusEventRollupAtSessionEnd(cwd) {
|
|
96
|
+
for (const [event, counts] of eventRollupCounts) {
|
|
97
|
+
logLatency({
|
|
98
|
+
type: "phase",
|
|
99
|
+
phase: "session_end_bus_rollup",
|
|
100
|
+
filePath: cwd,
|
|
101
|
+
durationMs: 0,
|
|
102
|
+
metadata: { event, ...counts },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
resetBusEventRollupCounts();
|
|
106
|
+
}
|
|
51
107
|
export function getBusEventsLogPath() {
|
|
52
108
|
return BUS_EVENTS_LOG_FILE;
|
|
53
109
|
}
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
import { logBusEvent } from "./bus-events-logger.js";
|
|
21
21
|
import { normalizeFilePath } from "./path-utils.js";
|
|
22
22
|
import { appendRecentTouches } from "./recent-touches.js";
|
|
23
|
+
import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
|
|
23
24
|
export const BUS_FILES_TOUCHED_EVENT = "pilens:files:touched";
|
|
24
25
|
export const BUS_FILES_TOUCHED_VERSION = 1;
|
|
25
|
-
|
|
26
|
+
const liveEmitter = createLiveBusEmitter();
|
|
26
27
|
let hasLoggedFailure = false;
|
|
27
28
|
// Log-once-per-process guards for the two structural-no-op outcomes below
|
|
28
29
|
// (see bus-events-logger.ts's module doc for why these aren't logged on
|
|
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
|
|
|
38
39
|
* no `pi.events`).
|
|
39
40
|
*/
|
|
40
41
|
export function wireBusEmitter(emitFn) {
|
|
41
|
-
|
|
42
|
+
liveEmitter.wire(emitFn);
|
|
43
|
+
}
|
|
44
|
+
export function wireBusEmitterGetter(getter) {
|
|
45
|
+
liveEmitter.wireGetter(getter);
|
|
42
46
|
}
|
|
43
47
|
/** Test-only: reset module state between test files. */
|
|
44
48
|
export function _resetForTests() {
|
|
45
|
-
|
|
49
|
+
liveEmitter.reset();
|
|
46
50
|
hasLoggedFailure = false;
|
|
47
51
|
hasLoggedUnwired = false;
|
|
48
52
|
hasLoggedDisabled = false;
|
|
@@ -102,7 +106,14 @@ export function publishFilesTouched(args) {
|
|
|
102
106
|
}).catch((err) => {
|
|
103
107
|
args.dbg?.(`bus-publish: recent-touches append failed: ${err}`);
|
|
104
108
|
});
|
|
105
|
-
|
|
109
|
+
const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
|
|
110
|
+
event: BUS_FILES_TOUCHED_EVENT,
|
|
111
|
+
cwd: normalizeFilePath(args.cwd),
|
|
112
|
+
reason: args.reason,
|
|
113
|
+
}));
|
|
114
|
+
if (resolution.outcome === "stale-session")
|
|
115
|
+
return;
|
|
116
|
+
if (resolution.outcome === "unwired") {
|
|
106
117
|
if (!hasLoggedUnwired) {
|
|
107
118
|
hasLoggedUnwired = true;
|
|
108
119
|
logBusEvent({
|
|
@@ -114,6 +125,7 @@ export function publishFilesTouched(args) {
|
|
|
114
125
|
}
|
|
115
126
|
return;
|
|
116
127
|
}
|
|
128
|
+
const busEmit = resolution.emit;
|
|
117
129
|
try {
|
|
118
130
|
const payload = {
|
|
119
131
|
v: BUS_FILES_TOUCHED_VERSION,
|
|
@@ -129,6 +141,7 @@ export function publishFilesTouched(args) {
|
|
|
129
141
|
}));
|
|
130
142
|
}
|
|
131
143
|
busEmit(BUS_FILES_TOUCHED_EVENT, payload);
|
|
144
|
+
hasLoggedFailure = false;
|
|
132
145
|
logBusEvent({
|
|
133
146
|
event: BUS_FILES_TOUCHED_EVENT,
|
|
134
147
|
outcome: "emitted",
|
|
@@ -144,9 +157,11 @@ export function publishFilesTouched(args) {
|
|
|
144
157
|
cwd: normalizeFilePath(args.cwd),
|
|
145
158
|
reason: args.reason,
|
|
146
159
|
error: String(err),
|
|
160
|
+
ctxSource: resolution.ctxSource,
|
|
147
161
|
});
|
|
148
162
|
if (!hasLoggedFailure) {
|
|
149
163
|
hasLoggedFailure = true;
|
|
164
|
+
recordStaleBusFailure(BUS_FILES_TOUCHED_EVENT, err);
|
|
150
165
|
args.dbg?.(`bus-publish: pilens:files:touched emit failed (further failures suppressed): ${err}`);
|
|
151
166
|
}
|
|
152
167
|
}
|
|
@@ -9,6 +9,8 @@ import * as fs from "node:fs";
|
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { getProjectDataDir } from "../file-utils.js";
|
|
11
11
|
import { readJsonCache } from "../json-cache-read.js";
|
|
12
|
+
import { resolvePackagePath } from "../package-root.js";
|
|
13
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
12
14
|
// v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
|
|
13
15
|
// and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
|
|
14
16
|
// v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
|
|
@@ -20,7 +22,37 @@ import { readJsonCache } from "../json-cache-read.js";
|
|
|
20
22
|
// the language's own. A v5 tsx entry was hashed over tsx files only, so a
|
|
21
23
|
// typescript-rule edit never invalidated it; v5 entries self-miss anyway (the
|
|
22
24
|
// hash input set changed), the bump just makes the semantics break explicit.
|
|
23
|
-
|
|
25
|
+
// v7 (#1118): computeRuleHash now CONTENT-hashes project-local rule files
|
|
26
|
+
// instead of trusting mtime+size alone. mtime+size is the review-graph
|
|
27
|
+
// first-filter, not the full gold standard (size:mtimeMs + confirmContentChanged)
|
|
28
|
+
// — a rule-file edit that preserves both (git checkout timestamp restoration, a
|
|
29
|
+
// same-length tweak, a formatter that preserves mtime) replayed a stale
|
|
30
|
+
// compiled set from disk and re-persisted it under the still-matching
|
|
31
|
+
// fingerprint, poisoning every future process. The bump forces every v6 entry
|
|
32
|
+
// (hashed under the old metadata-only formula) to miss once on upgrade, so a
|
|
33
|
+
// silently-poisoned v6 entry can't be trusted by the new code.
|
|
34
|
+
export const CACHE_VERSION = "v7";
|
|
35
|
+
/**
|
|
36
|
+
* Bundled rule files ship with the extension and are immutable within a
|
|
37
|
+
* process — they can't change mid-session, so the cheap mtime+size
|
|
38
|
+
* fingerprint (v6's whole-set formula) is sufficient for them, and there are
|
|
39
|
+
* ~705 of them across all languages, so unconditionally reading+hashing their
|
|
40
|
+
* bytes on every edit would be the unconditional hot-path hashing the
|
|
41
|
+
* event-loop discipline forbids (RuleCache.get runs on the per-edit
|
|
42
|
+
* tree-sitter runner hot path).
|
|
43
|
+
*
|
|
44
|
+
* Project-local rule files (under `<project>/rules/tree-sitter-queries/`,
|
|
45
|
+
* see `ruleFilesForLanguage`) are the opposite: mutable, and a SMALL set (the
|
|
46
|
+
* handful a developer actually maintains) — cheap to content-hash. Splitting
|
|
47
|
+
* on this prefix mirrors the split `yaml-rule-parser.ts`/`ast-grep-napi.ts`
|
|
48
|
+
* already use for the ast-grep side of the same class (#1105): project-origin
|
|
49
|
+
* trees get a content-hash CONFIRM, bundled trees stay mtime-cheap.
|
|
50
|
+
*/
|
|
51
|
+
const BUNDLED_RULES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
|
|
52
|
+
function isBundledRuleFile(resolvedFile) {
|
|
53
|
+
return (resolvedFile === BUNDLED_RULES_ROOT ||
|
|
54
|
+
resolvedFile.startsWith(BUNDLED_RULES_ROOT + path.sep));
|
|
55
|
+
}
|
|
24
56
|
export class RuleCache {
|
|
25
57
|
cacheFile;
|
|
26
58
|
cacheDir;
|
|
@@ -38,9 +70,19 @@ export class RuleCache {
|
|
|
38
70
|
computeRuleHash(ruleFiles) {
|
|
39
71
|
const hash = crypto.createHash("sha256");
|
|
40
72
|
for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
73
|
+
const resolved = path.resolve(file);
|
|
74
|
+
if (!fs.existsSync(resolved))
|
|
75
|
+
continue;
|
|
76
|
+
const stat = fs.statSync(resolved);
|
|
77
|
+
hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
|
|
78
|
+
// Content-CONFIRM only the project-local, mutable subset (a handful of
|
|
79
|
+
// files) — mtime+size alone is a first filter, not proof of freshness,
|
|
80
|
+
// and a preserved-mtime+size edit here would otherwise replay (and
|
|
81
|
+
// re-persist) a stale compiled set. Bundled files (~705, immutable
|
|
82
|
+
// within a process) skip this: the metadata fingerprint stays their
|
|
83
|
+
// whole story, keeping the common no-project-rules case as cheap as v6.
|
|
84
|
+
if (!isBundledRuleFile(resolved)) {
|
|
85
|
+
hash.update(fs.readFileSync(resolved));
|
|
44
86
|
}
|
|
45
87
|
}
|
|
46
88
|
return hash.digest("hex").slice(0, 16);
|
|
@@ -74,7 +116,7 @@ export class RuleCache {
|
|
|
74
116
|
ruleHash: this.computeRuleHash(ruleFiles),
|
|
75
117
|
queries,
|
|
76
118
|
};
|
|
77
|
-
|
|
119
|
+
writeFileAtomic(this.cacheFile, JSON.stringify(entry, null, 2));
|
|
78
120
|
this.pruneStaleVersions();
|
|
79
121
|
}
|
|
80
122
|
catch {
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* All paths are relative to project root (process.cwd()).
|
|
10
10
|
*/
|
|
11
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
11
12
|
import * as fs from "node:fs";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import { getProjectDataDir } from "./file-utils.js";
|
|
15
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
14
16
|
import { readJsonCache } from "./json-cache-read.js";
|
|
15
17
|
import { normalizeMapKey } from "./path-utils.js";
|
|
16
18
|
// --- Defaults ---
|
|
@@ -21,6 +23,8 @@ const DEFAULT_TURN_STATE = {
|
|
|
21
23
|
maxCycles: 3,
|
|
22
24
|
lastUpdated: "",
|
|
23
25
|
};
|
|
26
|
+
export const MCP_TURN_STATE_OWNER_ID = `mcp-${process.pid}`;
|
|
27
|
+
const TURN_OWNER_STALE_MS = 30 * 60 * 1000;
|
|
24
28
|
// --- Helpers ---
|
|
25
29
|
function getLensDir(cwd) {
|
|
26
30
|
return getProjectDataDir(cwd);
|
|
@@ -36,7 +40,7 @@ export class CacheManager {
|
|
|
36
40
|
log;
|
|
37
41
|
constructor(verbose = false) {
|
|
38
42
|
this.log = verbose
|
|
39
|
-
? (
|
|
43
|
+
? createSubsystemLogger("cache")
|
|
40
44
|
: () => { };
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
@@ -107,10 +111,45 @@ export class CacheManager {
|
|
|
107
111
|
timestamp: new Date().toISOString(),
|
|
108
112
|
...extraMeta,
|
|
109
113
|
};
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
writeFileAtomic(cachePath, JSON.stringify(data, null, 2), {
|
|
115
|
+
bestEffort: false,
|
|
116
|
+
});
|
|
117
|
+
writeFileAtomic(metaPath, JSON.stringify(meta, null, 2), {
|
|
118
|
+
bestEffort: false,
|
|
119
|
+
});
|
|
112
120
|
this.log(`Cache written: ${scanner}`);
|
|
113
121
|
}
|
|
122
|
+
/** Inspect a cache without changing the behavior of readCache consumers. */
|
|
123
|
+
inspectCache(scanner, cwd, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
124
|
+
const cachePath = path.join(getCacheDir(cwd), `${scanner}.json`);
|
|
125
|
+
const metaPath = path.join(getCacheDir(cwd), `${scanner}.meta.json`);
|
|
126
|
+
for (const cachePathname of [cachePath, metaPath]) {
|
|
127
|
+
try {
|
|
128
|
+
fs.statSync(cachePathname);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err.code === "ENOENT")
|
|
132
|
+
return "missing";
|
|
133
|
+
return "unreadable";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const meta = readJsonCache(metaPath, (parsed) => parsed);
|
|
138
|
+
if (!meta || typeof meta.timestamp !== "string")
|
|
139
|
+
return "malformed";
|
|
140
|
+
const timestamp = new Date(meta.timestamp).getTime();
|
|
141
|
+
if (!Number.isFinite(timestamp))
|
|
142
|
+
return "malformed";
|
|
143
|
+
const age = Date.now() - timestamp;
|
|
144
|
+
if (age < 0 || age > maxAgeMs)
|
|
145
|
+
return age < 0 ? "malformed" : "stale";
|
|
146
|
+
const data = readJsonCache(cachePath, (parsed) => parsed);
|
|
147
|
+
return data === undefined ? "malformed" : "fresh";
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return "unreadable";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
114
153
|
/**
|
|
115
154
|
* Check if a cache entry is fresh (exists and not expired).
|
|
116
155
|
*/
|
|
@@ -175,16 +214,57 @@ export class CacheManager {
|
|
|
175
214
|
fs.mkdirSync(lensDir, { recursive: true });
|
|
176
215
|
const statePath = getTurnStatePath(cwd);
|
|
177
216
|
state.lastUpdated = new Date().toISOString();
|
|
178
|
-
|
|
217
|
+
writeFileAtomic(statePath, JSON.stringify(state, null, 2));
|
|
218
|
+
}
|
|
219
|
+
/** Return whether a writer may read/write this workspace worklist. */
|
|
220
|
+
getTurnStateAccess(cwd, owner) {
|
|
221
|
+
const state = this.readTurnState(cwd);
|
|
222
|
+
if (!state.owner) {
|
|
223
|
+
if (!state.sessionId || state.sessionId === owner.id)
|
|
224
|
+
return "owned";
|
|
225
|
+
// Pre-owner files have no liveness information. Preserve the existing
|
|
226
|
+
// stale-session eviction behavior for this legacy shape.
|
|
227
|
+
return "available";
|
|
228
|
+
}
|
|
229
|
+
if (state.owner.kind === owner.kind &&
|
|
230
|
+
state.owner.id === owner.id) {
|
|
231
|
+
return "owned";
|
|
232
|
+
}
|
|
233
|
+
return this.isTurnStateOwnerStale(state.owner) ? "available" : "foreign-live";
|
|
234
|
+
}
|
|
235
|
+
isTurnStateOwnerStale(owner) {
|
|
236
|
+
if (owner.pid > 0 && owner.pid !== process.pid) {
|
|
237
|
+
try {
|
|
238
|
+
process.kill(owner.pid, 0);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const lastSeen = Date.parse(owner.lastSeen);
|
|
246
|
+
return !Number.isFinite(lastSeen) || Date.now() - lastSeen > TURN_OWNER_STALE_MS;
|
|
179
247
|
}
|
|
180
248
|
/**
|
|
181
249
|
* Add or update a file's modified ranges in turn state.
|
|
182
|
-
* Merges overlapping ranges.
|
|
250
|
+
* Merges overlapping ranges. `sessionId:null` deliberately preserves the
|
|
251
|
+
* current owner; callers must provide an explicit owner id to claim a stale
|
|
252
|
+
* worklist.
|
|
183
253
|
*/
|
|
184
|
-
addModifiedRange(filePath, range, importsChanged, cwd, sessionId) {
|
|
254
|
+
addModifiedRange(filePath, range, importsChanged, cwd, sessionId, ownerKind = "pi") {
|
|
185
255
|
const state = this.readTurnState(cwd);
|
|
186
|
-
if (sessionId)
|
|
256
|
+
if (sessionId) {
|
|
257
|
+
const owner = {
|
|
258
|
+
kind: ownerKind,
|
|
259
|
+
id: sessionId,
|
|
260
|
+
pid: process.pid,
|
|
261
|
+
lastSeen: new Date().toISOString(),
|
|
262
|
+
};
|
|
263
|
+
if (this.getTurnStateAccess(cwd, owner) === "foreign-live")
|
|
264
|
+
return state;
|
|
187
265
|
state.sessionId = sessionId;
|
|
266
|
+
state.owner = owner;
|
|
267
|
+
}
|
|
188
268
|
const normalizedPath = this.toTurnStateKey(filePath, cwd);
|
|
189
269
|
const existing = state.files[normalizedPath];
|
|
190
270
|
if (existing) {
|
|
@@ -209,19 +289,27 @@ export class CacheManager {
|
|
|
209
289
|
/**
|
|
210
290
|
* Clear turn state (after turn_end processes it).
|
|
211
291
|
*/
|
|
212
|
-
clearTurnState(cwd) {
|
|
292
|
+
clearTurnState(cwd, owner) {
|
|
293
|
+
const currentState = this.readTurnState(cwd);
|
|
294
|
+
const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
|
|
295
|
+
if (!isCurrentOwner && process.pid !== currentState.owner?.pid)
|
|
296
|
+
return false;
|
|
213
297
|
const state = {
|
|
214
298
|
...DEFAULT_TURN_STATE,
|
|
215
299
|
files: {}, // fresh object — DEFAULT_TURN_STATE.files can be polluted by addModifiedRange
|
|
216
300
|
lastUpdated: new Date().toISOString(),
|
|
217
301
|
};
|
|
218
302
|
this.writeTurnState(state, cwd);
|
|
303
|
+
return true;
|
|
219
304
|
}
|
|
220
305
|
/**
|
|
221
306
|
* Increment turn cycle counter.
|
|
222
307
|
*/
|
|
223
|
-
incrementTurnCycle(cwd) {
|
|
308
|
+
incrementTurnCycle(cwd, owner) {
|
|
224
309
|
const state = this.readTurnState(cwd);
|
|
310
|
+
const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
|
|
311
|
+
if (!isCurrentOwner && process.pid !== state.owner?.pid)
|
|
312
|
+
return state;
|
|
225
313
|
state.turnCycles++;
|
|
226
314
|
this.writeTurnState(state, cwd);
|
|
227
315
|
return state;
|