@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.
Files changed (707) hide show
  1. package/README.md +13 -9
  2. package/THIRD_PARTY_NOTICES.md +2 -2
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  9. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  10. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  11. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  12. package/node_modules/@dietrichgebert/ponytail/README.md +68 -72
  13. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +7 -0
  14. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  15. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  16. package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
  17. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  18. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -32
  19. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  20. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +17 -16
  21. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +9 -10
  22. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -57
  23. package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
  24. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  25. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  26. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  32. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  33. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  34. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  35. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  36. package/node_modules/@tifan/pi-recap/package.json +1 -1
  37. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  38. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  39. package/node_modules/pi-antigravity/README.md +23 -2
  40. package/node_modules/pi-antigravity/package.json +5 -3
  41. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  42. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  43. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  44. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  45. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  46. package/node_modules/pi-antigravity/src/index.ts +52 -137
  47. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  48. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  49. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  50. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  51. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  52. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  53. package/node_modules/pi-lens/CHANGELOG.md +1064 -0
  54. package/node_modules/pi-lens/README.md +115 -25
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  56. package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
  57. package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
  58. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  59. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  60. package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
  61. package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
  62. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  63. package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
  64. package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
  65. package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
  66. package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
  67. package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
  68. package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
  69. package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
  70. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  71. package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
  72. package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
  74. package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
  75. package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
  76. package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
  77. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  79. package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
  80. package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
  81. package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
  82. package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
  83. package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
  84. package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
  85. package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
  86. package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
  87. package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
  88. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  89. package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
  90. package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
  91. package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
  92. package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
  93. package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
  94. package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
  95. package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
  96. package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
  97. package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
  98. package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
  99. package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
  100. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  101. package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
  102. package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
  103. package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
  104. package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
  105. package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
  106. package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
  107. package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
  108. package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
  109. package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
  110. package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
  111. package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
  112. package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
  113. package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
  114. package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
  115. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  116. package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
  117. package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
  118. package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
  119. package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
  120. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  121. package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
  122. package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
  123. package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
  124. package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
  125. package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
  126. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  127. package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
  128. package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
  129. package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
  130. package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
  131. package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
  132. package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
  134. package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
  135. package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
  136. package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
  137. package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
  138. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  139. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  140. package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
  141. package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
  142. package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
  143. package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
  144. package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
  145. package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
  146. package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
  147. package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
  148. package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
  149. package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
  150. package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
  151. package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
  152. package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
  153. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  154. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  155. package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
  156. package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
  157. package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
  158. package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
  159. package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
  160. package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
  161. package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
  162. package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
  163. package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
  164. package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
  165. package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
  166. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  167. package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
  168. package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
  169. package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
  170. package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
  171. package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
  172. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  173. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  174. package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
  175. package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
  176. package/node_modules/pi-lens/dist/index.js +16456 -8335
  177. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  178. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  179. package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
  180. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  181. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  182. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  183. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  184. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  185. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  186. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
  187. package/node_modules/pi-lens/docs/configuration.md +20 -1
  188. package/node_modules/pi-lens/docs/environment-variables.md +52 -2
  189. package/node_modules/pi-lens/docs/features.md +8 -4
  190. package/node_modules/pi-lens/docs/globalconfig.md +1 -0
  191. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  192. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
  193. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  194. package/node_modules/pi-lens/docs/mcp.md +21 -5
  195. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  196. package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
  197. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  198. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
  199. package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
  200. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  201. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
  202. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  203. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  204. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  205. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  206. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  207. package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
  208. package/node_modules/pi-lens/docs/settings.md +1 -0
  209. package/node_modules/pi-lens/docs/usage.md +5 -1
  210. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  211. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  212. package/node_modules/pi-lens/package.json +18 -6
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  215. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  216. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  217. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  218. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  219. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  220. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  221. package/package.json +15 -10
  222. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  223. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  224. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  225. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  226. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  227. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  228. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  229. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  230. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  231. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  232. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  233. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  234. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  235. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  236. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  237. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  238. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  239. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  240. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  241. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  242. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  243. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  244. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  245. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  246. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  247. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  248. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  249. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  250. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  251. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  252. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  253. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  254. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  255. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  256. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  257. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  258. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  259. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  260. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  261. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  262. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  263. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  264. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  265. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  266. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  267. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  268. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  269. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  270. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  271. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  272. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  273. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  274. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  275. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  276. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  277. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  278. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  279. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  280. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  281. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  282. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  283. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  284. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  285. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  286. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  287. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  288. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  289. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  290. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  291. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  292. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  293. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  294. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  295. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  296. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  297. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  298. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  299. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  300. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  301. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  302. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  303. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  304. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  305. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  306. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  307. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  308. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  309. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  310. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  311. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  312. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  313. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  314. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  315. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  316. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  317. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  318. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  319. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  320. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  321. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  322. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  323. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  324. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  325. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  326. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  327. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  328. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  329. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  330. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  331. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  332. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  333. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  334. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  335. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  336. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  337. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  338. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  339. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  340. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  341. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  342. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  343. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  344. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  345. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  346. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  347. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  348. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  349. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  350. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  351. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  352. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  353. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  354. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  355. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  356. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  357. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  358. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  359. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  360. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  361. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  362. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  363. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  364. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  365. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  366. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  367. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  368. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  369. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  370. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  371. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  372. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  373. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  374. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  375. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  376. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  377. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  378. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  379. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  380. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  381. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  382. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  383. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  384. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  385. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  386. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  406. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  407. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  408. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  409. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  410. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  411. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  412. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  413. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  414. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  415. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  416. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  417. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  418. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  419. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  420. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  421. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  422. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  423. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  424. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  425. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  426. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  427. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  428. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  429. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  430. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  431. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  432. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  433. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  434. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  435. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  436. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  437. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  438. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  439. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  440. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  441. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  442. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  443. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  444. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  445. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  446. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  447. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  448. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  449. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  450. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  451. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  452. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  453. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  454. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  455. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  456. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  457. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  458. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  459. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  460. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  461. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  462. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  463. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  464. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  465. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  466. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  467. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  468. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  469. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  470. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  471. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  472. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  473. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  474. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  475. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  476. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  477. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  478. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  479. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  480. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  481. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  482. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  483. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  484. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  485. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  486. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  487. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  488. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  489. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  490. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  491. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  492. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  493. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  494. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  495. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  496. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  497. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  498. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  499. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  500. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  501. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  502. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  503. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  504. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  505. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  506. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  507. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  508. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  509. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  510. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  511. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  512. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  513. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  514. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  515. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  516. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  517. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  518. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  519. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  520. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  521. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  522. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  523. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  524. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  525. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  526. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  527. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  528. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  529. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  530. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  531. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  532. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  533. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  534. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  535. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  536. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  537. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  538. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  539. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  540. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  541. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  542. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  543. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  544. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  545. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  546. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  547. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  548. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  549. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  550. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  551. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  552. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  553. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  554. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  555. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  556. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  557. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  558. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  559. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  560. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  561. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  562. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  563. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  564. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  565. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  566. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  567. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  568. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  569. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  570. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  571. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  572. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  573. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  574. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  575. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  576. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  577. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  578. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  579. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  580. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  581. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  582. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  583. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  584. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  585. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  586. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  587. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  588. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  589. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  590. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  591. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  592. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  593. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  594. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  595. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  596. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  597. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  598. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  599. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  600. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  601. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  602. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  603. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  604. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  605. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  606. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  607. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  608. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  609. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  610. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  611. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  612. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  613. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  614. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  615. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  616. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  617. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  618. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  619. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  620. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  621. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  622. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  623. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  624. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  625. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  626. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  627. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  628. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  629. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  630. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  631. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  632. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  633. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  634. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  635. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  636. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  637. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  638. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  639. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  640. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  641. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  642. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  643. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  644. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  645. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  646. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  647. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  648. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  649. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  650. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  651. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  652. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  653. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  654. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  655. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  656. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  657. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  658. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  659. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  660. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  661. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  662. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  663. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  664. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  665. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  666. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  667. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  668. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  669. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  670. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  671. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  672. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  673. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  674. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  675. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  676. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  677. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  678. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  679. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  680. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  681. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  682. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  683. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  684. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  685. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  686. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  687. package/node_modules/pi-lens/dist/i18n.js +0 -69
  688. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  689. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  690. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  691. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  692. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  693. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  694. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  695. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  696. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  697. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  698. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  699. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  700. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  701. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  702. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  703. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  704. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  705. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  706. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  707. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
@@ -1,1244 +0,0 @@
1
- /**
2
- * Path utilities for pi-lens
3
- *
4
- * Handles cross-platform path normalization, particularly
5
- * Windows case-insensitivity issues when using paths as Map keys.
6
- *
7
- * Approach (inspired by OpenCode's Filesystem.normalizePath):
8
- * - On Windows: try realpathSync.native() for canonical casing
9
- * - Falls back to lowercase for files that don't exist yet
10
- * - On non-Windows: adopt the on-disk casing of an existing path (#3098 — a
11
- * case-insensitive POSIX filesystem otherwise derives two Map keys for one
12
- * file); a path that does not exist is returned as-is, case-preserving
13
- * - Always convert backslashes to forward slashes for Map key consistency
14
- */
15
- import { existsSync, readFileSync, realpathSync, statSync, } from "node:fs";
16
- import * as os from "node:os";
17
- import * as path from "node:path";
18
- import { win32 } from "node:path";
19
- import { fileURLToPath, pathToFileURL } from "node:url";
20
- import { minimatch } from "./deps/minimatch.js";
21
- /**
22
- * Detect a positively Windows-shaped path, regardless of the host OS.
23
- *
24
- * A backslash anywhere in a path is not enough: it is a legal character in a
25
- * POSIX filename. Only a drive-letter prefix (`X:`), a UNC root (`\\`), or a
26
- * rooted backslash at position zero (`\`) selects Windows parsing.
27
- */
28
- export function isWindowsPath(filePath) {
29
- return /^[A-Za-z]:/.test(filePath) || filePath.startsWith("\\");
30
- }
31
- /**
32
- * Canonical backslash→forward-slash fold — the single sanctioned form of the
33
- * `p.replace(/\\/g, "/")` idiom otherwise hand-rolled across the codebase
34
- * (~138 sites, #1193). PURE separator normalization: it does NOT resolve,
35
- * canonicalize, lowercase, or collapse repeated slashes — reach for
36
- * `normalizeFilePath`/`normalizeMapKey`/`normalizeEphemeralMapKey` when a
37
- * canonical map *key* (case-fold / realpath) is what you need. Consolidating on
38
- * this funnels the scattered transform and makes a shape-2 lint/ast-grep rule
39
- * possible for the first time: today a bare inline `.replace(/\\/g, "/")` is
40
- * byte-identical to the sanctioned use so it can't be ruled (#1158); once
41
- * everything routes through `toPosix`, an *un-migrated* inline `.replace`
42
- * becomes detectable.
43
- */
44
- export function toPosix(filePath) {
45
- return filePath.replace(/\\/g, "/");
46
- }
47
- /** Return whether `filePath` is fully qualified under Windows semantics. */
48
- export function isFullyQualifiedWin32(filePath) {
49
- return win32.isAbsolute(filePath) && win32.parse(filePath).root.length > 1;
50
- }
51
- /** Return whether `filePath` is fully qualified under POSIX semantics. */
52
- export function isFullyQualifiedPosix(filePath) {
53
- return path.posix.isAbsolute(filePath) && !isWindowsPath(filePath);
54
- }
55
- /**
56
- * Return whether `filePath` is fully qualified under the host's semantics.
57
- *
58
- * In particular, `/foo` is rooted-relative under Win32 (ambient-drive
59
- * dependent) but fully qualified under POSIX.
60
- */
61
- export function isFullyQualified(filePath) {
62
- return process.platform === "win32"
63
- ? isFullyQualifiedWin32(filePath)
64
- : isFullyQualifiedPosix(filePath);
65
- }
66
- /**
67
- * Shape-based absolute check (mirrors `toProjectRelativePath`'s idiom): a
68
- * Windows-shaped path (drive-letter/UNC) is parsed with `win32.isAbsolute`
69
- * regardless of host OS, since the host-default `path.isAbsolute` returns
70
- * FALSE for a Windows-shaped path on POSIX (#1150 class) and would let a
71
- * cross-platform-persisted relative path slip past this guard on Linux CI.
72
- * MUST run on the RAW `filePath`, before `normalizeMapKey`: on Windows,
73
- * `normalizeMapKey`'s nonexistent-path fallback (`resolveNonExisting`) calls
74
- * `win32.resolve`, which silently makes any relative path absolute against
75
- * `process.cwd()` — checking the normalized value would defeat this guard
76
- * entirely on Windows.
77
- *
78
- * Promoted from a private copy in `clients/review-graph/service.ts` (#2477)
79
- * so `clients/dispatch/dispatcher.ts`'s baseline-key guard (#2489) can share
80
- * the same sanctioned check instead of hand-rolling a second one.
81
- */
82
- export function isAbsoluteFilePath(filePath) {
83
- const p = isWindowsPath(filePath) ? win32 : path;
84
- return p.isAbsolute(filePath);
85
- }
86
- /**
87
- * Split a path into its non-empty segments on EITHER separator (`\` or `/`),
88
- * regardless of the running OS — the shape-safe form of `p.split(path.sep)` /
89
- * an inline `p.split(/[\\/]+/)`, which #1161/#1163 showed must not assume the
90
- * host separator for a possibly-cross-shaped path. Drops empty segments
91
- * (leading slash, drive-root, doubled separators).
92
- */
93
- export function splitPathSegments(filePath) {
94
- return filePath.split(/[\\/]+/).filter(Boolean);
95
- }
96
- /**
97
- * Adopt `canonical`'s CASING for the trailing segments of `held`, stopping at
98
- * the first segment that differs by more than case.
99
- *
100
- * `realpathSync.native` answers a different question than the one the POSIX
101
- * arm of `normalizeFilePath` asks: it resolves symlinks AND reports on-disk
102
- * casing, and we want only the second half. Walking from the tail and halting
103
- * at the first structural divergence separates them with no extra syscall:
104
- *
105
- * held /var/folders/T/x/SUB/a.ts (macOS tmpdir, mis-cased segment)
106
- * canonical /private/var/folders/T/x/sub/a.ts
107
- * result /var/folders/T/x/sub/a.ts (case fixed, symlink prefix kept)
108
- *
109
- * The symlink prefix matters: macOS's `os.tmpdir()` is `/var/folders/...`,
110
- * a symlink into `/private/var`, so a whole-string "is this a case variant"
111
- * test would decline to canonicalize exactly the paths the #1024 regression
112
- * test (and any macOS temp-dir workflow) runs on. Keeping the prefix is also
113
- * what bounds this change: on POSIX, `normalizeFilePath` still never resolves
114
- * a symlink, so a symlinked monorepo package keeps keying under the path the
115
- * caller held (refs #2490 — a cwd fold for its own sake broke every monorepo).
116
- *
117
- * PURE string algebra: this pins what the rewrite DOES, never what the kernel
118
- * reports — and from two strings alone it cannot tell "the same file, spelled
119
- * with different case" from "a different file whose name happens to be a case
120
- * variant". Any symlink whose BASENAME is a case variant of its target's
121
- * basename (`<root>/MyProject` → `work/myproject`, `node_modules/Foo` →
122
- * `../pkgs/foo`) has its name replaced while its own parent is kept, so the
123
- * result names a different place — or no place at all. Measured in #3159
124
- * review round 2 (F1): two different inodes collapsed onto ONE key (the #1024
125
- * defect inverted — false suppression), and a symlinked package keyed under a
126
- * path that does not exist on disk, breaking the #2490 bound above. The caller
127
- * therefore CONFIRMS every rewrite against the filesystem before adopting it
128
- * (see `normalizeFilePath`); do not use this function without that step.
129
- */
130
- function adoptCanonicalCasing(held, canonical) {
131
- const heldParts = held.split("/");
132
- const realParts = canonical.split("/");
133
- let i = heldParts.length - 1;
134
- let j = realParts.length - 1;
135
- let changed = false;
136
- while (i >= 0 && j >= 0) {
137
- const a = heldParts[i];
138
- const b = realParts[j];
139
- if (a !== b) {
140
- if (a.toLowerCase() !== b.toLowerCase())
141
- break;
142
- heldParts[i] = b;
143
- changed = true;
144
- }
145
- i--;
146
- j--;
147
- }
148
- // Identity-preserving when nothing moved — this runs on every POSIX map-key
149
- // derivation and the overwhelmingly common answer is "already canonical".
150
- // NOT a behavioural branch (both arms produce an equal string): it is the
151
- // same allocation guard `normalizeEphemeralMapKey` documents below.
152
- return changed ? heldParts.join("/") : held;
153
- }
154
- /**
155
- * Normalize a file path for consistent Map key usage.
156
- *
157
- * On Windows:
158
- * - If the file exists: uses realpathSync.native() to get the canonical
159
- * filesystem path (actual casing, resolved symlinks)
160
- * - If the file doesn't exist: resolves the path and lowercases
161
- * (needed for new files where we haven't written yet)
162
- *
163
- * On POSIX:
164
- * - Folds `.`, `..` and duplicate separators first (#3184, see below).
165
- * - If the file exists: adopts the on-disk CASING of the trailing segments
166
- * (see `adoptCanonicalCasing`) — no lowercasing, no symlink resolution.
167
- * - If the file doesn't exist: returns the path as-is, case-PRESERVING. On a
168
- * case-sensitive filesystem `SUB/a.ts` and `sub/a.ts` are two different
169
- * files, and nothing may fold one into the other; only the filesystem's own
170
- * answer for a path that EXISTS can tell the two apart, and for a path that
171
- * does not exist there is no such answer to ask for.
172
- *
173
- * Why the POSIX arm folds dot segments (#3184): the casing arm returns the
174
- * caller's own spelling whenever `adoptCanonicalCasing` changes nothing, and
175
- * for `<base>/src/../src/a.ts` it always changes nothing — `realpath` answers
176
- * a string with FEWER segments, which a casing-only rewrite cannot express, so
177
- * it declines and the caller's un-folded spelling came back as the map key.
178
- * Every canonical writer keys through `path.resolve` first (`ctx.filePath` =
179
- * `normalizeMapKey(resolveAgainstAncestors(...))`, `clients/dispatch/
180
- * runner-context.ts:49`), so a consumer that passes an ALREADY-absolute
181
- * agent-typed path straight in (`tools/lens-diagnostic-mark.ts`,
182
- * `clients/mcp/analyze.ts`) derived an orphan key that no reader could reach.
183
- * Folding here is pure string algebra — no cwd, no filesystem — so a relative
184
- * path stays relative (`src/../x` → `x`, `../x` → `../x`, never resolved
185
- * against `process.cwd()`; refs #2490, where a cwd fold broke every monorepo)
186
- * and a symlinked package still keys under the path the caller held. These
187
- * are `path.resolve`'s own TEXTUAL `..` semantics, which is exactly what
188
- * makes a folded reader key equal to the canonical writer's key: where a
189
- * `..` sits right after a symlinked directory, textual folding and the
190
- * kernel disagree, and both sides of every comparison take the textual
191
- * answer because every canonical writer already resolved that way.
192
- *
193
- * Why POSIX canonicalizes casing at all (#3098, the #1024 defect's live half):
194
- * a case-insensitive POSIX filesystem — macOS's default APFS, `nocase` vfat /
195
- * ntfs3 / cifs mounts — makes `SUB/a.ts` and `sub/a.ts` ONE file, so a raw
196
- * mis-cased write (`lens_diagnostic_mark` anchors under `path.resolve(cwd,
197
- * arg)`) and a `normalizeMapKey` read derived two anchors for one file and the
198
- * agent's own disposition mark silently never applied. A case-preserving POSIX
199
- * arm cannot close that: the two keys only become one if the normalizer asks
200
- * the filesystem which name is really on disk.
201
- *
202
- * `realpathSync.native` is `realpath(3)`. On Darwin it rebuilds every
203
- * component from the filesystem's own `ATTR_CMN_NAME` — Libc-1669.0.4
204
- * `stdlib/FreeBSD/realpath.c:233` (`getattrlist(resolved, &_rp_alist, …,
205
- * FSOPT_NOFOLLOW)` with `ATTR_CMN_NAME`) and `:348-354` ("attrs already has
206
- * the real name") — which is why it returns on-disk casing on APFS/HFS+.
207
- * MEASURED counter-example (#3098): a Linux ext4/tmpfs `chattr +F` casefold
208
- * directory aliases the two spellings but `realpath(3)` there returns the
209
- * spelling the caller asked with, so this arm is a no-op on casefolded Linux
210
- * directories — filed with the transcript as #3154.
211
- *
212
- * Always converts backslashes to forward slashes for consistent Map keys.
213
- */
214
- export function normalizeFilePath(filePath) {
215
- // Convert backslashes to forward slashes first
216
- const normalized = filePath.replace(/\\/g, "/");
217
- if (process.platform !== "win32" && !isWindowsPath(normalized)) {
218
- // #3184. `path.posix`, not the host default: this branch is already
219
- // committed to POSIX parsing of a slash-folded string (shape 2). Only
220
- // the POSIX arm needs this — the win32 arm below reaches `realpath` or
221
- // `win32.resolve`/`win32.normalize` on every path, all of which fold
222
- // dot segments already (measured: `C:\repo\src\..\src\a.ts` →
223
- // `c:/repo/src/a.ts` on this POSIX host, before this change) — and
224
- // folding BEFORE the arms would also move which arm a degenerate
225
- // drive-letter path selects (`path.posix.normalize("C:/repo/../..")`
226
- // is `"."`, no longer Windows-shaped).
227
- // Two inputs keep the caller's spelling instead:
228
- // - "" is a non-path sentinel in this codebase's path-typed fields
229
- // (see `normalizeLoggedPath`'s doc); `posix.normalize("")` invents
230
- // ".", the process cwd.
231
- // - a UNC root (`\\server\share`, slash-folded to `//server/share`)
232
- // reaches THIS arm on a POSIX host, because `isWindowsPath` tests
233
- // the already-folded string and sees no backslash; POSIX
234
- // `normalize` collapses its leading `//` to `/`, renaming the path
235
- // to an unrelated local one.
236
- const folded = normalized === "" || normalized.startsWith("//")
237
- ? normalized
238
- : path.posix.normalize(normalized);
239
- try {
240
- const canonical = realpathSync.native(folded);
241
- // Fast path, not a guard: both arms answer `folded` when the
242
- // strings match, but skipping the two `split`s there is a measured
243
- // 1.9 vs 2.3 microseconds per call on the per-edit seam (#3098).
244
- if (canonical === folded)
245
- return folded;
246
- const adopted = adoptCanonicalCasing(folded, canonical);
247
- if (adopted === folded)
248
- return folded;
249
- // The rewrite is string algebra and can land on a DIFFERENT file
250
- // (#3159 review round 2, F1 — see `adoptCanonicalCasing`). Adopt it
251
- // only once the filesystem agrees it still names the file the caller
252
- // held: `canonical` IS `realpath(normalized)`, so this asks exactly
253
- // "does the rewritten spelling resolve to the same file?". One extra
254
- // syscall, and only on the rare branch where casing actually moved —
255
- // never on an already-canonical path. A throw here (the rewritten
256
- // path does not exist, the #2490 monorepo case) lands in the catch
257
- // below and keeps the caller's spelling, which is the same answer.
258
- return realpathSync.native(adopted) === canonical ? adopted : folded;
259
- }
260
- catch {
261
- // Does not exist (or is unreadable): case-preserving, as above.
262
- return folded;
263
- }
264
- }
265
- // Windows: try realpathSync.native() for canonical casing
266
- // This resolves symlinks and returns the actual filesystem casing
267
- try {
268
- const canonical = realpathSync.native(filePath);
269
- return canonical.replace(/\\/g, "/");
270
- }
271
- catch {
272
- // File doesn't exist yet (new file) — resolve path and lowercase
273
- // We need to walk up the directory tree to find the nearest existing
274
- // parent, resolve its casing, then append the non-existent parts
275
- try {
276
- return resolveNonExisting(filePath);
277
- }
278
- catch {
279
- // Last resort: just lowercase the resolved path
280
- const resolved = win32.normalize(win32.resolve(filePath));
281
- return resolved.replace(/\\/g, "/").toLowerCase();
282
- }
283
- }
284
- }
285
- /** Resolve a filesystem identity once, retaining a usable absolute fallback. */
286
- export function realpathOrResolve(filePath) {
287
- const resolved = path.resolve(filePath);
288
- try {
289
- return realpathSync(resolved);
290
- }
291
- catch {
292
- return resolved;
293
- }
294
- }
295
- /**
296
- * Normalize a logged `filePath`/`cwd` value, but ONLY when it is already a
297
- * fully-qualified path (#2219, the #2141 class's sibling loggers). Several
298
- * NDJSON log-entry types reuse a `filePath`-typed field for non-path
299
- * sentinels alongside genuine paths — `"<quiet-window>"` in
300
- * `cascade-logger.ts`, `"<tree-sitter>"` in `tree-sitter-logger.ts`, a shell
301
- * command or an empty placeholder in `latency-logger.ts`. `normalizeFilePath`
302
- * resolves a relative-looking string against the CURRENT process cwd (see
303
- * `resolveNonExisting` above), so running it over one of those sentinels
304
- * would silently corrupt it into `"<repoRoot>/<quiet-window>"` instead of
305
- * normalizing it. Only a value that is already fully qualified can be the
306
- * #2141 mixed-raw/normalized-path defect; anything else is passed through
307
- * unchanged.
308
- *
309
- * #2229 review round 1, F1: this classifier checks BOTH host shapes
310
- * (`isFullyQualifiedWin32(value) || isFullyQualifiedPosix(value)`), not
311
- * `isFullyQualified(value)` (host-dispatched on `process.platform`). A
312
- * Windows-shaped absolute path (`C:\Users\...`) is exactly the log payload
313
- * this fix exists to normalize, but `isFullyQualified` on Linux CI routes to
314
- * `isFullyQualifiedPosix`, which rejects it (no leading `/`) — so on Linux
315
- * the guard silently no-ops for the very inputs the #2141 defect produces,
316
- * passing the raw backslash form straight through. Checking both shapes
317
- * makes the classifier's answer for a given STRING independent of which OS
318
- * is asking; `normalizeFilePath` itself still branches on `process.platform`
319
- * for how the file is resolved, but whether to normalize at all no longer
320
- * does (AGENTS.md shape 2, the #1024/#1150 OS-divergence class).
321
- */
322
- export function normalizeLoggedPath(value) {
323
- return isFullyQualifiedWin32(value) || isFullyQualifiedPosix(value)
324
- ? normalizeFilePath(value)
325
- : value;
326
- }
327
- /**
328
- * Resolve a non-existing path by finding the nearest existing parent,
329
- * getting its canonical casing, then appending the non-existent parts lowercased.
330
- *
331
- * Example: C:\Users\Foo\newdir\file.ts
332
- * - C:\Users\Foo exists → realpathSync gives C:\Users\Foo
333
- * - newdir\file.ts doesn't exist → lowercased
334
- * - Result: C:/Users/Foo/newdir/file.ts
335
- */
336
- function resolveNonExisting(filePath) {
337
- const resolved = win32.resolve(filePath);
338
- let current = resolved;
339
- const nonExistentParts = [];
340
- // Walk up until we find an existing directory
341
- while (true) {
342
- if (existsSync(current)) {
343
- // Found existing ancestor — get its canonical casing
344
- const canonical = realpathSync.native(current);
345
- if (nonExistentParts.length === 0) {
346
- return canonical.replace(/\\/g, "/");
347
- }
348
- // Append non-existent parts (lowercased for consistency)
349
- const tail = nonExistentParts.reverse().join("/").toLowerCase();
350
- const base = canonical.replace(/\\/g, "/");
351
- return base.endsWith("/") ? base + tail : `${base}/${tail}`;
352
- }
353
- // Use win32.dirname (not the platform-default dirname) so a
354
- // Windows-shaped path is parsed with win32 semantics regardless of the
355
- // running OS — consistent with the win32.resolve/win32.normalize this
356
- // branch already commits to. The platform-default POSIX dirname would
357
- // find no separator in a win32-resolved "C:\repo\..." path (its only
358
- // separators are backslashes), collapse to ".", stop the upward walk at
359
- // cwd, and mangle the key on Linux CI (refs #1150, the #1024
360
- // OS-divergence class).
361
- const parent = win32.dirname(current);
362
- if (parent === current) {
363
- // Reached filesystem root without finding existing dir
364
- // Fall back to full lowercase
365
- throw new Error("No existing parent found");
366
- }
367
- nonExistentParts.push(win32.basename(current));
368
- current = parent;
369
- }
370
- }
371
- /**
372
- * Convert a file:// URI to a normalized path.
373
- * Handles URL decoding and Windows drive letter normalization.
374
- */
375
- export function uriToPath(uri) {
376
- try {
377
- const filePath = fileURLToPath(uri);
378
- return normalizeFilePath(filePath);
379
- }
380
- catch {
381
- // Not a valid file:// URI, treat as plain path
382
- return normalizeFilePath(uri);
383
- }
384
- }
385
- /**
386
- * Decode a file:// URI to an on-disk path WITHOUT map-key normalization.
387
- *
388
- * `uriToPath` runs its result through `normalizeFilePath`, which on win32
389
- * lowercases the nonexistent tail of a path (see `resolveNonExisting`) and
390
- * canonicalizes an existing path to its real casing. That is correct for Map
391
- * keys, but DESTRUCTIVE for a real create/rename target: creating `NewFile.txt`
392
- * would write `newfile.txt`, and a legitimate case-only rename would collapse
393
- * to a no-op ("source and destination must differ"). Disk mutations must honor
394
- * the caller's intended casing, so they resolve their target through this
395
- * decode-only path while confinement/validation keep using the normalized
396
- * `uriToPath`. Non-win32 is unaffected either way (normalizeFilePath is a
397
- * near-identity there).
398
- */
399
- export function uriToDiskPath(uri) {
400
- try {
401
- return fileURLToPath(uri);
402
- }
403
- catch {
404
- // Not a valid file:// URI — treat as a plain path (matches uriToPath).
405
- return uri;
406
- }
407
- }
408
- /**
409
- * Convert a path to a file:// URI.
410
- * Does NOT normalize the path - URIs preserve original casing.
411
- */
412
- export function pathToUri(filePath) {
413
- return pathToFileURL(filePath).href;
414
- }
415
- /**
416
- * Normalize a Map key lookup for file paths.
417
- * Use this when getting/setting values in Maps that use file paths as keys.
418
- */
419
- export function normalizeMapKey(filePath) {
420
- return normalizeFilePath(filePath);
421
- }
422
- /**
423
- * Human-facing path relative to a project root when the file is inside it.
424
- *
425
- * Parses by path SHAPE, not host OS (refs #1150/#1152, shape-2 class #1163):
426
- * a Windows-shaped `filePath` (drive-letter/UNC — e.g. a persisted call-graph
427
- * symbol-key path `C:\repo\src\x.ts` rehydrated on a Linux CI run) is split
428
- * with `win32.*` regardless of `process.platform`. The host-default
429
- * `isAbsolute`/`relative` find no drive-letter anchor in a win32 path on POSIX:
430
- * `path.isAbsolute("C:\\repo\\x.ts")` returns FALSE on Linux, short-circuiting
431
- * to the raw absolute path instead of ever relativizing it — so a file that IS
432
- * under the project root renders as a full absolute path on Linux but the
433
- * expected `src/x.ts` on Windows (green-locally / wrong-on-CI, the #1024
434
- * divergence class). `win32.*` on a native POSIX path (Windows never sees one;
435
- * Linux native paths aren't Windows-shaped) is never selected, so same-OS
436
- * native paths are unchanged either way.
437
- */
438
- export function toProjectRelativePath(filePath, projectRoot) {
439
- const p = isWindowsPath(filePath) ? win32 : path;
440
- if (!p.isAbsolute(filePath))
441
- return filePath.replace(/\\/g, "/");
442
- const relative = p.relative(p.resolve(projectRoot), filePath);
443
- return relative && !relative.startsWith("..") && !p.isAbsolute(relative)
444
- ? relative.replace(/\\/g, "/")
445
- : filePath.replace(/\\/g, "/");
446
- }
447
- /**
448
- * Cheap, syntactic-only Map key normalization: slash-fold + (on Windows)
449
- * lowercase. No `realpathSync` / filesystem I/O.
450
- *
451
- * `normalizeMapKey` (via `normalizeFilePath`) calls `realpathSync.native()` to
452
- * get canonical on-disk casing — correct for maps that key long-lived state
453
- * shared across call sites (e.g. LSP/read-guard caches), but expensive when
454
- * the *point* of the cache is to avoid filesystem calls in the first place:
455
- * for a candidate path that does NOT exist (the common case for sibling-probe
456
- * memos), `normalizeFilePath` walks up the directory tree doing its own
457
- * `existsSync` calls to resolve the nearest existing ancestor — measured at
458
- * ~11x slower than the single `existsSync` probe such a cache is trying to
459
- * save (refs #191).
460
- *
461
- * Safe to use ONLY for ephemeral, single-process, single-walk caches whose
462
- * keys are produced by this process's own `path.join`/`path.resolve` calls
463
- * within the same run (so separators and casing are already consistent
464
- * modulo simple slash direction) — never for state shared across processes,
465
- * persisted, or compared against externally-supplied paths where symlink /
466
- * real-casing resolution actually matters.
467
- */
468
- export function normalizeEphemeralMapKey(filePath) {
469
- // Most hot-path keys on POSIX are already canonical slash-separated strings.
470
- // Preserve that identity instead of allocating a replacement string for each
471
- // file in a large diagnostics reconciliation.
472
- if (process.platform !== "win32" && !filePath.includes("\\"))
473
- return filePath;
474
- const slashed = filePath.replace(/\\/g, "/");
475
- return process.platform === "win32" ? slashed.toLowerCase() : slashed;
476
- }
477
- /**
478
- * Compare two file paths for equality, handling Windows case-insensitivity
479
- * and mixed separators (backslash vs forward slash).
480
- */
481
- export function pathsEqual(a, b) {
482
- return normalizeFilePath(a) === normalizeFilePath(b);
483
- }
484
- /**
485
- * Check if `child` is under `parent` directory.
486
- * Separator-agnostic and case-insensitive on Windows.
487
- */
488
- /**
489
- * Yield each directory from `startDir` up to (and including) the filesystem
490
- * root. Terminates when `path.dirname(current) === current` so it works on
491
- * Windows drive roots and POSIX `/` alike.
492
- *
493
- * Single source of truth for the half-dozen "walk up the directory tree
494
- * looking for X" loops that have accumulated across the codebase. Callers
495
- * that need an "is there a file named Y anywhere on the way up" check
496
- * should use `findNearestContaining` instead.
497
- */
498
- export function* walkUpDirs(startDir) {
499
- let current = path.resolve(startDir);
500
- while (true) {
501
- yield current;
502
- const parent = path.dirname(current);
503
- if (parent === current)
504
- return;
505
- current = parent;
506
- }
507
- }
508
- /**
509
- * Walk up from `startDir` and return the first directory that contains any
510
- * of `candidates` on disk. Returns `undefined` if none match.
511
- *
512
- * @example
513
- * findNearestContaining("/repo/pkg/src", ["package.json", "tsconfig.json"]);
514
- * // → "/repo/pkg" if pkg/package.json exists, "/repo" if only /repo/package.json
515
- */
516
- export function findNearestContaining(startDir, candidates) {
517
- for (const dir of walkUpDirs(startDir)) {
518
- for (const name of candidates) {
519
- if (existsSync(path.join(dir, name)))
520
- return dir;
521
- }
522
- }
523
- return undefined;
524
- }
525
- /**
526
- * Walk up from `startDir` and return the first matching FILE path (not just
527
- * the containing directory) for any of `names`, first-match-wins within each
528
- * directory in `names` order. Single source of truth for the "walk up
529
- * looking for one of these config filenames" loop that `opengrep-config.ts`,
530
- * `typos-config.ts`, `zizmor-config.ts`, and `sgconfig.ts` each hand-rolled
531
- * independently (refs #680), and that `php-cs-fixer-config.ts` now delegates
532
- * to as well (refs #2472 review F2).
533
- *
534
- * UNCEILINGED by default (refs #2472 review round 3, F1) — `options.homeDir`
535
- * is opt-in, not default-on. A prior version applied the SAME `$HOME`
536
- * ceiling as `findNearestMarkerRoot` unconditionally, which broke every one
537
- * of these tools' actual discovery contract: each of them treats a config
538
- * living directly at `$HOME` (`~/typos.toml`, `~/sgconfig.yml`, …) as the
539
- * user's legitimate GLOBAL config, and reads it itself regardless of pi-lens
540
- * — the ceiling didn't stop pi-lens from seeing an unrelated ancestor
541
- * config, it stopped pi-lens from seeing the SAME config the tool was about
542
- * to read on its own, so pi-lens silently fell back to (or, for typos,
543
- * injected and let its own shipped `_typos.toml` merge over) the user's
544
- * config where the tool's own resolver would have honored it. `php-cs-fixer`
545
- * makes the same mismatch concrete: its detection gates
546
- * (`hasPhpCsFixerConfig` via `findNearestContaining`, `phpCsFixerFormatter
547
- * .detect` via its own `findUp`) are both unceilinged, so a ceilinged
548
- * carriage here disagreed with its own gate — "config exists" from the gate,
549
- * "config not found" from the resolver — and dropped the very `--config`
550
- * argv #2472 exists to carry. Pass `options.homeDir` only when a caller
551
- * affirmatively wants the ceiling (a config found at or above THAT directory
552
- * is never returned); omitting it walks all the way to the filesystem root,
553
- * matching `findNearestContaining`'s unceilinged behavior and every
554
- * underlying tool's own discovery.
555
- *
556
- * Distinct from `findNearestContaining`, which returns the containing
557
- * directory rather than the matched file path — use that one when the caller
558
- * only needs "is one of these present nearby", not which file it is.
559
- *
560
- * @example
561
- * findLocalToolConfig(cwd, ["typos.toml", "_typos.toml", ".typos.toml"]);
562
- * // → "/repo/typos.toml" if present, else undefined
563
- */
564
- export function findLocalToolConfig(startDir, names, options = {}) {
565
- const homeDir = options.homeDir !== undefined ? path.resolve(options.homeDir) : undefined;
566
- for (const dir of walkUpDirs(startDir || process.cwd())) {
567
- if (homeDir !== undefined && isAtOrAboveHomeDir(dir, homeDir)) {
568
- return undefined;
569
- }
570
- for (const name of names) {
571
- const candidate = path.join(dir, name);
572
- if (existsSync(candidate))
573
- return candidate;
574
- }
575
- }
576
- return undefined;
577
- }
578
- /**
579
- * Accept only a real Git repository marker: a directory with HEAD, or a
580
- * worktree/submodule marker file whose first line starts with `gitdir:`.
581
- */
582
- export function isRealGitMarker(markerPath) {
583
- try {
584
- const marker = statSync(markerPath);
585
- if (marker.isDirectory())
586
- return existsSync(path.join(markerPath, "HEAD"));
587
- if (!marker.isFile())
588
- return false;
589
- return readFileSync(markerPath, "utf8")
590
- .split(/\r?\n/, 1)[0]
591
- .startsWith("gitdir:");
592
- }
593
- catch {
594
- return false;
595
- }
596
- }
597
- /**
598
- * Walk up from `startDir` looking for a directory containing any of
599
- * `markers`, the same containment-aware climb `knip-client.ts` and
600
- * `dead-code-client.ts` each used to hand-roll independently (refs #625):
601
- *
602
- * - Never resolves at or above `$HOME` (via `isAtOrAboveHomeDir`) — a
603
- * marker found there has escaped the user's workspace.
604
- * - If `options.boundaries` is given and one is found before any `marker`,
605
- * stops and returns `null` rather than continuing past it.
606
- * - Depth-capped at 64 climbs, matching the callers' existing safety bound
607
- * (guards a pathological symlink loop; real depths are ~10).
608
- * - Returns `null` — never `startDir` — when nothing is found. Callers
609
- * must treat `null` as "no project here", not fall back to the start
610
- * directory (a `null`-swallowing fallback was the #250/#296 bug class:
611
- * scanning $HOME wholesale from a bare cwd).
612
- *
613
- * For a plain "find nearest containing directory" with no boundary concept,
614
- * use `findNearestContaining` instead. Distinct from `startup-scan.ts`'s
615
- * `findNearestProjectRoot` (fixed marker list, no boundaries, no home-check —
616
- * that caller applies `isAtOrAboveHomeDir` itself afterward); named
617
- * differently here to avoid confusion between the two.
618
- */
619
- export function findNearestMarkerRoot(startDir, markers, options = {}) {
620
- const boundaries = options.boundaries ?? [];
621
- const homeDir = path.resolve(options.homeDir ?? os.homedir());
622
- const markerPredicate = options.markerPredicate ?? (() => true);
623
- let current = path.resolve(startDir);
624
- for (let depth = 0; depth < 64; depth++) {
625
- if (isAtOrAboveHomeDir(current, homeDir))
626
- return null;
627
- if (markers.some((m) => existsSync(path.join(current, m)) &&
628
- markerPredicate(path.join(current, m))))
629
- return current;
630
- if (boundaries.some((m) => existsSync(path.join(current, m))))
631
- return null;
632
- const parent = path.dirname(current);
633
- if (parent === current)
634
- return null;
635
- current = parent;
636
- }
637
- return null;
638
- }
639
- /**
640
- * True when `dir` is the home directory OR an ancestor of it (`/home`,
641
- * `C:\Users`, the filesystem root, …). A project-root search that climbs to
642
- * such a directory has escaped the user's workspace — walking down from it
643
- * scans unrelated trees (the #250 runaway). Use this as the single shared
644
- * ceiling on any upward project-root resolution, instead of an exact
645
- * `=== os.homedir()` check (which a marker found *above* `$HOME` slips past).
646
- * A normal project *under* home (e.g. `~/code/app`) is NOT at-or-above home,
647
- * so it still resolves fine. Refs #253.
648
- *
649
- * ONE expression, deliberately: `path.relative` alone answers "is home inside
650
- * `dir`, or the same directory as `dir`", and it answers with the PLATFORM's
651
- * path-equality semantics — case-folding on win32, case-sensitive on POSIX.
652
- * The `resolvedDir === resolvedHome` shortcut that used to precede it was
653
- * case-SENSITIVE on every platform, so `c:\Users\jane` (the lowercase-drive
654
- * form VS Code URIs produce, and the form 46 records in a real `latency.log`
655
- * carry) missed the equality test AND was then rejected by a `rel !== ""`
656
- * clause — reporting "not at home" for the home directory itself and letting
657
- * every ceilinged walker climb straight past HOME (#2544 review F1).
658
- *
659
- * rel === "" ⇢ `dir` IS home
660
- * rel relative, no leading `..` ⇢ home is INSIDE `dir` (an ancestor)
661
- * rel starts with `..` ⇢ home is elsewhere — sibling, or the
662
- * prefix trap `C:\Users\jane2` → home
663
- * `C:\Users\jane` gives `..\jane`
664
- * rel absolute ⇢ different Windows drive
665
- *
666
- * Same shape as `isUnderDir` above; keep the two in step.
667
- *
668
- * `pathImpl` exists so the ubuntu Unit tests lane — the authoritative one —
669
- * can exercise the WIN32 semantics this helper's whole bug was about. Every
670
- * assertion that matters here (drive-letter folding, cross-drive `rel`) is
671
- * win32-only, so with the ambient `path` hardcoded the ceiling was enforced
672
- * only on a maintainer's dev box and CI's diff was structurally zero (#2544
673
- * review F2). Production never passes it; `path.win32`/`path.posix` are pure,
674
- * host-independent implementations, so `tests/clients/path-utils.test.ts` runs
675
- * the same table under both on every lane. Lane-level fix tracked as #2536.
676
- */
677
- export function isAtOrAboveHomeDir(dir, homeDir = os.homedir(), pathImpl = path) {
678
- const rel = pathImpl.relative(pathImpl.resolve(dir), pathImpl.resolve(homeDir));
679
- return !rel.startsWith("..") && !pathImpl.isAbsolute(rel);
680
- }
681
- /**
682
- * Rewrite a `$HOME`-anchored path to its `~` form for a DIAGNOSTIC surface.
683
- *
684
- * `~/.pi-lens/config.json` says everything an operator needs about which file
685
- * won, and `C:/Users/jane.doe/.pi-lens/config.json` says that plus the
686
- * account name. The second is the shape a global config path always has, so any
687
- * projection that names one leaks an identifier by default rather than by
688
- * accident (#2440 review finding F5).
689
- *
690
- * Deliberately NOT built on `normalizeFilePath`/`isUnderDir`: those resolve
691
- * through `realpathSync`, and a redaction helper on a diagnostic path must be
692
- * pure, total, and unable to throw for a file that no longer exists. This is a
693
- * string rewrite and nothing else.
694
- *
695
- * A path that is not under home is returned UNCHANGED — separators included.
696
- * Normalizing unrelated paths on the way past would make the helper's blast
697
- * radius every path any projection ever carries, for no redaction benefit.
698
- */
699
- export function homeRelativePath(filePath, homeDir = os.homedir()) {
700
- if (filePath.length === 0)
701
- return filePath;
702
- const candidate = toPosix(filePath);
703
- const home = toPosix(homeDir).replace(/\/+$/, "");
704
- if (home.length === 0)
705
- return filePath;
706
- // Windows paths are case-insensitive, so the COMPARISON folds case there
707
- // while the returned tail keeps the caller's own casing.
708
- const fold = (text) => process.platform === "win32" ? text.toLowerCase() : text;
709
- const foldedHome = fold(home);
710
- const foldedCandidate = fold(candidate);
711
- if (foldedCandidate === foldedHome)
712
- return "~";
713
- if (!foldedCandidate.startsWith(`${foldedHome}/`))
714
- return filePath;
715
- return `~${candidate.slice(home.length)}`;
716
- }
717
- export function isUnderDir(child, parent) {
718
- const normChild = normalizeFilePath(child);
719
- const normParent = normalizeFilePath(parent);
720
- // Ensure parent ends with / for prefix matching
721
- const parentPrefix = normParent.endsWith("/") ? normParent : `${normParent}/`;
722
- return normChild === normParent || normChild.startsWith(parentPrefix);
723
- }
724
- const VENDOR_DIR_NAMES = new Set([
725
- "node_modules",
726
- "vendor",
727
- "vendors",
728
- "third_party",
729
- "third-party",
730
- ]);
731
- /**
732
- * Returns true when a file should be treated as external/vendor and excluded
733
- * from pipelines (LSP, diagnostics, complexity, read-guard, etc.).
734
- *
735
- * Cases:
736
- * 1. Outside the project root entirely (e.g. global npm packages, system files)
737
- * 2. Inside the project but under a vendor directory (node_modules, vendor, third_party, etc.)
738
- */
739
- export function isExternalOrVendorFile(filePath, projectRoot) {
740
- if (!isUnderDir(filePath, projectRoot))
741
- return true;
742
- const normalized = normalizeFilePath(filePath);
743
- const rootNorm = normalizeFilePath(projectRoot);
744
- const rel = normalized.startsWith(rootNorm + "/")
745
- ? normalized.slice(rootNorm.length + 1)
746
- : normalized;
747
- return rel.split("/").some((seg) => VENDOR_DIR_NAMES.has(seg));
748
- }
749
- /**
750
- * Shared marker-glob semantics for every "does this directory contain a file
751
- * matching this glob" probe (#895 review): match against the entry NAME only,
752
- * `dot: true` so dotfile markers match, `nocase` on win32 to match the
753
- * filesystem (and the project ignore matcher). The three marker probes —
754
- * language-profile.ts `hasProjectMarker`, workspace-topology.ts
755
- * `hasBasenameMarker`, lsp/server.ts `markerExists` — must all route their
756
- * glob matching through here rather than call minimatch with hand-copied
757
- * options.
758
- */
759
- export function nameMatchesMarkerGlob(name, pattern) {
760
- return minimatch(name, pattern, {
761
- dot: true,
762
- nocase: process.platform === "win32",
763
- });
764
- }
765
- /**
766
- * Files/symlinks-only marker-glob probe over a directory listing — a
767
- * *directory* named like a marker (e.g. a `Foo.csproj/` dir) is not a project
768
- * file (#201).
769
- */
770
- export function direntsHaveMarkerGlobMatch(entries, pattern) {
771
- return entries.some((entry) => (entry.isFile() || entry.isSymbolicLink()) &&
772
- nameMatchesMarkerGlob(entry.name, pattern));
773
- }
774
- /**
775
- * Cargo `[workspace] members`/`exclude` entries.
776
- *
777
- * KNOWN LIMITATION (#1671 F6, documented rather than implemented, preserved
778
- * byte-for-byte by #2591's fold): a recursive `**` component is NOT supported
779
- * and a pattern containing one never matches — cargo workspaces that rely on
780
- * `**` to pull in an arbitrarily-nested crate tree under-hoist (the crate stays
781
- * independently rooted instead of joining the workspace). Folding cargo onto
782
- * uv's `crosses-components` globstar would silently change Rust-LSP root
783
- * selection, so the divergence is kept as a dialect value, not resolved.
784
- *
785
- * `characterClasses: false` is likewise a preserved divergence, not a
786
- * considered choice: cargo's pre-fold segment compiler escaped `[` and `]`
787
- * into literals, so `crates/[ab]` names a directory literally called `[ab]`.
788
- * Real cargo (the `glob` crate) would read it as a class; changing that here
789
- * would be the same unreviewed Rust-hoisting change.
790
- */
791
- export const CARGO_WORKSPACE_MEMBER_DIALECT = {
792
- name: "cargo",
793
- globstar: "pattern-never-matches",
794
- wildcardCrossesSeparator: false,
795
- characterClasses: false,
796
- normalizePattern: (pattern) => pattern.replace(/\/+$/, ""),
797
- };
798
- /**
799
- * uv `[tool.uv.workspace] members`, pinned to
800
- * `astral-sh/uv@3c979abda4530fe9bf3d92e9bcf5c5575e3b3126`,
801
- * `crates/uv-workspace/src/workspace.rs` `is_included_in_workspace`: the glob is
802
- * `normalize_path`d first (so a leading `./` and any interior `.` component are
803
- * not part of the pattern) and matched with
804
- * `MatchOptions { require_literal_separator: true, ..MatchOptions::new() }`.
805
- */
806
- export const UV_WORKSPACE_MEMBERS_DIALECT = {
807
- name: "uv-members",
808
- globstar: "crosses-components",
809
- wildcardCrossesSeparator: false,
810
- characterClasses: true,
811
- normalizePattern: (pattern) => path.posix.normalize(toPosix(pattern)),
812
- };
813
- /**
814
- * uv `[tool.uv.workspace] exclude`, same upstream file and SHA
815
- * (`WorkspaceExclusions::matches`): an exclusion is matched with
816
- * `Pattern::matches_path`, i.e. `MatchOptions::new()` defaults, where
817
- * `require_literal_separator` is FALSE — so a `*` in an exclusion DOES cross
818
- * `/` (`exclude = ['packages/a*c']` excludes `packages/a/b/c`). Two different
819
- * option sets inside one tool, which is why the dialect is an object and the
820
- * two uv constants are not one.
821
- *
822
- * Before #2591 this was a documented limitation: minimatch cannot express a
823
- * separator-crossing `*`, so pi-lens under-excluded. The dialect object can, so
824
- * it is now implemented rather than documented.
825
- */
826
- export const UV_WORKSPACE_EXCLUDE_DIALECT = {
827
- name: "uv-exclude",
828
- globstar: "crosses-components",
829
- wildcardCrossesSeparator: true,
830
- characterClasses: true,
831
- normalizePattern: (pattern) => path.posix.normalize(toPosix(pattern)),
832
- };
833
- /**
834
- * Index of the `]` closing the character class opened at `open`, or `-1` when
835
- * the run is unterminated (in which case the `[` is a literal). A leading `!`
836
- * or `^` negates, and a `]` in first position is a literal class member — the
837
- * shape both minimatch and rust `glob` accept.
838
- */
839
- function findCharacterClassEnd(segment, open) {
840
- let i = open + 1;
841
- if (segment[i] === "!" || segment[i] === "^")
842
- i += 1;
843
- if (segment[i] === "]")
844
- i += 1;
845
- for (; i < segment.length; i += 1)
846
- if (segment[i] === "]")
847
- return i;
848
- return -1;
849
- }
850
- /**
851
- * May a wildcard with this separator policy consume `ch`?
852
- *
853
- * A separator-confined wildcard (`[^/]` in the regex this replaced) takes
854
- * anything but `/`. A separator-CROSSING one — uv `exclude`'s `*`/`?`, and
855
- * every `**` in either uv dialect — spelled `.` in that regex, which is every
856
- * code unit EXCEPT the four line terminators. That exclusion is preserved
857
- * deliberately rather than "fixed": minimatch's globstar is `.`-based too, so
858
- * a `\n` inside a directory name has never matched across a `**` in either
859
- * implementation, and the differential oracle would flag a change here as a
860
- * divergence rather than an improvement (#2603).
861
- */
862
- function stepAcceptsChar(crossesSeparator, ch) {
863
- return crossesSeparator
864
- ? ch !== "\n" && ch !== "\r" && ch !== "\u2028" && ch !== "\u2029"
865
- : ch !== "/";
866
- }
867
- /**
868
- * Compile a normalized workspace-member pattern into a step list, or
869
- * `undefined` when it cannot be compiled at all.
870
- *
871
- * A `**` component consumes zero or more path components — except as the LAST
872
- * component, where it requires at least one (`a/**` matches `a/b`, not `a`),
873
- * reproducing both minimatch's and rust `glob`'s answer. It is emitted as
874
- * `split → one → star → literal "/"`, i.e. "nothing, or one-or-more characters
875
- * followed by the `/` that ends them", after the separator that precedes it.
876
- * That is the same language as the `(?:/.+)?/` group the previous compiler
877
- * emitted — `X(?:/.+)?/Y` and `X/(?:.+/)?Y` both denote `X/Y` ∪ `X/.+/Y` — in a
878
- * form with no nested quantifier.
879
- */
880
- function compileWorkspaceMemberPattern(pattern, dialect) {
881
- const isGlobstar = (component) => component === "**" && dialect.globstar === "crosses-components";
882
- const components = pattern.split("/");
883
- const steps = [];
884
- let needSeparator = false;
885
- for (let c = 0; c < components.length; c += 1) {
886
- if (needSeparator)
887
- steps.push({ kind: "literal", char: "/" });
888
- needSeparator = false;
889
- const component = components[c];
890
- if (isGlobstar(component)) {
891
- if (c === components.length - 1) {
892
- // `.+` — a trailing `**` requires at least one character.
893
- steps.push({ kind: "one", crossesSeparator: true });
894
- steps.push({ kind: "star", crossesSeparator: true });
895
- continue;
896
- }
897
- // `(?:.+/)?` — zero or more whole components. The group already ends
898
- // at a `/`, so the next component must NOT emit one.
899
- const split = { kind: "split", alternative: -1 };
900
- steps.push(split);
901
- steps.push({ kind: "one", crossesSeparator: true });
902
- steps.push({ kind: "star", crossesSeparator: true });
903
- steps.push({ kind: "literal", char: "/" });
904
- split.alternative = steps.length;
905
- continue;
906
- }
907
- for (let i = 0; i < component.length; i += 1) {
908
- const ch = component[i];
909
- if (ch === "*") {
910
- steps.push({
911
- kind: "star",
912
- crossesSeparator: dialect.wildcardCrossesSeparator,
913
- });
914
- continue;
915
- }
916
- if (ch === "?") {
917
- steps.push({
918
- kind: "one",
919
- crossesSeparator: dialect.wildcardCrossesSeparator,
920
- });
921
- continue;
922
- }
923
- if (ch === "[" && dialect.characterClasses) {
924
- const close = findCharacterClassEnd(component, i);
925
- if (close !== -1) {
926
- // `\` and a first-position `]` are literal class MEMBERS in glob;
927
- // left alone they would be a regex escape and an empty-class
928
- // terminator (`[]ab]` is `[]` + `ab]` in JS), so both are escaped.
929
- const body = component.slice(i + 1, close).replace(/[\\\]]/g, "\\$&");
930
- const source = body.startsWith("!")
931
- ? `[^${body.slice(1)}]`
932
- : `[${body}]`;
933
- let match;
934
- try {
935
- match = new RegExp(`^${source}$`);
936
- }
937
- catch {
938
- // A glob character class is not a JS character class: `[z-a]`
939
- // is a legal glob (matching nothing, since the range is empty)
940
- // and an illegal RegExp ("Range out of order"). Fail CLOSED —
941
- // an uncompilable pattern declares no member and excludes
942
- // nothing — which is also the answer the deleted minimatch call
943
- // gave (#2591 review round 2, F2).
944
- return undefined;
945
- }
946
- steps.push({ kind: "class", match });
947
- i = close;
948
- continue;
949
- }
950
- }
951
- steps.push({ kind: "literal", char: ch });
952
- }
953
- needSeparator = true;
954
- }
955
- return steps;
956
- }
957
- /**
958
- * Does the whole of `subject` match the whole of `steps`?
959
- *
960
- * A memoized (step index, path index) table, filled once, bottom-up:
961
- * `table[s][p]` is "steps `s…` match `subject[p…]`". Every cell reads only
962
- * cells with a larger step index or a larger path index, so one backward
963
- * double loop fills the table with no recursion and no re-entry — the match is
964
- * O(steps x characters) in time and space, with no path through it that can
965
- * take exponential time (#2603).
966
- *
967
- * The compiled whole-path regex this replaced was correct but backtracking:
968
- * every `**` emitted its own nullable `.+`, and N of them explored 2^N splits
969
- * of a non-matching subject. #2591 collapsed CONSECUTIVE `**`s, which is a
970
- * normalization that cannot fire across a separating component, so
971
- * `("**\/*" x12)/zzz` against a 40-component path still took 124900 ms (#2603);
972
- * the same shapes are microseconds here. There is nothing left to collapse for
973
- * speed, so no collapse is done: `a/**\/**\/b` compiles to two adjacent
974
- * `(?:.+/)?` groups, which denote the same language as one and cost the same
975
- * table.
976
- */
977
- function matchesWorkspaceMemberSteps(steps, subject) {
978
- const stepCount = steps.length;
979
- const width = subject.length + 1;
980
- // One byte per (step, position) cell; `1` means "the rest matches from here".
981
- const table = new Uint8Array((stepCount + 1) * width);
982
- for (let p = subject.length; p >= 0; p -= 1) {
983
- const atEnd = p === subject.length;
984
- // The empty step list matches only the empty remainder.
985
- table[stepCount * width + p] = atEnd ? 1 : 0;
986
- for (let s = stepCount - 1; s >= 0; s -= 1) {
987
- const step = steps[s];
988
- const next = (s + 1) * width;
989
- let matched = 0;
990
- switch (step.kind) {
991
- case "literal":
992
- matched =
993
- !atEnd && subject[p] === step.char ? table[next + p + 1] : 0;
994
- break;
995
- case "one":
996
- matched =
997
- !atEnd && stepAcceptsChar(step.crossesSeparator, subject[p])
998
- ? table[next + p + 1]
999
- : 0;
1000
- break;
1001
- case "class":
1002
- matched =
1003
- !atEnd && step.match.test(subject[p]) ? table[next + p + 1] : 0;
1004
- break;
1005
- case "star":
1006
- // Consume nothing, or one more character and stay on this step.
1007
- matched =
1008
- table[next + p] === 1 ||
1009
- (!atEnd &&
1010
- stepAcceptsChar(step.crossesSeparator, subject[p]) &&
1011
- table[s * width + p + 1] === 1)
1012
- ? 1
1013
- : 0;
1014
- break;
1015
- case "split":
1016
- matched =
1017
- table[step.alternative * width + p] === 1 || table[next + p] === 1
1018
- ? 1
1019
- : 0;
1020
- break;
1021
- }
1022
- table[s * width + p] = matched;
1023
- }
1024
- }
1025
- return table[0] === 1;
1026
- }
1027
- /**
1028
- * Does `relativePath` — a `/`-separated path relative to the workspace root —
1029
- * match one declared workspace-member (or workspace-exclude) `pattern` under
1030
- * `dialect`?
1031
- *
1032
- * This is the ONE workspace-member matcher (#2591). `clients/lsp/server.ts`'s
1033
- * `cargoWorkspaceDeclaresMember` and `clients/python-environment.ts`'s
1034
- * `isUvWorkspaceMember` are thin callers of it; neither keeps a private segment
1035
- * compiler or minimatch options block any more.
1036
- *
1037
- * `clients/review-graph/workspace-modules.ts`'s `expandWorkspacePattern` is
1038
- * deliberately NOT a caller: npm/pnpm `workspaces` entries are EXPANDED against
1039
- * the filesystem (one `readdir` of the directory preceding the first `*`,
1040
- * yielding the directories that exist and hold a manifest) rather than tested
1041
- * against a candidate path. It answers "which directories does this pattern
1042
- * name", not "does this pattern name this directory"; folding it in would mean
1043
- * giving this pure function a filesystem.
1044
- *
1045
- * The supported syntax is the intersection of the two upstream dialects: `*`,
1046
- * `?`, `**`, `[abc]`/`[!abc]` (dialect-gated), and literals. minimatch-only
1047
- * extensions the pre-fold uv path inherited by accident — brace expansion,
1048
- * extglobs, leading-`!` negation, leading-`#` comments — are NOT honored, and
1049
- * uv's own `glob` crate does not honor them either (same pinned SHA), so
1050
- * dropping them moves uv toward upstream rather than away from it.
1051
- *
1052
- * Matching is NON-BACKTRACKING (#2603): the pattern compiles to a list of
1053
- * bounded steps and {@link matchesWorkspaceMemberSteps} decides it with one
1054
- * memoized (step, position) table, O(steps x characters), no matter how many
1055
- * `**`s the pattern carries. The anchored whole-path regex this replaced was
1056
- * correct but explored 2^N splits of a non-matching path for N `**`
1057
- * components — 124900 ms for `("**\/*" x12)/zzz` against a 40-component path,
1058
- * on a call `detectPythonEnvironment` awaits with no timeout, i.e. a wedged
1059
- * turn rather than a slow answer. The wall-clock half of that fix lives in
1060
- * `tests/clients/workspace-glob-nonbacktracking-budget.test.ts`; the answers
1061
- * are unchanged, pinned by the dialect table and the minimatch differential in
1062
- * `tests/clients/path-utils.test.ts`.
1063
- *
1064
- * A pattern that cannot be compiled at all answers `false` for every path —
1065
- * it declares no member and excludes nothing (#2591 review round 2, F2). The
1066
- * only such patterns today carry an empty character-class range (`[z-a]`:
1067
- * legal glob, illegal JS RegExp). Reachability note, recorded rather than
1068
- * assumed away: no such pattern can reach here through a manifest right now,
1069
- * because `parseTomlStringArray` (`clients/cargo-manifest.ts`) captures an
1070
- * array body non-greedily up to the FIRST `]`, so any entry containing a `]`
1071
- * loses its closing quote and is dropped before it becomes a pattern. That is
1072
- * a property of the TOML reader, not of this matcher, and it is not this
1073
- * function's to rely on — the character-class axis stays because upstream
1074
- * cargo and uv both support classes, and the reader's gap may be closed later.
1075
- */
1076
- export function matchesWorkspaceMemberPattern(pattern, relativePath, dialect) {
1077
- const normalized = dialect.normalizePattern(pattern);
1078
- if (dialect.globstar === "pattern-never-matches" &&
1079
- normalized.includes("**")) {
1080
- return false;
1081
- }
1082
- const steps = compileWorkspaceMemberPattern(normalized, dialect);
1083
- return (steps !== undefined && matchesWorkspaceMemberSteps(steps, relativePath));
1084
- }
1085
- /**
1086
- * Narrow no-break space, U+202F. macOS writes it before AM/PM in screenshot
1087
- * file names; users type an ordinary space.
1088
- */
1089
- const NARROW_NO_BREAK_SPACE = "\u202F";
1090
- /** Right single quotation mark, U+2019. macOS writes it; users type U+0027. */
1091
- const RIGHT_SINGLE_QUOTE = "\u2019";
1092
- /**
1093
- * The unicode space class pi folds to U+0020, copied character-for-character
1094
- * from `@earendil-works/pi-coding-agent/dist/utils/paths.js:6`
1095
- * (`UNICODE_SPACES`, source `src/utils/paths.ts`). Widening or narrowing this
1096
- * set makes pi-lens resolve a different file than pi does.
1097
- */
1098
- const HOST_UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
1099
- /**
1100
- * Mirror pi's `normalizeWindowsShellPath`
1101
- * (`@earendil-works/pi-coding-agent/dist/utils/paths.js:47-56`): convert Git
1102
- * Bash, MSYS, Cygwin, and WSL drive paths to a form native Windows APIs
1103
- * accept.
1104
- */
1105
- function hostNormalizeWindowsShellPath(filePath) {
1106
- if (!filePath.startsWith("/") ||
1107
- filePath.startsWith("//") ||
1108
- filePath.includes("\\")) {
1109
- return filePath;
1110
- }
1111
- const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
1112
- if (!match)
1113
- return filePath;
1114
- const suffix = match[2]?.replaceAll("/", "\\");
1115
- return `${match[1].toUpperCase()}:\\${suffix ?? ""}`;
1116
- }
1117
- /**
1118
- * Mirror pi's `normalizePath`
1119
- * (`@earendil-works/pi-coding-agent/dist/utils/paths.js:57-79`, source
1120
- * `src/utils/paths.ts`), in pi's order, which is load-bearing: the unicode
1121
- * fold runs BEFORE the `@` strip, and the tilde expansion before the
1122
- * `file://` conversion.
1123
- *
1124
- * The `win32` step is gated on `process.platform`, exactly as pi gates it.
1125
- * This is a deliberate exception to the usual probe-the-filesystem rule
1126
- * (AGENTS.md shape 2): pi-lens runs in pi's own process, so mirroring the
1127
- * host's own platform branch is what keeps the two resolvers in agreement.
1128
- * Shape-based parsing here would DIVERGE from the host, not protect against
1129
- * it.
1130
- */
1131
- export function normalizeHostToolPath(input, options = {}) {
1132
- let normalized = input;
1133
- if (options.normalizeUnicodeSpaces) {
1134
- normalized = normalized.replace(HOST_UNICODE_SPACES, " ");
1135
- }
1136
- if (options.stripAtPrefix && normalized.startsWith("@")) {
1137
- normalized = normalized.slice(1);
1138
- }
1139
- if (process.platform === "win32") {
1140
- normalized = hostNormalizeWindowsShellPath(normalized);
1141
- }
1142
- // `homedir()` is resolved inside the branch, as pi does. This runs on every
1143
- // tool_call and a `~` path is the rare case.
1144
- if (normalized === "~")
1145
- return os.homedir();
1146
- if (normalized.startsWith("~/") ||
1147
- (process.platform === "win32" && normalized.startsWith("~\\"))) {
1148
- return path.join(os.homedir(), normalized.slice(2));
1149
- }
1150
- if (normalized.startsWith("file://")) {
1151
- try {
1152
- return fileURLToPath(normalized);
1153
- }
1154
- catch {
1155
- // pi lets a malformed file: URL throw out of normalizePath, but pi-lens
1156
- // is advisory instrumentation on the same event: a URL pi rejects must
1157
- // degrade to "no path" here, never take down the tool_call handler.
1158
- return normalized;
1159
- }
1160
- }
1161
- return normalized;
1162
- }
1163
- /**
1164
- * Mirror pi's `resolveToCwd`
1165
- * (`@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js:42-44`,
1166
- * source `src/core/tools/path-utils.ts:~44-46`) \u2014 the BASE resolution every
1167
- * read/edit/write path goes through before the variant ladder below ever
1168
- * runs.
1169
- *
1170
- * Two details are pi's, not ours, and both matter:
1171
- * - the input is normalized with `normalizeUnicodeSpaces` + `stripAtPrefix`;
1172
- * the BASE DIR is normalized with neither (`resolvePath`,
1173
- * `dist/utils/paths.js:80-84`).
1174
- * - an already-absolute input is re-resolved on its own, ignoring the cwd.
1175
- */
1176
- export function resolveHostToolPath(input, baseDir) {
1177
- const normalized = normalizeHostToolPath(input, {
1178
- normalizeUnicodeSpaces: true,
1179
- stripAtPrefix: true,
1180
- });
1181
- const normalizedBaseDir = normalizeHostToolPath(baseDir);
1182
- return path.isAbsolute(normalized)
1183
- ? path.resolve(normalized)
1184
- : path.resolve(normalizedBaseDir, normalized);
1185
- }
1186
- /**
1187
- * Mirror pi's read-path fallback ladder (#1655 item 5).
1188
- *
1189
- * pi does NOT open `resolve(cwd, input.path)`. `resolveReadPath`
1190
- * (`@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js:45-70`,
1191
- * source `src/core/tools/path-utils.ts:52-83`) resolves, and when that path
1192
- * does not exist it silently retries four unicode/spacing variants in this
1193
- * exact order:
1194
- *
1195
- * 1. narrow no-break space before `AM.`/`PM.` (`tryMacOSScreenshotPath`)
1196
- * 2. NFD normalization (`tryNFDVariant`) — macOS stores names decomposed
1197
- * 3. U+0027 → U+2019 (`tryCurlyQuoteVariant`)
1198
- * 4. NFD + curly quote combined
1199
- *
1200
- * Each candidate is used only when it DIFFERS from the resolved path and the
1201
- * file exists; otherwise pi falls back to the resolved path. So the file pi
1202
- * actually read can differ from what a naive `path.resolve` produces, and
1203
- * pi-lens keyed its read guard, LSP touch, and dispatch off the naive form —
1204
- * silently doing nothing for exactly those files.
1205
- *
1206
- * Order matters: it is pi's, so pi-lens picks the same file pi did when more
1207
- * than one variant happens to exist.
1208
- *
1209
- * @param resolvedPath an already-resolved absolute path (the naive form)
1210
- * @param fileExists injectable existence probe; defaults to `existsSync`
1211
- */
1212
- export function resolveHostPathVariants(resolvedPath, fileExists = existsSync) {
1213
- if (fileExists(resolvedPath)) {
1214
- return { path: resolvedPath, unresolved: false, triedVariants: [] };
1215
- }
1216
- const nfd = resolvedPath.normalize("NFD");
1217
- const candidates = [
1218
- {
1219
- variant: "narrow-nbsp",
1220
- candidate: resolvedPath.replace(/ (AM|PM)\./gi, `${NARROW_NO_BREAK_SPACE}$1.`),
1221
- },
1222
- { variant: "nfd", candidate: nfd },
1223
- {
1224
- variant: "curly-quote",
1225
- candidate: resolvedPath.replaceAll("'", RIGHT_SINGLE_QUOTE),
1226
- },
1227
- {
1228
- variant: "nfd-curly-quote",
1229
- candidate: nfd.replaceAll("'", RIGHT_SINGLE_QUOTE),
1230
- },
1231
- ];
1232
- const triedVariants = [];
1233
- for (const { variant, candidate } of candidates) {
1234
- // pi skips a candidate identical to the resolved path, so pi-lens does
1235
- // too — otherwise a no-op "variant" would be reported as a match.
1236
- if (candidate === resolvedPath)
1237
- continue;
1238
- triedVariants.push(variant);
1239
- if (fileExists(candidate)) {
1240
- return { path: candidate, variant, unresolved: false, triedVariants };
1241
- }
1242
- }
1243
- return { path: resolvedPath, unresolved: true, triedVariants };
1244
- }