@herbertgao/pi-extensions 2026.8.4 → 2026.8.5
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 +4 -4
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
- 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/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 +5 -8
- 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 +8 -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 +15 -2
- 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/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -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 +8 -7
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
- package/node_modules/pi-lens/CHANGELOG.md +2440 -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/agent-behavior-client.js +21 -4
- 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 +29 -25
- 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-publish.js +10 -3
- 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/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 +14 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
- 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 +347 -75
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
- 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 +1085 -681
- 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 +14 -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/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -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 +10 -3
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +139 -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/format-events-publish.js +16 -3
- 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 +681 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
- 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 +390 -99
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
- package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
- 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 +23 -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 +27 -4
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
- 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 +196 -32
- 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 +123 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
- 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/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 +47 -13
- 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/runner-adapters/call-graph-impact.js +8 -0
- 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/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/runtime-agent-end.js +107 -73
- package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
- package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -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 +218 -23
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
- package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -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 +1 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -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 +33 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +49361 -36888
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +211 -44
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
- 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/docs/agent-guide.md +2 -2
- 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 +5 -2
- 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 +31 -2
- package/node_modules/pi-lens/docs/globalconfig.md +45 -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 +12 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +18 -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-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-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-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-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 +55 -2
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
- package/node_modules/pi-mcp-adapter/README.md +34 -3
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
- 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/metadata-cache.ts +27 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +3 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +36 -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 +42 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +93 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/package.json +11 -12
- 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
|
@@ -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);
|
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as nodeFs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
+
import { isReadableSourceFile } from "./file-kinds.js";
|
|
18
19
|
import { countFileLines } from "./read-guard-tool-lines.js";
|
|
19
|
-
// Source-ish extensions worth registering. Anchored end-check → linear (no
|
|
20
|
-
// catastrophic backtracking).
|
|
21
|
-
const READABLE_EXT_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|sh|rs|go|cs|java|kt|rb|php|c|cpp|cc|h|hpp|json|jsonc|yaml|yml|toml|md|txt|env|cfg|conf|ini|html|css|scss|less|xml|sql|vue|svelte)$/i;
|
|
22
20
|
function stripQuotes(token) {
|
|
23
21
|
if (token.length >= 2) {
|
|
24
22
|
const first = token[0];
|
|
@@ -32,14 +30,123 @@ function stripQuotes(token) {
|
|
|
32
30
|
function stripAnsi(value) {
|
|
33
31
|
return value.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
34
32
|
}
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
33
|
+
export function tokenizeShellCommand(command) {
|
|
34
|
+
const segments = [];
|
|
35
|
+
let tokens = [];
|
|
36
|
+
let word = "";
|
|
37
|
+
let quote;
|
|
38
|
+
let escaped = false;
|
|
39
|
+
let unsupported = false;
|
|
40
|
+
let atTokenStart = true;
|
|
41
|
+
const flushWord = () => {
|
|
42
|
+
if (word)
|
|
43
|
+
tokens.push(word);
|
|
44
|
+
word = "";
|
|
45
|
+
atTokenStart = true;
|
|
46
|
+
};
|
|
47
|
+
const flushSegment = () => {
|
|
48
|
+
flushWord();
|
|
49
|
+
if (tokens.length > 0 || unsupported)
|
|
50
|
+
segments.push({ tokens, unsupported });
|
|
51
|
+
tokens = [];
|
|
52
|
+
unsupported = false;
|
|
53
|
+
atTokenStart = true;
|
|
54
|
+
};
|
|
55
|
+
for (let i = 0; i < command.length; i++) {
|
|
56
|
+
const ch = command[i];
|
|
57
|
+
const next = command[i + 1];
|
|
58
|
+
if (quote === "single") {
|
|
59
|
+
if (ch === "'")
|
|
60
|
+
quote = undefined;
|
|
61
|
+
else
|
|
62
|
+
word += ch;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (quote === "double") {
|
|
66
|
+
if (escaped) {
|
|
67
|
+
word += ch;
|
|
68
|
+
escaped = false;
|
|
69
|
+
}
|
|
70
|
+
else if (ch === "\\")
|
|
71
|
+
escaped = true;
|
|
72
|
+
else if (ch === '"')
|
|
73
|
+
quote = undefined;
|
|
74
|
+
else
|
|
75
|
+
word += ch;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (escaped) {
|
|
79
|
+
// A backslash-newline is a shell line continuation, not part of the
|
|
80
|
+
// command argument. Keeping the newline here makes a continued git command
|
|
81
|
+
// visible to command guards.
|
|
82
|
+
if (ch !== "\n")
|
|
83
|
+
word += ch;
|
|
84
|
+
escaped = false;
|
|
85
|
+
atTokenStart = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === "\\") {
|
|
89
|
+
// Bash uses backslash as a general escape, but command inputs on
|
|
90
|
+
// Windows routinely contain native paths (C:\\src\\file.ts). Preserve
|
|
91
|
+
// backslashes before ordinary path characters while still honoring
|
|
92
|
+
// shell escapes and line continuations.
|
|
93
|
+
if (next === "\n" || /[\s\\'\";$|&<>]/.test(next ?? "")) {
|
|
94
|
+
escaped = true;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
word += ch;
|
|
98
|
+
atTokenStart = false;
|
|
99
|
+
}
|
|
100
|
+
atTokenStart = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === "'") {
|
|
104
|
+
quote = "single";
|
|
105
|
+
atTokenStart = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (ch === '"') {
|
|
109
|
+
quote = "double";
|
|
110
|
+
atTokenStart = false;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (ch === "#" && atTokenStart) {
|
|
114
|
+
while (i + 1 < command.length && command[i + 1] !== "\n")
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (/\s/.test(ch)) {
|
|
119
|
+
flushWord();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === ";" || ch === "\n" || ch === "|" || ch === "&") {
|
|
123
|
+
flushWord();
|
|
124
|
+
if (ch === "|" && next === "|")
|
|
125
|
+
i++;
|
|
126
|
+
else if (ch === "&" && next === "&")
|
|
127
|
+
i++;
|
|
128
|
+
else if (ch === "|" || ch === "&")
|
|
129
|
+
unsupported = true;
|
|
130
|
+
flushSegment();
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (ch === "<" || ch === ">") {
|
|
134
|
+
flushWord();
|
|
135
|
+
unsupported = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
word += ch;
|
|
139
|
+
atTokenStart = false;
|
|
140
|
+
}
|
|
141
|
+
if (quote || escaped)
|
|
142
|
+
unsupported = true;
|
|
143
|
+
flushSegment();
|
|
144
|
+
return segments;
|
|
38
145
|
}
|
|
39
146
|
/** Resolve a token to an absolute path if it looks like a source file. */
|
|
40
147
|
function resolveCandidate(token, cwd) {
|
|
41
148
|
const cleaned = stripQuotes(token);
|
|
42
|
-
if (!cleaned || cleaned.startsWith("-") || !
|
|
149
|
+
if (!cleaned || cleaned.startsWith("-") || !isReadableSourceFile(cleaned)) {
|
|
43
150
|
return null;
|
|
44
151
|
}
|
|
45
152
|
return path.isAbsolute(cleaned) ? cleaned : path.resolve(cwd, cleaned);
|
|
@@ -52,8 +159,67 @@ function parseCountFlag(token) {
|
|
|
52
159
|
const n = Number.parseInt(digits, 10);
|
|
53
160
|
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
54
161
|
}
|
|
55
|
-
function
|
|
56
|
-
return command.
|
|
162
|
+
function commandSegments(command) {
|
|
163
|
+
return tokenizeShellCommand(command).map((segment) => segment.tokens);
|
|
164
|
+
}
|
|
165
|
+
/** Find redirect targets without treating quoted `>` characters as syntax. */
|
|
166
|
+
function extractRedirectTargets(command) {
|
|
167
|
+
const targets = [];
|
|
168
|
+
let quote;
|
|
169
|
+
let escaped = false;
|
|
170
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
171
|
+
const ch = command[i];
|
|
172
|
+
if (quote === "single") {
|
|
173
|
+
if (ch === "'")
|
|
174
|
+
quote = undefined;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (quote === "double") {
|
|
178
|
+
if (escaped)
|
|
179
|
+
escaped = false;
|
|
180
|
+
else if (ch === "\\")
|
|
181
|
+
escaped = true;
|
|
182
|
+
else if (ch === '"')
|
|
183
|
+
quote = undefined;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (escaped) {
|
|
187
|
+
escaped = false;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ch === "\\") {
|
|
191
|
+
escaped = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === "'") {
|
|
195
|
+
quote = "single";
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (ch === '"') {
|
|
199
|
+
quote = "double";
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (ch !== ">")
|
|
203
|
+
continue;
|
|
204
|
+
while (i + 1 < command.length && /\s/.test(command[i + 1]))
|
|
205
|
+
i += 1;
|
|
206
|
+
if (command[i + 1] === ">")
|
|
207
|
+
i += 1;
|
|
208
|
+
while (i + 1 < command.length && /\s/.test(command[i + 1]))
|
|
209
|
+
i += 1;
|
|
210
|
+
let target = "";
|
|
211
|
+
for (i += 1; i < command.length; i += 1) {
|
|
212
|
+
const next = command[i];
|
|
213
|
+
if (/\s/.test(next) || next === ";" || next === "|" || next === "&") {
|
|
214
|
+
i -= 1;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
target += next;
|
|
218
|
+
}
|
|
219
|
+
if (target)
|
|
220
|
+
targets.push(target);
|
|
221
|
+
}
|
|
222
|
+
return targets;
|
|
57
223
|
}
|
|
58
224
|
/**
|
|
59
225
|
* Extract the line ranges a bash command explicitly showed the agent.
|
|
@@ -93,11 +259,9 @@ export function extractReadPathsFromCommand(command, cwd) {
|
|
|
93
259
|
seen.add(key);
|
|
94
260
|
spans.push({ filePath: file.abs, offset, limit });
|
|
95
261
|
};
|
|
96
|
-
for (const
|
|
97
|
-
|
|
98
|
-
if (!segment)
|
|
262
|
+
for (const tokens of commandSegments(command)) {
|
|
263
|
+
if (tokens.length === 0)
|
|
99
264
|
continue;
|
|
100
|
-
const tokens = segment.split(/\s+/);
|
|
101
265
|
const verb = path.basename(tokens[0] ?? "");
|
|
102
266
|
const args = tokens.slice(1);
|
|
103
267
|
if (["cat", "bat", "less", "more", "nl"].includes(verb)) {
|
|
@@ -247,8 +411,7 @@ function parseGrepLineWithoutFile(line, file) {
|
|
|
247
411
|
function collectGrepCommandFiles(command, cwd) {
|
|
248
412
|
const files = new Set();
|
|
249
413
|
let hasLineNumberGrep = false;
|
|
250
|
-
for (const
|
|
251
|
-
const tokens = tokenizeSegment(rawSegment.trim());
|
|
414
|
+
for (const tokens of commandSegments(command)) {
|
|
252
415
|
const verb = path.basename(stripQuotes(tokens[0] ?? ""));
|
|
253
416
|
if (verb !== "grep" && verb !== "egrep" && verb !== "fgrep")
|
|
254
417
|
continue;
|
|
@@ -311,15 +474,22 @@ export function extractWrittenPathsFromCommand(command, cwd) {
|
|
|
311
474
|
if (abs)
|
|
312
475
|
out.add(abs);
|
|
313
476
|
};
|
|
314
|
-
for (const
|
|
315
|
-
|
|
316
|
-
|
|
477
|
+
for (const tokens of commandSegments(command)) {
|
|
478
|
+
if (tokens.length === 0)
|
|
479
|
+
continue;
|
|
480
|
+
// Redirect targets are collected by a quote-aware scanner. The shared
|
|
481
|
+
// tokenizer supplies the normalized command arguments; it deliberately
|
|
482
|
+
// does not expose shell redirection operators as arguments.
|
|
483
|
+
for (const target of extractRedirectTargets(command))
|
|
484
|
+
add(target);
|
|
485
|
+
// A command may contain multiple segments; only the first pass should
|
|
486
|
+
// attach redirects, otherwise targets would be duplicated harmlessly but
|
|
487
|
+
// needlessly rescanned.
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
for (const tokens of commandSegments(command)) {
|
|
491
|
+
if (tokens.length === 0)
|
|
317
492
|
continue;
|
|
318
|
-
// Redirect targets: the token after `>` / `>>` (optionally prefixed by a
|
|
319
|
-
// file descriptor like `2>` or `&>`, with or without a space).
|
|
320
|
-
for (const m of segment.matchAll(/>>?\s*([^\s>|&]+)/g))
|
|
321
|
-
add(m[1]);
|
|
322
|
-
const tokens = segment.split(/\s+/);
|
|
323
493
|
const verb = path.basename(tokens[0] ?? "");
|
|
324
494
|
const args = tokens.slice(1);
|
|
325
495
|
if (verb === "tee" || verb === "touch") {
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* Requires: npm install @biomejs/biome (or npx @biomejs/biome)
|
|
8
8
|
* Docs: https://biomejs.dev/
|
|
9
9
|
*/
|
|
10
|
+
import { createSubsystemLogger } from "./extension-log.js";
|
|
10
11
|
import * as fs from "node:fs";
|
|
11
12
|
import * as path from "node:path";
|
|
12
13
|
import { isFileKind } from "./file-kinds.js";
|
|
13
14
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
14
15
|
import { findGlobalBinary } from "./package-manager.js";
|
|
15
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
17
|
+
import { biomeConfigArgs } from "./tool-policy.js";
|
|
18
|
+
import { resolveManagedToolClient } from "./dispatch/runners/utils/runner-helpers.js";
|
|
16
19
|
// --- Client ---
|
|
17
20
|
export class BiomeClient {
|
|
18
21
|
biomeAvailable = null;
|
|
@@ -29,7 +32,7 @@ export class BiomeClient {
|
|
|
29
32
|
log;
|
|
30
33
|
constructor(verbose = false) {
|
|
31
34
|
this.log = verbose
|
|
32
|
-
? (
|
|
35
|
+
? createSubsystemLogger("biome")
|
|
33
36
|
: () => { };
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
@@ -87,7 +90,7 @@ export class BiomeClient {
|
|
|
87
90
|
}
|
|
88
91
|
async spawnBiomeAsync(args, timeout = 15000, cwd) {
|
|
89
92
|
const { cmd, args: prefix } = await this.getBiomeBinary(cwd);
|
|
90
|
-
return safeSpawnAsync(cmd, [...prefix, ...args], { timeout });
|
|
93
|
+
return safeSpawnAsync(cmd, [...prefix, ...args], { timeout, cwd });
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* Ensure Biome is available, auto-installing if necessary.
|
|
@@ -112,27 +115,22 @@ export class BiomeClient {
|
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
117
|
async doEnsureAvailable() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
this.log("Biome auto-install failed");
|
|
134
|
-
this.biomeAvailable = false;
|
|
135
|
-
return false;
|
|
118
|
+
const outcome = await resolveManagedToolClient({
|
|
119
|
+
toolId: "biome",
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
probe: async () => {
|
|
122
|
+
const result = await this.spawnBiomeAsync(["--version"], 10000);
|
|
123
|
+
return !result.error && result.status === 0
|
|
124
|
+
? { outcome: "success", value: true }
|
|
125
|
+
: { outcome: "missing" };
|
|
126
|
+
},
|
|
127
|
+
acceptInstalled: (installedPath) => {
|
|
128
|
+
this.autoInstalledBinaryPath = installedPath;
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
this.biomeAvailable = outcome.outcome === "success";
|
|
133
|
+
return this.biomeAvailable;
|
|
136
134
|
}
|
|
137
135
|
/**
|
|
138
136
|
* Check if a file is supported by Biome
|
|
@@ -142,8 +140,10 @@ export class BiomeClient {
|
|
|
142
140
|
}
|
|
143
141
|
/**
|
|
144
142
|
* Async auto-fix variant for pipeline use (non-blocking spawn).
|
|
143
|
+
* `cwd` is the dispatch language root (used for config discovery); when
|
|
144
|
+
* omitted it defaults to the file's directory.
|
|
145
145
|
*/
|
|
146
|
-
async fixFileAsync(filePath) {
|
|
146
|
+
async fixFileAsync(filePath, cwd) {
|
|
147
147
|
if (!(await this.ensureAvailable())) {
|
|
148
148
|
return {
|
|
149
149
|
success: false,
|
|
@@ -163,7 +163,11 @@ export class BiomeClient {
|
|
|
163
163
|
}
|
|
164
164
|
try {
|
|
165
165
|
const before = await fs.promises.readFile(absolutePath, "utf-8");
|
|
166
|
-
const
|
|
166
|
+
const configCwd = cwd ?? path.dirname(absolutePath);
|
|
167
|
+
// Shared config-args seam (#1247): the lint runner consumes the same
|
|
168
|
+
// builder, so `lint --write` can never drift to biome's default
|
|
169
|
+
// config when a user config or the package fallback exists.
|
|
170
|
+
const result = await this.spawnBiomeAsync(["lint", "--write", ...biomeConfigArgs(configCwd), absolutePath], 15000, configCwd);
|
|
167
171
|
if (result.error) {
|
|
168
172
|
return {
|
|
169
173
|
success: false,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logExtension } from "./extension-log.js";
|
|
1
2
|
let bootstrapPromise = null;
|
|
2
3
|
/**
|
|
3
4
|
* A stand-in for an analysis client whose module failed to load (an unresolved
|
|
@@ -28,11 +29,19 @@ export function degradedClient() {
|
|
|
28
29
|
*/
|
|
29
30
|
async function logBootstrapFailures(failures) {
|
|
30
31
|
for (const { name, err } of failures) {
|
|
31
|
-
|
|
32
|
+
logExtension({
|
|
33
|
+
subsystem: "bootstrap",
|
|
34
|
+
message: `analyzer "${name}" disabled (degraded mode): ${err?.message ?? String(err)}`,
|
|
35
|
+
metadata: { analyzer: name },
|
|
36
|
+
});
|
|
32
37
|
}
|
|
33
38
|
try {
|
|
34
39
|
const { collectInstallDiagnostics, formatInstallDiagnostics } = await import("./install-diagnostics.js");
|
|
35
|
-
|
|
40
|
+
logExtension({
|
|
41
|
+
subsystem: "bootstrap",
|
|
42
|
+
message: formatInstallDiagnostics(collectInstallDiagnostics(), failures[0]?.err),
|
|
43
|
+
metadata: { kind: "install_diagnostics" },
|
|
44
|
+
});
|
|
36
45
|
}
|
|
37
46
|
catch {
|
|
38
47
|
// the per-analyzer lines above already named the failures
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Small insertion-ordered LRU used for process-lifetime memo tables. */
|
|
2
|
+
export class BoundedLruCache {
|
|
3
|
+
maxEntries;
|
|
4
|
+
entries = new Map();
|
|
5
|
+
constructor(maxEntries) {
|
|
6
|
+
this.maxEntries = maxEntries;
|
|
7
|
+
}
|
|
8
|
+
get(key) {
|
|
9
|
+
const value = this.entries.get(key);
|
|
10
|
+
if (value !== undefined) {
|
|
11
|
+
this.entries.delete(key);
|
|
12
|
+
this.entries.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
has(key) { return this.entries.has(key); }
|
|
17
|
+
set(key, value) {
|
|
18
|
+
this.entries.delete(key);
|
|
19
|
+
this.entries.set(key, value);
|
|
20
|
+
while (this.entries.size > this.maxEntries) {
|
|
21
|
+
const oldest = this.entries.keys().next().value;
|
|
22
|
+
if (oldest === undefined)
|
|
23
|
+
break;
|
|
24
|
+
this.entries.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
delete(key) { return this.entries.delete(key); }
|
|
28
|
+
clear() { this.entries.clear(); }
|
|
29
|
+
get size() { return this.entries.size; }
|
|
30
|
+
keys() { return this.entries.keys(); }
|
|
31
|
+
values() { return this.entries.values(); }
|
|
32
|
+
entriesArray() { return [...this.entries.entries()]; }
|
|
33
|
+
[Symbol.iterator]() { return this.entries[Symbol.iterator](); }
|
|
34
|
+
}
|