@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
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* Requires: npm install -D @ast-grep/cli
|
|
8
8
|
* Rules: ./rules/ directory
|
|
9
9
|
*/
|
|
10
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
10
11
|
import * as fs from "node:fs";
|
|
11
12
|
import * as os from "node:os";
|
|
12
13
|
import * as path from "node:path";
|
|
13
14
|
import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
|
|
14
15
|
import { resolvePackagePath } from "./package-root.js";
|
|
15
|
-
import { SgRunner } from "./sg-runner.js";
|
|
16
|
+
import { SgRunner, } from "./sg-runner.js";
|
|
16
17
|
// --- Client ---
|
|
17
18
|
function extractDebugAst(raw) {
|
|
18
19
|
const lines = raw.split(/\r?\n/);
|
|
@@ -53,7 +54,7 @@ const VALIDATION_SNIPPETS = {
|
|
|
53
54
|
html: { ext: "html", source: "<main>pi-lens</main>\n" },
|
|
54
55
|
java: { ext: "java", source: "class Main { void run() {} }\n" },
|
|
55
56
|
javascript: { ext: "js", source: "const piLensValidate = 1;\n" },
|
|
56
|
-
json: { ext: "json", source:
|
|
57
|
+
json: { ext: "json", source: '{"piLensValidate":true}\n' },
|
|
57
58
|
kotlin: { ext: "kt", source: "fun main() {}\n" },
|
|
58
59
|
lua: { ext: "lua", source: "local pi_lens_validate = 1\n" },
|
|
59
60
|
php: { ext: "php", source: "<?php $piLensValidate = 1;\n" },
|
|
@@ -66,7 +67,10 @@ const VALIDATION_SNIPPETS = {
|
|
|
66
67
|
};
|
|
67
68
|
function validationSnippetFor(language) {
|
|
68
69
|
const key = language.toLowerCase().replace(/^"|"$/g, "");
|
|
69
|
-
return VALIDATION_SNIPPETS[key] ?? {
|
|
70
|
+
return (VALIDATION_SNIPPETS[key] ?? {
|
|
71
|
+
ext: key.replace(/[^a-z0-9_-]/gi, "") || "txt",
|
|
72
|
+
source: "pi_lens_validate\n",
|
|
73
|
+
});
|
|
70
74
|
}
|
|
71
75
|
function validateInputShape(value, maxChars, label) {
|
|
72
76
|
if (value.includes("\0"))
|
|
@@ -110,7 +114,7 @@ export class AstGrepClient {
|
|
|
110
114
|
? projectRuleDir
|
|
111
115
|
: resolvePackagePath(import.meta.url, "rules"));
|
|
112
116
|
this.log = verbose
|
|
113
|
-
? (
|
|
117
|
+
? createSubsystemLogger("ast-grep")
|
|
114
118
|
: () => { };
|
|
115
119
|
this.ruleManager = new AstGrepRuleManager(this.ruleDir, this.log);
|
|
116
120
|
this.runner = new SgRunner(verbose);
|
|
@@ -129,9 +133,18 @@ export class AstGrepClient {
|
|
|
129
133
|
const allMatches = [];
|
|
130
134
|
for (const scanPath of paths) {
|
|
131
135
|
if (apply) {
|
|
132
|
-
// Stale-preview check: dry-run first
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
// Stale-preview check: dry-run first. Preserve CLI failures instead of
|
|
137
|
+
// treating an invalid rule as a stale, no-match preview.
|
|
138
|
+
const preCheck = await this.tempScanDetailed(scanPath, ruleYaml);
|
|
139
|
+
if (preCheck.failure || preCheck.error) {
|
|
140
|
+
return {
|
|
141
|
+
matches: allMatches,
|
|
142
|
+
totalMatches: allMatches.length,
|
|
143
|
+
applied: false,
|
|
144
|
+
error: preCheck.error ?? "ast-grep preview failed",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (preCheck.matches.length === 0) {
|
|
135
148
|
return {
|
|
136
149
|
matches: [],
|
|
137
150
|
totalMatches: 0,
|
|
@@ -162,12 +175,31 @@ export class AstGrepClient {
|
|
|
162
175
|
* Routes through sg scan --config rather than sg run -p.
|
|
163
176
|
* Each path is scanned independently; results are merged.
|
|
164
177
|
*/
|
|
165
|
-
async
|
|
178
|
+
async tempScanDetailed(dir, ruleYaml, options = {}) {
|
|
179
|
+
// Keep tests and older embedders that replace the runner with the historic
|
|
180
|
+
// match-only seam working. The production SgRunner always has the detailed
|
|
181
|
+
// method, so failures cannot be mistaken for an empty result there.
|
|
182
|
+
const detailed = this.runner.tempScanDetailedAsync;
|
|
183
|
+
if (detailed) {
|
|
184
|
+
return detailed.call(this.runner, dir, "agent-rule", ruleYaml, 30_000, options);
|
|
185
|
+
}
|
|
186
|
+
const matches = await this.runner.tempScanAsync(dir, "agent-rule", ruleYaml, 30_000, options);
|
|
187
|
+
return { matches, status: 0 };
|
|
188
|
+
}
|
|
189
|
+
async searchWithRule(ruleYaml, paths, options = {}) {
|
|
166
190
|
const allMatches = [];
|
|
167
191
|
for (const scanPath of paths) {
|
|
168
192
|
try {
|
|
169
|
-
const results = await this.
|
|
170
|
-
|
|
193
|
+
const results = await this.tempScanDetailed(scanPath, ruleYaml, options);
|
|
194
|
+
if (results.failure || results.error) {
|
|
195
|
+
return {
|
|
196
|
+
matches: allMatches,
|
|
197
|
+
totalMatches: allMatches.length,
|
|
198
|
+
error: results.error ||
|
|
199
|
+
`ast-grep scan failed (${results.failure ?? "unknown failure"})`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
allMatches.push(...results.matches);
|
|
171
203
|
}
|
|
172
204
|
catch (err) {
|
|
173
205
|
return {
|
|
@@ -227,11 +259,24 @@ export class AstGrepClient {
|
|
|
227
259
|
if (options?.strictness)
|
|
228
260
|
args.push("--strictness", options.strictness);
|
|
229
261
|
args.push(tmpFile);
|
|
230
|
-
const result = await this.runner.execRaw(args);
|
|
262
|
+
const result = await this.runner.execRaw(args, 10_000, options);
|
|
231
263
|
const stderr = result.stderr.trim();
|
|
232
264
|
const stdout = result.stdout.trim();
|
|
233
265
|
if (result.error)
|
|
234
266
|
return { valid: false, error: result.error };
|
|
267
|
+
if (result.status !== 0 && !(result.status === 1 && !stderr)) {
|
|
268
|
+
return {
|
|
269
|
+
valid: false,
|
|
270
|
+
error: stderr ||
|
|
271
|
+
`ast-grep validation failed with exit code ${result.status}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (result.outputTruncated) {
|
|
275
|
+
return {
|
|
276
|
+
valid: false,
|
|
277
|
+
error: "ast-grep validation output was truncated",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
235
280
|
if (stderrHasError(stderr))
|
|
236
281
|
return { valid: false, error: stderr };
|
|
237
282
|
const warning = stderr || stdout || undefined;
|
|
@@ -249,7 +294,7 @@ export class AstGrepClient {
|
|
|
249
294
|
}
|
|
250
295
|
}
|
|
251
296
|
}
|
|
252
|
-
async validateRule(ruleYaml) {
|
|
297
|
+
async validateRule(ruleYaml, options = {}) {
|
|
253
298
|
const shapeError = validateInputShape(ruleYaml, MAX_VALIDATE_RULE_CHARS, "rule");
|
|
254
299
|
if (shapeError)
|
|
255
300
|
return { valid: false, error: shapeError };
|
|
@@ -258,7 +303,14 @@ export class AstGrepClient {
|
|
|
258
303
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lens-sg-rule-"));
|
|
259
304
|
try {
|
|
260
305
|
fs.writeFileSync(path.join(tmpDir, `snippet.${snippet.ext}`), snippet.source, "utf-8");
|
|
261
|
-
await this.
|
|
306
|
+
const result = await this.tempScanDetailed(tmpDir, ruleYaml, options);
|
|
307
|
+
if (result.failure || result.error) {
|
|
308
|
+
return {
|
|
309
|
+
valid: false,
|
|
310
|
+
error: result.error ||
|
|
311
|
+
`ast-grep rule validation failed (${result.failure ?? "unknown failure"})`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
262
314
|
return { valid: true };
|
|
263
315
|
}
|
|
264
316
|
catch (err) {
|
|
@@ -332,7 +384,7 @@ export class AstGrepClient {
|
|
|
332
384
|
args.push("--strictness", options.strictness);
|
|
333
385
|
}
|
|
334
386
|
args.push(...paths);
|
|
335
|
-
const result = await this.runner.exec(args);
|
|
387
|
+
const result = await this.runner.exec(args, options);
|
|
336
388
|
return {
|
|
337
389
|
matches: result.matches,
|
|
338
390
|
totalMatches: result.totalMatches,
|
|
@@ -505,8 +557,8 @@ message: found
|
|
|
505
557
|
}
|
|
506
558
|
return exports;
|
|
507
559
|
}
|
|
508
|
-
formatMatches(matches, isDryRun = false, showModeIndicator = false) {
|
|
509
|
-
return this.runner.formatMatches(matches, isDryRun,
|
|
560
|
+
formatMatches(matches, isDryRun = false, showModeIndicator = false, maxItems = 50) {
|
|
561
|
+
return this.runner.formatMatches(matches, isDryRun, maxItems, showModeIndicator);
|
|
510
562
|
}
|
|
511
563
|
/**
|
|
512
564
|
* Format diagnostics for LLM consumption
|
|
@@ -21,7 +21,12 @@ const NODE_KIND_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
21
21
|
* Returns true when at least one structural-intent field is present.
|
|
22
22
|
*/
|
|
23
23
|
export function hasStructuralIntent(intent) {
|
|
24
|
-
return !!(intent.
|
|
24
|
+
return !!(intent.nodeKind ||
|
|
25
|
+
intent.insideKind ||
|
|
26
|
+
intent.hasKind ||
|
|
27
|
+
intent.hasDescendantKind ||
|
|
28
|
+
intent.follows ||
|
|
29
|
+
intent.precedes);
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Synthesize an ast-grep YAML rule for replace operations.
|
|
@@ -33,26 +38,48 @@ export function synthesizeReplaceRule(intent) {
|
|
|
33
38
|
return `${base}fix: ${JSON.stringify(intent.rewrite)}\n`;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
|
-
* Synthesize an ast-grep YAML rule from a pattern
|
|
41
|
+
* Synthesize an ast-grep YAML rule from a pattern (or node kind) and
|
|
42
|
+
* structural constraints.
|
|
37
43
|
*
|
|
38
|
-
* The generated rule uses `stopBy: end` on `inside`
|
|
39
|
-
*
|
|
44
|
+
* The generated rule uses `stopBy: end` on `inside` and
|
|
45
|
+
* `hasDescendantKind`, so those two explicit recursive forms climb all
|
|
46
|
+
* ancestors/descendants. `hasKind` intentionally retains ast-grep's default
|
|
47
|
+
* immediate-child semantics for compatibility.
|
|
40
48
|
*
|
|
41
|
-
* @throws if pattern is
|
|
49
|
+
* @throws if neither pattern nor nodeKind is supplied, or both are supplied
|
|
42
50
|
*/
|
|
43
51
|
export function synthesizeRule(intent) {
|
|
44
|
-
|
|
52
|
+
const hasPattern = !!intent.pattern?.trim();
|
|
53
|
+
const hasNodeKind = !!intent.nodeKind?.trim();
|
|
54
|
+
if (hasPattern === hasNodeKind) {
|
|
55
|
+
throw new Error("provide exactly one of pattern or nodeKind");
|
|
56
|
+
}
|
|
57
|
+
const pattern = intent.pattern;
|
|
58
|
+
if (hasPattern && pattern)
|
|
59
|
+
assertSafePattern(pattern, "pattern");
|
|
60
|
+
if (intent.hasKind?.trim() && intent.hasDescendantKind?.trim()) {
|
|
61
|
+
throw new Error("hasKind and hasDescendantKind are mutually exclusive");
|
|
62
|
+
}
|
|
45
63
|
// Canonical language name for the YAML header (ast-grep is case-sensitive here).
|
|
46
64
|
const language = canonicalLanguage(intent.lang);
|
|
47
|
-
const rule =
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
const rule = hasNodeKind
|
|
66
|
+
? { kind: assertSafeNodeKind(intent.nodeKind ?? "", "nodeKind") }
|
|
67
|
+
: { pattern };
|
|
50
68
|
if (intent.insideKind) {
|
|
51
|
-
rule.inside = {
|
|
69
|
+
rule.inside = {
|
|
70
|
+
kind: assertSafeNodeKind(intent.insideKind, "insideKind"),
|
|
71
|
+
stopBy: "end",
|
|
72
|
+
};
|
|
52
73
|
}
|
|
53
74
|
if (intent.hasKind) {
|
|
54
75
|
rule.has = { kind: assertSafeNodeKind(intent.hasKind, "hasKind") };
|
|
55
76
|
}
|
|
77
|
+
if (intent.hasDescendantKind) {
|
|
78
|
+
rule.has = {
|
|
79
|
+
kind: assertSafeNodeKind(intent.hasDescendantKind, "hasDescendantKind"),
|
|
80
|
+
stopBy: "end",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
56
83
|
if (intent.follows) {
|
|
57
84
|
assertSafePattern(intent.follows, "follows");
|
|
58
85
|
rule.follows = { pattern: intent.follows };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Owns the generic atomic-write `.tmp-<pid>-<threadId>-<seq>` namespace. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { threadId } from "node:worker_threads";
|
|
5
|
+
let stageSeq = 0;
|
|
6
|
+
export const STAGE_TMP_PATTERN = /\.tmp-(0|[1-9]\d*)-(0|[1-9]\d*)-(0|[1-9]\d*)$/;
|
|
7
|
+
const STAGE_TMP_PID_PATTERN = /\.tmp-(0|[1-9]\d*)-(?:0|[1-9]\d*)-(?:0|[1-9]\d*)$/;
|
|
8
|
+
export function stagePathFor(targetPath) {
|
|
9
|
+
return `${targetPath}.tmp-${process.pid}-${threadId}-${stageSeq++}`;
|
|
10
|
+
}
|
|
11
|
+
export function stageOwnerPidFromName(name) {
|
|
12
|
+
if (!STAGE_TMP_PATTERN.test(name))
|
|
13
|
+
return undefined;
|
|
14
|
+
const match = STAGE_TMP_PID_PATTERN.exec(name);
|
|
15
|
+
if (!match)
|
|
16
|
+
return undefined;
|
|
17
|
+
const pid = Number(match[1]);
|
|
18
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
|
|
19
|
+
}
|
|
20
|
+
export async function sweepOwnStagingFiles(directories, options) {
|
|
21
|
+
const uniqueDirectories = [...new Set(directories)].filter(Boolean);
|
|
22
|
+
const result = { directories: uniqueDirectories.length, scanned: 0, removed: 0, liveSkipped: 0, truncated: false };
|
|
23
|
+
for (const directory of uniqueDirectories) {
|
|
24
|
+
let handle;
|
|
25
|
+
try {
|
|
26
|
+
handle = await fs.promises.opendir(directory);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
let reachedEntryCap = true;
|
|
33
|
+
for (let i = 0; i < options.maxEntries; i++) {
|
|
34
|
+
const entry = await handle.read();
|
|
35
|
+
if (entry === null) {
|
|
36
|
+
reachedEntryCap = false;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
result.scanned++;
|
|
40
|
+
if (!entry.isFile())
|
|
41
|
+
continue;
|
|
42
|
+
const pid = stageOwnerPidFromName(entry.name);
|
|
43
|
+
if (pid === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (pid === process.pid || options.isPidAlive(pid)) {
|
|
46
|
+
result.liveSkipped++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await fs.promises.rm(path.join(directory, entry.name), { force: true });
|
|
51
|
+
result.removed++;
|
|
52
|
+
}
|
|
53
|
+
catch { /* leave it for the next bounded sweep */ }
|
|
54
|
+
}
|
|
55
|
+
result.truncated ||= reachedEntryCap;
|
|
56
|
+
}
|
|
57
|
+
catch { /* best-effort directory scan */ }
|
|
58
|
+
finally {
|
|
59
|
+
await handle.close().catch(() => { });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
@@ -1,16 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared atomic tmp+rename file writer (closes #762).
|
|
3
3
|
*
|
|
4
|
-
* The `${target}.tmp
|
|
5
|
-
*
|
|
4
|
+
* The `${target}.tmp-…` + `renameSync` shape was independently hand-rolled in
|
|
5
|
+
* five places (`instance-registry.ts`, `session-state-store.ts`,
|
|
6
6
|
* `recent-touches.ts`, `review-graph/builder.ts`, `diagnostic-dispositions.ts`)
|
|
7
|
-
* as each one picked up the need for a
|
|
8
|
-
* partially-written file
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* as each one picked up the need for a reader to never observe a
|
|
8
|
+
* partially-written file. Five independent copies of the same shape invite
|
|
9
|
+
* drift (e.g. forgetting the tmp-file cleanup on the failure path) — this
|
|
10
|
+
* module is the single implementation the rest re-use.
|
|
11
|
+
*
|
|
12
|
+
* ## What this guarantees (#1205)
|
|
13
|
+
*
|
|
14
|
+
* Data is written in full to a staging file that no other writer can name,
|
|
15
|
+
* then `rename()`d over the target. On POSIX, `rename()` replaces the
|
|
16
|
+
* destination atomically by construction. On Windows, libuv uses
|
|
17
|
+
* `MoveFileEx` with `MOVEFILE_REPLACE_EXISTING`, which is atomic in practice
|
|
18
|
+
* on a same-volume NTFS replace but — unlike POSIX `rename()` — is not a
|
|
19
|
+
* formally guaranteed atomic primitive (`ReplaceFile` /
|
|
20
|
+
* `SetFileInformationByHandle` are the stronger APIs). Therefore, modulo that
|
|
21
|
+
* caveat:
|
|
22
|
+
*
|
|
23
|
+
* - **Crash-safe replacement.** A crash mid-write leaves the target holding
|
|
24
|
+
* its previous complete contents plus an orphan staging file; it never
|
|
25
|
+
* leaves the target half-written.
|
|
26
|
+
* - **Tear-free publication.** Any reader — same process or not — observes
|
|
27
|
+
* either the fully-old or the fully-new file at the target path. Because
|
|
28
|
+
* the staging name is unique *per call* (pid + thread id + a monotonic
|
|
29
|
+
* per-thread counter), this now holds for concurrent writes from the same
|
|
30
|
+
* process too, on any thread. Before #1205 the staging name was only
|
|
31
|
+
* `.tmp-${pid}`, so two in-flight same-process writes to one target
|
|
32
|
+
* shared a staging inode; the first `rename` published it while the
|
|
33
|
+
* second writer was still writing into it, publishing a torn hybrid file.
|
|
34
|
+
*
|
|
35
|
+
* ## What this explicitly does NOT provide
|
|
36
|
+
*
|
|
37
|
+
* - **No read-modify-write isolation.** Concurrent read → mutate → write
|
|
38
|
+
* cycles still lose updates: each writer publishes the state it computed
|
|
39
|
+
* from the snapshot it read, and last-rename-wins silently discards the
|
|
40
|
+
* other's mutation. Callers needing this must serialize themselves (see
|
|
41
|
+
* `lspChildRemovalTail` in `instance-registry.ts`).
|
|
42
|
+
* - **No ordering.** Nothing sequences concurrent writers, within or across
|
|
43
|
+
* processes. The winner is whichever `rename` lands last, which is not
|
|
44
|
+
* necessarily the write that started or finished last.
|
|
45
|
+
* - **No mutual exclusion.** This is not a lock. Overlapping writes are
|
|
46
|
+
* permitted and expected; only *torn* output is prevented.
|
|
47
|
+
* - **No fsync/durability guarantee.** Neither the staging file nor the
|
|
48
|
+
* parent directory is fsync'd, so a power loss (as opposed to a process
|
|
49
|
+
* crash) may lose the rename.
|
|
50
|
+
* - **No self-reaping.** A process killed between the staging write and the
|
|
51
|
+
* rename leaves an orphan staging file. The next session_start performs a
|
|
52
|
+
* bounded, liveness-checked sweep through the instance-reaper lifecycle
|
|
53
|
+
* seam (#1228); this writer never watches or reaps its own files.
|
|
54
|
+
* - **Windows-specific loss under `bestEffort: true`.** `MoveFileEx` with
|
|
55
|
+
* `MOVEFILE_REPLACE_EXISTING` fails if a reader holds the destination
|
|
56
|
+
* open without `FILE_SHARE_DELETE`. On Windows this silently drops the
|
|
57
|
+
* write (swallowed like any other `bestEffort: true` failure); the
|
|
58
|
+
* identical code path succeeds on POSIX, where `rename()` does not care
|
|
59
|
+
* whether a reader has the destination open.
|
|
14
60
|
*
|
|
15
61
|
* Per-write-site error policy varies and is NOT a detail this helper should
|
|
16
62
|
* paper over:
|
|
@@ -29,8 +75,62 @@
|
|
|
29
75
|
* it swallowed. See the #757 CHANGELOG entry for the full reasoning.
|
|
30
76
|
*/
|
|
31
77
|
import * as fs from "node:fs";
|
|
78
|
+
export { STAGE_TMP_PATTERN, stageOwnerPidFromName, stagePathFor, } from "./atomic-write-staging.js";
|
|
79
|
+
import { stagePathFor } from "./atomic-write-staging.js";
|
|
80
|
+
/**
|
|
81
|
+
* Monotonic counter that makes each staging path unique per CALL, not merely
|
|
82
|
+
* per process (#1205).
|
|
83
|
+
*
|
|
84
|
+
* A counter is used rather than `randomBytes`: it is allocation-free and
|
|
85
|
+
* synchronous on what are hot per-turn/per-touch write paths, and it is
|
|
86
|
+
* *exactly* as unique as needed — collisions only matter between two writes
|
|
87
|
+
* live at the same instant, and a monotonic counter makes those impossible by
|
|
88
|
+
* construction rather than merely improbable.
|
|
89
|
+
*
|
|
90
|
+
* The counter alone is per-THREAD, not per-process: a worker gets its own
|
|
91
|
+
* module instance and so its own `_stageSeq` starting at 0, while sharing
|
|
92
|
+
* `process.pid` with the main thread and every sibling worker. Two threads
|
|
93
|
+
* would therefore each mint `${target}.tmp-<samepid>-0` — the exact collision
|
|
94
|
+
* the counter exists to prevent. `threadId` (0 on the main thread, distinct
|
|
95
|
+
* per worker) closes that axis, so the three parts cover the three ways two
|
|
96
|
+
* writes can be concurrent: `process.pid` across processes, `threadId` across
|
|
97
|
+
* threads, `_stageSeq` within one thread (#1217).
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* Staging path for one write call: `${targetPath}.tmp-<pid>-<threadId>-<seq>`.
|
|
101
|
+
*
|
|
102
|
+
* Exported so that callers which cannot use {@link writeFileAtomic} wholesale
|
|
103
|
+
* — `gzip-stage-write.ts` needs its own streaming pipeline — still source the
|
|
104
|
+
* *name* here rather than re-deriving the scheme, and so that sweepers of
|
|
105
|
+
* orphaned staging files stay in lockstep with it (see
|
|
106
|
+
* {@link STAGE_TMP_PATTERN}).
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* Matches the trailing staging-file marker produced by {@link stagePathFor},
|
|
110
|
+
* for the session-start sweeper that garbage-collects staging files orphaned
|
|
111
|
+
* by a crashed process (#1228). The writer itself does not reap or watch files.
|
|
112
|
+
*
|
|
113
|
+
* Only the current four-component shape is matched:
|
|
114
|
+
* `.tmp-<pid>-<threadId>-<seq>` (#1217). Older one- and two-number suffixes
|
|
115
|
+
* are deliberately left alone: their shape is indistinguishable from
|
|
116
|
+
* legitimate user filenames, so deletion safety outweighs reclaiming the
|
|
117
|
+
* handful of pre-#1208 leftovers.
|
|
118
|
+
*
|
|
119
|
+
* CAUTION for any sweeper adopting this pattern: it matches staging files by
|
|
120
|
+
* shape, not by writer identity, so it also matches this process's own
|
|
121
|
+
* still-being-written staging files. A safe sweeper must separately protect
|
|
122
|
+
* `process.pid` and use the shared conservative liveness check for foreign
|
|
123
|
+
* pids. The review-graph builder's narrower stage sweep intentionally remains
|
|
124
|
+
* separate because it owns a different `.stage-<pid>-<generation>` namespace.
|
|
125
|
+
*
|
|
126
|
+
* Anchoring is correct for both consumer styles in this repo today
|
|
127
|
+
* (matching against a full path or a bare basename), but this is confined to
|
|
128
|
+
* pi-lens's own cache directories — a consumer sweeping a directory it does
|
|
129
|
+
* not fully control should not assume every match is one of this module's
|
|
130
|
+
* staging files.
|
|
131
|
+
*/
|
|
32
132
|
/**
|
|
33
|
-
* Synchronous atomic write of text or binary data to
|
|
133
|
+
* Synchronous atomic write of text or binary data to a per-call staging file,
|
|
34
134
|
* then `fs.renameSync` over `targetPath`. On any failure (from either step),
|
|
35
135
|
* attempts a best-effort `fs.rmSync(tmp, { force: true })` cleanup, then
|
|
36
136
|
* either swallows (default) or rethrows per `options.bestEffort`.
|
|
@@ -41,7 +141,7 @@ import * as fs from "node:fs";
|
|
|
41
141
|
*/
|
|
42
142
|
export function writeFileAtomic(targetPath, data, options) {
|
|
43
143
|
const bestEffort = options?.bestEffort ?? true;
|
|
44
|
-
const tmpPath =
|
|
144
|
+
const tmpPath = stagePathFor(targetPath);
|
|
45
145
|
try {
|
|
46
146
|
fs.writeFileSync(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
|
|
47
147
|
fs.renameSync(tmpPath, targetPath);
|
|
@@ -60,12 +160,12 @@ export function writeFileAtomic(targetPath, data, options) {
|
|
|
60
160
|
/**
|
|
61
161
|
* Async counterpart of {@link writeFileAtomic}, built on `fs.promises` instead
|
|
62
162
|
* of the sync `fs` API — for call sites that must not block the event loop
|
|
63
|
-
* (e.g. writes on a hot per-turn/per-touch path). Same tmp-naming,
|
|
64
|
-
* and `bestEffort` semantics.
|
|
163
|
+
* (e.g. writes on a hot per-turn/per-touch path). Same per-call tmp-naming,
|
|
164
|
+
* cleanup, and `bestEffort` semantics.
|
|
65
165
|
*/
|
|
66
166
|
export async function writeFileAtomicAsync(targetPath, data, options) {
|
|
67
167
|
const bestEffort = options?.bestEffort ?? true;
|
|
68
|
-
const tmpPath =
|
|
168
|
+
const tmpPath = stagePathFor(targetPath);
|
|
69
169
|
try {
|
|
70
170
|
await fs.promises.writeFile(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
|
|
71
171
|
await fs.promises.rename(tmpPath, targetPath);
|