@herbertgao/pi-extensions 2026.10.1 → 2026.10.2
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 +13 -9
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/README.md +68 -72
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +7 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
- package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -32
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +17 -16
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +9 -10
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -57
- package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
- package/node_modules/@herbertgao/pi-agency/README.md +40 -0
- package/node_modules/@herbertgao/pi-agency/package.json +53 -0
- package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
- package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
- package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
- package/node_modules/@tifan/pi-recap/package.json +1 -1
- package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
- package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
- package/node_modules/pi-antigravity/README.md +23 -2
- package/node_modules/pi-antigravity/package.json +5 -3
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
- package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
- package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
- package/node_modules/pi-antigravity/src/image/image.ts +86 -47
- package/node_modules/pi-antigravity/src/image/native.ts +69 -0
- package/node_modules/pi-antigravity/src/index.ts +52 -137
- package/node_modules/pi-antigravity/src/search/search.ts +130 -31
- package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
- package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
- package/node_modules/pi-antigravity/src/types/types.ts +1 -1
- package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
- package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
- package/node_modules/pi-lens/CHANGELOG.md +1064 -0
- package/node_modules/pi-lens/README.md +115 -25
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
- package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
- package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
- package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
- package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
- package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
- package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
- package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
- package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
- package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
- package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
- package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
- package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
- package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
- package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
- package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
- package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
- package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
- package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
- package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
- package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
- package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
- package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
- package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
- package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
- package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
- package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
- package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
- package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
- package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
- package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
- package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
- package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
- package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
- package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
- package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
- package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
- package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
- package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
- package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
- package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
- package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
- package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
- package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
- package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
- package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
- package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
- package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
- package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
- package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
- package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
- package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
- package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
- package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
- package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
- package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
- package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
- package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
- package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
- package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
- package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
- package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
- package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
- package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
- package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
- package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
- package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
- package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
- package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
- package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
- package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
- package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
- package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
- package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
- package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
- package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
- package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
- package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
- package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
- package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
- package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
- package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
- package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
- package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
- package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
- package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
- package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
- package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
- package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
- package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
- package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
- package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
- package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
- package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
- package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
- package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
- package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
- package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
- package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
- package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
- package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
- package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
- package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
- package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
- package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
- package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
- package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
- package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
- package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
- package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
- package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
- package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
- package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
- package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
- package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
- package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
- package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
- package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
- package/node_modules/pi-lens/dist/index.js +16456 -8335
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
- package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
- package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
- package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
- package/node_modules/pi-lens/dist/probes/installer.js +135 -0
- package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
- package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
- package/node_modules/pi-lens/docs/agent-guide.md +13 -4
- package/node_modules/pi-lens/docs/agent-tools.md +42 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
- package/node_modules/pi-lens/docs/configuration.md +20 -1
- package/node_modules/pi-lens/docs/environment-variables.md +52 -2
- package/node_modules/pi-lens/docs/features.md +8 -4
- package/node_modules/pi-lens/docs/globalconfig.md +1 -0
- package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
- package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
- package/node_modules/pi-lens/docs/mcp.md +21 -5
- package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
- package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
- package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
- package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
- package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
- package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
- package/node_modules/pi-lens/docs/settings.md +1 -0
- package/node_modules/pi-lens/docs/usage.md +5 -1
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
- package/node_modules/pi-lens/package.json +18 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
- package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
- package/package.json +15 -10
- package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
- package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
- package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
- package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
- package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
- package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
- package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
- package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
- package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
- package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
- package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
- package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
- package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
- package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
- package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
- package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
- package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
- package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
- package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
- package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
- package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
- package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
- package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
- package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
- package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
- package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
- package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
- package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
- package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
- package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
- package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
- package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
- package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
- package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
- package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
- package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
- package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
- package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
- package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
- package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
- package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
- package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
- package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
- package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
- package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
- package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
- package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
- package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
- package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
- package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
- package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
- package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
- package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
- package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
- package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
- package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
- package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
- package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
- package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
- package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
- package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
- package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
- package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
- package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
- package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
- package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
- package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
- package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
- package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
- package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
- package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
- package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
- package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
- package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
- package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
- package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
- package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
- package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
- package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
- package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
- package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
- package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
- package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
- package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
- package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
- package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
- package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
- package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
- package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
- package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
- package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
- package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
- package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
- package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
- package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
- package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
- package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
- package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
- package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
- package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
- package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
- package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
- package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
- package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
- package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
- package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
- package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
- package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
- package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
- package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
- package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
- package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
- package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
- package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
- package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
- package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
- package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
- package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
- package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
- package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
- package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
- package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
- package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
- package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
- package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
- package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
- package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
- package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
- package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
- package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
- package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
- package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
- package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
- package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
- package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
- package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
- package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
- package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
- package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
- package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
- package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
- package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
- package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
- package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
- package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
- package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
- package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
- package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
- package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
- package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
- package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
- package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
- package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
- package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
- package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
- package/node_modules/pi-lens/dist/i18n.js +0 -69
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
- package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
- package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
- package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
- package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
- package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
- package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
- package/node_modules/pi-lens/dist/tools/shared.js +0 -29
- package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
|
@@ -1,1701 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared runner utilities for pi-lens dispatch system
|
|
3
|
-
*
|
|
4
|
-
* Extracted common patterns from multiple runners to reduce duplication:
|
|
5
|
-
* - Venv-aware command finders
|
|
6
|
-
* - Availability checkers with caching
|
|
7
|
-
* - Config file finders
|
|
8
|
-
*/
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { logSessionStart } from "../../../sessionstart-logger.js";
|
|
13
|
-
import { incrementDegradationCount } from "../../../degradation-ledger.js";
|
|
14
|
-
import { getGlobalPiLensDir } from "../../../file-utils.js";
|
|
15
|
-
import { createGenerationSource, } from "../../../generation-guard.js";
|
|
16
|
-
import { PathKeyedMap } from "../../../path-keyed-map.js";
|
|
17
|
-
import { normalizeEphemeralMapKey, normalizeMapKey, } from "../../../path-utils.js";
|
|
18
|
-
import { ensureTool, findManagedToolBinary, getInstallAttempt, getLastEnsureResolutionSource, getToolInstallStrategy, isSpawnableCommand, resetPathWalkMemo, } from "../../../installer/index.js";
|
|
19
|
-
import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/config.js";
|
|
20
|
-
import { findGlobalBinary, findLocalBinAt, findLocalBinsUpwards, findLocalBinUpwards, VENDOR_BIN_DIRS, VENV_BIN_DIRS, } from "../../../package-manager.js";
|
|
21
|
-
import { logLatency } from "../../../latency-logger.js";
|
|
22
|
-
import { safeSpawnAsync } from "../../../safe-spawn.js";
|
|
23
|
-
import { probeToolAsync } from "../../../tool-probe.js";
|
|
24
|
-
import { compareOrdinal } from "../../../string-utils.js";
|
|
25
|
-
import { getToolCommandSpec, shouldAutoInstallTool, } from "../../../tool-policy.js";
|
|
26
|
-
import { isInSpawnTimeoutCooldown } from "../../../spawn-timeout-cooldown.js";
|
|
27
|
-
import { resolveToolCwd } from "../../../tool-cwd.js";
|
|
28
|
-
import { createAvailabilityProbeFlight } from "../../../availability-probe-flight.js";
|
|
29
|
-
import { classifyProbeFailure, createAvailabilityLatch, describeInstallAttempt, describeProbeEvidence, isLatchingOutcome, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./availability-policy.js";
|
|
30
|
-
export { createAvailabilityLatch, classifyProbeFailure, describeProbeEvidence, describeUnavailability, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./availability-policy.js";
|
|
31
|
-
/**
|
|
32
|
-
* True when the LSP runner will cover `ctx.filePath` via the given PRIMARY server
|
|
33
|
-
* id. Used by CLI runners that duplicate a linter a warm LSP already wraps
|
|
34
|
-
* (taplo↔`toml` LSP = `taplo lsp`; shellcheck↔`bash` LSP runs shellcheck
|
|
35
|
-
* internally) so they SELF-SKIP and stop double-reporting the same findings (#233)
|
|
36
|
-
* — the same dormant-when-LSP-covers pattern the ast-grep napi runner uses.
|
|
37
|
-
*
|
|
38
|
-
* Non-spawning and conservative: honors the `no-lsp` kill switch + per-server
|
|
39
|
-
* disable/config, and only matches when this server is the SELECTED primary for
|
|
40
|
-
* the file (first non-auxiliary candidate). The caller additionally gates on tool
|
|
41
|
-
* availability, so coverage never regresses when the LSP is absent/disabled.
|
|
42
|
-
*/
|
|
43
|
-
export function lspPrimaryCoversFile(ctx, serverId) {
|
|
44
|
-
if (ctx.pi?.getFlag?.("no-lsp"))
|
|
45
|
-
return false;
|
|
46
|
-
if (isServerDisabled(serverId, ctx.filePath))
|
|
47
|
-
return false;
|
|
48
|
-
const primary = getServersForFileWithConfig(ctx.filePath).find((s) => s.role !== "auxiliary");
|
|
49
|
-
return primary?.id === serverId;
|
|
50
|
-
}
|
|
51
|
-
const _realThisDir = (() => {
|
|
52
|
-
if (typeof __dirname !== "undefined")
|
|
53
|
-
return __dirname;
|
|
54
|
-
return path.dirname(fileURLToPath(import.meta.url));
|
|
55
|
-
})();
|
|
56
|
-
let _thisDir = _realThisDir;
|
|
57
|
-
/**
|
|
58
|
-
* Test-only override for pi-lens's OWN install-tree root (#2544 round 4 F1).
|
|
59
|
-
* `_thisDir` is fixed at module load from the running file's own location, so
|
|
60
|
-
* a test cannot otherwise place it under a synthetic HOME to prove the
|
|
61
|
-
* own-install walk's `ceiling: false` exemption without writing into this
|
|
62
|
-
* process's REAL, shared `node_modules` tree.
|
|
63
|
-
*/
|
|
64
|
-
export function _setThisDirForTests(dir) {
|
|
65
|
-
_thisDir = dir ?? _realThisDir;
|
|
66
|
-
}
|
|
67
|
-
// Managed tools directory (~/.pi-lens/tools) — where ensureTool() installs binaries
|
|
68
|
-
const _managedToolsDir = path.join(getGlobalPiLensDir(), "tools");
|
|
69
|
-
/**
|
|
70
|
-
* Managed-shim candidates for a Node CLI tool, in the installer's own
|
|
71
|
-
* preference order (`getToolPath`'s managed-first branch: `.cmd`, then `.exe`,
|
|
72
|
-
* then extensionless on Windows; extensionless elsewhere).
|
|
73
|
-
*
|
|
74
|
-
* The pi-lens dir is read per call, never memoized at module load, so tests that
|
|
75
|
-
* point `getGlobalPiLensDir` at a temp home still see their own tree.
|
|
76
|
-
*/
|
|
77
|
-
function managedNodeToolCandidates(tool) {
|
|
78
|
-
const base = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin", tool);
|
|
79
|
-
return process.platform === "win32"
|
|
80
|
-
? [`${base}.cmd`, `${base}.exe`, base]
|
|
81
|
-
: [base];
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Verification verdicts for managed shims, keyed by path + mtime + size, so a
|
|
85
|
-
* reinstall re-verifies and a session start re-arms.
|
|
86
|
-
*
|
|
87
|
-
* A plain `Map`, not a `PathKeyedMap`: every path keyed here is produced by
|
|
88
|
-
* `path.join` from one of two producers — `managedNodeToolCandidates` and the
|
|
89
|
-
* installer's `findManagedToolBinary` (#2140) — so the write and read forms are
|
|
90
|
-
* the same string by construction and cannot diverge on case or separator.
|
|
91
|
-
*/
|
|
92
|
-
const managedBinaryVerdicts = new Map();
|
|
93
|
-
/**
|
|
94
|
-
* Verifications in progress, keyed the same way. Concurrent first touches of
|
|
95
|
-
* one shim share the single probe instead of each spawning their own — the
|
|
96
|
-
* same in-flight-share shape `resolveInstallInFlightByCwd` uses for installs
|
|
97
|
-
* (#1674 review F2).
|
|
98
|
-
*/
|
|
99
|
-
const managedVerifyInFlight = new Map();
|
|
100
|
-
/**
|
|
101
|
-
* Budget for the managed-shim verification spawn.
|
|
102
|
-
*
|
|
103
|
-
* 5s, not the checker's default probe budget: a cold npm shim on a cold file
|
|
104
|
-
* cache measured just over 2s in the #1674 review, so a tighter budget spends
|
|
105
|
-
* the whole wait and still learns nothing. This runs once per shim per session
|
|
106
|
-
* on the success path, and the installer's own verification keeps its more
|
|
107
|
-
* generous 10s.
|
|
108
|
-
*/
|
|
109
|
-
const MANAGED_VERIFY_TIMEOUT_MS = 5000;
|
|
110
|
-
/** First cooldown after a verification that never got a fair run. */
|
|
111
|
-
const MANAGED_VERIFY_COOLDOWN_MS = 60_000;
|
|
112
|
-
/**
|
|
113
|
-
* Ceiling for the cooldown ladder — a shim that keeps stalling is still
|
|
114
|
-
* re-probed every 15 minutes.
|
|
115
|
-
*/
|
|
116
|
-
const MANAGED_VERIFY_COOLDOWN_MAX_MS = 15 * 60_000;
|
|
117
|
-
function managedVerifyCooldownMs(attempts) {
|
|
118
|
-
return Math.min(MANAGED_VERIFY_COOLDOWN_MAX_MS, MANAGED_VERIFY_COOLDOWN_MS * 2 ** (attempts - 1));
|
|
119
|
-
}
|
|
120
|
-
async function runManagedVerification(candidate, stamp, priorAttempts, generation, verificationArgs = ["--version"]) {
|
|
121
|
-
let transient = false;
|
|
122
|
-
let ok;
|
|
123
|
-
try {
|
|
124
|
-
const { verifyToolBinary } = await import("../../../installer/index.js");
|
|
125
|
-
ok = await verifyToolBinary(candidate, undefined, () => {
|
|
126
|
-
transient = true;
|
|
127
|
-
}, MANAGED_VERIFY_TIMEOUT_MS, verificationArgs);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
// The verifier itself could not run — installer-isolated unit tests mock
|
|
131
|
-
// this module without it, and a throw here says nothing about the shim.
|
|
132
|
-
// "Cannot verify" is not "broken": keep the on-disk answer.
|
|
133
|
-
transient = true;
|
|
134
|
-
ok = false;
|
|
135
|
-
}
|
|
136
|
-
// An unspawnable prober is never a durable verdict (#1569): a timeout or a
|
|
137
|
-
// spawn-boundary failure says nothing about the shim, so it never demotes
|
|
138
|
-
// the candidate. It IS remembered, under a cooldown, so the wait is paid
|
|
139
|
-
// once per window rather than on every resolve (#1674 review F1).
|
|
140
|
-
// A verification that straddles a session boundary answers ITS caller, but
|
|
141
|
-
// its verdict belongs to the session that asked. Writing it into the fresh
|
|
142
|
-
// session would hand the new session the old one's cooldown — the exact
|
|
143
|
-
// re-arm this state clears at `session_start` (#1674 review F5). Same guard
|
|
144
|
-
// the install seam's in-flight share uses.
|
|
145
|
-
if (!ok && transient) {
|
|
146
|
-
const attempts = priorAttempts + 1;
|
|
147
|
-
generation.guardedWrite(stamp, () => {
|
|
148
|
-
managedBinaryVerdicts.set(stamp, {
|
|
149
|
-
verdict: "unverified",
|
|
150
|
-
attempts,
|
|
151
|
-
retryAtMs: Date.now() + managedVerifyCooldownMs(attempts),
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
return "unverified";
|
|
155
|
-
}
|
|
156
|
-
generation.guardedWrite(stamp, () => {
|
|
157
|
-
managedBinaryVerdicts.set(stamp, { verdict: ok ? "ok" : "broken" });
|
|
158
|
-
});
|
|
159
|
-
if (!ok) {
|
|
160
|
-
// Once per shim per session, at the moment the verdict is reached — the
|
|
161
|
-
// memo answers every later call, so this cannot become per-dispatch spam.
|
|
162
|
-
logSessionStart(`dispatch availability: managed shim ${candidate} exists but does not run; falling through to PATH`);
|
|
163
|
-
}
|
|
164
|
-
return ok ? "ok" : "broken";
|
|
165
|
-
}
|
|
166
|
-
async function verifyManagedCandidate(candidate, verificationArgs = ["--version"]) {
|
|
167
|
-
let stamp;
|
|
168
|
-
try {
|
|
169
|
-
const stat = fs.statSync(candidate);
|
|
170
|
-
stamp = `${candidate}:${stat.mtimeMs}:${stat.size}`;
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
return "absent";
|
|
174
|
-
}
|
|
175
|
-
const memo = managedBinaryVerdicts.get(stamp);
|
|
176
|
-
let priorAttempts = 0;
|
|
177
|
-
if (memo) {
|
|
178
|
-
if (memo.verdict !== "unverified")
|
|
179
|
-
return memo.verdict;
|
|
180
|
-
if (Date.now() < memo.retryAtMs)
|
|
181
|
-
return "unverified";
|
|
182
|
-
priorAttempts = memo.attempts;
|
|
183
|
-
}
|
|
184
|
-
const existing = managedVerifyInFlight.get(stamp);
|
|
185
|
-
if (existing)
|
|
186
|
-
return existing;
|
|
187
|
-
const generation = availabilityGeneration.capture();
|
|
188
|
-
const probe = runManagedVerification(candidate, stamp, priorAttempts, generation, verificationArgs).finally(() => {
|
|
189
|
-
// A settling old-session probe must not evict the live entry a new
|
|
190
|
-
// session already started for the same shim (#1674 review F5).
|
|
191
|
-
generation.guardedWrite(stamp, () => {
|
|
192
|
-
if (managedVerifyInFlight.get(stamp) === probe) {
|
|
193
|
-
managedVerifyInFlight.delete(stamp);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
managedVerifyInFlight.set(stamp, probe);
|
|
198
|
-
return probe;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* The managed shim for a Node CLI tool (`~/.pi-lens/tools/node_modules/.bin/<tool>`),
|
|
202
|
-
* or null when no candidate on disk actually runs.
|
|
203
|
-
*
|
|
204
|
-
* Verification uses the installer's own `verifyToolBinary`, the same check its
|
|
205
|
-
* managed-first branch runs before returning a managed path. A bare
|
|
206
|
-
* `existsSync` let a broken shim shadow a working PATH binary — the on-disk
|
|
207
|
-
* file wins the race and then fails every spawn behind it (#1657). knip and
|
|
208
|
-
* jscpd each carried a line-for-line copy of this resolver; #1476 folds them
|
|
209
|
-
* into one definition so the next managed tool inherits the fast path instead
|
|
210
|
-
* of a fourth copy.
|
|
211
|
-
*
|
|
212
|
-
* A settled verdict is memoized per file identity for the session, so the fast
|
|
213
|
-
* path answers without a spawn after the first call (#1467). A probe that
|
|
214
|
-
* cannot run keeps the optimistic answer rather than turning a stall into a
|
|
215
|
-
* "missing tool", and is memoized under a bounded cooldown ladder so the wait
|
|
216
|
-
* is paid once per window instead of on every resolve — while a shim that
|
|
217
|
-
* stalls on a cold cache still gets re-probed later (#1674 review F1).
|
|
218
|
-
* Concurrent first touches share one probe (#1674 review F2).
|
|
219
|
-
*/
|
|
220
|
-
export async function findManagedNodeToolBinary(tool, verificationArgs = ["--version"]) {
|
|
221
|
-
for (const candidate of managedNodeToolCandidates(tool)) {
|
|
222
|
-
const verdict = await verifyManagedCandidate(candidate, verificationArgs);
|
|
223
|
-
if (verdict === "ok" || verdict === "unverified")
|
|
224
|
-
return candidate;
|
|
225
|
-
}
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* The release-managed binary for a tool (`~/.pi-lens/bin/<tool>`), or null when
|
|
230
|
-
* the registry does not put this tool there / nothing on disk actually runs.
|
|
231
|
-
*
|
|
232
|
-
* `findManagedToolBinary` is the INSTALLER's own lookup — the one `getToolPath`
|
|
233
|
-
* runs before PATH for every github/maven/archive-strategy tool, and the one
|
|
234
|
-
* `getToolEnvironment` puts at the front of a spawn's PATH. Calling it here is
|
|
235
|
-
* what makes probe and spawn resolve through a single definition instead of two
|
|
236
|
-
* (#2140): before this, the probe fell straight to the bare command name, missed
|
|
237
|
-
* PATH, and latched `missing` — while the install fallback a few hundred ms
|
|
238
|
-
* later handed the caller the very binary sitting in that directory. Every
|
|
239
|
-
* session, for every tool the user already had (7 unavailable/available pairs in
|
|
240
|
-
* one 3h dogfood window).
|
|
241
|
-
*
|
|
242
|
-
* Verification is the same `verifyManagedCandidate` the npm-shim rung above
|
|
243
|
-
* uses, for the same reason (#1657): `findManagedToolBinary` answers from a bare
|
|
244
|
-
* `fs.access`, and an on-disk binary that cannot run must not shadow a working
|
|
245
|
-
* PATH binary — it falls through instead.
|
|
246
|
-
*/
|
|
247
|
-
async function findManagedReleaseBinary(tool, verificationArgs = ["--version"]) {
|
|
248
|
-
const candidate = await findManagedToolBinary(tool);
|
|
249
|
-
if (!candidate)
|
|
250
|
-
return null;
|
|
251
|
-
const verdict = await verifyManagedCandidate(candidate, verificationArgs);
|
|
252
|
-
return verdict === "ok" || verdict === "unverified" ? candidate : null;
|
|
253
|
-
}
|
|
254
|
-
// =============================================================================
|
|
255
|
-
// VENV-AWARE COMMAND FINDER
|
|
256
|
-
// =============================================================================
|
|
257
|
-
/**
|
|
258
|
-
* Find a command in venv first, then fall back to global.
|
|
259
|
-
* Checks common venv locations (.venv, venv) before trying global.
|
|
260
|
-
*
|
|
261
|
-
* The resolved path is returned verbatim: every spawn consumer runs with
|
|
262
|
-
* `shell: false` (safe-spawn, #817), so wrapping it in quotes would make it
|
|
263
|
-
* a literal filename that ENOENTs on every platform (#1508).
|
|
264
|
-
*
|
|
265
|
-
* The venv directory list is `VENV_BIN_DIRS` (`package-manager.ts`), shared
|
|
266
|
-
* with `formatters.ts`'s `findInVenv` and `dead-code-client.ts`'s vulture
|
|
267
|
-
* candidates — three independent spellings of the same four directories before
|
|
268
|
-
* #2544 review F1.
|
|
269
|
-
*
|
|
270
|
-
* Resolution is a fixed lookup at `cwd` ONLY (`findLocalBinAt`, no ancestor
|
|
271
|
-
* walk). #2544 review round 3 folded this onto the shared ANCESTOR walker,
|
|
272
|
-
* on the reasoning that a monorepo package should find its repo-root venv —
|
|
273
|
-
* but that was an unrequested behaviour change with no #2514 defect behind
|
|
274
|
-
* it (#2514 is about the HOME ceiling, not about climbing at all), and the
|
|
275
|
-
* inversion is real: `~/code/app` with no venv of its own would resolve
|
|
276
|
-
* `~/code/.venv/bin/ruff` — an unrelated SIBLING project's interpreter —
|
|
277
|
-
* ahead of PATH. Reverted in round 4 F2. The HOME ceiling is moot here: a
|
|
278
|
-
* lookup at exactly `cwd` can never climb past it.
|
|
279
|
-
*/
|
|
280
|
-
export function createVenvFinder(command, windowsExt = "", verificationArgs = ["--version"]) {
|
|
281
|
-
return async (cwd) => {
|
|
282
|
-
const venvBin = findLocalBinAt(command, cwd, {
|
|
283
|
-
windowsExt,
|
|
284
|
-
binDirs: VENV_BIN_DIRS,
|
|
285
|
-
});
|
|
286
|
-
if (venvBin)
|
|
287
|
-
return venvBin;
|
|
288
|
-
// Managed-dir install (~/.pi-lens/tools/node_modules/.bin/<command>) — the
|
|
289
|
-
// same shim `ensureTool()` installs npm-strategy tools into. Checked BEFORE
|
|
290
|
-
// the bare-name PATH fallback (#1638): without this, a tool that only
|
|
291
|
-
// exists in the managed dir never resolves here, so `resolveAvailableOrInstall`
|
|
292
|
-
// clears the checker's cache on every install recovery for nothing — the
|
|
293
|
-
// very next probe misses PATH again, spawns a doomed `--version` process,
|
|
294
|
-
// and only THEN falls through to `ensureTool`'s own cache. One managed-dir
|
|
295
|
-
// check settles it without a spawn — after one verification per shim per
|
|
296
|
-
// session, so a shim that cannot run falls through to PATH instead of
|
|
297
|
-
// shadowing a working binary (#1657).
|
|
298
|
-
const managed = await findManagedNodeToolBinary(command, verificationArgs);
|
|
299
|
-
if (managed)
|
|
300
|
-
return managed;
|
|
301
|
-
// Release-managed install (`~/.pi-lens/bin/<command>`) — where `ensureTool`
|
|
302
|
-
// puts every github/maven/archive-strategy tool. Same rung, same reason as
|
|
303
|
-
// the npm shim above, for the other half of the managed families (#2140).
|
|
304
|
-
const release = await findManagedReleaseBinary(command, verificationArgs);
|
|
305
|
-
if (release)
|
|
306
|
-
return release;
|
|
307
|
-
// Fall back to global
|
|
308
|
-
return command;
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
/** Typed client-facing install seam for ordered/custom candidate probes. */
|
|
312
|
-
export async function resolveManagedToolClient(options) {
|
|
313
|
-
const probed = await options.probe();
|
|
314
|
-
if (probed.outcome !== "missing")
|
|
315
|
-
return probed;
|
|
316
|
-
if (!shouldAutoInstallTool(options.toolId))
|
|
317
|
-
return probed;
|
|
318
|
-
const state = installStateFor(options.cwd, options.toolId);
|
|
319
|
-
if (state.suppressed)
|
|
320
|
-
return probed;
|
|
321
|
-
const installed = await ensureTool(options.toolId);
|
|
322
|
-
if (!installed) {
|
|
323
|
-
noteInstallFailure(options.toolId, options.cwd);
|
|
324
|
-
return probed;
|
|
325
|
-
}
|
|
326
|
-
const value = await options.acceptInstalled(installed);
|
|
327
|
-
if (value === null) {
|
|
328
|
-
noteInstallFailure(options.toolId, options.cwd);
|
|
329
|
-
return { outcome: "non-installable" };
|
|
330
|
-
}
|
|
331
|
-
noteInstallSuccess(options.toolId, options.cwd);
|
|
332
|
-
return { outcome: "success", value };
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Child environment for managed-installable tools. Keeping this beside the
|
|
336
|
-
* probe/install seam makes managed npm shims visible to every standalone
|
|
337
|
-
* client, rather than only to Knip (#1289).
|
|
338
|
-
*/
|
|
339
|
-
export async function getManagedToolEnvironment(_toolId, cwd) {
|
|
340
|
-
let env;
|
|
341
|
-
try {
|
|
342
|
-
const { getToolEnvironment } = await import("../../../installer/index.js");
|
|
343
|
-
env = await getToolEnvironment();
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
// Installer-isolated unit tests historically mock only ensureTool. The
|
|
347
|
-
// ambient fallback preserves that isolation; production always exports it.
|
|
348
|
-
env = { ...process.env };
|
|
349
|
-
}
|
|
350
|
-
if (!cwd)
|
|
351
|
-
return env;
|
|
352
|
-
const separator = process.platform === "win32" ? ";" : ":";
|
|
353
|
-
const currentPath = env.PATH || env.Path || process.env.PATH || "";
|
|
354
|
-
const localBin = path.join(cwd, "node_modules", ".bin");
|
|
355
|
-
const augmentedPath = `${localBin}${separator}${currentPath}`;
|
|
356
|
-
return {
|
|
357
|
-
...env,
|
|
358
|
-
PATH: augmentedPath,
|
|
359
|
-
...(process.platform === "win32" ? { Path: augmentedPath } : {}),
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
/** Read-only managed/PATH discovery for spawn-time resolution memos. */
|
|
363
|
-
export async function discoverManagedTool(toolId) {
|
|
364
|
-
return (await ensureTool(toolId, { allowInstall: false })) ?? null;
|
|
365
|
-
}
|
|
366
|
-
// This is session-scoped state, not a process-global tool/path cache. The cwd
|
|
367
|
-
// key is normalized by PathKeyedMap and is cleared at session_start. A failed
|
|
368
|
-
// install must not become an install attempt on every eligible file/turn.
|
|
369
|
-
const installAttemptsByCwd = new PathKeyedMap(normalizeMapKey);
|
|
370
|
-
const resolveInstallInFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
371
|
-
// Which (cwd, toolId) pairs already got their ONE compensating `available`
|
|
372
|
-
// row this session for a GENUINE correction (#1612 review F2).
|
|
373
|
-
// `checker.reset()` clears the checker's own cache on every install recovery,
|
|
374
|
-
// so the NEXT call re-probes PATH from scratch — and that probe keeps missing
|
|
375
|
-
// for a managed-dir-only install (createVenvFinder never searches the managed
|
|
376
|
-
// tools dir; tracked separately as its own bug). Without this memo, every one
|
|
377
|
-
// of those repeat probe failures looks like a fresh correction and re-logs
|
|
378
|
-
// "available", even though only the first call actually ran an install.
|
|
379
|
-
//
|
|
380
|
-
// A correction is genuine only when a latched `unavailable` row for the same
|
|
381
|
-
// pair stood before it. `verifyOrInstallCommand` also emits through this seam
|
|
382
|
-
// with NO prior latch behind it (biome-check and oxlint reach it with no
|
|
383
|
-
// checker probe at all), and burning this memo on such a row silences the
|
|
384
|
-
// LATER genuine latch-then-recover for the same pair — the #1606 defect back
|
|
385
|
-
// through the #1612 seam (#1657). Those rows dedupe against
|
|
386
|
-
// `uncorrectedEmissionsByCwd` instead, so repeats stay suppressed without
|
|
387
|
-
// pre-empting the real correction.
|
|
388
|
-
const correctedAvailabilityByCwd = new PathKeyedMap(normalizeMapKey);
|
|
389
|
-
// Which (cwd, toolId) pairs already emitted an `available` row that corrected
|
|
390
|
-
// nothing (no latched `unavailable` stood before it). Same one-row-per-session
|
|
391
|
-
// bound as the memo above, kept in a SEPARATE scope so it cannot answer for a
|
|
392
|
-
// correction that has not happened yet (#1657).
|
|
393
|
-
const uncorrectedEmissionsByCwd = new PathKeyedMap(normalizeMapKey);
|
|
394
|
-
// Which (cwd, tool) pairs currently stand latched `unavailable` in the durable
|
|
395
|
-
// log. Recorded where such a row is actually written, so "was there anything
|
|
396
|
-
// to correct" is read from the log's own history rather than assumed by the
|
|
397
|
-
// caller. Session-scoped: `resetDispatchAvailabilityState` clears it, so a new
|
|
398
|
-
// session re-arms instead of inheriting the last one's latches (#1657).
|
|
399
|
-
const latchedUnavailableByCwd = new PathKeyedMap(normalizeMapKey);
|
|
400
|
-
// Checkers are created by runner modules and may also be created dynamically.
|
|
401
|
-
// Keep the session reset as a generation rather than retaining every checker
|
|
402
|
-
// reset closure forever.
|
|
403
|
-
//
|
|
404
|
-
// #1754: the counter itself is now the shared `GenerationSource` primitive, so
|
|
405
|
-
// the capture-before-await/check-after guards below are one implementation
|
|
406
|
-
// instead of five hand-rolled ones, and a dropped straddling write is visible
|
|
407
|
-
// in the degradation ledger instead of silent.
|
|
408
|
-
const availabilityGeneration = createGenerationSource("dispatch-availability");
|
|
409
|
-
export const getDispatchAvailabilityGeneration = () => availabilityGeneration.current();
|
|
410
|
-
const checkerProbeFlights = createAvailabilityProbeFlight({ generation: getDispatchAvailabilityGeneration });
|
|
411
|
-
export function recordAvailabilityProbeOverrun(tool, key, elapsedMs, budgetMs, failure) {
|
|
412
|
-
if (budgetMs === undefined || elapsedMs <= budgetMs)
|
|
413
|
-
return;
|
|
414
|
-
if (failure === "timeout" && elapsedMs <= budgetMs * 2)
|
|
415
|
-
return;
|
|
416
|
-
incrementDegradationCount({
|
|
417
|
-
kind: "availability-probe-overrun",
|
|
418
|
-
subject: `${tool}:${key}`,
|
|
419
|
-
reason: `probe took ${Math.round(elapsedMs)}ms; budget was ${Math.round(budgetMs)}ms`,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
function installStateFor(cwd, toolId) {
|
|
423
|
-
let states = installAttemptsByCwd.get(cwd);
|
|
424
|
-
if (!states) {
|
|
425
|
-
states = new Map();
|
|
426
|
-
installAttemptsByCwd.set(cwd, states);
|
|
427
|
-
}
|
|
428
|
-
let state = states.get(toolId);
|
|
429
|
-
if (!state) {
|
|
430
|
-
state = { attempts: 0, suppressed: false };
|
|
431
|
-
states.set(toolId, state);
|
|
432
|
-
}
|
|
433
|
-
return state;
|
|
434
|
-
}
|
|
435
|
-
function noteInstallFailure(toolId, cwd) {
|
|
436
|
-
const state = installStateFor(cwd, toolId);
|
|
437
|
-
state.attempts += 1;
|
|
438
|
-
state.suppressed = true;
|
|
439
|
-
logSessionStart(`dispatch availability ${toolId}: install attempt ${state.attempts} failed; suppressing retries until the next session or a successful install`);
|
|
440
|
-
}
|
|
441
|
-
function noteInstallSuccess(toolId, cwd) {
|
|
442
|
-
const states = installAttemptsByCwd.get(cwd);
|
|
443
|
-
states?.delete(toolId);
|
|
444
|
-
if (states?.size === 0)
|
|
445
|
-
installAttemptsByCwd.delete(cwd);
|
|
446
|
-
}
|
|
447
|
-
function hasId(map, cwd, id) {
|
|
448
|
-
return map.get(cwd)?.has(id) ?? false;
|
|
449
|
-
}
|
|
450
|
-
function addId(map, cwd, id) {
|
|
451
|
-
let ids = map.get(cwd);
|
|
452
|
-
if (!ids) {
|
|
453
|
-
ids = new Set();
|
|
454
|
-
map.set(cwd, ids);
|
|
455
|
-
}
|
|
456
|
-
ids.add(id);
|
|
457
|
-
}
|
|
458
|
-
function deleteIds(map, cwd, ids) {
|
|
459
|
-
const existing = map.get(cwd);
|
|
460
|
-
if (!existing)
|
|
461
|
-
return;
|
|
462
|
-
for (const id of ids)
|
|
463
|
-
existing.delete(id);
|
|
464
|
-
if (existing.size === 0)
|
|
465
|
-
map.delete(cwd);
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* True once a compensating `available` row has already fired for this
|
|
469
|
-
* (cwd, toolId) pair this session; `noteAvailabilityCorrected` records it the
|
|
470
|
-
* first time. #1612 review F2's "once per correction" — see
|
|
471
|
-
* `correctedAvailabilityByCwd`'s comment for why repeats need suppressing.
|
|
472
|
-
*/
|
|
473
|
-
function hasCorrectedAvailability(cwd, toolId) {
|
|
474
|
-
return hasId(correctedAvailabilityByCwd, cwd, toolId);
|
|
475
|
-
}
|
|
476
|
-
function noteAvailabilityCorrected(cwd, toolId) {
|
|
477
|
-
addId(correctedAvailabilityByCwd, cwd, toolId);
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Record that a latched `unavailable` row now stands for this pair. Called
|
|
481
|
-
* from each seam that writes one (the availability checker and the shared
|
|
482
|
-
* probe latch), so a later compensating row can tell a genuine correction
|
|
483
|
-
* from a no-op emission (#1657).
|
|
484
|
-
*/
|
|
485
|
-
function noteLatchedUnavailable(cwd, tool) {
|
|
486
|
-
addId(latchedUnavailableByCwd, cwd, tool);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Drop the latch record for a pair, because the row it described no longer
|
|
490
|
-
* stands. Called on every `available` verdict as well as on the compensating
|
|
491
|
-
* row itself, so a latch cannot outlive the state it describes and later back
|
|
492
|
-
* a "correction" that corrects nothing (#1674 review F3).
|
|
493
|
-
*/
|
|
494
|
-
function clearLatchedUnavailable(cwd, ...tools) {
|
|
495
|
-
deleteIds(latchedUnavailableByCwd, cwd, tools);
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Every name a latched row for this pair could have been written under. A
|
|
499
|
-
* checker logs its COMMAND (sometimes a resolved absolute path), the install
|
|
500
|
-
* seam knows a toolId, and the two are not always the same string. One
|
|
501
|
-
* derivation feeds BOTH the lookup and the clear, so the two can never
|
|
502
|
-
* disagree about which key the latch lives under (#1674 review F3).
|
|
503
|
-
*/
|
|
504
|
-
function latchKeysFor(toolId, command) {
|
|
505
|
-
const keys = [toolId];
|
|
506
|
-
if (command)
|
|
507
|
-
keys.push(command, path.basename(command));
|
|
508
|
-
return keys;
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* True when a latched `unavailable` row stands for any of these keys. A false
|
|
512
|
-
* positive only restores the pre-#1657 behavior (the row emits and burns the
|
|
513
|
-
* correction memo); a false negative still emits the row, under the
|
|
514
|
-
* uncorrected scope. Neither can swallow a genuine correction.
|
|
515
|
-
*/
|
|
516
|
-
function hasLatchedUnavailable(cwd, tools) {
|
|
517
|
-
return tools.some((tool) => hasId(latchedUnavailableByCwd, cwd, tool));
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Which installer family resolved a tool, for the compensating row's
|
|
521
|
-
* `evidence.source` (#1612 review F1). Reads the registry's own
|
|
522
|
-
* `installStrategy` rather than hand-mapping per toolId — the two cannot
|
|
523
|
-
* drift apart because there is only one list.
|
|
524
|
-
*/
|
|
525
|
-
function sourceTagForToolId(toolId) {
|
|
526
|
-
switch (getToolInstallStrategy(toolId)) {
|
|
527
|
-
case "npm":
|
|
528
|
-
return "managed-dir";
|
|
529
|
-
case "pip":
|
|
530
|
-
return "pip-user";
|
|
531
|
-
case "github":
|
|
532
|
-
return "github-release";
|
|
533
|
-
case "archive":
|
|
534
|
-
return "archive-dist";
|
|
535
|
-
case "maven":
|
|
536
|
-
return "maven-jar";
|
|
537
|
-
default:
|
|
538
|
-
// "gem" has no CLI runner behind this seam yet, and an unknown toolId
|
|
539
|
-
// means the registry lookup itself missed — either way, no tag beats
|
|
540
|
-
// a guessed one.
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* `binary`/`source` for a probe that resolved through one of pi-lens's OWN
|
|
546
|
-
* managed installs rather than through PATH or a project venv (#2140). A reader
|
|
547
|
-
* of latency.log could otherwise not tell the two apart, and the whole point of
|
|
548
|
-
* the fix is that the managed directories now answer where PATH used to miss.
|
|
549
|
-
*
|
|
550
|
-
* BOTH managed rungs are asked, in the order `createVenvFinder` walks them
|
|
551
|
-
* (#2140 review F1). The first version asked only about the release directory,
|
|
552
|
-
* so every npm-shim hit — knip, jscpd, madge, pyright, biome, htmlhint,
|
|
553
|
-
* stylelint — logged an evidence-free row indistinguishable from a PATH hit,
|
|
554
|
-
* while the doc claimed the opposite. Each rung is asked of THE function that
|
|
555
|
-
* produced its own paths (`managedNodeToolCandidates`, `findManagedToolBinary`)
|
|
556
|
-
* and settled by string identity, never by a path-prefix predicate over a
|
|
557
|
-
* directory: a second opinion about which paths are managed is exactly the
|
|
558
|
-
* parallel-list drift `sourceTagForToolId` exists to avoid, and it would answer
|
|
559
|
-
* differently for case or separator variants.
|
|
560
|
-
*
|
|
561
|
-
* `binary` present IS the managed hit; `source` names the family whenever the
|
|
562
|
-
* command is a registry id (`sourceTagForToolId` reads the registry's own
|
|
563
|
-
* `installStrategy`). A managed shim whose COMMAND is not itself a registry id
|
|
564
|
-
* — `markdownlint-cli2`, installed under registry id `markdownlint` — carries
|
|
565
|
-
* `binary` alone rather than a guessed family. `binary` is a BASENAME, never
|
|
566
|
-
* the absolute path — same rule as every other evidence field (#1568 review).
|
|
567
|
-
*/
|
|
568
|
-
async function describeManagedResolution(tool, resolved) {
|
|
569
|
-
const managed = managedNodeToolCandidates(tool).includes(resolved) ||
|
|
570
|
-
(await findManagedToolBinary(tool)) === resolved;
|
|
571
|
-
if (!managed)
|
|
572
|
-
return {};
|
|
573
|
-
const source = sourceTagForToolId(tool);
|
|
574
|
-
return {
|
|
575
|
-
binary: path.basename(resolved),
|
|
576
|
-
...(source !== undefined && { source }),
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* How to tag a not-fresh install resolution in the compensating row's
|
|
581
|
-
* evidence (#1636 review, carried over from #1615's verify). `getInstallAttempt`
|
|
582
|
-
* answers `undefined` for BOTH a genuine cache hit and a plain PATH/managed-dir
|
|
583
|
-
* discovery, and answers `declined`/`skipped` for a policy refusal that still
|
|
584
|
-
* hands back whatever discovery found — three different facts a single
|
|
585
|
-
* `"cache"` tag used to flatten into one. `declined`/`skipped` take priority
|
|
586
|
-
* over the source map: a project-trust decline overwrites whatever the
|
|
587
|
-
* discovery pass underneath it found (see `ensureTool`'s trust-gate branch in
|
|
588
|
-
* installer/index.ts), so by the time this reads it, "declined" is the honest
|
|
589
|
-
* answer regardless of how the binary was actually found.
|
|
590
|
-
*/
|
|
591
|
-
function resolvedTagForAttempt(toolId, attempt) {
|
|
592
|
-
if (attempt?.outcome === "declined" || attempt?.outcome === "skipped") {
|
|
593
|
-
return "declined";
|
|
594
|
-
}
|
|
595
|
-
return getLastEnsureResolutionSource(toolId) === "path" ? "path" : "cache";
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Emit the ONE compensating `available` row for a (cwd, toolId) correction,
|
|
599
|
-
* after a latched unavailable probe recovers via `ensureTool`. This is the
|
|
600
|
-
* SINGLE row constructor for every caller behind this seam —
|
|
601
|
-
* `resolveAvailableOrInstallUnshared` (#1612) and `verifyOrInstallCommand`
|
|
602
|
-
* (#1636) both call it rather than each hand-rolling their own, which is
|
|
603
|
-
* exactly the drift #1610's own review caught the first time this shape
|
|
604
|
-
* shipped twice.
|
|
605
|
-
*
|
|
606
|
-
* Gated on the once-per-correction memo (`correctedAvailabilityByCwd`,
|
|
607
|
-
* #1612 review F2): `checker.reset()` / a cleared probe cache means the NEXT
|
|
608
|
-
* call for the same (cwd, toolId) can resolve the same tool again with no
|
|
609
|
-
* fresh attempt behind it, and that must not re-log a "fresh correction"
|
|
610
|
-
* every time.
|
|
611
|
-
*
|
|
612
|
-
* `latchKeys` are every name a latched `unavailable` row for this pair could
|
|
613
|
-
* carry. A row that clears one of them is a genuine correction and burns the
|
|
614
|
-
* correction memo; a row with nothing to correct dedupes in its own scope, so
|
|
615
|
-
* it cannot silence the genuine latch-then-recover that follows it for the
|
|
616
|
-
* same pair (#1657).
|
|
617
|
-
*/
|
|
618
|
-
function emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installedPath, latchKeys) {
|
|
619
|
-
if (hasCorrectedAvailability(cwd, toolId))
|
|
620
|
-
return;
|
|
621
|
-
const correctsLatchedRow = hasLatchedUnavailable(cwd, latchKeys);
|
|
622
|
-
if (correctsLatchedRow) {
|
|
623
|
-
noteAvailabilityCorrected(cwd, toolId);
|
|
624
|
-
// Clear every key the latch could live under, not just the toolId: the
|
|
625
|
-
// checker records the row under its COMMAND, and a leftover entry would
|
|
626
|
-
// later back a "correction" with nothing behind it (#1674 review F3).
|
|
627
|
-
clearLatchedUnavailable(cwd, ...latchKeys);
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
if (hasId(uncorrectedEmissionsByCwd, cwd, toolId))
|
|
631
|
-
return;
|
|
632
|
-
addId(uncorrectedEmissionsByCwd, cwd, toolId);
|
|
633
|
-
}
|
|
634
|
-
// `installed`/`installedPath` can come back truthy WITHOUT ensureTool
|
|
635
|
-
// running an install this call — its in-memory cache, its on-disk probe
|
|
636
|
-
// cache, getToolPath finding it already present, or a concurrent peer's
|
|
637
|
-
// install all return a path with no fresh attempt recorded. Deriving from
|
|
638
|
-
// `getInstallAttempt` (rather than asserting "succeeded" because the path
|
|
639
|
-
// is truthy, or hand-rolling a second mapping) is what #1610's OWN
|
|
640
|
-
// failure-path mirror bug was caught on review; read the REAL outcome
|
|
641
|
-
// through the one function that already knows how, don't assume it. Hard
|
|
642
|
-
// import, no `typeof` guard (#1636 review): a genuinely removed export
|
|
643
|
-
// must throw here, not silently read as "not attempted".
|
|
644
|
-
const attempt = getInstallAttempt(toolId);
|
|
645
|
-
const installEvidence = describeInstallAttempt(attempt);
|
|
646
|
-
const evidence = {
|
|
647
|
-
...installEvidence,
|
|
648
|
-
binary: path.basename(installedPath),
|
|
649
|
-
source: sourceTagForToolId(toolId),
|
|
650
|
-
// Set alongside any non-"succeeded" outcome: the path is truthy but
|
|
651
|
-
// nothing ran this call, so it came from an already-known-good answer
|
|
652
|
-
// rather than a fresh attempt (#1612 review F2).
|
|
653
|
-
...(installEvidence.install !== "succeeded" && {
|
|
654
|
-
resolved: resolvedTagForAttempt(toolId, attempt),
|
|
655
|
-
}),
|
|
656
|
-
// The whole point of the memo split, on the record: a reader can tell a
|
|
657
|
-
// row that cleared a latched `unavailable` from one that corrected
|
|
658
|
-
// nothing, without re-deriving it from the surrounding rows (#1674
|
|
659
|
-
// review F4).
|
|
660
|
-
correctsLatchedRow,
|
|
661
|
-
};
|
|
662
|
-
logAvailabilityDecision({
|
|
663
|
-
tool: toolId,
|
|
664
|
-
verdict: "available",
|
|
665
|
-
outcome: "success",
|
|
666
|
-
cause: "ok",
|
|
667
|
-
elapsedMs: Date.now() - installStartedAt,
|
|
668
|
-
// `checker.reset()`/a fresh probe just cleared whatever cache stood
|
|
669
|
-
// behind this call, so nothing is actually held pinned the instant
|
|
670
|
-
// this row is written — the very next probe starts fresh
|
|
671
|
-
// (#1612 review F3). Writing `true` here would claim a durability
|
|
672
|
-
// this call doesn't have.
|
|
673
|
-
latched: false,
|
|
674
|
-
classifiedBy: "caller",
|
|
675
|
-
evidence,
|
|
676
|
-
}, cwd);
|
|
677
|
-
}
|
|
678
|
-
/** Reset availability/install suppression at the session boundary. */
|
|
679
|
-
export function resetDispatchAvailabilityState() {
|
|
680
|
-
installAttemptsByCwd.clear();
|
|
681
|
-
resolveInstallInFlightByCwd.clear();
|
|
682
|
-
correctedAvailabilityByCwd.clear();
|
|
683
|
-
uncorrectedEmissionsByCwd.clear();
|
|
684
|
-
latchedUnavailableByCwd.clear();
|
|
685
|
-
managedBinaryVerdicts.clear();
|
|
686
|
-
managedVerifyInFlight.clear();
|
|
687
|
-
resetPathWalkMemo();
|
|
688
|
-
availabilityGeneration.bump();
|
|
689
|
-
checkerProbeFlights.clear();
|
|
690
|
-
cwdProbeFlights.clear();
|
|
691
|
-
// The generation bump invalidates dispatcherProbeFlights in its owner module.
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Create a cached availability checker for a command.
|
|
695
|
-
* The checker will look for the command in venv first, then global.
|
|
696
|
-
*
|
|
697
|
-
* `versionArgs` defaults to `["--version"]` but some tools reject that flag and
|
|
698
|
-
* expose version under a subcommand instead (e.g. `zig version`, not
|
|
699
|
-
* `zig --version`). Passing the wrong probe makes the runner silently skip on
|
|
700
|
-
* every machine, so toolchains with a non-standard version command must override
|
|
701
|
-
* this.
|
|
702
|
-
*
|
|
703
|
-
* ## Latch policy (#1467)
|
|
704
|
-
*
|
|
705
|
-
* A `missing` / `non-installable` verdict is durable and is cached for the
|
|
706
|
-
* session. A `transient` verdict — timeout, abort, EAGAIN — is NOT: it is
|
|
707
|
-
* cached only for a bounded cooldown, after which the next caller re-probes.
|
|
708
|
-
* An installed tool therefore recovers on its own, without a host restart.
|
|
709
|
-
*/
|
|
710
|
-
export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"], options = {}) {
|
|
711
|
-
const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
712
|
-
const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
713
|
-
let checkerGeneration = availabilityGeneration.current();
|
|
714
|
-
let checkerFlightGeneration = 0;
|
|
715
|
-
const findCommand = createVenvFinder(command, windowsExt, versionArgs);
|
|
716
|
-
function ensureCurrentGeneration() {
|
|
717
|
-
if (checkerGeneration === availabilityGeneration.current())
|
|
718
|
-
return;
|
|
719
|
-
cacheByCwd.clear();
|
|
720
|
-
inFlightByCwd.clear();
|
|
721
|
-
checkerGeneration = availabilityGeneration.current();
|
|
722
|
-
checkerFlightGeneration++;
|
|
723
|
-
}
|
|
724
|
-
const reset = () => {
|
|
725
|
-
cacheByCwd.clear();
|
|
726
|
-
inFlightByCwd.clear();
|
|
727
|
-
checkerGeneration = availabilityGeneration.current();
|
|
728
|
-
checkerFlightGeneration++;
|
|
729
|
-
};
|
|
730
|
-
function getCache(cwd) {
|
|
731
|
-
ensureCurrentGeneration();
|
|
732
|
-
const key = path.resolve(cwd || process.cwd());
|
|
733
|
-
const existing = cacheByCwd.get(key);
|
|
734
|
-
if (existing)
|
|
735
|
-
return existing;
|
|
736
|
-
const created = {
|
|
737
|
-
available: null,
|
|
738
|
-
command: null,
|
|
739
|
-
outcome: null,
|
|
740
|
-
cause: null,
|
|
741
|
-
elapsedMs: 0,
|
|
742
|
-
retryAtMs: 0,
|
|
743
|
-
transientAttempts: 0,
|
|
744
|
-
};
|
|
745
|
-
cacheByCwd.set(key, created);
|
|
746
|
-
return created;
|
|
747
|
-
}
|
|
748
|
-
/** Record a verdict on the cache and emit exactly one decision record. */
|
|
749
|
-
function noteDecision(cache, resolvedCwd, verdict) {
|
|
750
|
-
cache.available = verdict.available;
|
|
751
|
-
cache.outcome = verdict.outcome;
|
|
752
|
-
cache.cause = verdict.cause;
|
|
753
|
-
cache.elapsedMs = verdict.elapsedMs;
|
|
754
|
-
let retryAfterMs;
|
|
755
|
-
if (verdict.available) {
|
|
756
|
-
cache.retryAtMs = 0;
|
|
757
|
-
cache.transientAttempts = 0;
|
|
758
|
-
}
|
|
759
|
-
else if (isLatchingOutcome(verdict.outcome)) {
|
|
760
|
-
cache.retryAtMs = 0;
|
|
761
|
-
cache.transientAttempts = 0;
|
|
762
|
-
}
|
|
763
|
-
else {
|
|
764
|
-
cache.transientAttempts += 1;
|
|
765
|
-
retryAfterMs = transientRetryDelayMs(cache.transientAttempts, verdict.cause);
|
|
766
|
-
cache.retryAtMs = Date.now() + retryAfterMs;
|
|
767
|
-
}
|
|
768
|
-
const latched = verdict.available || isLatchingOutcome(verdict.outcome);
|
|
769
|
-
// Remember the pair whose durable row now says "unavailable and staying
|
|
770
|
-
// that way" — that is the row a later compensating `available` row
|
|
771
|
-
// corrects, and the only thing that makes such a row a correction
|
|
772
|
-
// (#1657).
|
|
773
|
-
if (!verdict.available && latched) {
|
|
774
|
-
noteLatchedUnavailable(resolvedCwd, command);
|
|
775
|
-
}
|
|
776
|
-
else if (verdict.available) {
|
|
777
|
-
// The latched row this pair carried no longer stands: THIS row says
|
|
778
|
-
// the tool is back. Dropping the record here keeps a stale latch from
|
|
779
|
-
// backing a later "correction" that corrects nothing (#1674 F3).
|
|
780
|
-
clearLatchedUnavailable(resolvedCwd, ...latchKeysFor(command));
|
|
781
|
-
}
|
|
782
|
-
logAvailabilityDecision({
|
|
783
|
-
tool: command,
|
|
784
|
-
verdict: verdict.available ? "available" : "unavailable",
|
|
785
|
-
outcome: verdict.outcome,
|
|
786
|
-
cause: verdict.cause,
|
|
787
|
-
elapsedMs: verdict.elapsedMs,
|
|
788
|
-
latched,
|
|
789
|
-
// Every caller passes this (#2209); a direct key, not a conditional
|
|
790
|
-
// spread, keeps it visible to the call-shaped classifiedBy sweep.
|
|
791
|
-
classifiedBy: verdict.classifiedBy,
|
|
792
|
-
...(verdict.evidence !== undefined && { evidence: verdict.evidence }),
|
|
793
|
-
...(verdict.hostStallMs !== undefined && {
|
|
794
|
-
hostStallMs: verdict.hostStallMs,
|
|
795
|
-
}),
|
|
796
|
-
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
797
|
-
budgetMs: options.probeTimeout ?? 5000,
|
|
798
|
-
}, resolvedCwd);
|
|
799
|
-
}
|
|
800
|
-
async function isAvailableAsync(cwd) {
|
|
801
|
-
ensureCurrentGeneration();
|
|
802
|
-
const resolvedCwd = cwd || process.cwd();
|
|
803
|
-
const cache = getCache(resolvedCwd);
|
|
804
|
-
if (cache.available === false) {
|
|
805
|
-
// A durable "this machine does not have the tool" stays cached; a
|
|
806
|
-
// transient probe failure only holds until its cooldown expires, so an
|
|
807
|
-
// installed tool cannot be disabled for the life of the process by one
|
|
808
|
-
// slow second at warm-up (#1467).
|
|
809
|
-
if (cache.outcome !== "transient")
|
|
810
|
-
return false;
|
|
811
|
-
if (Date.now() < cache.retryAtMs)
|
|
812
|
-
return false;
|
|
813
|
-
cache.available = null;
|
|
814
|
-
}
|
|
815
|
-
if (cache.available === true && cache.command) {
|
|
816
|
-
if (await isSpawnableCommand(cache.command))
|
|
817
|
-
return true;
|
|
818
|
-
// Cached-positive spawn feedback: a removed absolute path or vanished
|
|
819
|
-
// PATH command must fall through to a fresh probe immediately.
|
|
820
|
-
cache.available = null;
|
|
821
|
-
cache.command = null;
|
|
822
|
-
cache.outcome = null;
|
|
823
|
-
cache.cause = null;
|
|
824
|
-
}
|
|
825
|
-
const key = path.resolve(resolvedCwd);
|
|
826
|
-
const existing = inFlightByCwd.get(key);
|
|
827
|
-
if (existing)
|
|
828
|
-
return existing;
|
|
829
|
-
const promiseGeneration = checkerGeneration;
|
|
830
|
-
let promise;
|
|
831
|
-
promise = (async () => {
|
|
832
|
-
const fastPath = await options.fastPath?.();
|
|
833
|
-
if (fastPath) {
|
|
834
|
-
cache.command = fastPath;
|
|
835
|
-
noteDecision(cache, resolvedCwd, {
|
|
836
|
-
available: true,
|
|
837
|
-
outcome: "success",
|
|
838
|
-
cause: "fast-path",
|
|
839
|
-
elapsedMs: 0,
|
|
840
|
-
// Caller-asserted, and justifiably: an on-disk shim IS the tool, so
|
|
841
|
-
// there is no spawn to derive anything from (#1500).
|
|
842
|
-
classifiedBy: "caller",
|
|
843
|
-
});
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
// A bad/removed workspace must not be mistaken for a missing tool and
|
|
847
|
-
// trigger an install. This async probe stays off the synchronous dispatch
|
|
848
|
-
// burst and makes the failure taxonomy explicit at the seam.
|
|
849
|
-
try {
|
|
850
|
-
const cwdStat = await fs.promises.stat(resolvedCwd);
|
|
851
|
-
if (!cwdStat.isDirectory()) {
|
|
852
|
-
noteDecision(cache, resolvedCwd, {
|
|
853
|
-
available: false,
|
|
854
|
-
outcome: "non-installable",
|
|
855
|
-
cause: "bad-cwd",
|
|
856
|
-
elapsedMs: 0,
|
|
857
|
-
// The workspace is gone. Asserted from a stat, not a probe, and it
|
|
858
|
-
// must never read as "the tool is missing" (#1500).
|
|
859
|
-
classifiedBy: "caller",
|
|
860
|
-
});
|
|
861
|
-
return false;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
catch {
|
|
865
|
-
noteDecision(cache, resolvedCwd, {
|
|
866
|
-
available: false,
|
|
867
|
-
outcome: "non-installable",
|
|
868
|
-
cause: "bad-cwd",
|
|
869
|
-
elapsedMs: 0,
|
|
870
|
-
classifiedBy: "caller",
|
|
871
|
-
});
|
|
872
|
-
return false;
|
|
873
|
-
}
|
|
874
|
-
// Resolution is measured separately from the spawn (#2140 review F2).
|
|
875
|
-
// It is no longer free — the managed rungs stat two directories and,
|
|
876
|
-
// on a first touch, pay their own verification spawn — and it runs
|
|
877
|
-
// BEFORE `startedAt`, so neither `durationMs` nor
|
|
878
|
-
// `recordAvailabilityProbeOverrun` can see it. Folding it into
|
|
879
|
-
// `durationMs` would instead charge the probe budget for work that
|
|
880
|
-
// budget does not govern, so the two spans are reported side by side.
|
|
881
|
-
const resolveStartedAt = Date.now();
|
|
882
|
-
const cmd = await findCommand(resolvedCwd);
|
|
883
|
-
const resolveMs = Date.now() - resolveStartedAt;
|
|
884
|
-
// #1995: a command cooling down after a RUNTIME timeout (lint or
|
|
885
|
-
// autofix lane blew its real budget) must not re-probe on every
|
|
886
|
-
// edit - the positive verdict is effectively cooled. Consult-only:
|
|
887
|
-
// probe timeouts do NOT arm the cooldown here, because a single
|
|
888
|
-
// slow --version under host stall (#1467) is transient evidence,
|
|
889
|
-
// and session-long suppression from it would contradict the
|
|
890
|
-
// #1494 retry ladder this classification exists for.
|
|
891
|
-
if (isInSpawnTimeoutCooldown(cmd)) {
|
|
892
|
-
noteDecision(cache, resolvedCwd, {
|
|
893
|
-
available: false,
|
|
894
|
-
outcome: "transient",
|
|
895
|
-
cause: "probe-timeout",
|
|
896
|
-
elapsedMs: 0,
|
|
897
|
-
classifiedBy: "caller",
|
|
898
|
-
evidence: {
|
|
899
|
-
command: cmd,
|
|
900
|
-
status: null,
|
|
901
|
-
},
|
|
902
|
-
});
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
|
-
const env = await options.environment?.(resolvedCwd);
|
|
906
|
-
// The probe budget is enforced by a HOST-side timer, so host event-loop
|
|
907
|
-
// stalls are charged to the child. Measure the stall that overlapped the
|
|
908
|
-
// window and hand it to the classifier (#1467).
|
|
909
|
-
const stallSampler = startHostStallSampler();
|
|
910
|
-
const startedAt = Date.now();
|
|
911
|
-
let result;
|
|
912
|
-
let hostStallMs;
|
|
913
|
-
let probeJoined = false;
|
|
914
|
-
try {
|
|
915
|
-
const shared = checkerProbeFlights.run(`checker:${command}|${versionArgs.join("|")}|${key}|${checkerFlightGeneration}|${windowsExt}|${cmd}|${JSON.stringify(Object.entries(env ?? {}).sort(([a], [b]) => compareOrdinal(a, b)))}`, () => safeSpawnAsync(cmd, versionArgs, {
|
|
916
|
-
timeout: options.probeTimeout ?? 5000,
|
|
917
|
-
cwd: resolvedCwd,
|
|
918
|
-
env,
|
|
919
|
-
input: "",
|
|
920
|
-
}));
|
|
921
|
-
probeJoined = shared.joined;
|
|
922
|
-
result = await shared.promise;
|
|
923
|
-
}
|
|
924
|
-
finally {
|
|
925
|
-
hostStallMs = stallSampler.stop();
|
|
926
|
-
}
|
|
927
|
-
const elapsedMs = Date.now() - startedAt;
|
|
928
|
-
recordAvailabilityProbeOverrun(command, resolvedCwd, elapsedMs, options.probeTimeout ?? 5000, result.failure);
|
|
929
|
-
if (!result.error && result.status === 0) {
|
|
930
|
-
cache.command = cmd;
|
|
931
|
-
noteDecision(cache, resolvedCwd, {
|
|
932
|
-
available: true,
|
|
933
|
-
outcome: "success",
|
|
934
|
-
cause: "ok",
|
|
935
|
-
elapsedMs,
|
|
936
|
-
hostStallMs,
|
|
937
|
-
classifiedBy: probeJoined ? "joined" : "probe",
|
|
938
|
-
evidence: {
|
|
939
|
-
...describeProbeEvidence(result),
|
|
940
|
-
...(await describeManagedResolution(command, cmd)),
|
|
941
|
-
resolveMs,
|
|
942
|
-
},
|
|
943
|
-
});
|
|
944
|
-
return true;
|
|
945
|
-
}
|
|
946
|
-
const { outcome, cause, evidence } = classifyProbeFailure(result, {
|
|
947
|
-
hostStallMs,
|
|
948
|
-
unclassifiedFailureOutcome: options.unclassifiedFailureOutcome,
|
|
949
|
-
});
|
|
950
|
-
// Only a TYPED tool-not-found invalidates the PATH walk memo; an
|
|
951
|
-
// `unclassifiedFailureOutcome: "missing"` compatibility verdict is a
|
|
952
|
-
// guess, not evidence that PATH changed.
|
|
953
|
-
if (result.spawnFailure?.kind === "tool-not-found")
|
|
954
|
-
resetPathWalkMemo();
|
|
955
|
-
noteDecision(cache, resolvedCwd, {
|
|
956
|
-
available: false,
|
|
957
|
-
outcome,
|
|
958
|
-
cause,
|
|
959
|
-
elapsedMs,
|
|
960
|
-
hostStallMs,
|
|
961
|
-
classifiedBy: probeJoined ? "joined" : "probe",
|
|
962
|
-
evidence,
|
|
963
|
-
});
|
|
964
|
-
return false;
|
|
965
|
-
})().finally(() => {
|
|
966
|
-
// A session reset clears this map and a caller may immediately start a
|
|
967
|
-
// replacement probe for the same cwd. The old promise must not delete
|
|
968
|
-
// that newer-generation entry when it settles.
|
|
969
|
-
if (checkerGeneration === promiseGeneration &&
|
|
970
|
-
inFlightByCwd.get(key) === promise) {
|
|
971
|
-
inFlightByCwd.delete(key);
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
inFlightByCwd.set(key, promise);
|
|
975
|
-
return promise;
|
|
976
|
-
}
|
|
977
|
-
function getCommand(cwd) {
|
|
978
|
-
ensureCurrentGeneration();
|
|
979
|
-
const cache = getCache(cwd || process.cwd());
|
|
980
|
-
return cache.command;
|
|
981
|
-
}
|
|
982
|
-
function getOutcome(cwd) {
|
|
983
|
-
ensureCurrentGeneration();
|
|
984
|
-
return getCache(cwd || process.cwd()).outcome;
|
|
985
|
-
}
|
|
986
|
-
function getVerdict(cwd) {
|
|
987
|
-
ensureCurrentGeneration();
|
|
988
|
-
const cache = getCache(cwd || process.cwd());
|
|
989
|
-
return {
|
|
990
|
-
outcome: cache.outcome,
|
|
991
|
-
cause: cache.cause,
|
|
992
|
-
elapsedMs: cache.elapsedMs,
|
|
993
|
-
latched: cache.available !== false ||
|
|
994
|
-
isLatchingOutcome(cache.outcome ?? "missing"),
|
|
995
|
-
retryAtMs: cache.retryAtMs,
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
return { isAvailableAsync, getCommand, getOutcome, getVerdict, reset };
|
|
999
|
-
}
|
|
1000
|
-
const cwdProbeFlights = createAvailabilityProbeFlight({
|
|
1001
|
-
generation: getDispatchAvailabilityGeneration,
|
|
1002
|
-
});
|
|
1003
|
-
/**
|
|
1004
|
-
* Per-cwd cached availability probe for spawn signatures that don't fit
|
|
1005
|
-
* `createAvailabilityChecker` — multi-arg subcommands like `cargo clippy
|
|
1006
|
-
* --version` and `mix credo --version`, or a dynamically-resolved
|
|
1007
|
-
* `<cmd> --version` such as eslint's local-then-global binary. Each cwd is
|
|
1008
|
-
* probed at most once per verdict; concurrent first-time callers share the
|
|
1009
|
-
* in-flight promise.
|
|
1010
|
-
*
|
|
1011
|
-
* Those three ARE the consumers. `npx biome --version` used to be named here as
|
|
1012
|
-
* one and never was: biome resolves through `resolveManagedToolClient` and its
|
|
1013
|
-
* own latch in `biome-client.ts`.
|
|
1014
|
-
*
|
|
1015
|
-
* ## Latch policy (#1494)
|
|
1016
|
-
*
|
|
1017
|
-
* This used to cache the boolean forever, so one stalled `eslint --version`
|
|
1018
|
-
* disabled eslint for that cwd until the process restarted. The verdict now
|
|
1019
|
-
* goes through `availability-policy.ts`: the probe returns the SPAWN RESULT,
|
|
1020
|
-
* the policy classifies it, and only a durable outcome (`missing` /
|
|
1021
|
-
* `non-installable`) latches. A timeout, abort or host stall expires on a
|
|
1022
|
-
* bounded cooldown and the next caller re-probes.
|
|
1023
|
-
*/
|
|
1024
|
-
export function createCwdCachedProbe(probe, options) {
|
|
1025
|
-
const latchByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
1026
|
-
const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
|
|
1027
|
-
let probeGeneration = availabilityGeneration.current();
|
|
1028
|
-
function clear() {
|
|
1029
|
-
latchByCwd.clear();
|
|
1030
|
-
inFlightByCwd.clear();
|
|
1031
|
-
}
|
|
1032
|
-
function ensureCurrentGeneration() {
|
|
1033
|
-
if (probeGeneration === availabilityGeneration.current())
|
|
1034
|
-
return;
|
|
1035
|
-
clear();
|
|
1036
|
-
probeGeneration = availabilityGeneration.current();
|
|
1037
|
-
}
|
|
1038
|
-
function latchFor(key) {
|
|
1039
|
-
const existing = latchByCwd.get(key);
|
|
1040
|
-
if (existing)
|
|
1041
|
-
return existing;
|
|
1042
|
-
const created = createAvailabilityLatch();
|
|
1043
|
-
latchByCwd.set(key, created);
|
|
1044
|
-
return created;
|
|
1045
|
-
}
|
|
1046
|
-
function note(latch, key, verdict) {
|
|
1047
|
-
let retryAfterMs;
|
|
1048
|
-
if (verdict.available) {
|
|
1049
|
-
latch.noteAvailable(verdict.cause);
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
const delay = latch.noteUnavailable(verdict.outcome, verdict.cause);
|
|
1053
|
-
if (delay > 0)
|
|
1054
|
-
retryAfterMs = delay;
|
|
1055
|
-
}
|
|
1056
|
-
const latched = verdict.available || isLatchingOutcome(verdict.outcome);
|
|
1057
|
-
// Same correction bookkeeping as the checker seam above (#1657).
|
|
1058
|
-
if (!verdict.available && latched) {
|
|
1059
|
-
noteLatchedUnavailable(key, options.tool);
|
|
1060
|
-
}
|
|
1061
|
-
else if (verdict.available) {
|
|
1062
|
-
clearLatchedUnavailable(key, ...latchKeysFor(options.tool));
|
|
1063
|
-
}
|
|
1064
|
-
logAvailabilityDecision({
|
|
1065
|
-
tool: options.tool,
|
|
1066
|
-
verdict: verdict.available ? "available" : "unavailable",
|
|
1067
|
-
outcome: verdict.outcome,
|
|
1068
|
-
cause: verdict.cause,
|
|
1069
|
-
elapsedMs: verdict.elapsedMs,
|
|
1070
|
-
latched,
|
|
1071
|
-
hostStallMs: verdict.hostStallMs,
|
|
1072
|
-
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
1073
|
-
...(options.budgetMs !== undefined && { budgetMs: options.budgetMs }),
|
|
1074
|
-
// Every verdict here is derived from the probe this seam just ran.
|
|
1075
|
-
classifiedBy: verdict.classifiedBy ?? "probe",
|
|
1076
|
-
...(verdict.evidence !== undefined && { evidence: verdict.evidence }),
|
|
1077
|
-
}, key);
|
|
1078
|
-
}
|
|
1079
|
-
const run = (cwd) => {
|
|
1080
|
-
ensureCurrentGeneration();
|
|
1081
|
-
const key = path.resolve(cwd || process.cwd());
|
|
1082
|
-
const latch = latchFor(key);
|
|
1083
|
-
const memo = latch.read();
|
|
1084
|
-
if (memo !== null)
|
|
1085
|
-
return Promise.resolve(memo);
|
|
1086
|
-
const existing = inFlightByCwd.get(key);
|
|
1087
|
-
if (existing)
|
|
1088
|
-
return existing;
|
|
1089
|
-
const promiseGeneration = probeGeneration;
|
|
1090
|
-
let promise;
|
|
1091
|
-
promise = (async () => {
|
|
1092
|
-
// The probe budget is enforced by a HOST-side timer, so a stalled event
|
|
1093
|
-
// loop expires it while the child is still healthy. Measure the stall
|
|
1094
|
-
// that overlapped the window and hand it to the classifier (#1467).
|
|
1095
|
-
const stallSampler = startHostStallSampler();
|
|
1096
|
-
const startedAt = Date.now();
|
|
1097
|
-
let result;
|
|
1098
|
-
let thrown;
|
|
1099
|
-
let probeJoined = false;
|
|
1100
|
-
try {
|
|
1101
|
-
const shared = cwdProbeFlights.run(`cwd:${options.tool}|${options.flightKeyComponent ?? ""}|${key}`, () => probe(key));
|
|
1102
|
-
probeJoined = shared.joined;
|
|
1103
|
-
result = await shared.promise;
|
|
1104
|
-
}
|
|
1105
|
-
catch (error) {
|
|
1106
|
-
thrown = error;
|
|
1107
|
-
}
|
|
1108
|
-
const hostStallMs = stallSampler.stop();
|
|
1109
|
-
const elapsedMs = Date.now() - startedAt;
|
|
1110
|
-
const shape = result ?? {
|
|
1111
|
-
error: thrown instanceof Error ? thrown : new Error(String(thrown)),
|
|
1112
|
-
};
|
|
1113
|
-
recordAvailabilityProbeOverrun(options.tool, key, elapsedMs, options.budgetMs, shape.failure);
|
|
1114
|
-
// A probe that threw carries its errno in the Error, which is exactly
|
|
1115
|
-
// what the taxonomy reads — so a thrown EAGAIN stays transient instead
|
|
1116
|
-
// of collapsing into an untyped `false`.
|
|
1117
|
-
if (result && !result.error && result.status === 0) {
|
|
1118
|
-
note(latch, key, {
|
|
1119
|
-
available: true,
|
|
1120
|
-
outcome: "success",
|
|
1121
|
-
cause: "ok",
|
|
1122
|
-
elapsedMs,
|
|
1123
|
-
hostStallMs,
|
|
1124
|
-
classifiedBy: probeJoined ? "joined" : "probe",
|
|
1125
|
-
evidence: describeProbeEvidence(result, options.tool),
|
|
1126
|
-
});
|
|
1127
|
-
return true;
|
|
1128
|
-
}
|
|
1129
|
-
const { outcome, cause, evidence } = classifyProbeFailure(shape, {
|
|
1130
|
-
hostStallMs,
|
|
1131
|
-
command: options.tool,
|
|
1132
|
-
unclassifiedFailureOutcome: options.unclassifiedFailureOutcome,
|
|
1133
|
-
});
|
|
1134
|
-
note(latch, key, {
|
|
1135
|
-
available: false,
|
|
1136
|
-
outcome,
|
|
1137
|
-
cause,
|
|
1138
|
-
elapsedMs,
|
|
1139
|
-
hostStallMs,
|
|
1140
|
-
classifiedBy: probeJoined ? "joined" : "probe",
|
|
1141
|
-
evidence,
|
|
1142
|
-
});
|
|
1143
|
-
return false;
|
|
1144
|
-
})().finally(() => {
|
|
1145
|
-
// A session reset clears the map and a caller may immediately start a
|
|
1146
|
-
// replacement probe for the same cwd; the old promise must not delete
|
|
1147
|
-
// that newer-generation entry when it settles.
|
|
1148
|
-
if (probeGeneration === promiseGeneration &&
|
|
1149
|
-
inFlightByCwd.get(key) === promise) {
|
|
1150
|
-
inFlightByCwd.delete(key);
|
|
1151
|
-
}
|
|
1152
|
-
});
|
|
1153
|
-
inFlightByCwd.set(key, promise);
|
|
1154
|
-
return promise;
|
|
1155
|
-
};
|
|
1156
|
-
const cached = run;
|
|
1157
|
-
cached.getVerdict = (cwd) => {
|
|
1158
|
-
ensureCurrentGeneration();
|
|
1159
|
-
const latch = latchFor(path.resolve(cwd || process.cwd()));
|
|
1160
|
-
const outcome = latch.getOutcome();
|
|
1161
|
-
return {
|
|
1162
|
-
outcome,
|
|
1163
|
-
cause: latch.getCause(),
|
|
1164
|
-
elapsedMs: 0,
|
|
1165
|
-
// Read the OUTCOME, never `read()`: `read()` answers `null` both for a
|
|
1166
|
-
// cwd that was never probed and for a transient verdict whose cooldown
|
|
1167
|
-
// expired, and `!== false` turned both of those into `latched: true` —
|
|
1168
|
-
// the opposite of the truth in the expired case.
|
|
1169
|
-
latched: outcome !== null && isLatchingOutcome(outcome),
|
|
1170
|
-
retryAtMs: latch.getRetryAtMs(),
|
|
1171
|
-
};
|
|
1172
|
-
};
|
|
1173
|
-
cached.reset = () => {
|
|
1174
|
-
clear();
|
|
1175
|
-
probeGeneration = availabilityGeneration.current();
|
|
1176
|
-
};
|
|
1177
|
-
return cached;
|
|
1178
|
-
}
|
|
1179
|
-
export function resolveNodeToolCommand(cwd, toolName, windowsExt = ".cmd") {
|
|
1180
|
-
const isWin = process.platform === "win32";
|
|
1181
|
-
const binName = isWin ? `${toolName}${windowsExt}` : toolName;
|
|
1182
|
-
const local = path.join(cwd, "node_modules", ".bin", binName);
|
|
1183
|
-
if (fs.existsSync(local))
|
|
1184
|
-
return local;
|
|
1185
|
-
return toolName;
|
|
1186
|
-
}
|
|
1187
|
-
export function resolveToolCommand(cwd, toolId) {
|
|
1188
|
-
const spec = getToolCommandSpec(toolId);
|
|
1189
|
-
if (!spec)
|
|
1190
|
-
return null;
|
|
1191
|
-
return resolveNodeToolCommand(cwd, spec.command, spec.windowsExt ?? ".cmd");
|
|
1192
|
-
}
|
|
1193
|
-
/**
|
|
1194
|
-
* Composer's `vendor/bin/<tool>` from the nearest ancestor that has one.
|
|
1195
|
-
*
|
|
1196
|
-
* Delegates to THE shared bin walker (#2544 review F1): this was a fifth
|
|
1197
|
-
* private copy of that climb, unceilinged, so a `vendor/bin` at or above
|
|
1198
|
-
* `$HOME` — which can never be this project's own Composer install — was
|
|
1199
|
-
* resolved as the project's tool (#2514, same class as `findInVendorBin`,
|
|
1200
|
-
* which had already been folded).
|
|
1201
|
-
*/
|
|
1202
|
-
export function resolveVendorToolCommand(cwd, toolName, windowsExt = ".bat") {
|
|
1203
|
-
return (findLocalBinUpwards(toolName, cwd, {
|
|
1204
|
-
windowsExt,
|
|
1205
|
-
binDirs: VENDOR_BIN_DIRS,
|
|
1206
|
-
}) ?? null);
|
|
1207
|
-
}
|
|
1208
|
-
export async function resolveToolCommandWithInstallFallback(cwd, toolId, timeout = 5000) {
|
|
1209
|
-
const spec = getToolCommandSpec(toolId);
|
|
1210
|
-
if (!spec)
|
|
1211
|
-
return null;
|
|
1212
|
-
return resolveCommandWithInstallFallback(resolveToolCommand(cwd, toolId) ?? spec.command, spec.managedToolId ?? toolId, cwd, spec.versionArgs ?? ["--version"], timeout);
|
|
1213
|
-
}
|
|
1214
|
-
async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
1215
|
-
// Skip the --version spawn when the command isn't even on disk — the ~μs
|
|
1216
|
-
// stat/PATH walk beats a guaranteed-to-fail spawn round-trip.
|
|
1217
|
-
const spawnable = await isSpawnableCommand(command);
|
|
1218
|
-
if (spawnable) {
|
|
1219
|
-
const versionCheck = await safeSpawnAsync(command, versionArgs, {
|
|
1220
|
-
timeout,
|
|
1221
|
-
cwd,
|
|
1222
|
-
input: "",
|
|
1223
|
-
});
|
|
1224
|
-
if (!versionCheck.error && versionCheck.status === 0) {
|
|
1225
|
-
return command;
|
|
1226
|
-
}
|
|
1227
|
-
// A command that was found but rejected its probe is not fixed by a
|
|
1228
|
-
// reinstall. This also covers permissions and malformed shims.
|
|
1229
|
-
return null;
|
|
1230
|
-
}
|
|
1231
|
-
if (!shouldAutoInstallTool(toolId))
|
|
1232
|
-
return null;
|
|
1233
|
-
const state = installStateFor(cwd, toolId);
|
|
1234
|
-
if (state.suppressed)
|
|
1235
|
-
return null;
|
|
1236
|
-
const installStartedAt = Date.now();
|
|
1237
|
-
const installed = await ensureTool(toolId);
|
|
1238
|
-
if (installed) {
|
|
1239
|
-
noteInstallSuccess(toolId, cwd);
|
|
1240
|
-
// A latched `unavailable` row MAY stand here, written by whatever probe
|
|
1241
|
-
// the caller ran first — but not always. The on-disk pre-check above
|
|
1242
|
-
// logs nothing, and two runners reach this seam with no checker probe at
|
|
1243
|
-
// all (biome-check and oxlint call
|
|
1244
|
-
// `resolveToolCommandWithInstallFallback` directly, #1657). So the row is
|
|
1245
|
-
// emitted either way, and only a call that really clears a latched row is
|
|
1246
|
-
// recorded as a correction. Without the row, the durable log keeps saying
|
|
1247
|
-
// the tool is off after the installer just brought it back — the third
|
|
1248
|
-
// seam of #1606/#1610/#1612 (#1636).
|
|
1249
|
-
emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installed, latchKeysFor(toolId, command));
|
|
1250
|
-
return installed;
|
|
1251
|
-
}
|
|
1252
|
-
noteInstallFailure(toolId, cwd);
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
1256
|
-
const versionCheck = await safeSpawnAsync(command.cmd, [...command.args, ...versionArgs], { timeout, cwd, input: "" });
|
|
1257
|
-
if (!versionCheck.error && versionCheck.status === 0) {
|
|
1258
|
-
return command;
|
|
1259
|
-
}
|
|
1260
|
-
const installed = await verifyOrInstallCommand(command.cmd, toolId, cwd, versionArgs, timeout);
|
|
1261
|
-
if (!installed) {
|
|
1262
|
-
return null;
|
|
1263
|
-
}
|
|
1264
|
-
if (installed === command.cmd) {
|
|
1265
|
-
return command;
|
|
1266
|
-
}
|
|
1267
|
-
return { cmd: installed, args: [] };
|
|
1268
|
-
}
|
|
1269
|
-
export async function resolveCommandWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
|
|
1270
|
-
return verifyOrInstallCommand(command, toolId, cwd, versionArgs, timeout);
|
|
1271
|
-
}
|
|
1272
|
-
async function resolveAvailableOrInstallUnshared(checker, toolId, cwd) {
|
|
1273
|
-
const available = await checker.isAvailableAsync(cwd);
|
|
1274
|
-
if (available) {
|
|
1275
|
-
return checker.getCommand(cwd);
|
|
1276
|
-
}
|
|
1277
|
-
// Only a typed ENOENT/missing-command result is repairable by installing.
|
|
1278
|
-
// Probe failures caused by bad cwd, permissions, rejected flags, aborts, and
|
|
1279
|
-
// timeouts are unavailable/non-installable and must not enter an install loop.
|
|
1280
|
-
if (checker.getOutcome?.(cwd) !== "missing")
|
|
1281
|
-
return null;
|
|
1282
|
-
if (!shouldAutoInstallTool(toolId))
|
|
1283
|
-
return null;
|
|
1284
|
-
const state = installStateFor(cwd, toolId);
|
|
1285
|
-
if (state.suppressed) {
|
|
1286
|
-
return null;
|
|
1287
|
-
}
|
|
1288
|
-
const installStartedAt = Date.now();
|
|
1289
|
-
const installed = await ensureTool(toolId);
|
|
1290
|
-
if (installed) {
|
|
1291
|
-
noteInstallSuccess(toolId, cwd);
|
|
1292
|
-
checker.reset?.();
|
|
1293
|
-
// The PATH probe above already wrote a latched `unavailable` row via
|
|
1294
|
-
// `checker.isAvailableAsync`. Without a compensating row here, the durable
|
|
1295
|
-
// log keeps saying the tool is off after the installer just brought it
|
|
1296
|
-
// back — the shape #1606/PR #1610 fixed in `ensureViaInstaller`, shared
|
|
1297
|
-
// here by ~16 runners behind this helper (#1612), and by
|
|
1298
|
-
// `verifyOrInstallCommand` for the third seam (#1636).
|
|
1299
|
-
//
|
|
1300
|
-
// `checker.reset()` above clears the checker's cache, so the NEXT call
|
|
1301
|
-
// re-probes PATH from scratch — and for a managed-dir-only install that
|
|
1302
|
-
// probe misses again unless `createVenvFinder` finds it first (#1638).
|
|
1303
|
-
// Left unguarded, every one of those repeat misses would re-fire this
|
|
1304
|
-
// row, claiming a fresh install each time — `emitCompensatingAvailableRow`
|
|
1305
|
-
// gates on the once-per-correction memo to prevent that.
|
|
1306
|
-
//
|
|
1307
|
-
// The probe above returned the latching `missing` outcome, so a latched
|
|
1308
|
-
// `unavailable` row stands for this pair right now — recorded under the
|
|
1309
|
-
// checker's own command name. Record it under the toolId too, so the
|
|
1310
|
-
// emitter can READ that a correction is happening from the same ledger
|
|
1311
|
-
// every other caller reads, instead of being told so by an argument
|
|
1312
|
-
// (#1674 review F3).
|
|
1313
|
-
noteLatchedUnavailable(cwd, toolId);
|
|
1314
|
-
// This IS a genuine correction: the probe above returned the latching
|
|
1315
|
-
// `missing` outcome, so a latched `unavailable` row stands for this pair
|
|
1316
|
-
// right now (#1657).
|
|
1317
|
-
emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installed, latchKeysFor(toolId, checker.getCommand(cwd) ?? undefined));
|
|
1318
|
-
return installed;
|
|
1319
|
-
}
|
|
1320
|
-
noteInstallFailure(toolId, cwd);
|
|
1321
|
-
return null;
|
|
1322
|
-
}
|
|
1323
|
-
/** Share the complete probe/install transaction for each cwd/tool pair. */
|
|
1324
|
-
export function resolveAvailableOrInstall(checker, toolId, cwd) {
|
|
1325
|
-
const key = normalizeEphemeralMapKey(cwd);
|
|
1326
|
-
let byTool = resolveInstallInFlightByCwd.get(key);
|
|
1327
|
-
if (!byTool) {
|
|
1328
|
-
byTool = new Map();
|
|
1329
|
-
resolveInstallInFlightByCwd.set(key, byTool);
|
|
1330
|
-
}
|
|
1331
|
-
const existing = byTool.get(toolId);
|
|
1332
|
-
if (existing)
|
|
1333
|
-
return existing;
|
|
1334
|
-
// Same capture-before-await guard as the managed-verify seam, via the same
|
|
1335
|
-
// primitive (#1754): a settling old-session transaction must not evict the
|
|
1336
|
-
// entry a NEW session already installed for this (cwd, tool).
|
|
1337
|
-
const generation = availabilityGeneration.capture();
|
|
1338
|
-
const promise = resolveAvailableOrInstallUnshared(checker, toolId, cwd).finally(() => {
|
|
1339
|
-
generation.guardedWrite(`${toolId}@${key}`, () => {
|
|
1340
|
-
const current = resolveInstallInFlightByCwd.get(key);
|
|
1341
|
-
if (current?.get(toolId) === promise) {
|
|
1342
|
-
current.delete(toolId);
|
|
1343
|
-
if (current.size === 0)
|
|
1344
|
-
resolveInstallInFlightByCwd.delete(key);
|
|
1345
|
-
}
|
|
1346
|
-
});
|
|
1347
|
-
});
|
|
1348
|
-
byTool.set(toolId, promise);
|
|
1349
|
-
return promise;
|
|
1350
|
-
}
|
|
1351
|
-
// =============================================================================
|
|
1352
|
-
// SHARED AST-GREP AVAILABILITY
|
|
1353
|
-
// =============================================================================
|
|
1354
|
-
/**
|
|
1355
|
-
* Shared ast-grep availability across all slop runners, behind the transient-
|
|
1356
|
-
* aware latch (#1476). This module-level memo carried the same shape `SgRunner`
|
|
1357
|
-
* did — one failed sweep, including a timeout, disabled ast-grep for every slop
|
|
1358
|
-
* runner for the life of the process.
|
|
1359
|
-
*/
|
|
1360
|
-
const sgLatch = createAvailabilityLatch();
|
|
1361
|
-
let sgCmd = null;
|
|
1362
|
-
let sgCmdArgs = [];
|
|
1363
|
-
/** Classification of the current sweep, accumulated across candidates. */
|
|
1364
|
-
let sgSweepSawTransient = false;
|
|
1365
|
-
let sgSweepTransientCause = "probe-timeout";
|
|
1366
|
-
let sgSweepHostStallMs = 0;
|
|
1367
|
-
/**
|
|
1368
|
-
* Candidates that were UNREACHABLE, in ask order (#1568).
|
|
1369
|
-
*
|
|
1370
|
-
* The sweep stops at the first candidate that answers, so at the moment of a
|
|
1371
|
-
* win this list is exactly the set of candidates ahead of the winner that never
|
|
1372
|
-
* got a fair hearing — i.e. the preferred tiers the winner did not really beat.
|
|
1373
|
-
*
|
|
1374
|
-
* Basenames, because tier 1 is an absolute `node_modules/.bin` path and this
|
|
1375
|
-
* list is written to latency.log (#1568 review F3).
|
|
1376
|
-
*/
|
|
1377
|
-
let sgSweepUnreachable = [];
|
|
1378
|
-
/**
|
|
1379
|
-
* Candidates that were probed and answered DURABLY missing this sweep — a
|
|
1380
|
-
* real ENOENT/non-installable verdict, not a stall (#1593). The retained-arm
|
|
1381
|
-
* fallback below only knows the sweep saw SOME transient candidate; without
|
|
1382
|
-
* this list it cannot tell "the memoized winner itself just proved absent"
|
|
1383
|
-
* from "an unrelated sibling merely stalled", and re-serves a command this
|
|
1384
|
-
* very sweep disproved.
|
|
1385
|
-
*/
|
|
1386
|
-
let sgSweepDurablyMissing = [];
|
|
1387
|
-
function isAstGrepVersionOutput(output) {
|
|
1388
|
-
return /\bast[- ]grep\b/i.test(output);
|
|
1389
|
-
}
|
|
1390
|
-
async function probeAstGrepCommandAsync(cmd, argsPrefix = []) {
|
|
1391
|
-
const sampler = startHostStallSampler();
|
|
1392
|
-
let check;
|
|
1393
|
-
let hostStallMs;
|
|
1394
|
-
try {
|
|
1395
|
-
check = await probeToolAsync(cmd, [...argsPrefix, "--version"], {
|
|
1396
|
-
timeout: 5000,
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
finally {
|
|
1400
|
-
hostStallMs = sampler.stop();
|
|
1401
|
-
sgSweepHostStallMs += hostStallMs;
|
|
1402
|
-
}
|
|
1403
|
-
if (!check.error &&
|
|
1404
|
-
check.status === 0 &&
|
|
1405
|
-
isAstGrepVersionOutput(`${check.stdout}\n${check.stderr}`)) {
|
|
1406
|
-
return true;
|
|
1407
|
-
}
|
|
1408
|
-
const { outcome, cause } = classifyProbeFailure(check, { hostStallMs });
|
|
1409
|
-
if (outcome === "transient") {
|
|
1410
|
-
sgSweepSawTransient = true;
|
|
1411
|
-
sgSweepTransientCause = cause;
|
|
1412
|
-
const name = path.basename(cmd);
|
|
1413
|
-
if (!sgSweepUnreachable.includes(name))
|
|
1414
|
-
sgSweepUnreachable.push(name);
|
|
1415
|
-
}
|
|
1416
|
-
else if (!sgSweepDurablyMissing.includes(cmd)) {
|
|
1417
|
-
sgSweepDurablyMissing.push(cmd);
|
|
1418
|
-
}
|
|
1419
|
-
return false;
|
|
1420
|
-
}
|
|
1421
|
-
/** Pre-filter local node_modules/.bin candidates that actually exist on disk. */
|
|
1422
|
-
function buildSgLocalBins() {
|
|
1423
|
-
const isWin = process.platform === "win32";
|
|
1424
|
-
const hasBash = !!(process.env.MSYSTEM ||
|
|
1425
|
-
process.env.GIT_SHELL ||
|
|
1426
|
-
process.env.BASH);
|
|
1427
|
-
const extensions = isWin
|
|
1428
|
-
? hasBash
|
|
1429
|
-
? ["", ".exe", ".cmd"]
|
|
1430
|
-
: [".cmd", ".exe", ""]
|
|
1431
|
-
: [""];
|
|
1432
|
-
const binaryCandidates = ["ast-grep", "sg"].flatMap((base) => extensions.map((ext) => `${base}${ext}`));
|
|
1433
|
-
// THE shared bin walk (#2544 review F1). This used to be a private
|
|
1434
|
-
// `findNodeBinRoots` climb with no HOME ceiling, so on a box with a
|
|
1435
|
-
// home-level `node_modules` (the pi-extensions manifest installs its own
|
|
1436
|
-
// bins, #2514) `$HOME` itself came back as a bin root and an unrelated
|
|
1437
|
-
// ast-grep was probed as if it were this project's. `windowsExt: ""`
|
|
1438
|
-
// because the candidate names already carry their extensions — in a
|
|
1439
|
-
// shell-dependent ORDER (`hasBash` above) that a single `windowsExt`
|
|
1440
|
-
// cannot express.
|
|
1441
|
-
//
|
|
1442
|
-
// The PROJECT walk (from `process.cwd()`) keeps the default HOME ceiling —
|
|
1443
|
-
// a `node_modules/.bin/ast-grep` at or above `$HOME` can never be THIS
|
|
1444
|
-
// project's own dependency. pi-lens's OWN install-tree walk (from
|
|
1445
|
-
// `_thisDir`) is exempt (`ceiling: false`, #2544 round 4 F1): `pi install
|
|
1446
|
-
// npm:pi-lens` installs into the HOME-level manifest
|
|
1447
|
-
// (`~/package.json` → `~/node_modules/pi-lens`), so pi-lens's bundled
|
|
1448
|
-
// `@ast-grep/cli` shim can sit AT `~/node_modules/.bin/ast-grep` — exactly
|
|
1449
|
-
// the directory the PROJECT ceiling exists to stop at. A previous version
|
|
1450
|
-
// of this comment claimed the own install always lives "under"
|
|
1451
|
-
// `~/.pi/extensions/…`, below the ceiling; that is false for the
|
|
1452
|
-
// HOME-level manifest layout, and the round-2 ceiling silently made this
|
|
1453
|
-
// walk return nothing for it (PATH, `findGlobalBinary` and npx do not
|
|
1454
|
-
// cover this location either). `_managedToolsDir` (`~/.pi-lens/tools`) is
|
|
1455
|
-
// a fixed location, not a walk, so it is checked directly as before —
|
|
1456
|
-
// also own-install, since `ensureTool()` puts npm-strategy installs there.
|
|
1457
|
-
const projectWalkOptions = { windowsExt: "" };
|
|
1458
|
-
const ownInstallWalkOptions = { windowsExt: "", ceiling: false };
|
|
1459
|
-
const bins = [
|
|
1460
|
-
...findLocalBinsUpwards(binaryCandidates, _thisDir, ownInstallWalkOptions).map((binPath) => ({ path: binPath, source: "own-install" })),
|
|
1461
|
-
...findLocalBinsUpwards(binaryCandidates, process.cwd(), projectWalkOptions).map((binPath) => ({ path: binPath, source: "project" })),
|
|
1462
|
-
];
|
|
1463
|
-
for (const candidate of binaryCandidates) {
|
|
1464
|
-
const managedBin = path.join(_managedToolsDir, "node_modules", ".bin", candidate);
|
|
1465
|
-
if (fs.existsSync(managedBin)) {
|
|
1466
|
-
bins.push({ path: managedBin, source: "own-install" });
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
return bins;
|
|
1470
|
-
}
|
|
1471
|
-
let sgAvailableInFlight = null;
|
|
1472
|
-
let sgAvailabilityGeneration = availabilityGeneration.current();
|
|
1473
|
-
function ensureCurrentSgGeneration() {
|
|
1474
|
-
if (sgAvailabilityGeneration === availabilityGeneration.current())
|
|
1475
|
-
return;
|
|
1476
|
-
sgLatch.reset();
|
|
1477
|
-
sgCmd = null;
|
|
1478
|
-
sgCmdArgs = [];
|
|
1479
|
-
sgAvailableInFlight = null;
|
|
1480
|
-
sgAvailabilityGeneration = availabilityGeneration.current();
|
|
1481
|
-
}
|
|
1482
|
-
export async function isSgAvailableAsync() {
|
|
1483
|
-
ensureCurrentSgGeneration();
|
|
1484
|
-
// `read()` returns null when the last verdict was transient and its cooldown
|
|
1485
|
-
// expired: re-probe rather than stay dead for the session (#1476).
|
|
1486
|
-
const memo = sgLatch.read();
|
|
1487
|
-
if (memo !== null)
|
|
1488
|
-
return memo;
|
|
1489
|
-
if (sgAvailableInFlight)
|
|
1490
|
-
return sgAvailableInFlight;
|
|
1491
|
-
const flight = (async () => {
|
|
1492
|
-
const startedAt = Date.now();
|
|
1493
|
-
sgSweepSawTransient = false;
|
|
1494
|
-
sgSweepTransientCause = "probe-timeout";
|
|
1495
|
-
sgSweepHostStallMs = 0;
|
|
1496
|
-
sgSweepUnreachable = [];
|
|
1497
|
-
sgSweepDurablyMissing = [];
|
|
1498
|
-
// 1. Local node_modules/.bin (own-install tree, then project tree)
|
|
1499
|
-
for (const candidate of buildSgLocalBins()) {
|
|
1500
|
-
if (await probeAstGrepCommandAsync(candidate.path)) {
|
|
1501
|
-
sgCmd = candidate.path;
|
|
1502
|
-
sgCmdArgs = [];
|
|
1503
|
-
noteSgAvailable(startedAt, { source: candidate.source });
|
|
1504
|
-
return true;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
// 2. Global PATH
|
|
1508
|
-
for (const cmd of ["ast-grep", "sg"]) {
|
|
1509
|
-
if (await probeAstGrepCommandAsync(cmd)) {
|
|
1510
|
-
sgCmd = cmd;
|
|
1511
|
-
sgCmdArgs = [];
|
|
1512
|
-
noteSgAvailable(startedAt, { source: "global" });
|
|
1513
|
-
return true;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
// 2b. Any package manager's global bin dir (npm/pnpm/yarn/bun) — catches
|
|
1517
|
-
// `pnpm add -g @ast-grep/cli` installs whose bin dir is off PATH (#375).
|
|
1518
|
-
for (const name of ["ast-grep", "sg"]) {
|
|
1519
|
-
const globalBin = await findGlobalBinary(name);
|
|
1520
|
-
if (globalBin && (await probeAstGrepCommandAsync(globalBin))) {
|
|
1521
|
-
sgCmd = globalBin;
|
|
1522
|
-
sgCmdArgs = [];
|
|
1523
|
-
noteSgAvailable(startedAt, { source: "global" });
|
|
1524
|
-
return true;
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
// 3. npx --no (cache-only, no silent download).
|
|
1528
|
-
if (await probeAstGrepCommandAsync("npx", ["--no", "--", "ast-grep"])) {
|
|
1529
|
-
sgCmd = "npx";
|
|
1530
|
-
sgCmdArgs = ["--no", "--", "ast-grep"];
|
|
1531
|
-
noteSgAvailable(startedAt, { source: "npx" });
|
|
1532
|
-
return true;
|
|
1533
|
-
}
|
|
1534
|
-
// Nothing answered, and nothing answered TRANSIENTLY, while the verdict we
|
|
1535
|
-
// are about to overwrite is a provisional win whose command we still hold.
|
|
1536
|
-
// #1476's principle applies to the result as much as to the probe: a
|
|
1537
|
-
// timeout says nothing about the tool, so it cannot erase a command this
|
|
1538
|
-
// process proved working one cooldown ago. Keep serving it and re-arm
|
|
1539
|
-
// (#1568 review F1) — UNLESS this very sweep just proved the memoized
|
|
1540
|
-
// command durably missing (#1593): a sibling tier stalling is not license
|
|
1541
|
-
// to re-serve a winner that ENOENTed a moment ago in the same pass.
|
|
1542
|
-
if (sgSweepSawTransient &&
|
|
1543
|
-
sgLatch.isProvisional() &&
|
|
1544
|
-
sgCmd !== null &&
|
|
1545
|
-
!sgSweepDurablyMissing.includes(sgCmd)) {
|
|
1546
|
-
noteSgAvailable(startedAt, { retained: true });
|
|
1547
|
-
return true;
|
|
1548
|
-
}
|
|
1549
|
-
// A timeout on ANY candidate is evidence about the host, not the tool.
|
|
1550
|
-
noteSgUnavailable(startedAt, sgSweepSawTransient ? "transient" : "missing", sgSweepSawTransient ? sgSweepTransientCause : "not-found");
|
|
1551
|
-
return false;
|
|
1552
|
-
})().finally(() => {
|
|
1553
|
-
// Identity-guarded release (#1968's pattern): clear the slot only if THIS
|
|
1554
|
-
// flight still owns it. `ensureCurrentSgGeneration()` above nulls the slot
|
|
1555
|
-
// on a generation bump (a session reset arriving mid-flight) so the NEXT
|
|
1556
|
-
// caller starts a fresh flight rather than sharing this one's stale-generation
|
|
1557
|
-
// answer — that fresh flight is a live successor. Without the guard, this
|
|
1558
|
-
// flight's own late settlement would stomp the slot back to null out from
|
|
1559
|
-
// under that successor, and the caller after it would start a redundant
|
|
1560
|
-
// THIRD probe instead of sharing the still-running second one.
|
|
1561
|
-
if (sgAvailableInFlight === flight)
|
|
1562
|
-
sgAvailableInFlight = null;
|
|
1563
|
-
});
|
|
1564
|
-
sgAvailableInFlight = flight;
|
|
1565
|
-
return flight;
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Record a successful shared-ast-grep sweep, with one decision record.
|
|
1569
|
-
*
|
|
1570
|
-
* A win reached while an EARLIER candidate was unreachable is provisional
|
|
1571
|
-
* (#1568). The sweep stops at the first candidate that answers, so
|
|
1572
|
-
* `sgSweepSawTransient` at this point means precisely "a tier this one is
|
|
1573
|
-
* supposed to lose to never got a fair hearing" — the winner is used now, but
|
|
1574
|
-
* caching it for the session would pin a healthy PATH ast-grep behind `npx`
|
|
1575
|
-
* until the next restart.
|
|
1576
|
-
*
|
|
1577
|
-
* `retained` marks the other provisional case (#1568 review F1): no candidate
|
|
1578
|
-
* answered at all, so the winner being reported is the one the previous sweep
|
|
1579
|
-
* found, kept rather than discarded on a timeout.
|
|
1580
|
-
*
|
|
1581
|
-
* `source` (omitted for a `retained` win, which resolved nothing new this
|
|
1582
|
-
* sweep) logs one `tool_bin_resolved` phase so F1's own-install-vs-project
|
|
1583
|
-
* ceiling distinction is visible in `latency.log` instead of only inferable
|
|
1584
|
-
* from a bin's absence (#2544 round 4 F4).
|
|
1585
|
-
*/
|
|
1586
|
-
function noteSgAvailable(startedAt, opts = {}) {
|
|
1587
|
-
const provisional = sgSweepSawTransient;
|
|
1588
|
-
let retryAfterMs = 0;
|
|
1589
|
-
if (provisional) {
|
|
1590
|
-
retryAfterMs = sgLatch.noteProvisionallyAvailable(sgSweepTransientCause);
|
|
1591
|
-
}
|
|
1592
|
-
else {
|
|
1593
|
-
sgLatch.noteAvailable();
|
|
1594
|
-
}
|
|
1595
|
-
if (opts.source) {
|
|
1596
|
-
logLatency({
|
|
1597
|
-
type: "phase",
|
|
1598
|
-
phase: "tool_bin_resolved",
|
|
1599
|
-
filePath: "<pi-lens>",
|
|
1600
|
-
durationMs: Math.round(Date.now() - startedAt),
|
|
1601
|
-
metadata: {
|
|
1602
|
-
tool: "ast-grep",
|
|
1603
|
-
source: opts.source,
|
|
1604
|
-
ceilingApplied: opts.source === "project",
|
|
1605
|
-
},
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
logAvailabilityDecision({
|
|
1609
|
-
tool: "ast-grep",
|
|
1610
|
-
verdict: "available",
|
|
1611
|
-
outcome: "success",
|
|
1612
|
-
cause: provisional ? sgSweepTransientCause : "ok",
|
|
1613
|
-
elapsedMs: Date.now() - startedAt,
|
|
1614
|
-
latched: !provisional,
|
|
1615
|
-
classifiedBy: "probe",
|
|
1616
|
-
hostStallMs: sgSweepHostStallMs,
|
|
1617
|
-
budgetMs: 5000,
|
|
1618
|
-
...(provisional && {
|
|
1619
|
-
provisional: true,
|
|
1620
|
-
unreachablePreferred: [...sgSweepUnreachable],
|
|
1621
|
-
...(opts.retained === true && { retained: true }),
|
|
1622
|
-
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
1623
|
-
}),
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
/** Record a failed shared-ast-grep sweep; a transient verdict expires. */
|
|
1627
|
-
function noteSgUnavailable(startedAt, outcome, cause) {
|
|
1628
|
-
const retryAfterMs = sgLatch.noteUnavailable(outcome, cause);
|
|
1629
|
-
logAvailabilityDecision({
|
|
1630
|
-
tool: "ast-grep",
|
|
1631
|
-
verdict: "unavailable",
|
|
1632
|
-
outcome,
|
|
1633
|
-
cause,
|
|
1634
|
-
elapsedMs: Date.now() - startedAt,
|
|
1635
|
-
latched: outcome !== "transient",
|
|
1636
|
-
hostStallMs: sgSweepHostStallMs,
|
|
1637
|
-
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
1638
|
-
budgetMs: 5000,
|
|
1639
|
-
// Sibling of noteSgAvailable's "probe": both report the same
|
|
1640
|
-
// candidate sweep this function ran (#2209).
|
|
1641
|
-
classifiedBy: "probe",
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
export function getSgCommand() {
|
|
1645
|
-
ensureCurrentSgGeneration();
|
|
1646
|
-
return {
|
|
1647
|
-
cmd: sgCmd ?? "npx",
|
|
1648
|
-
args: sgCmdArgs.length ? sgCmdArgs : ["--no", "--", "ast-grep"],
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
// =============================================================================
|
|
1652
|
-
// LOCAL-FIRST BINARY RESOLUTION
|
|
1653
|
-
// =============================================================================
|
|
1654
|
-
/**
|
|
1655
|
-
* Find a tool binary preferring local node_modules/.bin, then any installed
|
|
1656
|
-
* package manager's global bin dir (npm/pnpm/yarn/bun), then global PATH. Only
|
|
1657
|
-
* falls back to `npx --no` as a last resort — the universal cache-only exec
|
|
1658
|
-
* (npx ships with node and never silently downloads), so this stays
|
|
1659
|
-
* manager-agnostic without risking a surprise `dlx` fetch on pnpm/yarn/bun.
|
|
1660
|
-
*
|
|
1661
|
-
* Returns: { cmd, args } where args may include the `["--no", toolName]` npx
|
|
1662
|
-
* preamble.
|
|
1663
|
-
*/
|
|
1664
|
-
export async function resolveLocalFirstAsync(toolName, cwd, windowsExt = ".cmd") {
|
|
1665
|
-
const isWin = process.platform === "win32";
|
|
1666
|
-
const binName = isWin ? `${toolName}${windowsExt}` : toolName;
|
|
1667
|
-
// 1. Local node_modules/.bin (project-installed)
|
|
1668
|
-
const local = path.join(cwd, "node_modules", ".bin", binName);
|
|
1669
|
-
if (fs.existsSync(local))
|
|
1670
|
-
return { cmd: local, args: [] };
|
|
1671
|
-
// 2. Global bin dir of ANY installed manager (npm/pnpm/yarn/bun) — direct
|
|
1672
|
-
// file lookup, so it finds tools installed via `pnpm add -g` / `bun add -g`
|
|
1673
|
-
// (whose bin dirs are often off PATH) and survives PATH staleness after an
|
|
1674
|
-
// `install -g`. No spawn.
|
|
1675
|
-
const globalBin = await findGlobalBinary(toolName, windowsExt);
|
|
1676
|
-
if (globalBin)
|
|
1677
|
-
return { cmd: globalBin, args: [] };
|
|
1678
|
-
// 3. Global PATH (already installed system-wide, on PATH)
|
|
1679
|
-
const globalCheck = await probeToolAsync(toolName, ["--version"], {
|
|
1680
|
-
timeout: 3000,
|
|
1681
|
-
});
|
|
1682
|
-
if (!globalCheck.error && globalCheck.status === 0) {
|
|
1683
|
-
return { cmd: toolName, args: [] };
|
|
1684
|
-
}
|
|
1685
|
-
// 4. npx --no fallback — universal cache-only exec (no silent download)
|
|
1686
|
-
return { cmd: "npx", args: ["--no", toolName] };
|
|
1687
|
-
}
|
|
1688
|
-
// =============================================================================
|
|
1689
|
-
// PRE-BUILT CHECKERS FOR COMMON TOOLS
|
|
1690
|
-
// =============================================================================
|
|
1691
|
-
export const pyright = createAvailabilityChecker("pyright", ".exe");
|
|
1692
|
-
export const ruff = createAvailabilityChecker("ruff", ".exe");
|
|
1693
|
-
export const biome = createAvailabilityChecker("biome");
|
|
1694
|
-
export const sg = {
|
|
1695
|
-
isAvailableAsync: isSgAvailableAsync,
|
|
1696
|
-
getCommand: getSgCommand,
|
|
1697
|
-
};
|
|
1698
|
-
/** Shared cwd seam for runner probes and analysis children (#2777). */
|
|
1699
|
-
export function resolveRunnerCwd(ctx, tool) {
|
|
1700
|
-
return resolveToolCwd("runner", tool, ctx.filePath, ctx).cwd;
|
|
1701
|
-
}
|