@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,1610 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import { hashText, normalizeMessage, stableFindingId, } from "./finding-identity.js";
5
- import { applyWorkspaceEdit } from "./lsp/edits.js";
6
- import { getLSPService } from "./lsp/index.js";
7
- import { isUnderDir, normalizeMapKey } from "./path-utils.js";
8
- import { bounded, combineAbortSignals, withDeadline, } from "./deadline-utils.js";
9
- import { armDeferredLspWork, awaitDeferredLspWork, registerDeferredLspWork, } from "./deferred-lsp-work.js";
10
- import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
11
- import { recordLspMutationBatch, } from "./lsp-mutation.js";
12
- import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
13
- import { logActionableWarningsEvent } from "./actionable-warnings-logger.js";
14
- import { displayProjectDataPath, getProjectDataDir } from "./file-utils.js";
15
- import { commitDurableStore } from "./durable-store.js";
16
- import { establishToolAgreement } from "./tool-agreement.js";
17
- import { resolveLensToolName } from "./tool-config.js";
18
- let beforeWarningStateLockForTests = null;
19
- /** Test seam for a sibling process commit immediately before lock acquisition. */
20
- export function _setBeforeWarningStateLockForTests(hook) {
21
- beforeWarningStateLockForTests = hook;
22
- }
23
- /** #1816: was a local `relativeFile|tool|source|code|rule|normalizedMessage|
24
- * line` hash, hand-rolled independently of `diagnostic-dispositions.ts`'s
25
- * canonicalizing version — this is now the shared `finding-identity.js`
26
- * builder (which DOES canonicalize both `cwd` and `filePath` through
27
- * `normalizeMapKey` before relativizing, and hashes to 12 chars, matching
28
- * dispositions). See `legacyActionableWarningId` below for the pre-#1816
29
- * formula, kept only for on-disk suppression-store migration. */
30
- export function createActionableWarningId(args) {
31
- return stableFindingId("aw:", {
32
- cwd: args.cwd,
33
- filePath: args.filePath,
34
- parts: [
35
- args.tool,
36
- args.source,
37
- args.code,
38
- args.rule,
39
- normalizeMessage(args.message),
40
- args.line,
41
- ],
42
- });
43
- }
44
- /** PRE-#1816 id formula (raw, non-canonicalized `relativeFile`; 10-char
45
- * hash). `actionable-warning-state.json` is a keyed, persisted store — a
46
- * warning suppressed under the old formula must not silently reappear as
47
- * unsuppressed just because this module unified onto the canonical,
48
- * 12-char id. `suppressionFor`/`updateWarningState` use this ONLY to look up
49
- * and migrate a still-pending old entry forward; nothing ever WRITES under
50
- * this id. Do not canonicalize this function — that would make it identical
51
- * to `createActionableWarningId` and silently defeat the migration lookup
52
- * for every path that actually needed canonicalizing (the #533 class this
53
- * whole item exists to fix). Review-round F3 (#1816): this guard is only
54
- * provable under a MIS-CASED path fixture — a fixture whose raw and
55
- * canonical forms coincide (a bare mkdtempSync path) makes canonicalizing
56
- * this function a no-op, so the regression test must seed under a mis-cased
57
- * segment (see `actionable-warnings.test.ts`'s migration describe block) or
58
- * the guard passes vacuously either way. */
59
- function legacyActionableWarningId(args) {
60
- const rel = path.relative(args.cwd, args.filePath).replace(/\\/g, "/");
61
- const legacyRelativeFile = rel && !rel.startsWith("..") ? rel : normalizeMapKey(args.filePath);
62
- const parts = [
63
- legacyRelativeFile,
64
- args.tool ?? "",
65
- args.source ?? "",
66
- String(args.code ?? ""),
67
- args.rule ?? "",
68
- normalizeMessage(args.message),
69
- String(args.line ?? ""),
70
- ];
71
- return `aw:${hashText(parts.join("|"), 10)}`;
72
- }
73
- function actionSafety(action) {
74
- const kind = action.kind ?? "";
75
- if (!kind.startsWith("quickfix"))
76
- return { eligible: false, reason: "not_quickfix" };
77
- if (!action.isPreferred)
78
- return { eligible: false, reason: "not_preferred" };
79
- if (!action.edit)
80
- return { eligible: false, reason: "no_edit" };
81
- if (action.command)
82
- return { eligible: false, reason: "has_command" };
83
- return { eligible: true };
84
- }
85
- function serializeAction(action) {
86
- const safety = actionSafety(action);
87
- return {
88
- title: action.title,
89
- kind: action.kind,
90
- isPreferred: action.isPreferred,
91
- hasEdit: Boolean(action.edit),
92
- hasCommand: Boolean(action.command),
93
- autoFixEligible: safety.eligible,
94
- skipReason: safety.reason,
95
- };
96
- }
97
- function deserializeSuppressionState(contents) {
98
- try {
99
- const parsed = JSON.parse(contents ?? "");
100
- return parsed && typeof parsed === "object"
101
- ? parsed
102
- : {};
103
- }
104
- catch {
105
- return {};
106
- }
107
- }
108
- function readSuppressionState(cwd) {
109
- const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
110
- try {
111
- return deserializeSuppressionState(fs.readFileSync(statePath, "utf8"));
112
- }
113
- catch {
114
- return {};
115
- }
116
- }
117
- function updateWarningState(cwd, warnings) {
118
- const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
119
- fs.mkdirSync(path.dirname(statePath), { recursive: true });
120
- const hook = beforeWarningStateLockForTests;
121
- beforeWarningStateLockForTests = null;
122
- hook?.();
123
- commitDurableStore({
124
- path: statePath,
125
- deserialize: deserializeSuppressionState,
126
- merge: (state) => {
127
- const now = new Date().toISOString();
128
- state.warnings ??= {};
129
- for (const warning of warnings) {
130
- // #1816 migration: this warning may still be recorded under the
131
- // pre-#1816 id (raw relativeFile, 10-char hash) from before this
132
- // store unified onto the canonical id. Fold that entry forward
133
- // onto the current id and drop the stale key, so a warning
134
- // suppressed before the migration stays suppressed, and repeated
135
- // re-encounters converge the store onto one id per warning
136
- // instead of accumulating both forever.
137
- //
138
- // Review-round F1: this MUST read `warning.legacyId` — the value
139
- // `suppressionFor` already computed from the exact identity args
140
- // used at lookup time — and must NEVER re-derive a legacy id from
141
- // `warning.rule`/`tool`/`source`/`code`. Those fields hold
142
- // DISPLAY values that diverge from the id-construction args for
143
- // LSP-origin records (see `ActionableWarningRecord.legacyId`'s
144
- // doc comment), so a re-derivation here would compute a
145
- // different legacy id than the one `suppressionFor` checked,
146
- // permanently bifurcating the store.
147
- const legacyId = warning.legacyId;
148
- const legacyEntry = legacyId && legacyId !== warning.id
149
- ? state.warnings[legacyId]
150
- : undefined;
151
- const existing = state.warnings[warning.id] ?? legacyEntry ?? {};
152
- state.warnings[warning.id] = {
153
- ...existing,
154
- status: existing.status ?? "active",
155
- firstSeenAt: existing.firstSeenAt ?? now,
156
- lastSeenAt: now,
157
- seenCount: (existing.seenCount ?? 0) + 1,
158
- };
159
- if (legacyEntry && legacyId)
160
- delete state.warnings[legacyId];
161
- }
162
- return state;
163
- },
164
- serialize: (state) => JSON.stringify(state, null, 2),
165
- waitMs: 2_000,
166
- retryMs: 10,
167
- timeoutMessage: "timed out acquiring actionable warning store lock",
168
- onContention: "skip-log",
169
- logContention: () => logActionableWarningsEvent({
170
- event: "warning_state_write_dropped",
171
- metadata: { reason: "lock_contention" },
172
- }),
173
- });
174
- }
175
- /** Looks up suppression under the current id, falling back to the pre-#1816
176
- * id (see `legacyActionableWarningId`) so a warning suppressed before this
177
- * migration doesn't silently reappear as unsuppressed. `args` is the exact
178
- * identity shape both id builders take.
179
- *
180
- * Also RETURNS the `legacyId` it computed (review-round F1, #1816): the
181
- * caller carries it onto the record's `legacyId` field so
182
- * `updateWarningState` can migrate the SAME legacy id this lookup used,
183
- * instead of re-deriving one from the record's own `rule`/`tool`/`source`/
184
- * `code` fields later. Re-deriving is unsound for LSP-origin records —
185
- * `recordFromLspDiagnostic` passes no `rule` into this function (LSP
186
- * diagnostics don't have one), then sets `record.rule` afterward to
187
- * `${source}:${code}` purely for display. Recomputing from that display
188
- * value would silently compute a DIFFERENT legacy id than the one actually
189
- * checked here, permanently bifurcating the store: a suppression written
190
- * this turn under the current id would never be found again next turn. */
191
- function suppressionFor(cwd, id, args) {
192
- const state = readSuppressionState(cwd);
193
- const legacyId = legacyActionableWarningId({ cwd, ...args });
194
- const entry = state.warnings?.[id] ??
195
- (legacyId !== id ? state.warnings?.[legacyId] : undefined);
196
- return {
197
- suppressed: entry?.status === "suppressed",
198
- reason: entry?.reason,
199
- legacyId,
200
- };
201
- }
202
- export function recordFromDispatchDiagnostic(diagnostic, cwd) {
203
- if (diagnostic.semantic !== "warning")
204
- return undefined;
205
- // #1777: the old gate demanded `severity === "warning"` exactly, which was
206
- // invisible while the ast-grep runner collapsed every non-error tier to
207
- // "warning". Now that hint and info survive the dispatch path, admit them:
208
- // a fix is a fix, and the tier governs how loudly a finding renders, not
209
- // whether its fix is worth offering. `error` still routes to the blocking
210
- // path, so it stays out even when a runner leaves `semantic` at "warning".
211
- if (diagnostic.severity === "error")
212
- return undefined;
213
- if (!diagnostic.fixable && !diagnostic.fixSuggestion)
214
- return undefined;
215
- const filePath = path.resolve(cwd, diagnostic.filePath);
216
- const identityArgs = {
217
- filePath,
218
- tool: diagnostic.tool,
219
- code: diagnostic.code,
220
- rule: diagnostic.rule,
221
- message: diagnostic.message,
222
- line: diagnostic.line,
223
- };
224
- const id = createActionableWarningId({ cwd, ...identityArgs });
225
- const suppression = suppressionFor(cwd, id, identityArgs);
226
- return {
227
- id,
228
- filePath,
229
- displayPath: toRunnerDisplayPath(cwd, filePath),
230
- line: diagnostic.line,
231
- column: diagnostic.column,
232
- severity: diagnostic.severity,
233
- tool: diagnostic.tool,
234
- code: diagnostic.code,
235
- rule: diagnostic.rule,
236
- message: diagnostic.message,
237
- fixSuggestion: diagnostic.fixSuggestion,
238
- fixKind: diagnostic.fixKind,
239
- autoFixAvailable: diagnostic.autoFixAvailable,
240
- actions: [],
241
- suppressed: suppression.suppressed,
242
- suppressionReason: suppression.reason,
243
- legacyId: suppression.legacyId,
244
- origin: "dispatch",
245
- };
246
- }
247
- function lineInModifiedRanges(line, ranges) {
248
- if (line === undefined)
249
- return true;
250
- if (ranges.length === 0)
251
- return true;
252
- return ranges.some((range) => line >= range.start - 2 && line <= range.end + 2);
253
- }
254
- export function recordFromLspDiagnostic(diag, filePath, cwd) {
255
- const line = diag.range.start.line + 1;
256
- const column = diag.range.start.character + 1;
257
- // Keep agreement keyed to the producer that supplied the diagnostic. The
258
- // generic `lsp` label is only a last-resort identity: treating it as a
259
- // registered writer would establish every LSP quickfix without evidence.
260
- const producer = diag.source && diag.source !== "lsp" ? diag.source : diag.serverId;
261
- const source = diag.source ?? "lsp";
262
- const code = diag.code === undefined ? undefined : String(diag.code);
263
- const identityArgs = {
264
- filePath,
265
- tool: producer ?? "lsp",
266
- source,
267
- code,
268
- message: diag.message,
269
- line,
270
- };
271
- const id = createActionableWarningId({ cwd, ...identityArgs });
272
- const suppression = suppressionFor(cwd, id, identityArgs);
273
- return {
274
- id,
275
- filePath,
276
- displayPath: toRunnerDisplayPath(cwd, filePath),
277
- line,
278
- column,
279
- severity: "warning",
280
- tool: producer ?? "lsp",
281
- source,
282
- code,
283
- rule: code ? `${source}:${code}` : source,
284
- message: diag.message,
285
- actions: [],
286
- suppressed: suppression.suppressed,
287
- suppressionReason: suppression.reason,
288
- legacyId: suppression.legacyId,
289
- origin: "lsp",
290
- };
291
- }
292
- function mergeWarnings(records) {
293
- const byId = new Map();
294
- for (const record of records) {
295
- const existing = byId.get(record.id);
296
- if (!existing) {
297
- byId.set(record.id, { ...record, actions: [...record.actions] });
298
- continue;
299
- }
300
- existing.origin =
301
- existing.origin === record.origin ? existing.origin : "merged";
302
- existing.fixSuggestion ??= record.fixSuggestion;
303
- existing.fixKind ??= record.fixKind;
304
- existing.autoFixAvailable ||= record.autoFixAvailable;
305
- const seenActions = new Set(existing.actions.map((a) => `${a.kind ?? ""}|${a.title}`));
306
- for (const action of record.actions) {
307
- const key = `${action.kind ?? ""}|${action.title}`;
308
- if (!seenActions.has(key)) {
309
- existing.actions.push(action);
310
- seenActions.add(key);
311
- }
312
- }
313
- }
314
- return [...byId.values()].sort((a, b) => a.displayPath.localeCompare(b.displayPath) ||
315
- (a.line ?? 0) - (b.line ?? 0));
316
- }
317
- /**
318
- * #2504 — bounds on the LSP enrichment loop.
319
- *
320
- * This function runs on the AWAITED turn_end hook. With `includeLspCodeActions`
321
- * on and a cold LSP cache it opened every file it was handed and pulled fresh
322
- * per-file diagnostics serially at ~880 ms each: 147 files, 187 891 ms, for
323
- * `warnings: 0`. Three bounds, plus a project-root filter (it had opened
324
- * `~/.claude/plans/*.md` in an LSP client), plus a deferral: when the turn
325
- * primed NO cache, every file would be a fresh pull, so the whole loop moves
326
- * off the hook and delivers through the cached channel instead.
327
- */
328
- export const ACTIONABLE_WARNINGS_LSP_FILE_CAP = 25;
329
- export const ACTIONABLE_WARNINGS_LSP_BUDGET_MS = 2_500;
330
- export const ACTIONABLE_WARNINGS_DEFERRED_BUDGET_MS = 60_000;
331
- /**
332
- * #2504 review round 2 (F3): the per-round-trip bound. The batch deadlines
333
- * above are checked only BETWEEN files, so a single wedged `getDiagnostics`
334
- * (or `openFile`, or `codeAction`) was unbounded no matter how small the
335
- * batch budget was. Generous relative to the ~880 ms a real cold pull costs —
336
- * this is a wedge detector, not a latency target.
337
- */
338
- export const ACTIONABLE_WARNINGS_LSP_PULL_TIMEOUT_MS = 10_000;
339
- /**
340
- * #2504 review round 3 (S-2): the third bound, per FILE.
341
- *
342
- * The wall budgets above are re-checked BETWEEN files only — deliberately, so
343
- * that a file already opened is finished rather than half-enriched — and the
344
- * per-round-trip timeout bounds one call. Neither bounds the COUNT of calls
345
- * one file can demand: a generated or vendored file with hundreds of warnings
346
- * on modified lines costs one `codeAction` round trip each, all inside a
347
- * single between-files interval. This caps that fan-out. The abort signal
348
- * still escapes promptly — `boundedLspCall` checks it on every trip.
349
- */
350
- export const ACTIONABLE_WARNINGS_MAX_CODE_ACTIONS_PER_FILE = 25;
351
- /**
352
- * The in-flight deferred fresh-pull, if any. Exposed for tests only: the
353
- * deferral is fire-and-forget by design, and a test asserting that the work
354
- * still happens off-hook needs a handle to await.
355
- *
356
- * #2504 review round 2 (F3): the handle no longer lives here as a bare
357
- * module-level `let` that a second deferral silently overwrote (leaving the
358
- * first loop running, untracked and unstoppable) and that nothing ever reset.
359
- * `clients/deferred-lsp-work.ts` owns the single slot and its abort signal.
360
- */
361
- export function _awaitDeferredLspPullForTest() {
362
- return awaitDeferredLspWork();
363
- }
364
- /**
365
- * Both bounds on ONE LSP round trip (#2504 review round 2, F3), as AGENTS.md
366
- * requires of any async step in a sweep loop: a per-call timeout AND the abort
367
- * signal. Resolves `undefined` when either bound wins; the caller reads that
368
- * as "this file was not checked", never as "this file is clean".
369
- *
370
- * #2523 slice 2 folded this onto `bounded()`. It used to be `withDeadline`
371
- * (deadline only) raced against a hand-built `abortRace` leg carried on the
372
- * deps — the fifth private spelling of "deadline AND signal".
373
- *
374
- * What the fold did NOT fix, stated because the first draft of this PR claimed
375
- * it did (#2557 review F5): the old shape did not silently degrade any wait.
376
- * Both construction sites derived the leg from the signal in the same
377
- * expression (`args.signal ? makeAbortRace(args.signal) : undefined`), so
378
- * `abortRace` was absent exactly when `signal` was, and the behaviour before
379
- * and after this fold is identical on that axis. What the fold actually buys
380
- * is that the two fields which had to AGREE are now one — a pairing invariant
381
- * that lived in prose is gone rather than enforced — plus the abandonment now
382
- * reaching the ledger, and the leg's listener now being released per call
383
- * instead of living as long as the loop's signal does.
384
- *
385
- * ## The loop's residual budget is a THIRD bound, and it must not reach the
386
- * ledger (#2557 review F-A)
387
- *
388
- * Round 1 clamped `bounded()`'s own timer to
389
- * `Math.min(pullTimeoutMs, remainingMs)`, so whenever the LOOP's own wall
390
- * budget was already low, `bounded()` armed at the shrunken value and — on
391
- * firing — recorded `hook-await-exceeded` naming THAT value as "the budget".
392
- * A healthy pull that simply started late in a big batch was reported as
393
- * exceeding a budget that exists in no configuration (measured: 400 ms
394
- * pulls, 12 files, "exceeded 65ms budget after 77ms"), and because
395
- * `recordDegradationOnce` is rising-edge, that benign row could silence a
396
- * genuinely wedged server later in the same session.
397
- *
398
- * `bounded()`'s OWN timer is now always armed at the full `pullTimeoutMs`, so
399
- * it only ever fires — and only ever records — when a call outlives its own
400
- * configured per-trip budget (`metadata.budgetMs === pullTimeoutMs`, always).
401
- * The loop's shrinking residual is enforced SEPARATELY, by racing `bounded()`
402
- * against `withDeadline(…, { onTimeout: "undefined" })` keyed to the loop's
403
- * own `deadlineAt` — a deadline-only wait with no ledger connection of its
404
- * own, the same primitive `bounded()` itself is built to replace when a
405
- * caller needs BOTH bounds. The still-running `bounded()` call is not
406
- * cancelled (nothing here can cancel already-started work); if it later
407
- * genuinely outlives `pullTimeoutMs`, it still records, correctly, on its own
408
- * clock.
409
- */
410
- async function boundedLspCall(call, deps) {
411
- if (deps.signal?.aborted)
412
- return undefined;
413
- // #2504 review round 4 (F2): the loop's wall budget bounds THIS trip too. A
414
- // round trip that starts with 5 ms of budget left may not run for 10 s just
415
- // because the per-call timeout says so -- that is how one file held the
416
- // awaited hook for minutes past a spent batch budget.
417
- const remainingMs = deps.deadlineAt !== undefined ? deps.deadlineAt - Date.now() : undefined;
418
- // Returned BEFORE `bounded()` rather than handed to it as a zero budget: a
419
- // trip that never started did not exceed anything, and recording it as an
420
- // exceedance would attribute the loop's spent budget to whichever call
421
- // happened to be next.
422
- if (remainingMs !== undefined && remainingMs <= 0)
423
- return undefined;
424
- const inner = bounded(call().then((value) => ({ value })), {
425
- // Always the FULL per-trip budget -- never clamped to the loop's own
426
- // residual. The residual is enforced below, off the ledger.
427
- ms: deps.pullTimeoutMs,
428
- // Optional for the in-band caller; `bounded()` reads a missing signal
429
- // as one that never aborts, so the deadline half stays live.
430
- signal: deps.signal,
431
- // From the deps, never a literal: the in-band and deferred loops must
432
- // key separately (#2557 review F3).
433
- hook: deps.site.hook,
434
- label: deps.site.label,
435
- });
436
- const boxed = deps.deadlineAt !== undefined
437
- ? await withDeadline(inner, {
438
- deadlineAt: deps.deadlineAt,
439
- onTimeout: "undefined",
440
- })
441
- : await inner;
442
- // Boxed because an LSP pull legitimately resolves `undefined` (no
443
- // diagnostics for this file), which must stay distinguishable from a bound
444
- // firing even though both currently mean "not checked" to the caller.
445
- return boxed?.value;
446
- }
447
- /** Positive finite bound, else the default. Guards NaN from env/config. */
448
- function boundedNumber(value, fallback) {
449
- return value !== undefined && Number.isFinite(value) && value > 0
450
- ? value
451
- : fallback;
452
- }
453
- export async function buildActionableWarningsReport(args) {
454
- const cwd = path.resolve(args.cwd);
455
- const records = [...args.dispatchWarnings];
456
- const lspService = getLSPService();
457
- // #2504 review round 5 (F2): when each file's LSP observation FINISHED,
458
- // keyed by normalized path. assembleReport used to stamp every entry with
459
- // one `new Date()` taken at ASSEMBLY -- which for the deferred loop is the
460
- // loop's END, so a file read 60 s and 24 files ago claimed to be
461
- // milliseconds old. applyDeltaFreshnessGate compares that stamp against the
462
- // file's mtime to catch an out-of-band edit made after the observation, and
463
- // a stamp that late hands it a window which has already closed. Shared with
464
- // the deferred closure below, so the in-band entries it carries over keep
465
- // the stamps their own pass gave them.
466
- const observedAtByPath = new Map();
467
- // The conservative stand-in for an entry no LSP pull ever touched (a
468
- // dispatch-origin warning): the moment this build began. Never LATER than
469
- // the observation it stands in for, which is the direction that matters --
470
- // an over-old stamp costs a line number, an over-new one leaks a stale one.
471
- const buildStartedAt = new Date().toISOString();
472
- logActionableWarningsEvent({
473
- event: "report_started",
474
- sessionId: args.sessionId,
475
- metadata: {
476
- turnIndex: args.turnIndex,
477
- filesCount: args.files.length,
478
- dispatchWarningsCount: args.dispatchWarnings.length,
479
- deltaOnly: args.deltaOnly !== false,
480
- includeLspCodeActions: args.includeLspCodeActions,
481
- },
482
- });
483
- if (args.includeLspCodeActions) {
484
- const fileCap = Math.floor(boundedNumber(args.lspFileCap, ACTIONABLE_WARNINGS_LSP_FILE_CAP));
485
- const budgetMs = boundedNumber(args.lspBudgetMs, ACTIONABLE_WARNINGS_LSP_BUDGET_MS);
486
- // #2504 (1) project-root filter. The worklist this loop is handed had
487
- // accumulated paths from two other agents' scratchpads, `~/.claude/plans`
488
- // and `~/.plegma/work` — none of which belong to an LSP client rooted at
489
- // this project. Rejected before any file read.
490
- const eligible = [];
491
- let outsideRoot = 0;
492
- for (const file of args.files) {
493
- const filePath = path.resolve(cwd, file);
494
- if (normalizeMapKey(filePath) === normalizeMapKey(cwd) ||
495
- !isUnderDir(filePath, cwd)) {
496
- outsideRoot++;
497
- logActionableWarningsEvent({
498
- event: "lsp_file_skipped",
499
- sessionId: args.sessionId,
500
- filePath,
501
- metadata: { reason: "outside_project_root" },
502
- });
503
- continue;
504
- }
505
- if (!lspService.supportsLSP(filePath)) {
506
- logActionableWarningsEvent({
507
- event: "lsp_file_skipped",
508
- sessionId: args.sessionId,
509
- filePath,
510
- metadata: { reason: "no_lsp_support" },
511
- });
512
- continue;
513
- }
514
- eligible.push(filePath);
515
- }
516
- if (outsideRoot > 0) {
517
- args.dbg?.(`actionable_warnings: skipped ${outsideRoot} file(s) outside the project root`);
518
- }
519
- // #2504 (2) file cap.
520
- const capped = eligible.slice(0, fileCap);
521
- if (capped.length < eligible.length) {
522
- recordDegradationOnce({
523
- kind: "actionable-warnings-cap",
524
- subject: `${cwd}:file-cap`,
525
- reason: `LSP enrichment capped at ${fileCap} file(s); ${eligible.length - capped.length} modified file(s) were not checked for code actions this turn`,
526
- });
527
- }
528
- // Resolve every file's cache state BEFORE doing any LSP work. This is a
529
- // read + a sha256 per file (sub-millisecond) and it is what tells us
530
- // whether the turn primed the cache at all — the difference between a
531
- // loop that costs nothing and one that costs ~880 ms per file.
532
- const primed = [];
533
- const cold = [];
534
- for (const filePath of capped) {
535
- // Reuse the cache primed by the dispatch pipeline's touchFile earlier
536
- // in this turn — but only when it is verified current. A second
537
- // open+wait here costs ~1 s/file with the LSP cold, so we pass the
538
- // hash of the current file bytes: getLastKnownDiagnostics returns the
539
- // entry only if it was primed for the SAME content, so a previous
540
- // turn's diagnostics are never served as current. On any miss (no
541
- // entry, content drift, or an entry written without content) the file
542
- // needs a fresh read.
543
- const content = fs.existsSync(filePath)
544
- ? fs.readFileSync(filePath, "utf-8")
545
- : undefined;
546
- const contentHash = content !== undefined
547
- ? createHash("sha256").update(content).digest("hex")
548
- : undefined;
549
- const cached = contentHash !== undefined
550
- ? lspService.getLastKnownDiagnostics(filePath, contentHash)
551
- : undefined;
552
- (cached !== undefined ? primed : cold).push({
553
- filePath,
554
- content,
555
- cached,
556
- });
557
- }
558
- // #2504 (3) wall budget, raced against the turn's abort signal. Both
559
- // bounds, per AGENTS.md: neither a cap nor a deadline alone stops a
560
- // cancelled turn from paying for work nobody is waiting for.
561
- const deadline = Date.now() + budgetMs;
562
- const exhausted = () => args.signal?.aborted === true || Date.now() >= deadline;
563
- const pullTimeoutMs = boundedNumber(args.lspPullTimeoutMs, ACTIONABLE_WARNINGS_LSP_PULL_TIMEOUT_MS);
564
- // #2504 review round 2 (F3): the in-band loop gets the same per-call
565
- // bound and the same once-resolved service as the deferred one — the
566
- // deferral must not be the only path that is bounded.
567
- const inBandDeps = {
568
- lspService,
569
- pullTimeoutMs,
570
- // #2504 review round 4 (F2). This is the AWAITED hook: the batch
571
- // budget has to bound what happens INSIDE a file, not only how many
572
- // files get started.
573
- deadlineAt: deadline,
574
- signal: args.signal,
575
- // This loop IS on the awaited hook, so its exceedances are turn_end's.
576
- site: { hook: "turn_end", label: "lspEnrichmentRoundTrip" },
577
- };
578
- let unchecked = 0;
579
- const runInBand = async (targets) => {
580
- for (const target of targets) {
581
- if (exhausted()) {
582
- unchecked += targets.length - targets.indexOf(target);
583
- break;
584
- }
585
- records.push(...(await enrichFileFromLsp(cwd, args, target, inBandDeps)));
586
- // #2504 review round 5 (F2): stamped when THIS file's pull
587
- // returned, not when the report is assembled.
588
- observedAtByPath.set(normalizeMapKey(target.filePath), new Date().toISOString());
589
- }
590
- };
591
- // Cached files first: they are free, and whether ANY of them exist is
592
- // what decides the cold set's fate below.
593
- await runInBand(primed);
594
- // #2504 (4) cold-cache deferral. When the turn primed nothing, every
595
- // remaining file is a full open + diagnostic wait; that whole loop is
596
- // what held the terminal for 187 s. It still runs — off the awaited
597
- // hook — and lands in the same `actionable-warnings` cache the in-band
598
- // report goes to, so the findings reach the agent by the same channel,
599
- // one turn later at worst.
600
- if (cold.length > 0 && primed.length === 0) {
601
- // #2504 review round 2 (F3). The pre-fix loop captured
602
- // `args.signal` — the COMPLETED turn's `ctx.signal`, which
603
- // `index.ts` clears from the ambient slot in its `finally`, so it
604
- // could never fire. `armDeferredLspWork` returns the module slot's
605
- // live signal; `resetLSPService` fires it, which is how
606
- // session_shutdown, session_start and the idle reset all reach this
607
- // loop. The turn's own signal is still folded in so a genuine
608
- // mid-turn abort counts.
609
- //
610
- // #2504 review round 3 (F-A(d)): it returns `undefined` when an
611
- // EARLIER deferral still holds the slot. The incumbent wins — see
612
- // `armDeferredLspWork` — and this turn states its loss instead of
613
- // cancelling a loop that is about to publish.
614
- const deferredSignal = armDeferredLspWork();
615
- if (deferredSignal === undefined) {
616
- incrementDegradationCount({
617
- kind: "actionable-warnings-cap",
618
- subject: `${cwd}:deferral-declined`,
619
- // #2504 review round 4 (F1): the old wording ended "rather than
620
- // cancel it", which asserted a preservation that did not
621
- // happen -- the incumbent's report was then discarded whole by
622
- // the persisted-newer guard. With the per-file merge below it
623
- // is true, so it now says exactly WHAT survives and what does
624
- // not.
625
- reason: `an earlier deferred LSP pull is still running; ${cold.length} file(s) went unchecked for code actions this turn. What IS preserved is the incumbent loop's work: when it lands, its per-file entries are merged into whatever report is persisted then, for every file whose fileSeq has not advanced. This turn's own cold files are the loss, and nothing re-derives them -- the next report is a delta over a different file set`,
626
- });
627
- logActionableWarningsEvent({
628
- event: "lsp_pull_deferral_declined",
629
- sessionId: args.sessionId,
630
- metadata: { turnIndex: args.turnIndex, files: cold.length },
631
- });
632
- args.dbg?.(`actionable_warnings: an earlier deferred LSP pull still holds the slot — skipping ${cold.length} fresh pull(s) this turn`);
633
- }
634
- else {
635
- const carried = [...records];
636
- const deferredArgs = args;
637
- const loopSignal = combineAbortSignals(args.signal, deferredSignal) ?? deferredSignal;
638
- const deferredDeps = {
639
- lspService,
640
- pullTimeoutMs,
641
- signal: loopSignal,
642
- // OFF the hook by construction: this loop is the answer to
643
- // turn_end's budget, not a spender of it, and it runs on its
644
- // own ACTIONABLE_WARNINGS_DEFERRED_BUDGET_MS deadline. Charging
645
- // its exceedances to `turn_end` would make the hook look over
646
- // budget for having correctly deferred the work (#2557 F3).
647
- site: {
648
- hook: "off_hook",
649
- label: "deferredLspEnrichmentRoundTrip",
650
- },
651
- };
652
- const deferredWork = (async () => {
653
- // Yield a full macrotask first. Without this the loop would run
654
- // its first open+pull inside the awaited call's own microtask
655
- // drain — "deferred" only on paper, and still on the hook.
656
- await new Promise((resolve) => setTimeout(resolve, 0));
657
- const deferredRecords = [...carried];
658
- const deferredDeadline = Date.now() + ACTIONABLE_WARNINGS_DEFERRED_BUDGET_MS;
659
- // #2504 review round 4 (F2): set here, not at construction --
660
- // the deferred loop's budget starts when the loop does, one
661
- // macrotask after these deps were built.
662
- deferredDeps.deadlineAt = deferredDeadline;
663
- let deferredUnchecked = 0;
664
- let abortedMidLoop = false;
665
- for (const target of cold) {
666
- if (loopSignal.aborted || Date.now() >= deferredDeadline) {
667
- abortedMidLoop = loopSignal.aborted;
668
- deferredUnchecked += cold.length - cold.indexOf(target);
669
- break;
670
- }
671
- deferredRecords.push(...(await enrichFileFromLsp(cwd, deferredArgs, target, deferredDeps)));
672
- // #2504 review round 5 (F2). This is the loop the defect
673
- // was about: it can run for a minute, and one stamp taken
674
- // at its end described all of it.
675
- observedAtByPath.set(normalizeMapKey(target.filePath), new Date().toISOString());
676
- }
677
- if (deferredUnchecked > 0) {
678
- recordDegradationOnce({
679
- kind: "actionable-warnings-cap",
680
- subject: `${cwd}:deferred-budget`,
681
- reason: `deferred LSP enrichment stopped early; ${deferredUnchecked} file(s) were not checked for code actions`,
682
- });
683
- }
684
- // An ABORTED loop delivers nothing (#2504 review round 2, F3).
685
- // The service it was reading is gone — session_shutdown,
686
- // session_start, or the idle reset retired it — so its partial
687
- // record set describes nothing current, and publishing it would
688
- // be exactly the stale-clobber F2 guards against on the other
689
- // side.
690
- if (abortedMidLoop || loopSignal.aborted) {
691
- logActionableWarningsEvent({
692
- event: "lsp_pull_aborted",
693
- sessionId: deferredArgs.sessionId,
694
- metadata: {
695
- turnIndex: deferredArgs.turnIndex,
696
- unchecked: deferredUnchecked,
697
- },
698
- });
699
- return;
700
- }
701
- deferredArgs.onDeferredReport?.(assembleReport(cwd, deferredArgs, deferredRecords, {
702
- observedAtByPath,
703
- fallbackObservedAt: buildStartedAt,
704
- }));
705
- })().catch((err) => {
706
- args.dbg?.(`actionable_warnings: deferred LSP pull failed: ${err}`);
707
- });
708
- registerDeferredLspWork(deferredSignal, deferredWork);
709
- logActionableWarningsEvent({
710
- event: "lsp_pull_deferred",
711
- sessionId: args.sessionId,
712
- metadata: { turnIndex: args.turnIndex, files: cold.length },
713
- });
714
- args.dbg?.(`actionable_warnings: no LSP cache primed this turn — deferring ${cold.length} fresh pull(s) off the turn_end hook`);
715
- }
716
- }
717
- else {
718
- await runInBand(cold);
719
- }
720
- if (unchecked > 0) {
721
- recordDegradationOnce({
722
- kind: "actionable-warnings-cap",
723
- subject: `${cwd}:wall-budget`,
724
- reason: `LSP enrichment hit its ${Math.round(budgetMs)}ms turn budget; ${unchecked} file(s) were not checked for code actions this turn`,
725
- });
726
- }
727
- }
728
- return assembleReport(cwd, args, records, {
729
- observedAtByPath,
730
- fallbackObservedAt: buildStartedAt,
731
- });
732
- }
733
- /**
734
- * Enrich one file's LSP warnings into records. Split out of
735
- * `buildActionableWarningsReport` (#2504) so the in-band loop and the deferred
736
- * off-hook loop run byte-identical logic — the deferral must not become a
737
- * second, drifting copy of the enrichment.
738
- */
739
- export async function enrichFileFromLsp(cwd, args, target, deps) {
740
- const { lspService } = deps;
741
- const { filePath } = target;
742
- const out = [];
743
- let diags;
744
- let lspSource = "cache";
745
- if (target.cached !== undefined) {
746
- diags = target.cached;
747
- }
748
- else {
749
- // #2504 review round 2 (F3): never OPEN a file once the loop has been
750
- // signalled. The checks inside `boundedLspCall` cover the round trips;
751
- // this one covers the decision to touch the file at all, which is what
752
- // makes a session_shutdown landing mid-loop a no-op rather than one
753
- // more document handed to a service that is being torn down.
754
- if (deps.signal?.aborted) {
755
- logActionableWarningsEvent({
756
- event: "lsp_file_skipped",
757
- sessionId: args.sessionId,
758
- filePath,
759
- metadata: { reason: "aborted" },
760
- });
761
- return out;
762
- }
763
- try {
764
- if (target.content) {
765
- // #2504 review round 3 (F-B), the #240 shape. `openFile` resolves
766
- // `void`, so the bounded call cannot distinguish success from
767
- // either bound winning unless the success path returns a value of
768
- // its own. Round 2 discarded the result entirely: a 10 s timeout
769
- // or an abort "succeeded" silently, and the pull below then asked
770
- // the server about a document it had never received. The `[]` that
771
- // answers means UNKNOWN, but it was recorded
772
- // `lsp_file_checked lspSource:"fresh"` — a failed pull read as
773
- // clean, which is precisely what the comment on the pull forbids.
774
- const opened = await boundedLspCall(async () => {
775
- await lspService.openFile(filePath, target.content);
776
- return true;
777
- }, deps);
778
- if (opened === undefined) {
779
- logActionableWarningsEvent({
780
- event: "lsp_file_skipped",
781
- sessionId: args.sessionId,
782
- filePath,
783
- metadata: {
784
- reason: deps.signal?.aborted ? "aborted" : "open_timeout",
785
- pullTimeoutMs: deps.pullTimeoutMs,
786
- },
787
- });
788
- return out;
789
- }
790
- }
791
- const pulled = await boundedLspCall(() => lspService.getDiagnostics(filePath), deps);
792
- if (pulled === undefined) {
793
- // Either bound won. A failed pull is NEVER read as clean (#240):
794
- // the file is reported unchecked and contributes no records.
795
- logActionableWarningsEvent({
796
- event: "lsp_file_skipped",
797
- sessionId: args.sessionId,
798
- filePath,
799
- metadata: {
800
- reason: deps.signal?.aborted ? "aborted" : "pull_timeout",
801
- pullTimeoutMs: deps.pullTimeoutMs,
802
- },
803
- });
804
- return out;
805
- }
806
- diags = pulled;
807
- lspSource = "fresh";
808
- }
809
- catch (err) {
810
- args.dbg?.(`actionable_warnings: LSP diagnostics failed for ${filePath}: ${err}`);
811
- logActionableWarningsEvent({
812
- event: "lsp_file_skipped",
813
- sessionId: args.sessionId,
814
- filePath,
815
- metadata: { reason: "lsp_error", error: String(err) },
816
- });
817
- return out;
818
- }
819
- }
820
- const ranges = args.modifiedRangesByFile.get(normalizeMapKey(filePath)) ?? [];
821
- const diagsWarning = diags.filter((d) => d.severity === 2);
822
- let deltaFiltered = 0;
823
- let enriched = 0;
824
- let actionPulls = 0;
825
- let actionCapped = 0;
826
- let budgetStopped = 0;
827
- for (const diag of diagsWarning) {
828
- // #2504 review round 4 (F2). The wall budget used to be re-checked
829
- // BETWEEN files only, so a single file could keep the AWAITED hook for
830
- // openFile + getDiagnostics + up to 25 codeAction round trips long after
831
- // the batch budget expired. The loop deadline is threaded through deps
832
- // and re-read HERE, between round trips: a file already opened still
833
- // finishes its cheap work, but it cannot buy more LSP time.
834
- if (deps.deadlineAt !== undefined && Date.now() >= deps.deadlineAt) {
835
- budgetStopped = diagsWarning.length - diagsWarning.indexOf(diag);
836
- break;
837
- }
838
- const line = diag.range.start.line + 1;
839
- if (args.deltaOnly !== false && !lineInModifiedRanges(line, ranges)) {
840
- deltaFiltered++;
841
- continue;
842
- }
843
- // #2504 review round 3 (S-2): bound the per-file codeAction fan-out.
844
- if (actionPulls >= ACTIONABLE_WARNINGS_MAX_CODE_ACTIONS_PER_FILE) {
845
- actionCapped++;
846
- continue;
847
- }
848
- actionPulls++;
849
- const record = recordFromLspDiagnostic(diag, filePath, cwd);
850
- try {
851
- const actions = await boundedLspCall(() => lspService.codeAction(filePath, diag.range.start.line, diag.range.start.character, diag.range.end.line, diag.range.end.character), deps);
852
- record.actions = (actions ?? []).map(serializeAction).slice(0, 5);
853
- }
854
- catch (err) {
855
- args.dbg?.(`actionable_warnings: LSP codeAction failed for ${filePath}: ${err}`);
856
- }
857
- if (record.actions.length > 0) {
858
- out.push(record);
859
- enriched++;
860
- }
861
- }
862
- if (actionCapped > 0) {
863
- incrementDegradationCount({
864
- kind: "actionable-warnings-cap",
865
- subject: `${cwd}:code-action-fanout`,
866
- // #2504 review round 4 (S-2): "reported without fix actions" was
867
- // wrong in both halves. A capped warning is skipped BEFORE its record
868
- // is built, and a record with no action is dropped below anyway, so
869
- // it is not reported at all -- it never reaches the agent on this
870
- // channel.
871
- reason: `a file exceeded the ${ACTIONABLE_WARNINGS_MAX_CODE_ACTIONS_PER_FILE}-warning code-action cap; ${actionCapped} warning(s) in ${toRunnerDisplayPath(cwd, filePath)} were NOT reported this turn`,
872
- });
873
- }
874
- if (budgetStopped > 0) {
875
- incrementDegradationCount({
876
- kind: "actionable-warnings-cap",
877
- subject: `${cwd}:in-file-budget`,
878
- reason: `the LSP enrichment wall budget expired part-way through a file; ${budgetStopped} warning(s) in ${toRunnerDisplayPath(cwd, filePath)} were NOT checked for fix actions`,
879
- });
880
- }
881
- logActionableWarningsEvent({
882
- event: "lsp_file_checked",
883
- sessionId: args.sessionId,
884
- filePath,
885
- metadata: {
886
- diagsTotal: diags.length,
887
- diagsWarning: diagsWarning.length,
888
- deltaFiltered,
889
- enriched,
890
- actionCapped,
891
- budgetStopped,
892
- modifiedRangesCount: ranges.length,
893
- lspSource,
894
- },
895
- });
896
- return out;
897
- }
898
- /**
899
- * Assemble the report from a record set. Called once for the in-band report
900
- * and again, off-hook, when the deferred fresh pull completes (#2504).
901
- */
902
- function assembleReport(cwd, args, records, stamps) {
903
- const merged = mergeWarnings(records);
904
- updateWarningState(cwd, merged);
905
- // legacyId is #1816 migration bookkeeping for updateWarningState above —
906
- // strip it before the report leaves this function, so it never lands in
907
- // the `<project-data-dir>/cache/actionable-warnings.json` cache file or any
908
- // agent-facing rendering of a warning record.
909
- const reportWarnings = merged.map(({ legacyId: _legacyId, ...rest }) => rest);
910
- const byFile = new Map();
911
- for (const warning of reportWarnings) {
912
- const arr = byFile.get(warning.filePath) ?? [];
913
- arr.push(warning);
914
- byFile.set(warning.filePath, arr);
915
- }
916
- // #2504 review round 4 (F1): every entry carries the moment it was
917
- // assembled, so a report that later absorbs a deferred pull's entries can
918
- // still age each half honestly. For a report built in one pass they are all
919
- // the report stamp.
920
- const generatedAt = new Date().toISOString();
921
- const files = [...byFile.entries()].map(([filePath, warnings]) => ({
922
- filePath,
923
- displayPath: toRunnerDisplayPath(cwd, filePath),
924
- fileSeq: args.fileSeqByPath?.get(normalizeMapKey(filePath)),
925
- // #2504 review round 5 (F2): the moment THIS file was observed. The
926
- // report-level stamp remains the assembly moment and is only the
927
- // last-resort fallback, for a caller that supplied no observations
928
- // at all.
929
- generatedAt: stamps?.observedAtByPath.get(normalizeMapKey(filePath)) ??
930
- stamps?.fallbackObservedAt ??
931
- generatedAt,
932
- warnings,
933
- }));
934
- const summary = summarizeReportFiles(files);
935
- logActionableWarningsEvent({
936
- event: "report_complete",
937
- sessionId: args.sessionId,
938
- metadata: { turnIndex: args.turnIndex, summary },
939
- });
940
- return {
941
- generatedAt,
942
- scope: "turn_delta",
943
- sessionId: args.sessionId,
944
- turnIndex: args.turnIndex,
945
- projectSeqStart: args.projectSeqStart,
946
- projectSeqEnd: args.projectSeqEnd,
947
- deltaOnly: args.deltaOnly !== false,
948
- includeLspCodeActions: args.includeLspCodeActions,
949
- files,
950
- summary,
951
- };
952
- }
953
- /**
954
- * The report summary, derived from the per-file entries.
955
- *
956
- * ONE derivation (#2504 review round 4, F1): a merged report's summary has to
957
- * describe the merged file set, and a second hand-rolled tally beside
958
- * assembleReport's would be the mirrored-registry defect AGENTS.md names.
959
- * Every warning belongs to exactly one file entry, so summing over entries and
960
- * summing over the flat record list give the same numbers.
961
- */
962
- function summarizeReportFiles(files) {
963
- const warnings = files.flatMap((file) => file.warnings);
964
- const unsuppressed = warnings.filter((warning) => !warning.suppressed);
965
- const allActions = warnings.flatMap((warning) => warning.actions);
966
- const countTier = (tier) => unsuppressed.filter((warning) => warning.severity === tier).length;
967
- return {
968
- warnings: warnings.length,
969
- unsuppressed: unsuppressed.length,
970
- byTier: {
971
- warning: countTier("warning"),
972
- info: countTier("info"),
973
- hint: countTier("hint"),
974
- },
975
- suppressed: warnings.filter((warning) => warning.suppressed).length,
976
- files: files.length,
977
- actions: allActions.length,
978
- autoFixEligible: allActions.filter((action) => action.autoFixEligible)
979
- .length,
980
- };
981
- }
982
- /**
983
- * The ONE place an actionable-warnings report reaches disk.
984
- *
985
- * Deliberately NOT exported (#2504 review round 5, F1). Two writers used to
986
- * publish to this key: the in-band turn_end report, which OVERWROTE blindly,
987
- * and the deferred off-hook report, which read-modify-wrote. A deferred merge
988
- * that landed anywhere inside turn N+1's handleTurnEnd -- the cascade settle,
989
- * knip, madge, the test batch, the in-band LSP enrichment; a window seconds
990
- * wide, measured at 607 ms in the reviewer's trace -- was erased by that
991
- * turn's blind write, and the findings the whole deferral exists to deliver
992
- * were gone with it. A blind writer and a merging writer on one key can only
993
- * ever be a race. There is now one publisher and it always merges; the private
994
- * scope is what keeps it that way.
995
- */
996
- function writeActionableWarningsReport(cacheManager, cwd, report) {
997
- cacheManager.writeCache("actionable-warnings", report, cwd);
998
- }
999
- /**
1000
- * Merge a report about to be published into whatever is already persisted,
1001
- * PER FILE.
1002
- *
1003
- * #2504 review round 4 (F1) established the shape: a report is a MAP of
1004
- * per-file entries, each stamped with that file's fileSeq and its own
1005
- * observation time, so ordering belongs PER FILE and not to the report as a
1006
- * whole. Rounds 2 and 3 ordered whole reports -- publish, or discard on a
1007
- * newer turnIndex/projectSeqEnd -- and that composed with incumbent-wins into
1008
- * "publish nothing".
1009
- *
1010
- * #2504 review round 5 (F1) generalized it to BOTH publishers, because the
1011
- * in-band write was still blind and erased a deferred merge that landed
1012
- * mid-turn. The two directions are asymmetric and the asymmetry is the whole
1013
- * design:
1014
- *
1015
- * - A DEFERRED publish carries the OLDER observation. Everything persisted is
1016
- * newer and is kept unconditionally; the deferred entries upsert into it
1017
- * wherever their file has not moved.
1018
- * - An IN-BAND publish carries the NEWER observation. It is this turn's own
1019
- * report, and its identity (turnIndex, projectSeq window, deltaOnly) is
1020
- * published untouched. From the persisted report it carries forward ONLY
1021
- * entries a deferral produced -- the scope guard, see
1022
- * {@link ActionableWarningsReportFile.origin}. Carrying everything would
1023
- * accumulate every prior turn's findings into a report whose scope field
1024
- * says "turn_delta"; carrying nothing is the F1 defect.
1025
- *
1026
- * Where both halves hold a file the warnings are UNIONED through mergeWarnings
1027
- * (the same de-duplicating merge the dispatch/LSP union already uses), so a
1028
- * newer entry is never REPLACED by an older one -- it only gains what the
1029
- * older one found -- and the merged entry is aged by the OLDER of the two
1030
- * stamps, because it now holds both observations.
1031
- *
1032
- * Exported because the merge, not the write, is what has to be pinned: a test
1033
- * can hand it two reports and read the ordering decision directly.
1034
- */
1035
- export function mergeActionableWarningsReports(args) {
1036
- const { persisted, incoming, origin } = args;
1037
- const deferredPublish = origin === "deferred";
1038
- // The newer half wins identity and fileSeq; the older half is folded in.
1039
- // #2504 review round 6 (F1) removed a same-sessionId gate here, reasoning
1040
- // that the fileSeq equality check below subsumed it: pi's telemetry
1041
- // sessionId is STABLE across a quit->resume (setSessionLifecycle pins it,
1042
- // runtime-coordinator.ts:677-682), but resetForSession clears the live
1043
- // _fileSeq map for the new process, so a resumed process's getFileSeq
1044
- // answers 0 for a file a PRE-restart deferral had recorded at a nonzero
1045
- // seq -- the sessionId gate could not see that (same id, stale data), the
1046
- // equality check could.
1047
- // #2504 review round 7 (F4): that reasoning breaks at fileSeq 0. A file
1048
- // THIS process has never touched also answers getFileSeq 0 (a fresh
1049
- // RuntimeCoordinator's map starts empty), and an entry a DIFFERENT
1050
- // process stamped at fileSeq 0 can reach the persisted report entirely
1051
- // unrelated to a resume: `mcp/analyze.ts` and `mcp/session.ts` register
1052
- // modified ranges through their OWN CacheManager, which never bumps the
1053
- // extension runtime's `_fileSeq` map, so an MCP-touched file can persist
1054
- // at seq 0 under a foreign sessionId. `0 !== 0` then reads FALSE and the
1055
- // entry is carried as if it were this session's own unmoved file. The
1056
- // sessionId gate is restored ALONGSIDE the equality check, not instead of
1057
- // it: equality alone catches the resume-reset case (same session, seq
1058
- // forcibly reset to 0), the sessionId gate alone catches the
1059
- // foreign-session case (different session, seq coincidentally equal,
1060
- // often both 0) -- neither subsumes the other.
1061
- const sessionMismatch = !deferredPublish &&
1062
- persisted?.sessionId !== undefined &&
1063
- persisted.sessionId !== incoming.sessionId;
1064
- const newerHalf = deferredPublish ? (persisted?.files ?? []) : incoming.files;
1065
- const olderHalf = deferredPublish ? incoming.files : (persisted?.files ?? []);
1066
- const newerReport = deferredPublish ? persisted : incoming;
1067
- const olderReport = deferredPublish ? incoming : persisted;
1068
- const byPath = new Map();
1069
- for (const entry of newerHalf) {
1070
- byPath.set(normalizeMapKey(entry.filePath), entry);
1071
- }
1072
- const droppedFiles = [];
1073
- let mergedFiles = 0;
1074
- for (const entry of olderHalf) {
1075
- // The scope guard. An in-band publish exists to add THIS turn's
1076
- // findings; the only thing it owes the persisted report is the deferred
1077
- // work that would otherwise be erased between the read and the write.
1078
- // #2504 review round 6 (b): the marker spend (below) means an entry
1079
- // carried once and then dropped here on the NEXT in-band publish is
1080
- // expected, not a fault -- traced to dbg (informational) rather than
1081
- // the degradation ledger (which is for loss the agent didn't cause).
1082
- if (!deferredPublish && entry.origin !== "deferred") {
1083
- args.dbg?.(`actionable_warnings: in-band publish dropped ${entry.displayPath || entry.filePath} -- its carry-forward window (from an earlier deferred publish) already closed`);
1084
- continue;
1085
- }
1086
- const key = normalizeMapKey(entry.filePath);
1087
- const incumbent = byPath.get(key);
1088
- const liveBaseline = args.getFileSeq !== undefined;
1089
- const baselineSeq = args.getFileSeq?.(entry.filePath) ?? incumbent?.fileSeq;
1090
- // #2504 review round 6 (F1): the in-band carry-forward path, when a
1091
- // LIVE baseline is available, requires EXACT equality rather than
1092
- // "baseline advanced past it". An unmoved file's live fileSeq always
1093
- // equals its persisted entry's recorded fileSeq; any mismatch --
1094
- // higher (edited since) OR lower (a resumed process whose fileSeq map
1095
- // was cleared, #2504 r6 F1) -- means the entry no longer describes
1096
- // what is on disk now. The deferred-publish path keeps the coarser
1097
- // ">" check: a live process's own fileSeq only ever advances, so
1098
- // "advanced past it" and "mismatched" agree there, and the deferred
1099
- // loop's entries can legitimately equal a JUST-bumped incumbent seq
1100
- // it upserts into.
1101
- const stale = !deferredPublish && liveBaseline
1102
- ? sessionMismatch ||
1103
- (typeof entry.fileSeq === "number" && baselineSeq !== entry.fileSeq)
1104
- : typeof baselineSeq === "number" &&
1105
- typeof entry.fileSeq === "number" &&
1106
- baselineSeq > entry.fileSeq;
1107
- if (stale) {
1108
- droppedFiles.push(entry.displayPath || entry.filePath);
1109
- continue;
1110
- }
1111
- mergedFiles++;
1112
- const merged = incumbent
1113
- ? incumbent.reVerified || incumbent.reVerifyIncomplete
1114
- ? // #3170: the runtime just RE-OBSERVED this file (the bounded
1115
- // persistent-reverify pass) — the fresh observation supersedes
1116
- // the carried entry instead of unioning with it: a converged
1117
- // finding would otherwise survive its own supersession via the
1118
- // id-union in `mergeWarnings`.
1119
- {
1120
- ...incumbent,
1121
- fileSeq: incumbent.fileSeq ?? entry.fileSeq,
1122
- generatedAt: olderStamp(incumbent.generatedAt ?? newerReport?.generatedAt, entry.generatedAt ?? olderReport?.generatedAt),
1123
- warnings: incumbent.warnings,
1124
- }
1125
- : {
1126
- ...incumbent,
1127
- fileSeq: incumbent.fileSeq ?? entry.fileSeq,
1128
- generatedAt: olderStamp(incumbent.generatedAt ?? newerReport?.generatedAt, entry.generatedAt ?? olderReport?.generatedAt),
1129
- warnings: mergeWarnings([...incumbent.warnings, ...entry.warnings]),
1130
- }
1131
- : { ...entry };
1132
- if (deferredPublish) {
1133
- // Mark it, so the next in-band publish carries it forward across the
1134
- // window in which it would otherwise be overwritten.
1135
- merged.origin = "deferred";
1136
- }
1137
- else {
1138
- // Carried forward exactly once: the marker is spent here.
1139
- merged.origin = undefined;
1140
- }
1141
- byPath.set(key, merged);
1142
- }
1143
- const files = [...byPath.values()];
1144
- if (!deferredPublish) {
1145
- // This turn's report, with the rescued entries folded in. Its identity
1146
- // is published untouched: a carried entry's provenance lives ON the
1147
- // entry (its own fileSeq and generatedAt), which is exactly why round 4
1148
- // moved those fields there, and widening turnIndex/projectSeqStart to
1149
- // span the older half would make the report claim a window it never
1150
- // observed.
1151
- return {
1152
- report: { ...incoming, files, summary: summarizeReportFiles(files) },
1153
- mergedFiles,
1154
- droppedFiles,
1155
- // sessionMismatch is a single flag for the whole call, not per-entry:
1156
- // once true, every stale check above is forced true by the `||`, so
1157
- // EITHER every drop in this batch is a session-boundary drop, OR none
1158
- // are (droppedFiles is then purely fileSeq-mismatch drops).
1159
- droppedForSessionMismatch: sessionMismatch && droppedFiles.length > 0,
1160
- };
1161
- }
1162
- const base = persisted ?? incoming;
1163
- return {
1164
- report: {
1165
- ...base,
1166
- // The report-level stamp is only the fallback for entries with none
1167
- // of their own (a cache file from an older build), so it takes the
1168
- // NEWER of the two; per-entry stamps carry the real ages.
1169
- generatedAt: newerStamp(persisted?.generatedAt, incoming.generatedAt) ??
1170
- base.generatedAt,
1171
- turnIndex: Math.max(base.turnIndex, incoming.turnIndex),
1172
- projectSeqStart: minDefined(persisted?.projectSeqStart, incoming.projectSeqStart),
1173
- // max, so a merged report never claims to be fresher OR staler than
1174
- // the newest part it holds. checkActionableWarningsReportFresh still
1175
- // demands exact equality with the live projectSeq and re-checks every
1176
- // entry's fileSeq, so this widens nothing.
1177
- projectSeqEnd: maxDefined(persisted?.projectSeqEnd, incoming.projectSeqEnd),
1178
- includeLspCodeActions: base.includeLspCodeActions || incoming.includeLspCodeActions,
1179
- files,
1180
- summary: summarizeReportFiles(files),
1181
- },
1182
- mergedFiles,
1183
- droppedFiles,
1184
- // The deferred-publish path never sets sessionMismatch (gated by
1185
- // `!deferredPublish` above), so its drops are always fileSeq-mismatch.
1186
- droppedForSessionMismatch: false,
1187
- };
1188
- }
1189
- /**
1190
- * Publish an actionable-warnings report: read what is persisted, merge per
1191
- * file, write. THE choke point (#2504 review round 5, F1) -- every publisher
1192
- * goes through it, so no writer can clobber another's entries.
1193
- *
1194
- * The read and the merge and the write are one synchronous block, which is
1195
- * what makes this safe against the deferred callback: node runs no other
1196
- * continuation between them, so the only interleaving left is publish-vs-
1197
- * publish, and that is exactly what the merge resolves.
1198
- */
1199
- export function publishActionableWarningsReport(cacheManager, cwd, report, opts = {}) {
1200
- const origin = opts.origin ?? "in-band";
1201
- // No TTL: "what is already on disk" is a question about ORDERING, not
1202
- // freshness, so an entry old enough to have expired for a CONSUMER still
1203
- // has to be merged into rather than clobbered.
1204
- const persisted = cacheManager.readCache("actionable-warnings", cwd, Number.MAX_SAFE_INTEGER)?.data;
1205
- const merged = mergeActionableWarningsReports({
1206
- persisted,
1207
- incoming: report,
1208
- origin,
1209
- getFileSeq: opts.getFileSeq,
1210
- dbg: opts.dbg,
1211
- });
1212
- writeActionableWarningsReport(cacheManager, cwd, merged.report);
1213
- // The history records what THIS publish OBSERVED, not what it carried: a
1214
- // merged-in entry is already on the NDJSON from the publish that produced
1215
- // it, and appending the merged report would duplicate every carried row on
1216
- // every subsequent turn.
1217
- appendActionableWarningsHistory(cwd, report);
1218
- if (merged.mergedFiles > 0) {
1219
- opts.dbg?.(`actionable_warnings: ${origin} publish merged ${merged.mergedFiles} persisted file entry/entries`);
1220
- }
1221
- // #2504 review round 7 (F5): the deferred origin's own loss is already
1222
- // recorded by {@link writeDeferredActionableWarningsReport}, which calls
1223
- // through here first. The IN-BAND origin has the same shape of loss --
1224
- // this turn's own publish carries a carried-forward deferred entry
1225
- // forward only while its file has not moved, and a file that moved
1226
- // between the deferral and this publish is dropped -- but nothing counted
1227
- // or traced it: only the benign, expected scope-guard spend (an entry
1228
- // whose carry-forward window already closed) got a dbg line. Same
1229
- // accounting, mirrored for the origin that was silent.
1230
- if (origin === "in-band" && merged.droppedFiles.length > 0) {
1231
- // #2504 review round 8 (S2): "changed" is only true on the fileSeq-
1232
- // mismatch path. A foreign sessionId (a resumed process, or a
1233
- // different session's write) drops the same carried-forward entries
1234
- // without any file having moved -- name the actual cause instead of
1235
- // asserting a change that may not have happened.
1236
- const cause = merged.droppedForSessionMismatch
1237
- ? "the publish crossed a session boundary (a different session's write, or a resumed process) before this turn's in-band publish could keep them"
1238
- : "changed before this turn's in-band publish could keep them";
1239
- incrementDegradationCount({
1240
- kind: "actionable-warnings-inband-superseded",
1241
- subject: `${path.resolve(cwd)}:inband-carry-superseded`,
1242
- reason: `${merged.droppedFiles.length} carried-forward deferred file entry/entries ${cause} (${merged.droppedFiles.slice(0, 3).join(", ")}${merged.droppedFiles.length > 3 ? ", ..." : ""}); their earlier findings are LOST on this channel rather than published against content that has since moved`,
1243
- });
1244
- opts.dbg?.(`actionable_warnings: in-band publish dropped ${merged.droppedFiles.length} superseded carried-forward file entry/entries (${merged.droppedFiles.slice(0, 3).join(", ")}${merged.droppedFiles.length > 3 ? ", ..." : ""})`);
1245
- }
1246
- return merged;
1247
- }
1248
- /** The earlier of two ISO stamps; either may be missing or unparseable. */
1249
- function olderStamp(a, b) {
1250
- if (a === undefined)
1251
- return b;
1252
- if (b === undefined)
1253
- return a;
1254
- const at = Date.parse(a);
1255
- const bt = Date.parse(b);
1256
- if (!Number.isFinite(at))
1257
- return b;
1258
- if (!Number.isFinite(bt))
1259
- return a;
1260
- return at <= bt ? a : b;
1261
- }
1262
- /** The later of two ISO stamps; either may be missing or unparseable. */
1263
- function newerStamp(a, b) {
1264
- const older = olderStamp(a, b);
1265
- if (a === undefined)
1266
- return b;
1267
- if (b === undefined)
1268
- return a;
1269
- return older === a ? b : a;
1270
- }
1271
- function minDefined(a, b) {
1272
- if (typeof a !== "number")
1273
- return b;
1274
- if (typeof b !== "number")
1275
- return a;
1276
- return Math.min(a, b);
1277
- }
1278
- function maxDefined(a, b) {
1279
- if (typeof a !== "number")
1280
- return b;
1281
- if (typeof b !== "number")
1282
- return a;
1283
- return Math.max(a, b);
1284
- }
1285
- /**
1286
- * Publish a DEFERRED off-hook report.
1287
- *
1288
- * The deferred fresh-pull loop is stamped with the ORIGINATING turn's
1289
- * turnIndex/projectSeq and may run for up to
1290
- * ACTIONABLE_WARNINGS_DEFERRED_BUDGET_MS -- many turns in a busy session. It
1291
- * must therefore neither overwrite a newer report nor be thrown away because
1292
- * one exists; see mergeActionableWarningsReports for why replace-or-discard
1293
- * could only ever discard. Per-file loss is the only loss left, and it is
1294
- * recorded rather than silent.
1295
- *
1296
- * A thin wrapper over the choke point since #2504 review round 5 (F1). It
1297
- * exists only to own the degradation record for the per-file loss -- the
1298
- * "declined the write" branches it used to carry are gone, because a publish
1299
- * that merges has nothing to decline.
1300
- */
1301
- export function writeDeferredActionableWarningsReport(args) {
1302
- const { mergedFiles, droppedFiles } = publishActionableWarningsReport(args.cacheManager, args.cwd, args.report, { origin: "deferred", getFileSeq: args.getFileSeq, dbg: args.dbg });
1303
- if (droppedFiles.length > 0) {
1304
- // Bounded and counted: one subject per project, a tally per occurrence.
1305
- incrementDegradationCount({
1306
- kind: "actionable-warnings-deferred-superseded",
1307
- subject: `${path.resolve(args.cwd)}:deferred-file-superseded`,
1308
- reason: `${droppedFiles.length} file(s) changed while the deferred LSP pull was reading them (${droppedFiles.slice(0, 3).join(", ")}${droppedFiles.length > 3 ? ", ..." : ""}); their warnings are LOST on this channel rather than published against content that has since moved. Every file that did NOT change was merged into the persisted report`,
1309
- });
1310
- args.dbg?.(`turn_end: deferred actionable-warnings dropped ${droppedFiles.length} superseded file entry/entries`);
1311
- }
1312
- args.dbg?.(`turn_end: deferred actionable-warnings merged ${mergedFiles} file entry/entries into the persisted report`);
1313
- return { mergedFiles, droppedFiles: droppedFiles.length };
1314
- }
1315
- export function getActionableWarningsHistoryPath(cwd) {
1316
- return path.join(getProjectDataDir(cwd), "actionable-warnings.jsonl");
1317
- }
1318
- /**
1319
- * Append every actionable warning from this turn to the project's rolling
1320
- * NDJSON history. Mirrors `appendCodeQualityWarningsHistory` so the two
1321
- * advisory families have the same shape of cross-turn persistence:
1322
- *
1323
- * - One line per warning (not per turn).
1324
- * - Carries the stable `aw:<hash>` id so callers can correlate the same
1325
- * warning across turns / sessions.
1326
- * - Captures suppression state at write time so historical analyses can
1327
- * reconstruct what the agent actually saw.
1328
- * - Captures action counts (and autoFixEligible counts) — the LSP code-
1329
- * action enrichment is the actionable-warnings-only signal; preserving
1330
- * it lets later analyses ask "which warnings ship with an autofix?".
1331
- *
1332
- * Skips the write entirely when no warnings exist — matching the code-
1333
- * quality history's no-op-on-empty behaviour and keeping the file from
1334
- * accumulating 0-warning noise.
1335
- */
1336
- export function appendActionableWarningsHistory(cwd, report) {
1337
- const entries = [];
1338
- for (const file of report.files) {
1339
- for (const warning of file.warnings) {
1340
- entries.push({
1341
- timestamp: report.generatedAt,
1342
- sessionId: report.sessionId,
1343
- turnIndex: report.turnIndex,
1344
- projectSeq: report.projectSeqEnd,
1345
- filePath: warning.filePath,
1346
- displayPath: warning.displayPath,
1347
- fileSeq: file.fileSeq,
1348
- line: warning.line,
1349
- column: warning.column,
1350
- severity: warning.severity,
1351
- tool: warning.tool,
1352
- source: warning.source,
1353
- rule: warning.rule,
1354
- code: warning.code,
1355
- message: warning.message,
1356
- fixKind: warning.fixKind,
1357
- autoFixAvailable: warning.autoFixAvailable,
1358
- actionCount: warning.actions.length,
1359
- autoFixEligibleActionCount: warning.actions.filter((action) => action.autoFixEligible).length,
1360
- suppressed: warning.suppressed,
1361
- suppressionReason: warning.suppressionReason,
1362
- origin: warning.origin,
1363
- warningId: warning.id,
1364
- });
1365
- }
1366
- }
1367
- if (entries.length === 0)
1368
- return;
1369
- const historyPath = getActionableWarningsHistoryPath(cwd);
1370
- try {
1371
- fs.mkdirSync(path.dirname(historyPath), { recursive: true });
1372
- fs.appendFileSync(historyPath, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf8");
1373
- }
1374
- catch {
1375
- // Non-fatal — history write failure must never surface to the agent.
1376
- }
1377
- }
1378
- export function checkActionableWarningsReportFresh(args) {
1379
- const reportProjectSeqEnd = args.report.projectSeqEnd;
1380
- if (typeof reportProjectSeqEnd !== "number") {
1381
- return {
1382
- fresh: false,
1383
- reason: "missing_project_seq",
1384
- currentProjectSeq: args.currentProjectSeq,
1385
- };
1386
- }
1387
- if (reportProjectSeqEnd !== args.currentProjectSeq) {
1388
- return {
1389
- fresh: false,
1390
- reason: "project_seq_mismatch",
1391
- reportProjectSeqEnd,
1392
- currentProjectSeq: args.currentProjectSeq,
1393
- };
1394
- }
1395
- if (args.getFileSeq) {
1396
- for (const file of args.report.files) {
1397
- if (typeof file.fileSeq !== "number")
1398
- continue;
1399
- const currentFileSeq = args.getFileSeq(file.filePath);
1400
- if (currentFileSeq !== file.fileSeq) {
1401
- return {
1402
- fresh: false,
1403
- reason: "file_seq_mismatch",
1404
- reportProjectSeqEnd,
1405
- currentProjectSeq: args.currentProjectSeq,
1406
- filePath: file.filePath,
1407
- reportFileSeq: file.fileSeq,
1408
- currentFileSeq,
1409
- };
1410
- }
1411
- }
1412
- }
1413
- return {
1414
- fresh: true,
1415
- reportProjectSeqEnd,
1416
- currentProjectSeq: args.currentProjectSeq,
1417
- };
1418
- }
1419
- export async function applyConservativeActionableWarningFixes(args) {
1420
- const summary = {
1421
- considered: 0,
1422
- applied: 0,
1423
- changedFiles: [],
1424
- skipped: [],
1425
- };
1426
- const changedFiles = new Set();
1427
- const appliedResults = [];
1428
- let failedCount = 0;
1429
- const lspService = getLSPService();
1430
- const maxFixes = Math.max(0, args.maxFixes ?? 5);
1431
- for (const file of args.report.files) {
1432
- if (summary.applied >= maxFixes)
1433
- break;
1434
- for (const warning of file.warnings) {
1435
- if (summary.applied >= maxFixes)
1436
- break;
1437
- if (warning.suppressed)
1438
- continue;
1439
- const eligibleActions = warning.actions.filter((action) => action.autoFixEligible);
1440
- if (eligibleActions.length !== 1) {
1441
- if (eligibleActions.length > 1)
1442
- summary.skipped.push({
1443
- id: warning.id,
1444
- reason: "multiple_eligible_actions",
1445
- });
1446
- continue;
1447
- }
1448
- summary.considered++;
1449
- const agreement = establishToolAgreement(warning.tool, args.cwd);
1450
- if (agreement.decision === "decline") {
1451
- recordDegradationOnce({
1452
- kind: "autofix-agreement-unavailable",
1453
- subject: agreement.subject,
1454
- reason: agreement.reason,
1455
- });
1456
- summary.skipped.push({
1457
- id: warning.id,
1458
- reason: "tool_agreement_unavailable",
1459
- });
1460
- continue;
1461
- }
1462
- if (!warning.line || !warning.column) {
1463
- summary.skipped.push({ id: warning.id, reason: "missing_position" });
1464
- continue;
1465
- }
1466
- if (!lspService.supportsLSP(warning.filePath)) {
1467
- summary.skipped.push({ id: warning.id, reason: "no_lsp" });
1468
- continue;
1469
- }
1470
- try {
1471
- const content = fs.existsSync(warning.filePath)
1472
- ? fs.readFileSync(warning.filePath, "utf-8")
1473
- : undefined;
1474
- if (content)
1475
- await lspService.openFile(warning.filePath, content);
1476
- const line = warning.line - 1;
1477
- const character = warning.column - 1;
1478
- const actions = await lspService.codeAction(warning.filePath, line, character, line, character);
1479
- const title = eligibleActions[0]?.title;
1480
- const selected = actions.find((action) => action.title === title);
1481
- if (!selected) {
1482
- summary.skipped.push({ id: warning.id, reason: "action_not_found" });
1483
- continue;
1484
- }
1485
- const safety = actionSafety(selected);
1486
- if (!safety.eligible) {
1487
- summary.skipped.push({
1488
- id: warning.id,
1489
- reason: safety.reason ?? "not_safe",
1490
- });
1491
- continue;
1492
- }
1493
- const edit = selected.edit;
1494
- const applied = await applyWorkspaceEdit(edit, args.cwd, args.mutationContext
1495
- ? {
1496
- mutationContext: {
1497
- ...args.mutationContext,
1498
- emitSummary: false,
1499
- },
1500
- }
1501
- : undefined);
1502
- appliedResults.push(applied);
1503
- for (const changedFile of applied.files)
1504
- changedFiles.add(changedFile);
1505
- summary.applied++;
1506
- }
1507
- catch (err) {
1508
- failedCount++;
1509
- const partial = err.appliedWorkspaceEdit;
1510
- if (partial) {
1511
- appliedResults.push(partial);
1512
- for (const changedFile of partial.files)
1513
- changedFiles.add(changedFile);
1514
- }
1515
- const message = err instanceof Error ? err.message : String(err);
1516
- args.dbg?.(`actionable_warnings_autofix failed for ${warning.id}: ${message}`);
1517
- summary.skipped.push({ id: warning.id, reason: "apply_failed" });
1518
- }
1519
- }
1520
- }
1521
- summary.changedFiles = [...changedFiles];
1522
- if (args.mutationContext &&
1523
- (summary.considered > 0 || appliedResults.length > 0)) {
1524
- recordLspMutationBatch(args.mutationContext, {
1525
- results: appliedResults,
1526
- considered: summary.considered,
1527
- completed: summary.applied,
1528
- failedCount,
1529
- status: failedCount > 0
1530
- ? "failed"
1531
- : appliedResults.length > 0
1532
- ? "success"
1533
- : "skipped",
1534
- bookkeep: false,
1535
- });
1536
- }
1537
- return summary;
1538
- }
1539
- /**
1540
- * The turn-end advisory for this report.
1541
- *
1542
- * `cwd` is REQUIRED (#2521): the advisory names where the report lives, and
1543
- * that location is `getProjectDataDir(cwd)`-resolved, not a constant. It was a
1544
- * hardcoded `.pi-lens/cache/actionable-warnings.json` — correct only for a
1545
- * project that already had a legacy `.pi-lens/` directory — so in every other
1546
- * project the agent's `cat` of the advised path failed while the report sat
1547
- * unread under `~/.pi-lens/projects/<slug>/cache/`. An OPTIONAL `cwd` would
1548
- * have kept that bug alive behind a default, so callers must pass it.
1549
- */
1550
- export function formatActionableWarningsAdvisory(report, cwd, host = "pi", filterBuiltReport) {
1551
- // The cache record is deliberately kept raw: lens_diagnostics mode=delta
1552
- // applies dispositions on read. The turn-end advisory is a separate
1553
- // delivery surface, so filter the report AFTER its builder and after the
1554
- // deferred merge (which is where LSP-enriched rows enter), then derive the
1555
- // summary from the same per-file rows before rendering.
1556
- const filtered = filterBuiltReport?.(report);
1557
- const advisoryReport = filtered
1558
- ? {
1559
- ...report,
1560
- files: filtered.files,
1561
- summary: summarizeReportFiles(filtered.files),
1562
- }
1563
- : report;
1564
- if (advisoryReport.summary.unsuppressed === 0)
1565
- return undefined;
1566
- const files = advisoryReport.files.filter((file) => file.warnings.some((warning) => !warning.suppressed));
1567
- const fileList = files
1568
- .slice(0, 5)
1569
- .map((file) => ` ${file.displayPath}: ${file.warnings.filter((warning) => !warning.suppressed).length}${file.reVerifyIncomplete ? " (re-verify incomplete)" : ""}`)
1570
- .join("\n");
1571
- const more = files.length > 5 ? `\n ... and ${files.length - 5} more file(s)` : "";
1572
- const safe = advisoryReport.summary.autoFixEligible > 0
1573
- ? ` ${advisoryReport.summary.autoFixEligible} appear to have conservative preferred quickfixes.`
1574
- : "";
1575
- // #1777: hint and info are style opinions, so say how much of the count is
1576
- // opinion. The line appears only when a quiet tier is actually present —
1577
- // an all-warning turn already says everything in the count above.
1578
- const byTier = advisoryReport.summary.byTier;
1579
- const quiet = byTier ? byTier.hint + byTier.info : 0;
1580
- const tierLine = quiet > 0
1581
- ? `${quiet} of those are hint/info tier — style opinions, worth fixing only while you are already in that code.`
1582
- : undefined;
1583
- // #2521: lead with the TOOL route. `lens_diagnostics mode=delta` reads the
1584
- // same cache this advisory describes, so an agent never needs to know the
1585
- // store's layout — which is exactly the knowledge the old hardcoded path
1586
- // got wrong. The resolved file stays as a named fallback for a human
1587
- // reading the transcript, rendered through the one helper that cannot
1588
- // disagree with the writer.
1589
- const reportPath = displayProjectDataPath(cwd, "cache", "actionable-warnings.json");
1590
- // #2535 F3: a known tool with no mapping on this host resolves to
1591
- // undefined — omit the instruction rather than naming a dead tool.
1592
- // `lens_diagnostics` is pinned available on both hosts, so this is
1593
- // defensive only.
1594
- const diagnosticsTool = resolveLensToolName("lens_diagnostics", host);
1595
- return [
1596
- `🟡 Fixable warnings introduced this turn: ${advisoryReport.summary.unsuppressed}.${safe}`,
1597
- filtered && filtered.suppressed > 0
1598
- ? `suppressed by disposition: ${filtered.suppressed} finding(s).`
1599
- : undefined,
1600
- tierLine,
1601
- diagnosticsTool
1602
- ? `Use ${diagnosticsTool} with mode=delta to inspect these warnings.`
1603
- : undefined,
1604
- fileList ? `Files:\n${fileList}${more}` : undefined,
1605
- "If continuing in these files, resolve warnings that are safe and relevant. Do not apply broad refactors unless requested.",
1606
- `Raw report (only if you need the JSON): ${reportPath}`,
1607
- ]
1608
- .filter(Boolean)
1609
- .join("\n");
1610
- }