@herbertgao/pi-extensions 2026.10.1 → 2026.10.3

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 (934) hide show
  1. package/README.md +14 -17
  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/{remote-pi → @herbertgao/pi-agency}/LICENSE +1 -1
  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/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  34. package/node_modules/@herbertgao/pi-subagents/README.md +1 -1
  35. package/node_modules/@herbertgao/pi-subagents/package.json +4 -5
  36. package/node_modules/@herbertgao/pi-subagents/src/index.ts +1 -1
  37. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +1 -1
  38. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +48 -72
  39. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +1 -1
  40. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +15 -6
  41. package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +4 -1
  42. package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +9 -8
  43. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +6 -1
  44. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
  45. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  46. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +13 -1
  47. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  48. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  49. package/node_modules/@tifan/pi-recap/package.json +1 -1
  50. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  51. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  52. package/node_modules/pi-antigravity/README.md +23 -2
  53. package/node_modules/pi-antigravity/package.json +5 -3
  54. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  55. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  56. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  58. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  59. package/node_modules/pi-antigravity/src/index.ts +52 -137
  60. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  61. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  62. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  63. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  64. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  65. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  66. package/node_modules/pi-lens/CHANGELOG.md +1159 -0
  67. package/node_modules/pi-lens/README.md +116 -25
  68. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  69. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  70. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  71. package/node_modules/pi-lens/dist/chunk-2FQ62SPM.js +83 -0
  72. package/node_modules/pi-lens/dist/chunk-2UMYMRI3.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-34OCDWOS.js +48 -0
  74. package/node_modules/pi-lens/dist/chunk-3FERWMCH.js +144 -0
  75. package/node_modules/pi-lens/dist/chunk-3JL6IFD7.js +97 -0
  76. package/node_modules/pi-lens/dist/chunk-3JSEWUWI.js +327 -0
  77. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-3MRXIJ6I.js +79 -0
  79. package/node_modules/pi-lens/dist/chunk-3P4PF2BX.js +2250 -0
  80. package/node_modules/pi-lens/dist/chunk-3PNFWVHT.js +201 -0
  81. package/node_modules/pi-lens/dist/chunk-3TTPKMEF.js +162 -0
  82. package/node_modules/pi-lens/dist/chunk-5JHXUETW.js +326 -0
  83. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  84. package/node_modules/pi-lens/dist/chunk-64LU2OVH.js +341 -0
  85. package/node_modules/pi-lens/dist/chunk-6B5I6ICN.js +44 -0
  86. package/node_modules/pi-lens/dist/chunk-6P54TMWL.js +274 -0
  87. package/node_modules/pi-lens/dist/chunk-6UYA642J.js +145 -0
  88. package/node_modules/pi-lens/dist/chunk-6XX5HCLK.js +398 -0
  89. package/node_modules/pi-lens/dist/chunk-7AXO3ZDF.js +30 -0
  90. package/node_modules/pi-lens/dist/chunk-7BX5DWSA.js +1907 -0
  91. package/node_modules/pi-lens/dist/chunk-7ELY2CSM.js +902 -0
  92. package/node_modules/pi-lens/dist/chunk-7M4T5N3V.js +38 -0
  93. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  94. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  95. package/node_modules/pi-lens/dist/chunk-AFZYMAIV.js +756 -0
  96. package/node_modules/pi-lens/dist/chunk-ALQRYH6O.js +112 -0
  97. package/node_modules/pi-lens/dist/chunk-AXKPQ6ZI.js +1610 -0
  98. package/node_modules/pi-lens/dist/chunk-BHWHXMQ2.js +62 -0
  99. package/node_modules/pi-lens/dist/chunk-BJF25SUG.js +22 -0
  100. package/node_modules/pi-lens/dist/chunk-BNKLYOOO.js +18 -0
  101. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  102. package/node_modules/pi-lens/dist/chunk-CBD2B5CP.js +599 -0
  103. package/node_modules/pi-lens/dist/chunk-CFYISHUW.js +42 -0
  104. package/node_modules/pi-lens/dist/chunk-CMU4RMJB.js +30 -0
  105. package/node_modules/pi-lens/dist/chunk-CULQYFLU.js +14 -0
  106. package/node_modules/pi-lens/dist/chunk-D65GOCJB.js +289 -0
  107. package/node_modules/pi-lens/dist/chunk-D6YF5HKX.js +59 -0
  108. package/node_modules/pi-lens/dist/chunk-DMS52BEP.js +342 -0
  109. package/node_modules/pi-lens/dist/chunk-ELVWJYRV.js +211 -0
  110. package/node_modules/pi-lens/dist/chunk-EUT3B22Y.js +27 -0
  111. package/node_modules/pi-lens/dist/chunk-F5VLZHUW.js +146 -0
  112. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  113. package/node_modules/pi-lens/dist/chunk-FGALODG3.js +55 -0
  114. package/node_modules/pi-lens/dist/chunk-G3RLJYA2.js +2358 -0
  115. package/node_modules/pi-lens/dist/chunk-GAYGGICG.js +32 -0
  116. package/node_modules/pi-lens/dist/chunk-H2W2AXD3.js +419 -0
  117. package/node_modules/pi-lens/dist/chunk-H5EV3VMF.js +114 -0
  118. package/node_modules/pi-lens/dist/chunk-HREZQCT3.js +106 -0
  119. package/node_modules/pi-lens/dist/chunk-HSIRMK2D.js +517 -0
  120. package/node_modules/pi-lens/dist/chunk-IMSQR4WO.js +184 -0
  121. package/node_modules/pi-lens/dist/chunk-IQZXOZ7S.js +4816 -0
  122. package/node_modules/pi-lens/dist/chunk-IR6XFA5P.js +37 -0
  123. package/node_modules/pi-lens/dist/chunk-IUKPRZER.js +78 -0
  124. package/node_modules/pi-lens/dist/chunk-IUR76K6A.js +262 -0
  125. package/node_modules/pi-lens/dist/chunk-J3FZR3YY.js +98 -0
  126. package/node_modules/pi-lens/dist/chunk-J6DFZDFA.js +562 -0
  127. package/node_modules/pi-lens/dist/chunk-J7WJZ6W7.js +50 -0
  128. package/node_modules/pi-lens/dist/chunk-JONGANYH.js +202 -0
  129. package/node_modules/pi-lens/dist/chunk-KJCWXUUS.js +425 -0
  130. package/node_modules/pi-lens/dist/chunk-KJN4IOEJ.js +17 -0
  131. package/node_modules/pi-lens/dist/chunk-KMGPUKFD.js +25 -0
  132. package/node_modules/pi-lens/dist/chunk-LD7WFTRX.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  134. package/node_modules/pi-lens/dist/chunk-LNOMA3MP.js +169 -0
  135. package/node_modules/pi-lens/dist/chunk-MGIWSROG.js +2334 -0
  136. package/node_modules/pi-lens/dist/chunk-MQA2YBAH.js +308 -0
  137. package/node_modules/pi-lens/dist/chunk-MTDRLSI7.js +21720 -0
  138. package/node_modules/pi-lens/dist/chunk-N2IRDBZI.js +2355 -0
  139. package/node_modules/pi-lens/dist/chunk-N77WFUL7.js +50 -0
  140. package/node_modules/pi-lens/dist/chunk-N7YNJE5R.js +466 -0
  141. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  142. package/node_modules/pi-lens/dist/chunk-NFXP47RV.js +999 -0
  143. package/node_modules/pi-lens/dist/chunk-NJF2X5XF.js +45 -0
  144. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  145. package/node_modules/pi-lens/dist/chunk-O7HZX7CA.js +309 -0
  146. package/node_modules/pi-lens/dist/chunk-OC7LYAU5.js +708 -0
  147. package/node_modules/pi-lens/dist/chunk-OEX24ECI.js +348 -0
  148. package/node_modules/pi-lens/dist/chunk-OYOB3ZFG.js +78 -0
  149. package/node_modules/pi-lens/dist/chunk-OYTMZXRF.js +41 -0
  150. package/node_modules/pi-lens/dist/chunk-OZJINN4O.js +84 -0
  151. package/node_modules/pi-lens/dist/chunk-PCHDGUKT.js +23 -0
  152. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  153. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  154. package/node_modules/pi-lens/dist/chunk-RJFED73B.js +79 -0
  155. package/node_modules/pi-lens/dist/chunk-RTQNRM5X.js +27 -0
  156. package/node_modules/pi-lens/dist/chunk-S6AMWKPC.js +189 -0
  157. package/node_modules/pi-lens/dist/chunk-T4QZ6FEH.js +308 -0
  158. package/node_modules/pi-lens/dist/chunk-T54VJRAD.js +5456 -0
  159. package/node_modules/pi-lens/dist/chunk-TA5L5KWQ.js +167 -0
  160. package/node_modules/pi-lens/dist/chunk-TQFBZVCG.js +27 -0
  161. package/node_modules/pi-lens/dist/chunk-U52SAGRM.js +62 -0
  162. package/node_modules/pi-lens/dist/chunk-UEAHAMEI.js +5814 -0
  163. package/node_modules/pi-lens/dist/chunk-UGSMCQ4D.js +327 -0
  164. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  165. package/node_modules/pi-lens/dist/chunk-USHG4DLH.js +459 -0
  166. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  167. package/node_modules/pi-lens/dist/chunk-V5MEBST3.js +4139 -0
  168. package/node_modules/pi-lens/dist/chunk-VGEVL5EQ.js +29 -0
  169. package/node_modules/pi-lens/dist/chunk-VPWKF3GN.js +87 -0
  170. package/node_modules/pi-lens/dist/chunk-VSMJK3ZJ.js +1250 -0
  171. package/node_modules/pi-lens/dist/chunk-VYKKMYVT.js +560 -0
  172. package/node_modules/pi-lens/dist/chunk-VZHJS6DR.js +442 -0
  173. package/node_modules/pi-lens/dist/chunk-WATC3IQR.js +213 -0
  174. package/node_modules/pi-lens/dist/chunk-WPE5FPGR.js +54 -0
  175. package/node_modules/pi-lens/dist/chunk-WSEWKCXU.js +188 -0
  176. package/node_modules/pi-lens/dist/chunk-WW5BLB74.js +659 -0
  177. package/node_modules/pi-lens/dist/chunk-XGUL4SLQ.js +406 -0
  178. package/node_modules/pi-lens/dist/chunk-XHSCVSOL.js +170 -0
  179. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  180. package/node_modules/pi-lens/dist/chunk-YLN4K6WD.js +385 -0
  181. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  182. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  183. package/node_modules/pi-lens/dist/chunk-Z6BB4JLQ.js +478 -0
  184. package/node_modules/pi-lens/dist/chunk-Z7QQA2KT.js +27 -0
  185. package/node_modules/pi-lens/dist/chunk-ZETYB3XG.js +2626 -0
  186. package/node_modules/pi-lens/dist/chunk-ZMOMPX2Z.js +87 -0
  187. package/node_modules/pi-lens/dist/chunk-ZOEYVXSV.js +149 -0
  188. package/node_modules/pi-lens/dist/chunk-ZZUFZUQZ.js +12120 -0
  189. package/node_modules/pi-lens/dist/index.js +39712 -29876
  190. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  191. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  192. package/node_modules/pi-lens/dist/mcp/server.js +22688 -1573
  193. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  194. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  195. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  196. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  197. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  198. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  199. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -4
  200. package/node_modules/pi-lens/docs/configuration.md +28 -2
  201. package/node_modules/pi-lens/docs/custom-rules.md +25 -2
  202. package/node_modules/pi-lens/docs/environment-variables.md +86 -8
  203. package/node_modules/pi-lens/docs/features.md +45 -5
  204. package/node_modules/pi-lens/docs/globalconfig.md +2 -1
  205. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  206. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +37 -4
  207. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  208. package/node_modules/pi-lens/docs/mcp.md +21 -5
  209. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  210. package/node_modules/pi-lens/docs/pi-lens-fixer.md +160 -88
  211. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  212. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +736 -0
  213. package/node_modules/pi-lens/docs/pi-lens-monitor.md +75 -7
  214. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  215. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +189 -48
  216. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  217. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  218. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  219. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  220. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  221. package/node_modules/pi-lens/docs/schema/pi-lens-config-v1.json +530 -0
  222. package/node_modules/pi-lens/docs/servercapabilities.md +56 -49
  223. package/node_modules/pi-lens/docs/settings.md +1 -0
  224. package/node_modules/pi-lens/docs/usage.md +5 -1
  225. package/node_modules/pi-lens/docs/word-index.md +6 -0
  226. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  227. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  228. package/node_modules/pi-lens/package.json +18 -6
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
  237. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  238. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  239. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  240. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  241. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  242. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  243. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  244. package/node_modules/pi-web-access/README.md +10 -6
  245. package/node_modules/pi-web-access/ceramic.ts +219 -0
  246. package/node_modules/pi-web-access/curator-page.ts +3 -1
  247. package/node_modules/pi-web-access/curator-server.ts +1 -0
  248. package/node_modules/pi-web-access/dist/index.js +846 -588
  249. package/node_modules/pi-web-access/dist/mcp-cli.js +725 -516
  250. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  251. package/node_modules/pi-web-access/github-extract.ts +34 -2
  252. package/node_modules/pi-web-access/index.ts +72 -30
  253. package/node_modules/pi-web-access/openai-search.ts +3 -3
  254. package/node_modules/pi-web-access/package.json +2 -2
  255. package/node_modules/pi-web-access/page-query.ts +6 -9
  256. package/node_modules/pi-web-access/parallel-mcp.ts +2 -0
  257. package/node_modules/pi-web-access/query-rewrite.ts +2 -2
  258. package/node_modules/pi-web-access/storage.ts +49 -0
  259. package/node_modules/pi-web-access/summary-review.ts +7 -6
  260. package/node_modules/pi-web-access/utils.ts +21 -6
  261. package/node_modules/pi-web-access/web-tool-core.ts +1 -0
  262. package/package.json +19 -19
  263. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  264. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  265. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  266. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  267. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  268. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  269. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  270. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  271. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  272. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  273. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  274. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  275. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  276. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  277. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  278. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  279. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  280. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  281. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  282. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  283. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  284. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  285. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  286. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  287. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  288. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  289. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  290. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  291. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  292. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  293. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  294. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  295. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  296. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  297. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  298. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  299. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  300. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  301. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  302. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  303. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  304. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  305. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  306. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  307. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  308. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  309. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  310. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  311. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  312. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  313. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  314. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  315. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  316. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  317. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  318. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  319. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  320. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  321. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  322. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  323. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  324. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  325. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  326. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  327. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  328. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  329. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  330. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  331. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  332. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  333. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  334. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  335. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  336. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  337. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  338. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  339. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  340. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  341. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  342. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  343. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  344. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  345. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  346. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  347. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  348. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  349. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  350. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  351. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  352. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  353. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  354. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  355. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  356. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  357. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  358. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  359. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  360. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  361. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  362. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  363. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  364. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  365. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  366. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  367. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  368. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  369. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  370. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  371. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  372. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  373. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  374. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  375. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  376. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  377. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  378. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  379. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  380. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  381. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  382. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  383. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  384. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  385. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  386. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  406. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  407. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  408. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  409. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  410. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  411. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  412. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  413. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  414. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  415. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  416. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  417. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  418. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  419. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  420. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  421. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  422. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  423. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  424. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  425. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  426. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  427. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  428. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  429. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  430. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  431. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  432. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  433. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  434. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  435. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  436. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  437. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  438. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  439. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  440. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  441. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  442. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  443. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  444. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  445. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  446. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  447. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  448. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  449. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  450. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  451. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  452. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  453. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  454. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  455. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  456. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  457. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  458. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  459. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  460. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  461. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  462. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  463. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  464. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  465. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  466. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  467. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  468. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  469. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  470. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  471. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  472. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  473. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  474. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  475. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  476. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  477. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  478. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  479. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  480. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  481. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  482. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  483. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  484. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  485. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  486. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  487. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  488. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  489. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  490. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  491. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  492. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  493. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  494. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  495. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  496. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  497. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  498. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  499. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  500. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  501. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  502. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  503. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  504. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  505. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  506. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  507. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  508. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  509. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  510. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  511. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  512. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  513. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  514. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  515. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  516. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  517. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  518. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  519. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  520. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  521. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  522. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  523. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  524. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  525. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  526. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  527. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  528. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  529. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  530. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  531. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  532. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  533. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  534. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  535. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  536. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  537. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  538. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  539. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  540. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  541. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  542. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  543. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  544. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  545. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  546. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  547. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  548. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  549. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  550. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  551. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  552. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  553. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  554. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  555. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  556. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  557. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  558. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  559. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  560. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  561. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  562. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  563. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  564. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  565. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  566. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  567. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  568. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  569. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  570. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  571. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  572. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  573. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  574. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  575. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  576. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  577. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  578. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  579. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  580. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  581. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  582. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  583. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  584. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  585. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  586. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  587. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  588. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  589. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  590. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  591. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  592. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  593. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  594. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  595. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  596. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  597. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  598. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  599. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  600. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  601. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  602. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  603. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  604. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  605. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  606. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  607. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  608. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  609. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  610. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  611. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  612. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  613. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  614. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  615. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  616. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  617. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  618. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  619. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  620. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  621. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  622. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  623. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  624. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  625. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  626. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  627. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  628. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  629. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  630. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  631. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  632. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  633. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  634. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  635. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  636. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  637. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  638. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  639. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  640. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  641. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  642. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  643. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  644. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  645. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  646. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  647. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  648. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  649. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  650. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  651. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  652. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  653. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  654. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  655. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  656. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  657. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  658. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  659. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  660. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  661. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  662. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  663. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  664. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  665. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  666. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  667. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  668. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  669. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  670. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  671. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  672. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  673. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  674. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  675. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  676. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  677. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  678. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  679. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  680. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  681. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  682. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  683. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  684. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  685. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  686. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  687. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  688. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  689. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  690. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  691. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  692. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  693. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  694. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  695. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  696. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  697. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  698. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  699. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  700. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  701. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  702. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  703. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  704. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  705. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  706. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  707. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  708. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  709. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  710. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  711. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  712. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  713. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  714. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  715. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  716. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  717. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  718. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  719. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  720. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  721. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  722. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  723. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  724. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  725. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  726. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  727. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  728. package/node_modules/pi-lens/dist/i18n.js +0 -69
  729. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  730. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  731. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  732. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  733. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  734. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  735. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  736. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  737. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  738. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  739. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  740. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  741. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  742. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  743. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  744. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  745. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  746. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  747. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  748. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
  749. package/node_modules/pi-web-access/opencode-session-headers.ts +0 -24
  750. package/node_modules/remote-pi/README.md +0 -675
  751. package/node_modules/remote-pi/dist/actions/handlers.d.ts +0 -122
  752. package/node_modules/remote-pi/dist/actions/handlers.js +0 -160
  753. package/node_modules/remote-pi/dist/actions/handlers.js.map +0 -1
  754. package/node_modules/remote-pi/dist/actions/registry.d.ts +0 -39
  755. package/node_modules/remote-pi/dist/actions/registry.js +0 -58
  756. package/node_modules/remote-pi/dist/actions/registry.js.map +0 -1
  757. package/node_modules/remote-pi/dist/bin/supervisord.d.ts +0 -2
  758. package/node_modules/remote-pi/dist/bin/supervisord.js +0 -86
  759. package/node_modules/remote-pi/dist/bin/supervisord.js.map +0 -1
  760. package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +0 -58
  761. package/node_modules/remote-pi/dist/commands/builtin_mirror.js +0 -71
  762. package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +0 -1
  763. package/node_modules/remote-pi/dist/commands/list_commands.d.ts +0 -60
  764. package/node_modules/remote-pi/dist/commands/list_commands.js +0 -73
  765. package/node_modules/remote-pi/dist/commands/list_commands.js.map +0 -1
  766. package/node_modules/remote-pi/dist/config.d.ts +0 -62
  767. package/node_modules/remote-pi/dist/config.js +0 -115
  768. package/node_modules/remote-pi/dist/config.js.map +0 -1
  769. package/node_modules/remote-pi/dist/daemon/client.d.ts +0 -20
  770. package/node_modules/remote-pi/dist/daemon/client.js +0 -131
  771. package/node_modules/remote-pi/dist/daemon/client.js.map +0 -1
  772. package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +0 -176
  773. package/node_modules/remote-pi/dist/daemon/control_protocol.js +0 -63
  774. package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +0 -1
  775. package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +0 -45
  776. package/node_modules/remote-pi/dist/daemon/cron_log.js +0 -72
  777. package/node_modules/remote-pi/dist/daemon/cron_log.js.map +0 -1
  778. package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +0 -80
  779. package/node_modules/remote-pi/dist/daemon/cron_registry.js +0 -194
  780. package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +0 -1
  781. package/node_modules/remote-pi/dist/daemon/id.d.ts +0 -24
  782. package/node_modules/remote-pi/dist/daemon/id.js +0 -36
  783. package/node_modules/remote-pi/dist/daemon/id.js.map +0 -1
  784. package/node_modules/remote-pi/dist/daemon/install.d.ts +0 -170
  785. package/node_modules/remote-pi/dist/daemon/install.js +0 -547
  786. package/node_modules/remote-pi/dist/daemon/install.js.map +0 -1
  787. package/node_modules/remote-pi/dist/daemon/registry.d.ts +0 -63
  788. package/node_modules/remote-pi/dist/daemon/registry.js +0 -153
  789. package/node_modules/remote-pi/dist/daemon/registry.js.map +0 -1
  790. package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +0 -194
  791. package/node_modules/remote-pi/dist/daemon/rpc_child.js +0 -389
  792. package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +0 -1
  793. package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +0 -93
  794. package/node_modules/remote-pi/dist/daemon/supervisor.js +0 -596
  795. package/node_modules/remote-pi/dist/daemon/supervisor.js.map +0 -1
  796. package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +0 -26
  797. package/node_modules/remote-pi/dist/extension_ui_bridge.js +0 -341
  798. package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +0 -1
  799. package/node_modules/remote-pi/dist/index.d.ts +0 -213
  800. package/node_modules/remote-pi/dist/index.js +0 -4996
  801. package/node_modules/remote-pi/dist/index.js.map +0 -1
  802. package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +0 -10
  803. package/node_modules/remote-pi/dist/mcp/mesh_result.js +0 -42
  804. package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +0 -1
  805. package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +0 -16
  806. package/node_modules/remote-pi/dist/mcp/mesh_server.js +0 -327
  807. package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +0 -1
  808. package/node_modules/remote-pi/dist/mesh/canonical.d.ts +0 -30
  809. package/node_modules/remote-pi/dist/mesh/canonical.js +0 -61
  810. package/node_modules/remote-pi/dist/mesh/canonical.js.map +0 -1
  811. package/node_modules/remote-pi/dist/mesh/client.d.ts +0 -18
  812. package/node_modules/remote-pi/dist/mesh/client.js +0 -125
  813. package/node_modules/remote-pi/dist/mesh/client.js.map +0 -1
  814. package/node_modules/remote-pi/dist/mesh/encoding.d.ts +0 -38
  815. package/node_modules/remote-pi/dist/mesh/encoding.js +0 -216
  816. package/node_modules/remote-pi/dist/mesh/encoding.js.map +0 -1
  817. package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +0 -73
  818. package/node_modules/remote-pi/dist/mesh/self_revoke.js +0 -401
  819. package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +0 -1
  820. package/node_modules/remote-pi/dist/mesh/siblings.d.ts +0 -49
  821. package/node_modules/remote-pi/dist/mesh/siblings.js +0 -159
  822. package/node_modules/remote-pi/dist/mesh/siblings.js.map +0 -1
  823. package/node_modules/remote-pi/dist/mesh/types.d.ts +0 -34
  824. package/node_modules/remote-pi/dist/mesh/types.js +0 -11
  825. package/node_modules/remote-pi/dist/mesh/types.js.map +0 -1
  826. package/node_modules/remote-pi/dist/mesh/verify.d.ts +0 -17
  827. package/node_modules/remote-pi/dist/mesh/verify.js +0 -86
  828. package/node_modules/remote-pi/dist/mesh/verify.js.map +0 -1
  829. package/node_modules/remote-pi/dist/pairing/crypto.d.ts +0 -8
  830. package/node_modules/remote-pi/dist/pairing/crypto.js +0 -22
  831. package/node_modules/remote-pi/dist/pairing/crypto.js.map +0 -1
  832. package/node_modules/remote-pi/dist/pairing/handshake.d.ts +0 -28
  833. package/node_modules/remote-pi/dist/pairing/handshake.js +0 -113
  834. package/node_modules/remote-pi/dist/pairing/handshake.js.map +0 -1
  835. package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +0 -16
  836. package/node_modules/remote-pi/dist/pairing/noise-sha256.js +0 -103
  837. package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +0 -1
  838. package/node_modules/remote-pi/dist/pairing/qr.d.ts +0 -59
  839. package/node_modules/remote-pi/dist/pairing/qr.js +0 -125
  840. package/node_modules/remote-pi/dist/pairing/qr.js.map +0 -1
  841. package/node_modules/remote-pi/dist/pairing/storage.d.ts +0 -120
  842. package/node_modules/remote-pi/dist/pairing/storage.js +0 -604
  843. package/node_modules/remote-pi/dist/pairing/storage.js.map +0 -1
  844. package/node_modules/remote-pi/dist/protocol/codec.d.ts +0 -7
  845. package/node_modules/remote-pi/dist/protocol/codec.js +0 -49
  846. package/node_modules/remote-pi/dist/protocol/codec.js.map +0 -1
  847. package/node_modules/remote-pi/dist/protocol/types.d.ts +0 -393
  848. package/node_modules/remote-pi/dist/protocol/types.js +0 -2
  849. package/node_modules/remote-pi/dist/protocol/types.js.map +0 -1
  850. package/node_modules/remote-pi/dist/rooms.d.ts +0 -29
  851. package/node_modules/remote-pi/dist/rooms.js +0 -57
  852. package/node_modules/remote-pi/dist/rooms.js.map +0 -1
  853. package/node_modules/remote-pi/dist/session/address.d.ts +0 -49
  854. package/node_modules/remote-pi/dist/session/address.js +0 -58
  855. package/node_modules/remote-pi/dist/session/address.js.map +0 -1
  856. package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +0 -55
  857. package/node_modules/remote-pi/dist/session/agent_bridge.js +0 -146
  858. package/node_modules/remote-pi/dist/session/agent_bridge.js.map +0 -1
  859. package/node_modules/remote-pi/dist/session/bridge.d.ts +0 -37
  860. package/node_modules/remote-pi/dist/session/bridge.js +0 -130
  861. package/node_modules/remote-pi/dist/session/bridge.js.map +0 -1
  862. package/node_modules/remote-pi/dist/session/broker.d.ts +0 -189
  863. package/node_modules/remote-pi/dist/session/broker.js +0 -507
  864. package/node_modules/remote-pi/dist/session/broker.js.map +0 -1
  865. package/node_modules/remote-pi/dist/session/broker_remote.d.ts +0 -74
  866. package/node_modules/remote-pi/dist/session/broker_remote.js +0 -583
  867. package/node_modules/remote-pi/dist/session/broker_remote.js.map +0 -1
  868. package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +0 -33
  869. package/node_modules/remote-pi/dist/session/cwd_lock.js +0 -126
  870. package/node_modules/remote-pi/dist/session/cwd_lock.js.map +0 -1
  871. package/node_modules/remote-pi/dist/session/envelope.d.ts +0 -33
  872. package/node_modules/remote-pi/dist/session/envelope.js +0 -117
  873. package/node_modules/remote-pi/dist/session/envelope.js.map +0 -1
  874. package/node_modules/remote-pi/dist/session/global_config.d.ts +0 -33
  875. package/node_modules/remote-pi/dist/session/global_config.js +0 -73
  876. package/node_modules/remote-pi/dist/session/global_config.js.map +0 -1
  877. package/node_modules/remote-pi/dist/session/ipc.d.ts +0 -27
  878. package/node_modules/remote-pi/dist/session/ipc.js +0 -22
  879. package/node_modules/remote-pi/dist/session/ipc.js.map +0 -1
  880. package/node_modules/remote-pi/dist/session/leader_election.d.ts +0 -32
  881. package/node_modules/remote-pi/dist/session/leader_election.js +0 -106
  882. package/node_modules/remote-pi/dist/session/leader_election.js.map +0 -1
  883. package/node_modules/remote-pi/dist/session/local_config.d.ts +0 -55
  884. package/node_modules/remote-pi/dist/session/local_config.js +0 -157
  885. package/node_modules/remote-pi/dist/session/local_config.js.map +0 -1
  886. package/node_modules/remote-pi/dist/session/mesh_message.d.ts +0 -20
  887. package/node_modules/remote-pi/dist/session/mesh_message.js +0 -28
  888. package/node_modules/remote-pi/dist/session/mesh_message.js.map +0 -1
  889. package/node_modules/remote-pi/dist/session/mesh_node.d.ts +0 -116
  890. package/node_modules/remote-pi/dist/session/mesh_node.js +0 -573
  891. package/node_modules/remote-pi/dist/session/mesh_node.js.map +0 -1
  892. package/node_modules/remote-pi/dist/session/peer.d.ts +0 -139
  893. package/node_modules/remote-pi/dist/session/peer.js +0 -451
  894. package/node_modules/remote-pi/dist/session/peer.js.map +0 -1
  895. package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +0 -13
  896. package/node_modules/remote-pi/dist/session/peer_inventory.js +0 -48
  897. package/node_modules/remote-pi/dist/session/peer_inventory.js.map +0 -1
  898. package/node_modules/remote-pi/dist/session/peer_limits.d.ts +0 -9
  899. package/node_modules/remote-pi/dist/session/peer_limits.js +0 -21
  900. package/node_modules/remote-pi/dist/session/peer_limits.js.map +0 -1
  901. package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +0 -39
  902. package/node_modules/remote-pi/dist/session/setup_wizard.js +0 -63
  903. package/node_modules/remote-pi/dist/session/setup_wizard.js.map +0 -1
  904. package/node_modules/remote-pi/dist/session/tool_gate.d.ts +0 -5
  905. package/node_modules/remote-pi/dist/session/tool_gate.js +0 -11
  906. package/node_modules/remote-pi/dist/session/tool_gate.js.map +0 -1
  907. package/node_modules/remote-pi/dist/session/tools.d.ts +0 -24
  908. package/node_modules/remote-pi/dist/session/tools.js +0 -252
  909. package/node_modules/remote-pi/dist/session/tools.js.map +0 -1
  910. package/node_modules/remote-pi/dist/session/wizard.d.ts +0 -13
  911. package/node_modules/remote-pi/dist/session/wizard.js +0 -20
  912. package/node_modules/remote-pi/dist/session/wizard.js.map +0 -1
  913. package/node_modules/remote-pi/dist/settings.d.ts +0 -15
  914. package/node_modules/remote-pi/dist/settings.js +0 -52
  915. package/node_modules/remote-pi/dist/settings.js.map +0 -1
  916. package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +0 -37
  917. package/node_modules/remote-pi/dist/transport/peer_channel.js +0 -98
  918. package/node_modules/remote-pi/dist/transport/peer_channel.js.map +0 -1
  919. package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +0 -29
  920. package/node_modules/remote-pi/dist/transport/pi_forward_client.js +0 -62
  921. package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +0 -1
  922. package/node_modules/remote-pi/dist/transport/relay_client.d.ts +0 -91
  923. package/node_modules/remote-pi/dist/transport/relay_client.js +0 -206
  924. package/node_modules/remote-pi/dist/transport/relay_client.js.map +0 -1
  925. package/node_modules/remote-pi/dist/ui/footer.d.ts +0 -32
  926. package/node_modules/remote-pi/dist/ui/footer.js +0 -34
  927. package/node_modules/remote-pi/dist/ui/footer.js.map +0 -1
  928. package/node_modules/remote-pi/docs/daemon.md +0 -289
  929. package/node_modules/remote-pi/package.json +0 -92
  930. package/node_modules/remote-pi/service-templates/launchd.plist.template +0 -35
  931. package/node_modules/remote-pi/service-templates/systemd.service.template +0 -19
  932. package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +0 -10
  933. package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +0 -38
  934. package/node_modules/remote-pi/skills/agent-network/SKILL.md +0 -310
@@ -1,2571 +0,0 @@
1
- /**
2
- * lens_diagnostics tool — cached project diagnostic state (issue #159).
3
- *
4
- * Three modes:
5
- * delta (default) — fixable warnings from the current agent turn, read from
6
- * the actionable-warnings and code-quality-warnings caches.
7
- * all — all known diagnostic counts across every file pi-lens has
8
- * seen this session, read from the widget state.
9
- * full — active project-wide LSP diagnostic scan merged with all.
10
- */
11
- import { promises as fs } from "node:fs";
12
- import * as fsSync from "node:fs";
13
- import * as path from "node:path";
14
- import { BoundedLruCache } from "../clients/bounded-cache.js";
15
- import { Type } from "../clients/deps/typebox.js";
16
- import { anchorsForDiagnostic, applyDispositions, applyWeakDispositions, hasAnyDispositionMarks, hasStrictDispositionMarks, getDisposition, } from "../clients/diagnostic-dispositions.js";
17
- import { DEPENDENCY_DRIFT_MAX_DELIVERIES } from "../clients/blocker-freshness.js";
18
- import { freshnessFromMtime } from "../clients/freshness.js";
19
- import { applyFindingPolicy, loadProjectRulePolicyMap, } from "../clients/dispatch/finding-policy.js";
20
- import { gateFindingsByPathFreshness } from "../clients/advisory-provenance.js";
21
- import { formatCacheAgeLabel, markUnreconciledFindings, } from "../clients/finding-delivery-gate.js";
22
- import { normalizeRuleId } from "../clients/dispatch/rule-id-normalize.js";
23
- import { applyRulePolicy, } from "../clients/dispatch/rule-policy.js";
24
- import { compactRenderResult } from "./render-compact.js";
25
- import { combineAbortSignals } from "../clients/deadline-utils.js";
26
- import { getProjectIgnoreMatcher } from "../clients/file-utils.js";
27
- import { isAtOrAboveHomeDir, normalizeEphemeralMapKey, normalizeFilePath, normalizeMapKey, realpathOrResolve, } from "../clients/path-utils.js";
28
- import { getLSPService } from "../clients/lsp/index.js";
29
- import { retireInlineBlockerAndResyncGuard } from "../clients/git-guard.js";
30
- import { primaryServerId, resolveLspCwdForFile, } from "../clients/lsp/config.js";
31
- import { getFullScanWallClockMs } from "../clients/lsp/workspace-sweep-hold.js";
32
- import { demoteInferredProjectSweepResults } from "../clients/lsp/inferred-project.js";
33
- import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
34
- import { loadProjectDiagnosticsDeltaReport, loadProjectDiagnosticsSnapshot, PROJECT_DIAGNOSTICS_CACHE_VERSION, reconcileProjectDiagnosticsSnapshot, } from "../clients/project-diagnostics/cache.js";
35
- import { formatCacheAgeOld, formatNotRunEntry, } from "../clients/project-diagnostics/extractors.js";
36
- import { ANALYZER_IDS, fetchFreshProjectDiagnostics, } from "../clients/project-diagnostics/fresh-fetch.js";
37
- import { loadBootstrapClients } from "../clients/bootstrap.js";
38
- import { resolveLensToolName, } from "../clients/tool-config.js";
39
- import { generatedSkipNotice, scanTruncationNotice, } from "../clients/lens-engine.js";
40
- import { scanProjectDiagnostics } from "../clients/project-diagnostics/scanner.js";
41
- import { classifyDiagnosticTier, getFileDiagnosticSummaries, reconcileCorrelatedScanDiagnostics, reconcileScanDiagnostics, reconcileStaleWidgetDependencyBlockers, reconcileStaleWidgetFiles, widgetDiagnosticUri, } from "../clients/widget-state.js";
42
- import { claimPhaseOncePerSession, logLatency, } from "../clients/latency-logger.js";
43
- import { logExtension } from "../clients/extension-log.js";
44
- import { recordDegradationOnce } from "../clients/degradation-ledger.js";
45
- import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
46
- import { findAuxiliaryProfileForSource, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
47
- import { detectFileRole } from "../clients/file-role.js";
48
- import { STALE_LINE_MARKER } from "../clients/stale-marker.js";
49
- import { makeProgressReporter, scanningSummaryLine } from "./scan-progress.js";
50
- import { createLspDiagnosticsTool, LSP_SEVERITY_FILTERS, MAX_BATCH_FILES, renderBatchOutcomeLines, } from "./lsp-diagnostics.js";
51
- import { demotePastEofDiagnostics, PAST_EOF_STALE_MARKER, resyncDocumentOnPastEof, } from "../clients/diagnostic-line-freshness.js";
52
- // The widget state exposes the full per-file diagnostic set; this is the tool's
53
- // own generous display budget per file (independent of the TUI's 12 cap), to
54
- // keep output bounded on a pathologically broken file.
55
- const MAX_DIAGNOSTICS_PER_FILE = 50;
56
- // `paths` (#461) is a wrapper-facing scope restrictor (e.g. "git-staged files
57
- // only"), not a bulk-listing mechanism — a wrapper that hits this needs to
58
- // narrow, not paginate. Erroring (rather than silently truncating) means a
59
- // caller can never believe it checked files it didn't (issue's stated
60
- // invariant).
61
- const MAX_PATHS_ENTRIES = MAX_BATCH_FILES;
62
- // #1623: the reason rendered for every heavyweight-analyzer lane (gitleaks,
63
- // trivy, govulncheck, dead-code, knip, jscpd, madge, opengrep, test-runner)
64
- // when mode=full is called without refreshRunners=cheap/all/cached — the
65
- // "quick mode" gate that skips the expensive fresh-fetch entirely (see
66
- // `formatFullMode`'s `analyzersPromise`). One shared function so it renders
67
- // identically everywhere it's used, naming the tool THIS host can call
68
- // (#2535 F1 — the shared const named the pi tool on MCP too).
69
- function notRequestedReason(host) {
70
- return ("refreshRunners not requested this call (quick mode) — pass " +
71
- `refreshRunners=cheap/all/cached to ${resolveLensToolName("lens_diagnostics", host) ?? "diagnostics"} mode=full to run it`);
72
- }
73
- // Wall-clock ceiling for the whole mode=full scan. Even with per-file budgets
74
- // and abort-signal honoring, a pathological state (a language server hanging on
75
- // spawn/initialize across many files) could otherwise stall the tool
76
- // indefinitely — an unattended session was observed hung for ~8h. This hard cap
77
- // aborts the scan so it always returns (partial) rather than never. Env-tunable.
78
- // #1618: single source with `clients/runtime-turn.ts`'s idle-reset base delay
79
- // (see `getBaseLspIdleResetMs`), which derives itself from this value so the
80
- // two constants can no longer drift back into the idle-reset-fires-mid-sweep
81
- // relationship that caused #1618 in the first place.
82
- const FULL_SCAN_WALL_CLOCK_MS = getFullScanWallClockMs();
83
- // Binding verification is a fallback for result producers that supplied a
84
- // content hash without an already-computed disk verdict. Keep it one-file-at-a-
85
- // time and yield periodically: a full scan can contain thousands of results,
86
- // and a synchronous read loop here would freeze the host even though the LSP
87
- // work itself was asynchronous (#1198 follow-up).
88
- const FULL_SCAN_BINDING_YIELD_EVERY = 16;
89
- async function findFullScanBindingMismatches(results) {
90
- const mismatched = new WeakSet();
91
- let sinceYield = 0;
92
- for (const result of results) {
93
- if (result.boundToCurrentDisk === false) {
94
- mismatched.add(result);
95
- }
96
- else if (
97
- // A `true` verdict came from the LSP/cache binding seam and has already
98
- // compared its fingerprint with disk. Only legacy/partial producers need
99
- // this fallback read; `unknown` remains unknown when no hash exists.
100
- result.boundToCurrentDisk !== true &&
101
- result.contentHash !== undefined) {
102
- try {
103
- // Promise-based, sequential reads bound both event-loop occupancy and
104
- // outstanding file handles. A read failure deliberately leaves the
105
- // verdict unknown (the historical fail-open behavior).
106
- const content = await fs.readFile(result.filePath, "utf-8");
107
- if (hashDiagnosticContent(content) !== result.contentHash) {
108
- mismatched.add(result);
109
- }
110
- }
111
- catch {
112
- // Unreadable/deleted files cannot disprove the binding. Leave them
113
- // unknown for the existing caller policy, rather than manufacturing a
114
- // mismatch or a clean verdict here.
115
- }
116
- }
117
- sinceYield += 1;
118
- if (sinceYield >= FULL_SCAN_BINDING_YIELD_EVERY) {
119
- sinceYield = 0;
120
- await new Promise((resolve) => setImmediate(resolve));
121
- }
122
- }
123
- return mismatched;
124
- }
125
- export function createLensDiagnosticsTool(cacheManager, getCwd, getLspService = getLSPService,
126
- // Flush any debounced per-edit dispatches before reading, so files the agent
127
- // fixed earlier in the turn are re-dispatched and the widget reflects the
128
- // CURRENT state — not the pre-fix diagnostics still pending in the debounce
129
- // window. Injected (index wires `flushDebouncedToolResults`); optional so the
130
- // tool stays decoupled and testable.
131
- flushPending = async () => { },
132
- // #571: mode=full's own fresh LSP results are reconciled into the footer
133
- // (widget-state's `allDiagnostics`) for files this scan got a CONFIRMED
134
- // (non-timed-out) result for — otherwise an unedited file whose diagnostics
135
- // only changed because of a change to a file it depends on stays stale in
136
- // the footer forever. Each reconciled write draws a fresh token from the
137
- // SAME monotonic source `pipeline.ts`'s per-edit writes use
138
- // (`RuntimeCoordinator.nextWriteIndex()`, injected by index.ts) so the
139
- // existing `WriteOrderingGuard` (#555/#560) can tell this scan-originated
140
- // write apart from a concurrent, genuinely newer per-edit write for the
141
- // same file. Optional/undefined in tests — an omitted writeIndex always
142
- // proceeds (see `reconcileScanDiagnostics`'s doc comment).
143
- nextWriteIndex,
144
- // #798/#338: capture host UI methods synchronously from the active tool event.
145
- // The returned closure is safe for the later async sweep to invoke because it
146
- // never dereferences the session-guarded ctx.ui getter.
147
- captureLspStatusRepaint, getRuntime, isLensGuardEnabled = () => true) {
148
- const lspProbe = createLspDiagnosticsTool(nextWriteIndex, (info) => {
149
- const runtime = getRuntime?.();
150
- if (!runtime)
151
- return;
152
- const retired = retireInlineBlockerAndResyncGuard({
153
- runtime,
154
- cacheManager,
155
- cwd: info.cwd,
156
- filePath: info.filePath,
157
- ...(info.writeIndex === undefined
158
- ? {}
159
- : { writeIndex: info.writeIndex }),
160
- coveredSources: info.coveredSources,
161
- lensGuardEnabled: isLensGuardEnabled(),
162
- });
163
- if (retired) {
164
- logExtension({
165
- subsystem: "git-guard",
166
- level: "debug",
167
- message: `inline_blocker: retired for ${info.filePath}`,
168
- });
169
- }
170
- }, getLspService);
171
- return {
172
- name: "lens_diagnostics",
173
- label: "Project Diagnostics",
174
- description: 'Query pi-lens diagnostics from the session cache or an active LSP probe, at paths or workspace scope. Empty cache is not proof of clean; probe changed paths when findings are absent or stale. Example: `{source: "lsp", scope: "paths", paths: ["src/app.ts"]}`.',
175
- promptSnippet: "lens_diagnostics source=session reads the session cache and an empty cache is not proof of a clean file; use source=lsp scope=paths for changed files when cached findings are absent or stale",
176
- renderResult: compactRenderResult(({ details, args, isError, text }) => {
177
- if (details?.source === "lsp") {
178
- const count = details.totalDiagnostics ?? 0;
179
- const files = details.filesChecked ?? details.filesScanned ?? 0;
180
- const noun = count === 1 ? "diagnostic" : "diagnostics";
181
- const filePath = typeof details?.filePath === "string"
182
- ? details.filePath
183
- : Array.isArray(args?.paths) &&
184
- args.paths.length === 1 &&
185
- typeof args.paths[0] === "string"
186
- ? args.paths[0]
187
- : undefined;
188
- const singleFile = (details?.mode === "file" || files === 1) &&
189
- typeof filePath === "string"
190
- ? ` ${path.basename(filePath)}`
191
- : "";
192
- const scope = files > 1 ? ` across ${files} files` : singleFile;
193
- if (isError)
194
- return `lens_diagnostics lsp — ${text.split("\n")[0] ?? "error"}`;
195
- if ((details.navigationOnlyFiles ?? 0) > 0)
196
- return `lens_diagnostics${scope} — ${count} ${noun} · ${details.cleanFiles ?? 0} clean · ${details.navigationOnlyFiles} navigation-only`;
197
- if ((details.unconfirmedFiles ?? 0) > 0)
198
- return `lens_diagnostics${scope} — ${count} ${noun} · ${details.cleanFiles ?? 0} clean · ${details.unconfirmedFiles} unconfirmed${details.timedOutFiles ? ` (${details.timedOutFiles} timed out)` : ""}`;
199
- const outcomeCounts = details.outcomeCounts;
200
- const tooLargeLines = renderBatchOutcomeLines(details.outcomes ?? []);
201
- if (tooLargeLines.length > 0)
202
- return `lens_diagnostics${scope} — too large: ${tooLargeLines.join("; ")}`;
203
- const notConfirmed = outcomeCounts
204
- ? (outcomeCounts.inconclusive ?? 0) +
205
- (outcomeCounts.unavailable ?? 0) +
206
- (outcomeCounts.unsupported ?? 0) +
207
- (outcomeCounts.failed ?? 0) +
208
- (outcomeCounts.too_large ?? 0)
209
- : 0;
210
- if (notConfirmed > 0)
211
- return `lens_diagnostics${scope} — ${count} ${noun} · ${notConfirmed} checks not confirmed`;
212
- if ((details.incompleteFiles ?? 0) > 0)
213
- return `lens_diagnostics${scope} — incomplete (${details.incompleteFiles} files not confirmed)`;
214
- if (count === 0 && details.unconfirmed)
215
- return details.timedOut
216
- ? `lens_diagnostics${scope} — timed out (result may be incomplete)`
217
- : `lens_diagnostics${scope} — unconfirmed (server cannot confirm clean)`;
218
- return `lens_diagnostics${scope} — ${count} ${noun}`;
219
- }
220
- // Streaming progress partials render the live bar (see scanningSummaryLine)
221
- // instead of the details-driven summary, which would show "0 diagnostics"
222
- // mid-scan.
223
- const scanning = scanningSummaryLine(details, text);
224
- if (scanning)
225
- return scanning;
226
- const mode = details?.mode ?? (typeof args.mode === "string" ? args.mode : "delta");
227
- if (isError) {
228
- return `lens_diagnostics ${mode} — ${text.split("\n")[0] ?? "error"}`;
229
- }
230
- // #533: a "clean"/zero render must say so when heavyweight analyzers never
231
- // ran this session — a bare "clean" would otherwise misrepresent a cold
232
- // cache as a confirmed answer from those analyzers.
233
- const coldSuffix = details?.coldRunners && details.coldRunners.length > 0
234
- ? ` (${details.coldRunners.length} cold: ${details.coldRunners.join(", ")})`
235
- : "";
236
- const failedSuffix = details?.failedAnalyzers && details.failedAnalyzers.length > 0
237
- ? ` (${details.failedAnalyzers.length} failed: ${details.failedAnalyzers.map((item) => item.id).join(", ")})`
238
- : "";
239
- if (mode === "delta") {
240
- const aw = details?.actionableWarnings ?? 0;
241
- const cq = details?.qualityIssues ?? 0;
242
- const pd = details?.projectDiagnostics ?? 0;
243
- if (aw + cq + pd === 0)
244
- return `lens_diagnostics delta — clean${coldSuffix}${failedSuffix}`;
245
- return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}${failedSuffix}`;
246
- }
247
- // #1799: `semantic === "blocking"` iff `severity === "error"` holds for
248
- // RAW classification, but not for the rendered counts — a
249
- // dependency-drift demotion (#1631, widget-state.ts `isBlocking` vs
250
- // `countDiagnostics`) revokes an error's blocking authority (stale)
251
- // while the finding itself stays real evidence, so it lands in
252
- // `totalErrors` with `totalBlocking` unchanged. That is the one case
253
- // where the two totals genuinely disagree, and it must still render —
254
- // otherwise a file with drift-demoted errors reads as "clean". What
255
- // must NOT happen is printing both terms for the SAME findings (the
256
- // actual #1799 bug), so `errors` renders only when `blocking === 0`,
257
- // mirroring the per-file row's own guard below.
258
- const b = details?.totalBlocking ?? 0;
259
- const e = details?.totalErrors ?? 0;
260
- const w = details?.totalWarnings ?? 0;
261
- // #2414: hint/info tier never contributes to `b + e + w` — a session
262
- // with only style opinions is genuinely "clean" of code defects, but
263
- // the one-line summary still names the advisory count so it isn't
264
- // indistinguishable from a session with zero findings at all.
265
- const adv = details?.totalAdvisories ?? 0;
266
- const files = details?.filesWithIssues ?? details?.filesChecked ?? 0;
267
- if (b + e + w === 0) {
268
- const advisorySuffix = adv > 0 ? `, ${adv} hint${adv === 1 ? "" : "s"}` : "";
269
- return `lens_diagnostics ${mode} — clean${advisorySuffix} (${files} files)${coldSuffix}${failedSuffix}`;
270
- }
271
- const parts = [`${b} blocking`];
272
- if (b === 0 && e > 0)
273
- parts.push(`${e} errors`);
274
- parts.push(`${w} warnings`);
275
- return `lens_diagnostics ${mode} — ${parts.join(" · ")} (${files} files)${coldSuffix}${failedSuffix}`;
276
- }),
277
- parameters: Type.Object({
278
- source: Type.Optional(Type.String({
279
- enum: ["session", "lsp"],
280
- description: "Evidence source: session cache (empty cache is not proof of a " +
281
- "clean file — use source=lsp for changed files) or an active LSP probe.",
282
- })),
283
- scope: Type.Optional(Type.String({
284
- enum: ["paths", "workspace"],
285
- description:
286
- // #2860: no schema value maps to "current turn's delta" — the
287
- // default (omitted scope) already gives that for
288
- // source=session, and for source=lsp `delta` was byte-identical
289
- // to `paths` (round-2 verify N/F4), so exposing it would only
290
- // invite a caller to believe it does something distinct.
291
- "Coverage scope: explicit paths, or workspace (default: current turn's delta for source=session).",
292
- })),
293
- mode: Type.Optional(Type.String({
294
- enum: ["delta", "all", "full"],
295
- description: "delta = current turn; all = cache-only; full = active LSP scan of paths. Empty cache is not proof of clean.",
296
- })),
297
- path: Type.Optional(Type.String({
298
- description: "One file or directory for source=lsp checks.",
299
- })),
300
- concurrency: Type.Optional(Type.Number({
301
- description: "Source=lsp batch concurrency (maximum 16).",
302
- })),
303
- waitMs: Type.Optional(Type.Number({
304
- description: "Source=lsp per-file wait budget in milliseconds.",
305
- })),
306
- serverScope: Type.Optional(Type.String({
307
- enum: ["primary", "all"],
308
- description: "Source=lsp server coverage: primary or all.",
309
- })),
310
- refreshRunners: Type.Optional(Type.Union([
311
- Type.Boolean(),
312
- Type.String({ enum: ["cached", "cheap", "all", "none"] }),
313
- ], {
314
- description: "Analyzer refresh mode for full view.",
315
- })),
316
- maxProjectFiles: Type.Optional(Type.Number({
317
- description: "Project-file limit for cheap runners.",
318
- })),
319
- maxLspFiles: Type.Optional(Type.Number({
320
- description: "File limit for the LSP sweep.",
321
- })),
322
- includeGenerated: Type.Optional(Type.Boolean({
323
- description: "Include generated-name paths in full scans.",
324
- })),
325
- analysisRoot: Type.Optional(Type.String({
326
- description: "Explicit project directory for heavyweight analyzers; it must exist and resolve strictly below the home directory.",
327
- })),
328
- severity: Type.Optional(Type.String({
329
- enum: [...LSP_SEVERITY_FILTERS],
330
- description: "Filter by severity threshold (default: all): error shows errors; warning includes errors and warnings; information includes errors, warnings, and information; hint and all show every known tier.",
331
- })),
332
- paths: Type.Optional(Type.Array(Type.String(), {
333
- maxItems: MAX_PATHS_ENTRIES,
334
- description: "Files or directories to filter.",
335
- })),
336
- }),
337
- async execute(_toolCallId, params, signal, onUpdate, ctx) {
338
- const requestedSource = params.source;
339
- const requestedScope = params.scope;
340
- const legacyMode = params.mode;
341
- const source = requestedSource ?? "session";
342
- const scope = requestedScope ??
343
- (legacyMode === "delta" || legacyMode === undefined
344
- ? source === "lsp"
345
- ? "paths"
346
- : "delta"
347
- : "workspace");
348
- const cwd = ctx.cwd ?? getCwd();
349
- if (source === "lsp") {
350
- const lspParams = { ...params };
351
- delete lspParams.source;
352
- delete lspParams.scope;
353
- // #2860: explicit `path`/`paths` always win. `scope=workspace`
354
- // only supplies a `cwd` default (a full workspace sweep) when the
355
- // caller gave NEITHER — it must never override an explicit
356
- // narrower request (the #2052 root-eviction hang and the
357
- // SKILL.md "check a folder" recipe both depend on this: a
358
- // directory `path` under scope=workspace scans that directory,
359
- // not the whole project).
360
- const hasExplicitPath = typeof lspParams.path === "string" && lspParams.path.length > 0;
361
- const hasExplicitPaths = Array.isArray(lspParams.paths) && lspParams.paths.length > 0;
362
- if (scope === "workspace" && !hasExplicitPath && !hasExplicitPaths) {
363
- lspParams.path = cwd;
364
- }
365
- const lspStart = Date.now();
366
- const result = (await lspProbe.execute(_toolCallId, lspParams, signal, onUpdate, ctx.signal ? { cwd, signal: ctx.signal } : { cwd }));
367
- // #2860 F11: the folded route's own durable trace — without this,
368
- // F1's class of defect (a real LSP result rendering as "clean")
369
- // leaves no record in latency.log distinguishing source=lsp from
370
- // source=session. One record per tool call (not per file), same
371
- // convention as the mode=all `blocker_freshness_widget_gate`
372
- // phase above.
373
- logLatency({
374
- type: "phase",
375
- toolName: "lens_diagnostics",
376
- filePath: cwd,
377
- phase: "lens_diagnostics_lsp_route",
378
- durationMs: Date.now() - lspStart,
379
- metadata: {
380
- scope,
381
- isError: result.isError === true,
382
- totalDiagnostics: result.details?.totalDiagnostics ?? 0,
383
- cleanFiles: result.details?.cleanFiles ?? 0,
384
- unconfirmedFiles: result.details?.unconfirmedFiles ?? 0,
385
- timedOutFiles: result.details?.timedOutFiles ?? 0,
386
- },
387
- });
388
- return {
389
- ...result,
390
- isError: result.isError === true,
391
- details: { ...result.details, source, scope },
392
- };
393
- }
394
- if (requestedSource !== undefined) {
395
- const effectiveMode = legacyMode ??
396
- (scope === "workspace" ? "all" : scope === "paths" ? "all" : "delta");
397
- params = { ...params, mode: effectiveMode };
398
- }
399
- const repaintLspStatus = captureLspStatusRepaint?.(ctx);
400
- const mode = params.mode ?? "delta";
401
- const severity = params.severity ?? "all";
402
- const refreshRunners = params.refreshRunners;
403
- const parsePositiveInt = (value) => typeof value === "number" && Number.isFinite(value) && value > 0
404
- ? Math.floor(value)
405
- : undefined;
406
- const maxProjectFiles = parsePositiveInt(params.maxProjectFiles);
407
- const maxLspFiles = parsePositiveInt(params.maxLspFiles);
408
- const includeGenerated = params.includeGenerated === true;
409
- let pathsScope;
410
- try {
411
- pathsScope = resolvePathsScope(cwd, params.paths);
412
- }
413
- catch (err) {
414
- return {
415
- content: [
416
- {
417
- type: "text",
418
- text: err instanceof Error ? err.message : String(err),
419
- },
420
- ],
421
- isError: true,
422
- details: { mode, pathsError: true },
423
- };
424
- }
425
- // Escape aborts the agent *turn*, which fires ctx.signal (the turn-wired
426
- // abort); the positional signal is the tool-call signal. A registered
427
- // extension tool only reliably sees the turn abort via ctx.signal, so honor
428
- // BOTH — else a long mode=full scan ignores Escape (the reported bug).
429
- const abortSignal = combineAbortSignals(signal, ctx.signal);
430
- // Reflect the agent's just-made fixes before reporting: flush pending
431
- // per-edit dispatches (re-records fixed files), then drop entries whose
432
- // file changed on disk afterwards / was deleted (stale, e.g. external
433
- // edits). Together these stop fixed-this-session findings from lingering.
434
- await flushPending();
435
- const staleDropped = await reconcileStaleWidgetFiles();
436
- if (mode === "all") {
437
- // #1631 dependency-axis gate for the mode=all store. `reconcileStaleWidgetFiles`
438
- // (above) only sees the diagnosed file's OWN mtime; this demotes blocking
439
- // findings whose forward imports drifted out-of-band. Paired reconciles, one
440
- // read site — the same gate that covers the turn-end inline-blocker store.
441
- const dependencyGateStart = Date.now();
442
- const { demoted: dependencyDemoted, truncatedImports } = await reconcileStaleWidgetDependencyBlockers(cwd, getRuntime?.()?.turnIndex);
443
- logLatency({
444
- type: "phase",
445
- toolName: "lens_diagnostics",
446
- filePath: cwd,
447
- phase: "blocker_freshness_widget_gate",
448
- durationMs: Date.now() - dependencyGateStart,
449
- metadata: {
450
- demoted: dependencyDemoted,
451
- truncatedImports,
452
- surface: "mode=all",
453
- },
454
- });
455
- return formatAllMode(cwd, severity, undefined, undefined, staleDropped, pathsScope, dependencyDemoted);
456
- }
457
- if (mode === "full") {
458
- // Fold a hard wall-clock ceiling into the abort signal so the scan
459
- // always terminates (partial) even if a hung server would otherwise
460
- // stall it forever. AbortSignal.timeout aborts with a TimeoutError.
461
- const ceiling = AbortSignal.timeout(FULL_SCAN_WALL_CLOCK_MS);
462
- const fullSignal = combineAbortSignals(abortSignal, ceiling);
463
- // Stream a throttled progress bar: the full scan is opaque for minutes
464
- // otherwise.
465
- const onProgress = makeProgressReporter(onUpdate);
466
- return formatFullMode(cwd, severity, getLspService(), cacheManager, {
467
- refreshRunners,
468
- maxProjectFiles,
469
- maxLspFiles,
470
- includeGenerated,
471
- signal: fullSignal,
472
- wallClockMs: FULL_SCAN_WALL_CLOCK_MS,
473
- onProgress,
474
- onServerReady: repaintLspStatus,
475
- pathsScope,
476
- nextWriteIndex,
477
- runtime: getRuntime?.(),
478
- analysisRoot: typeof params.analysisRoot === "string"
479
- ? params.analysisRoot
480
- : undefined,
481
- host: ctx.host ?? "pi",
482
- });
483
- }
484
- return formatDeltaMode(cacheManager, cwd, severity, pathsScope);
485
- },
486
- };
487
- }
488
- // ── delta mode ────────────────────────────────────────────────────────────────
489
- function formatProjectDeltaDiagnostic(diagnostic, stale) {
490
- // #1944: a demoted row loses the authority marker along with its
491
- // coordinate. It kept the 🔴 while its line was replaced by the stale
492
- // marker, which is the same "changed the channel, not the body" defect the
493
- // turn-end advisory carried — `formatFullMode` (this file, the `d.stale`
494
- // arm of its marker) already drops the marker for exactly this reason.
495
- const marker = stale
496
- ? "○"
497
- : diagnostic.semantic === "blocking" || diagnostic.severity === "error"
498
- ? "🔴"
499
- : "ℹ";
500
- const rule = diagnostic.rule ?? diagnostic.code ?? diagnostic.runner;
501
- const where = stale ? STALE_LINE_MARKER : `L${diagnostic.line ?? "?"}`;
502
- return ` ${marker} ${where} ${rule} ${diagnostic.message}`;
503
- }
504
- /**
505
- * #1634 review round R3: `appendProjectDiagnosticsDeltaLines` re-serves the
506
- * persisted project-diagnostics DELTA report verbatim — the third of
507
- * `mode=delta`'s three arms (the other two, actionable/quality warnings, were
508
- * gated in the F3 pass above), and it carried the identical unfixed shape:
509
- * cited `file:line` findings from a report with its own `generatedAt`, no
510
- * freshness check. Same gate, same policy as `applyDeltaFreshnessGate`:
511
- * missing file -> DROP, edited-since -> DEMOTE (loses its line, keeps
512
- * rule/message), live -> unchanged. See `clients/finding-delivery-gate.ts`
513
- * surface `lens-diagnostics:mode-delta`.
514
- */
515
- function appendProjectDiagnosticsDeltaLines(groups, cwd, report, severity, includeFile) {
516
- const scoped = (report?.diagnostics ?? []).filter((diagnostic) => includeFile(diagnostic.filePath) &&
517
- matchesSeverity(projectDiagnosticToWidget(diagnostic, diagnostic.filePath), severity));
518
- const { "lens-diagnostics-delta-project": gated } = gateFindingsByPathFreshness({
519
- cwd,
520
- sources: {
521
- "lens-diagnostics-delta-project": {
522
- findings: scoped,
523
- scannedAt: report?.generatedAt,
524
- citedPath: (d) => d.filePath,
525
- onMissing: "drop",
526
- },
527
- },
528
- });
529
- const staleSet = new Set(gated.stale);
530
- // Concatenating live-then-stale reorders a file's demoted rows to the end
531
- // of its bucket below (rather than each diagnostic's original report
532
- // order) — cosmetic only, findings are neither dropped nor duplicated.
533
- const diagnostics = [...gated.live, ...gated.stale];
534
- const byFile = new Map();
535
- for (const diagnostic of diagnostics) {
536
- const filePath = path.resolve(diagnostic.filePath);
537
- const bucket = byFile.get(filePath) ?? [];
538
- bucket.push(diagnostic);
539
- byFile.set(filePath, bucket);
540
- }
541
- for (const [filePath, fileDiagnostics] of byFile) {
542
- const group = getDeltaFileGroup(groups, cwd, filePath);
543
- for (const diagnostic of fileDiagnostics) {
544
- group.projectLines.push(formatProjectDeltaDiagnostic(diagnostic, staleSet.has(diagnostic)));
545
- }
546
- }
547
- return diagnostics.length;
548
- }
549
- /**
550
- * Bounded content memo for {@link applyCachedDispositions}' strict-immediate
551
- * path: one read per (file revision), not per query. Keyed through
552
- * `normalizeEphemeralMapKey` (cheap slash-fold + win32-lowercase — these keys
553
- * are produced and consumed inside this process only) and validated against a
554
- * fresh stat's mtimeMs+size on every hit, so an edit invalidates without a
555
- * TTL. Insertion-ordered LRU cap bounds resident source bytes; repeated
556
- * queries and multi-file reports that cite the same file reuse one read.
557
- */
558
- export const CACHED_CONTENT_MEMO_CAP = 64;
559
- const cachedContentMemo = new BoundedLruCache(CACHED_CONTENT_MEMO_CAP);
560
- function readContentForAnchors(filePath, stat) {
561
- const key = normalizeEphemeralMapKey(filePath);
562
- const hit = cachedContentMemo.get(key);
563
- if (hit && hit.mtimeMs === stat.mtimeMs && hit.size === stat.size) {
564
- return hit.content;
565
- }
566
- let content;
567
- try {
568
- content = fsSync.readFileSync(filePath, "utf-8");
569
- }
570
- catch {
571
- return undefined;
572
- }
573
- cachedContentMemo.set(key, {
574
- mtimeMs: stat.mtimeMs,
575
- size: stat.size,
576
- content,
577
- });
578
- return content;
579
- }
580
- /** #2442 test-only: exercise the bounded content memo (LRU) directly,
581
- * without a full lens_diagnostics dispatch. */
582
- export function _readContentForAnchorsForTests(filePath, stat) {
583
- return readContentForAnchors(filePath, stat);
584
- }
585
- /**
586
- * ONE disposition seam for every cache-only lens_diagnostics surface —
587
- * mode=all summaries, mode=delta's actionable/quality warnings, the delta
588
- * project-diagnostics report, and the empty-delta carried-over note. Cached
589
- * findings predate any lens_diagnostic_mark, so each surface must re-apply
590
- * dispositions at query time; before this seam each site hand-picked its own
591
- * variant and three of them served stale STRICT false-positive marks via the
592
- * weak-only filter (a weak filter can never drop a strict-anchored mark, nor
593
- * any blocking finding — #1625 F1).
594
- *
595
- * Strict-immediate path: stat the file once, then compare its mtime against
596
- * the newest cached diagnostic's own observation stamp through the freshness
597
- * kernel (`freshnessFromMtime`). Fresh or stamp-less means the current bytes
598
- * still describe what these findings were observed against, so the content is
599
- * read (memoized per revision above) and the FULL applyDispositions filter
600
- * runs — a fresh false-positive mark converges immediately, including on
601
- * blocking findings.
602
- *
603
- * Weak fallback (zero further I/O): a file that is gone/unreadable, or whose
604
- * mtime moved AFTER these diagnostics were observed. Stale-content edge: the
605
- * cached findings describe a superseded revision, so their line numbers may no
606
- * longer align with the lines a mark hashed; deriving strict anchors from the
607
- * new content could match a DIFFERENT occurrence that merely shares the line
608
- * number and identical text, so the mark waits for the next real dispatch to
609
- * re-derive both sides against matching revisions. Weak suppress/defer marks
610
- * are intent-level and never look at content, so they keep applying here.
611
- */
612
- function applyCachedDispositions(cachedDiagnostics, cwd, filePath) {
613
- // Zero-I/O fast paths (#1625 F2 precedent): with no marks at all there is
614
- // nothing to filter; with only weak suppress/defer marks a strict anchor
615
- // can never match, so the weak filter serves them without any stat/read.
616
- if (!hasAnyDispositionMarks(cwd))
617
- return cachedDiagnostics;
618
- if (!hasStrictDispositionMarks(cwd)) {
619
- return applyWeakDispositions(cachedDiagnostics, cwd, filePath);
620
- }
621
- let stat;
622
- try {
623
- stat = fsSync.statSync(filePath);
624
- }
625
- catch {
626
- return applyWeakDispositions(cachedDiagnostics, cwd, filePath);
627
- }
628
- const referenceMs = cachedDiagnostics.reduce((latest, d) => Math.max(latest, d.observedAt ?? 0), 0);
629
- if (referenceMs > 0 &&
630
- freshnessFromMtime({ mtimeMs: stat.mtimeMs, referenceMs }).verdict ===
631
- "stale") {
632
- return applyWeakDispositions(cachedDiagnostics, cwd, filePath);
633
- }
634
- const content = readContentForAnchors(filePath, stat);
635
- if (content === undefined) {
636
- return applyWeakDispositions(cachedDiagnostics, cwd, filePath);
637
- }
638
- return applyDispositions(cachedDiagnostics, cwd, filePath, content);
639
- }
640
- /**
641
- * #755: drop project-diagnostics-delta findings disposed suppress/defer before
642
- * delta mode re-serves them. Anchored via `projectDiagnosticToWidget` so the
643
- * (tool, rule) an agent's mark binds to matches mode=full's own project-runner
644
- * filter (`applyInlineSuppressionsToSummaries`), keeping the two paths in
645
- * agreement. Goes through the shared {@link applyCachedDispositions} seam so
646
- * strict false-positive anchors apply immediately, like every other cache-only
647
- * surface. Also applies the project's
648
- * `.pi-lens.json` `rules.<id>.disable`/`select` policy so a project's policy
649
- * overlays the same delta report cache; the cache is otherwise insensitive to
650
- * a project-config edit (the per-edit path picks it up immediately, but a
651
- * non-dispatch tool query would replay the pre-edit state).
652
- */
653
- function filterDeltaReportDispositions(report, cwd, policyMap) {
654
- if (!report?.diagnostics.length)
655
- return report;
656
- const kept = report.diagnostics.filter((d) => applyCachedDispositions([projectDiagnosticToWidget(d, d.filePath)], cwd, d.filePath).length === 1);
657
- const policyKept = applyRulePolicy(kept, policyMap);
658
- if (policyKept.length === report.diagnostics.length)
659
- return report;
660
- return { ...report, diagnostics: policyKept };
661
- }
662
- /**
663
- * #1634 review round: `formatDeltaMode` re-serves the `actionable-warnings`/
664
- * `code-quality-warnings` caches verbatim — each cited `file:line`, no
665
- * freshness check — the SAME shape #1622 fixed for gitleaks/trivy-secrets,
666
- * unfixed here because `mode=delta` is the tool's DEFAULT and easy to miss
667
- * in a per-store sweep. Both reports carry a report-level `generatedAt`; a
668
- * warning is gated against it exactly like a scanner finding against
669
- * `scannedAt`: missing file → DROP (nothing to fix in a file that's gone),
670
- * edited-since → DEMOTE (loses its line, keeps its rule/message, marked
671
- * `STALE_LINE_MARKER`), live → unchanged. See
672
- * `clients/finding-delivery-gate.ts` surface `lens-diagnostics:mode-delta`.
673
- */
674
- function applyDeltaFreshnessGate(files, cwd, generatedAt) {
675
- // #2504 review round 4 (F1): an actionable-warnings report is no longer the
676
- // product of exactly ONE pass. A deferred off-hook LSP pull upserts its
677
- // per-file entries into whatever report is persisted when it lands, so one
678
- // report can carry entries observed minutes apart. Judging the older half
679
- // against the newer report-level stamp would pass an out-of-band edit made
680
- // in between off as live -- exactly the leak this gate exists to stop. Each
681
- // distinct stamp is gated on its own; the ordinary single-stamp report
682
- // takes one pass, as before, and the original file order is preserved.
683
- const stamps = new Set(files.map((file) => file.generatedAt ?? generatedAt ?? ""));
684
- if (stamps.size > 1) {
685
- const gatedByPath = new Map();
686
- for (const stamp of stamps) {
687
- const group = files.filter((file) => (file.generatedAt ?? generatedAt ?? "") === stamp);
688
- for (const gated of applyDeltaFreshnessGate(group.map((file) => ({
689
- filePath: file.filePath,
690
- warnings: file.warnings,
691
- })), cwd, stamp === "" ? undefined : stamp)) {
692
- gatedByPath.set(gated.filePath, gated);
693
- }
694
- }
695
- return files
696
- .map((file) => gatedByPath.get(file.filePath))
697
- .filter((entry) => entry !== undefined);
698
- }
699
- // Every entry shares one stamp here; prefer the per-entry one when it has
700
- // it, so a merged report's single surviving half is still aged by its own.
701
- const stampedAt = [...stamps][0];
702
- const effectiveAt = stampedAt !== undefined && stampedAt !== "" ? stampedAt : generatedAt;
703
- if (!effectiveAt)
704
- return files;
705
- const flat = [];
706
- for (const file of files) {
707
- for (const warning of file.warnings)
708
- flat.push({ filePath: file.filePath, warning });
709
- }
710
- if (flat.length === 0)
711
- return files;
712
- const { "lens-diagnostics-delta": gated } = gateFindingsByPathFreshness({
713
- cwd,
714
- sources: {
715
- "lens-diagnostics-delta": {
716
- findings: flat,
717
- scannedAt: effectiveAt,
718
- citedPath: (f) => f.filePath,
719
- onMissing: "drop",
720
- },
721
- },
722
- });
723
- // Two passes (live, then stale) reorder a file's demoted rows to the end
724
- // of its warnings array rather than the original report order — cosmetic
725
- // only, nothing is dropped or duplicated.
726
- const byFile = new Map();
727
- for (const f of gated.live) {
728
- const arr = byFile.get(f.filePath) ?? [];
729
- arr.push(f.warning);
730
- byFile.set(f.filePath, arr);
731
- }
732
- for (const f of gated.stale) {
733
- const arr = byFile.get(f.filePath) ?? [];
734
- // Fix B (#3167): carry the stamp the row was judged against so the render
735
- // can emit one age label per file group — the row's own observation stamp,
736
- // not the report-level one (the #2504 r4 multi-stamp case).
737
- arr.push({
738
- ...f.warning,
739
- stale: true,
740
- line: undefined,
741
- staleAsOf: effectiveAt,
742
- });
743
- byFile.set(f.filePath, arr);
744
- }
745
- return files
746
- .map((file) => ({
747
- filePath: file.filePath,
748
- warnings: byFile.get(file.filePath) ?? [],
749
- }))
750
- .filter((file) => file.warnings.length > 0);
751
- }
752
- function getDeltaFileGroup(groups, cwd, filePath) {
753
- const rel = path.relative(cwd, filePath);
754
- let group = groups.get(rel);
755
- if (!group) {
756
- group = {
757
- rel,
758
- actionableLines: [],
759
- qualityLines: [],
760
- projectLines: [],
761
- incomplete: false,
762
- };
763
- groups.set(rel, group);
764
- }
765
- return group;
766
- }
767
- // @delivery-surface: lens-diagnostics:mode-delta
768
- function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
769
- const actionableEntry = cacheManager.readCache("actionable-warnings", cwd);
770
- const qualityEntry = cacheManager.readCache("code-quality-warnings", cwd);
771
- // #3170: files whose re-verify pass could not complete inside its budget —
772
- // their rows render as carried, plus this explicit gap label (never a
773
- // false clean). Computed from the RAW actionable-warnings cache entries
774
- // because the freshness pipeline below rebuilds the file shape. The
775
- // re-verify only ever writes the actionable-warnings cache — the quality
776
- // report's file shape carries no such marker.
777
- const reverifyIncompletePaths = new Set((actionableEntry?.data?.files ?? [])
778
- .filter((file) => file.reVerifyIncomplete)
779
- .map((file) => normalizeMapKey(file.filePath)));
780
- const actionable = actionableEntry?.data;
781
- const quality = qualityEntry?.data;
782
- // Project rule policy (`.pi-lens.json` `rules.<id>.disable` /
783
- // `rules.<id>.select`) — output-only filtering applied consistently across
784
- // every mode. The cache-only delta/all paths would otherwise leak project-
785
- // policy oversight (the per-edit dispatch already filters, but the cache
786
- // snapshots predate the user's project config edit and would replay
787
- // filtered findings). Weak-anchored like weak disposition — zero I/O.
788
- const policyMap = loadProjectRulePolicyMap(cwd);
789
- const projectDelta = filterDeltaReportDispositions(loadProjectDiagnosticsDeltaReport(cwd), cwd, policyMap);
790
- const includeFile = createScopedFileFilter(cwd, pathsScope);
791
- // #755: delta re-serves the actionable/quality caches verbatim, but those
792
- // were filtered at DISPATCH time — before any lens_diagnostic_mark. Re-apply
793
- // dispositions here so a mark converges immediately, not only on the next
794
- // edit — through the shared {@link applyCachedDispositions} seam, so strict
795
- // false-positive marks land as fast as suppress/defer.
796
- const visibleWarningFiles = (files) => (files ?? [])
797
- .filter((file) => includeFile(file.filePath))
798
- .map((file) => ({
799
- filePath: file.filePath,
800
- // #2504 r4 (F1): carried through, so a merged report's older
801
- // entries keep their own age all the way to the freshness gate.
802
- generatedAt: file.generatedAt,
803
- warnings: applyRulePolicy(applyCachedDispositions(file.warnings ?? [], cwd, file.filePath), policyMap),
804
- }))
805
- .filter((file) => file.warnings.length > 0);
806
- const actionableFiles = applyDeltaFreshnessGate(visibleWarningFiles(actionable?.files), cwd, actionable?.generatedAt);
807
- const qualityFiles = applyDeltaFreshnessGate(visibleWarningFiles(quality?.files), cwd, quality?.generatedAt);
808
- const matchingWarnings = (warnings) => warnings.filter((warning) => matchesRecordSeverity(warning.severity, severity));
809
- const filteredActionableFiles = actionableFiles
810
- .map((file) => ({
811
- ...file,
812
- warnings: matchingWarnings(file.warnings),
813
- }))
814
- .filter((file) => file.warnings.length > 0);
815
- const filteredQualityFiles = qualityFiles
816
- .map((file) => ({
817
- ...file,
818
- warnings: matchingWarnings(file.warnings),
819
- }))
820
- .filter((file) => file.warnings.length > 0);
821
- // #3196: one grouping pass keyed by file — every tier below appends into
822
- // the SAME per-file group (see `DeltaFileGroup`/`getDeltaFileGroup`), so a
823
- // file present in more than one report always renders its rows under its
824
- // own header, wherever that header ends up in the final buffer.
825
- const groups = new Map();
826
- // Fixable warnings from actionable-warnings and quality cache entries retain
827
- // their own severity tier. Apply the same threshold semantics as the LSP path.
828
- // F5/F10 (#3168): at most ONE label group (age and/or #3170's re-verify
829
- // gap) per file. The actionable tier is folded first, so its stale row (if
830
- // any) wins the group's age label; the quality tier only supplies one when
831
- // the actionable tier had none for that file (#3168 F10(c)'s quality-only
832
- // shape). The `(re-verify incomplete)` flag is a fact about the file (from
833
- // the raw actionable cache, independent of which tier's rows happen to
834
- // render it), so it is recorded on the group the first time either tier
835
- // touches that file and never predicted.
836
- if (filteredActionableFiles.length > 0) {
837
- for (const file of filteredActionableFiles) {
838
- const group = getDeltaFileGroup(groups, cwd, file.filePath);
839
- for (const w of file.warnings) {
840
- const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
841
- group.actionableLines.push(` ⚠ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
842
- }
843
- const key = normalizeMapKey(file.filePath);
844
- if (reverifyIncompletePaths.has(key))
845
- group.incomplete = true;
846
- if (!group.staleRow) {
847
- const staleWarning = file.warnings.find((w) => w.stale);
848
- if (staleWarning)
849
- group.staleRow = { staleAsOf: staleWarning.staleAsOf };
850
- }
851
- }
852
- }
853
- // Quality issues
854
- if (filteredQualityFiles.length > 0) {
855
- for (const file of filteredQualityFiles) {
856
- const group = getDeltaFileGroup(groups, cwd, file.filePath);
857
- for (const w of file.warnings) {
858
- const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
859
- group.qualityLines.push(` ℹ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
860
- }
861
- const key = normalizeMapKey(file.filePath);
862
- if (reverifyIncompletePaths.has(key))
863
- group.incomplete = true;
864
- if (!group.staleRow) {
865
- const staleWarning = file.warnings.find((w) => w.stale);
866
- if (staleWarning)
867
- group.staleRow = { staleAsOf: staleWarning.staleAsOf };
868
- }
869
- }
870
- }
871
- const projectDeltaCount = appendProjectDiagnosticsDeltaLines(groups, cwd, projectDelta, severity, includeFile);
872
- const lines = [];
873
- for (const group of groups.values()) {
874
- lines.push(group.rel);
875
- lines.push(...group.actionableLines);
876
- lines.push(...group.qualityLines);
877
- lines.push(...group.projectLines);
878
- if (group.staleRow) {
879
- lines.push(` (${formatCacheAgeLabel(group.staleRow.staleAsOf)})`);
880
- }
881
- if (group.incomplete)
882
- lines.push(" (re-verify incomplete)");
883
- }
884
- const selectedActionableFiles = filteredActionableFiles;
885
- const selectedQualityFiles = filteredQualityFiles;
886
- const aw = selectedActionableFiles.reduce((count, file) => count + file.warnings.length, 0);
887
- const cq = selectedQualityFiles.reduce((count, file) => count + file.warnings.length, 0);
888
- if (lines.length === 0) {
889
- let text = `No ${severity === "all" ? "" : severity + " "}issues in the current turn delta.`;
890
- // Discoverability (#190): `delta` is current-turn-scoped, so it's empty
891
- // right after a resume even when prior findings were rehydrated into the
892
- // session-wide view. Point the agent at `mode=all` when that's the case.
893
- const carried = getFileDiagnosticSummaries()
894
- .filter((f) => includeFile(f.filePath))
895
- .map((f) => ({
896
- ...f,
897
- diagnostics: applyRulePolicy(applyCachedDispositions(f.diagnostics, cwd, f.filePath), policyMap),
898
- }))
899
- .filter((f) => f.diagnostics.length > 0);
900
- const carriedIssues = carried.reduce((n, f) => n + f.diagnostics.length, 0);
901
- if (carried.length > 0) {
902
- text += ` ${carriedIssues} finding${carriedIssues === 1 ? "" : "s"} across ${carried.length} file${carried.length === 1 ? "" : "s"} carried over from earlier this session — use mode=all to see them.`;
903
- }
904
- text += pathsScopeCacheOnlyNote(pathsScope);
905
- return {
906
- content: [{ type: "text", text }],
907
- details: { mode: "delta", warnings: 0, carriedOverFiles: carried.length },
908
- };
909
- }
910
- const summary = `\nSummary (turn delta): ${aw} actionable warning${aw === 1 ? "" : "s"} · ${cq} quality issue${cq === 1 ? "" : "s"} · ${projectDeltaCount} project diagnostic${projectDeltaCount === 1 ? "" : "s"}`;
911
- return {
912
- content: [{ type: "text", text: lines.join("\n") + summary }],
913
- details: {
914
- mode: "delta",
915
- actionableWarnings: aw,
916
- qualityIssues: cq,
917
- projectDiagnostics: projectDeltaCount,
918
- },
919
- };
920
- }
921
- // ── all mode ──────────────────────────────────────────────────────────────────
922
- function createCurrentIgnoreFilter(cwd) {
923
- try {
924
- const matcher = getProjectIgnoreMatcher(cwd);
925
- return (filePath) => !matcher.isIgnored(path.resolve(filePath), false);
926
- }
927
- catch {
928
- // Diagnostics should remain available even if an ignore config/root probe is
929
- // temporarily unreadable. Walkers already treat config load failures as
930
- // non-fatal; match that behavior for cached diagnostic presentation.
931
- return () => true;
932
- }
933
- }
934
- /**
935
- * Resolves the `paths` param into a scope predicate. Returns `undefined` when
936
- * `paths` was not provided (callers fall back to no restriction). Throws a
937
- * plain `Error` when the cap is exceeded — the caller turns that into a
938
- * clear tool error rather than silently truncating (a wrapper must not
939
- * believe it checked files it didn't).
940
- */
941
- function resolvePathsScope(cwd, rawPaths) {
942
- if (!Array.isArray(rawPaths) || rawPaths.length === 0)
943
- return undefined;
944
- if (rawPaths.length > MAX_PATHS_ENTRIES) {
945
- throw new Error(`paths has ${rawPaths.length} entries, over the ${MAX_PATHS_ENTRIES} cap. ` +
946
- "Narrow the list or omit paths to scan without a restriction.");
947
- }
948
- const entries = rawPaths
949
- .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
950
- .map((entry) => path.resolve(path.isAbsolute(entry) ? entry : path.resolve(cwd, entry)));
951
- const missing = [];
952
- const fileEntries = [];
953
- const existingEntries = [];
954
- const dirEntries = [];
955
- for (const entry of entries) {
956
- let stat;
957
- try {
958
- stat = fsSync.statSync(entry);
959
- }
960
- catch {
961
- // Nonexistent on disk right now — still treated as a file-key match
962
- // candidate for the delta/all cache filter (not just dropped): the
963
- // wrapper use case passes git output, where a deleted-but-staged path
964
- // legitimately has no on-disk file yet may still have cached findings
965
- // from before deletion. Excluded from `existingEntries` (nothing to
966
- // actively scan) and recorded in `missing` for mode=full's skipped-note.
967
- missing.push(entry);
968
- fileEntries.push(entry);
969
- continue;
970
- }
971
- if (stat.isDirectory()) {
972
- dirEntries.push(entry);
973
- }
974
- else {
975
- fileEntries.push(entry);
976
- existingEntries.push(entry);
977
- }
978
- }
979
- const fileKeys = new Set(fileEntries.map((f) => normalizeFilePath(f)));
980
- const dirPrefixes = dirEntries.map((d) => normalizeFilePath(d));
981
- const includeExplicitFile = (filePath) => fileKeys.has(normalizeFilePath(path.resolve(filePath)));
982
- const includeFile = (filePath) => {
983
- const key = normalizeFilePath(path.resolve(filePath));
984
- if (fileKeys.has(key))
985
- return true;
986
- return dirPrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}/`));
987
- };
988
- return {
989
- includeFile,
990
- includeExplicitFile,
991
- missing,
992
- existingEntries,
993
- hasDirectoryEntries: dirEntries.length > 0,
994
- };
995
- }
996
- /** Short, honest note for delta/all when `paths` filtered everything out — these modes are cache-only, so an unseen file legitimately shows nothing. */
997
- function pathsScopeCacheOnlyNote(scope) {
998
- if (!scope)
999
- return "";
1000
- return ("\n\nNote: paths restricts this to cached findings for files pi-lens has " +
1001
- "already dispatched this session — mode=delta/all can't see files it " +
1002
- "hasn't touched. Use mode=full to actively scan exactly these paths.");
1003
- }
1004
- /** Short note listing paths entries that don't exist on disk (mode=full only — the wrapper use case passes git output, where a deleted-but-staged file WILL appear). */
1005
- function pathsScopeMissingNote(scope) {
1006
- if (!scope || scope.missing.length === 0)
1007
- return "";
1008
- const shown = scope.missing.slice(0, 10).map((p) => path.basename(p));
1009
- const more = scope.missing.length > shown.length
1010
- ? ` (+${scope.missing.length - shown.length} more)`
1011
- : "";
1012
- return `\n\n⚠ Skipped ${scope.missing.length} path(s) not found on disk: ${shown.join(", ")}${more}`;
1013
- }
1014
- function createScopedFileFilter(cwd, pathsScope) {
1015
- const ignoreFile = createCurrentIgnoreFilter(cwd);
1016
- return (filePath) => {
1017
- if (!pathsScope)
1018
- return ignoreFile(filePath);
1019
- if (!pathsScope.includeFile(filePath))
1020
- return false;
1021
- return pathsScope.includeExplicitFile(filePath) || ignoreFile(filePath);
1022
- };
1023
- }
1024
- function filterProjectDiagnosticsSnapshot(snapshot, includeFile) {
1025
- if (!snapshot)
1026
- return undefined;
1027
- return {
1028
- ...snapshot,
1029
- diagnostics: snapshot.diagnostics.filter((d) => includeFile(d.filePath)),
1030
- };
1031
- }
1032
- function filterProjectDiagnosticsDeltaReport(report, includeFile) {
1033
- if (!report)
1034
- return undefined;
1035
- return {
1036
- ...report,
1037
- diagnostics: report.diagnostics.filter((d) => includeFile(d.filePath)),
1038
- };
1039
- }
1040
- /**
1041
- * Apply the project rule policy to a project-diagnostics snapshot/delta
1042
- * report's `diagnostics` list, in place of the collection's shape. Used by
1043
- * `formatFullMode` so `projectSnapshot.diagnostics.length` /
1044
- * `projectDelta.diagnostics.length` — which feed both the merged summaries
1045
- * and the `details.projectDiagnostics`/`details.projectDiagnosticsDelta`
1046
- * counts — already reflect the policy instead of reporting a pre-policy
1047
- * count that disagrees with the rendered text.
1048
- */
1049
- function applyProjectRulePolicy(value, policyMap) {
1050
- if (!value)
1051
- return undefined;
1052
- return {
1053
- ...value,
1054
- diagnostics: applyRulePolicy(value.diagnostics, policyMap),
1055
- };
1056
- }
1057
- /** A diagnostic counts as error-like when it blocks or has error severity.
1058
- * A `stale` (#1641 past-EOF) entry is excluded — its cited line no longer
1059
- * exists in the current file, so it can no longer count toward a hard stop. */
1060
- function isErrorLike(d) {
1061
- if (d.stale)
1062
- return false;
1063
- return d.semantic === "blocking" || d.severity === "error";
1064
- }
1065
- function matchesSeverity(d, severity) {
1066
- return matchesRecordSeverity(isErrorLike(d) ? "error" : d.severity, severity);
1067
- }
1068
- function matchesRecordSeverity(recordSeverity, requested) {
1069
- if (requested === "all")
1070
- return true;
1071
- if (requested === "error")
1072
- return recordSeverity === "error";
1073
- if (requested === "warning")
1074
- return recordSeverity === "error" || recordSeverity === "warning";
1075
- if (requested === "information")
1076
- return (recordSeverity === "error" ||
1077
- recordSeverity === "warning" ||
1078
- recordSeverity === "info" ||
1079
- recordSeverity === "note" ||
1080
- recordSeverity === "help");
1081
- if (requested === "hint")
1082
- return (recordSeverity === "error" ||
1083
- recordSeverity === "warning" ||
1084
- recordSeverity === "info" ||
1085
- recordSeverity === "note" ||
1086
- recordSeverity === "help" ||
1087
- recordSeverity === "hint");
1088
- return true;
1089
- }
1090
- /** Most-important first: blocking → error → warning/other, then by line. */
1091
- function severityRank(d) {
1092
- if (d.semantic === "blocking")
1093
- return 0;
1094
- if (d.severity === "error")
1095
- return 1;
1096
- if (d.severity === "warning")
1097
- return 2;
1098
- return 3;
1099
- }
1100
- function bySeverityThenLine(a, b) {
1101
- return severityRank(a) - severityRank(b) || (a.line ?? 0) - (b.line ?? 0);
1102
- }
1103
- function lspSeverityName(severity) {
1104
- if (severity === 1)
1105
- return "error";
1106
- if (severity === 2)
1107
- return "warning";
1108
- if (severity === 3)
1109
- return "info";
1110
- return "hint";
1111
- }
1112
- function lspRuleId(diagnostic) {
1113
- const code = diagnostic.code === undefined ? undefined : String(diagnostic.code);
1114
- if (diagnostic.source && code)
1115
- return `${diagnostic.source}:${code}`;
1116
- return diagnostic.source ?? code ?? "lsp";
1117
- }
1118
- function lspDiagnosticToWidget(diagnostic) {
1119
- const severity = lspSeverityName(diagnostic.severity);
1120
- const rule = lspRuleId(diagnostic);
1121
- return {
1122
- severity,
1123
- semantic: diagnostic.severity === 1 ? "blocking" : "warning",
1124
- message: diagnostic.message,
1125
- line: diagnostic.range.start.line + 1,
1126
- col: diagnostic.range.start.character + 1,
1127
- rule,
1128
- // #3041: keep auxiliary provenance. The footer-reconcile loop above already
1129
- // gives a swept aux finding its real tool id via `retagAuxiliaryDiagnostics`
1130
- // (#692); this second conversion of the SAME raw diagnostics did not, and
1131
- // dispositions are anchored by `tool` — so a `false-positive`/`suppress`
1132
- // mark recorded against the per-edit `ast-grep` finding never matched the
1133
- // `lsp`-labelled copy mode=full renders.
1134
- tool: findAuxiliaryProfileForSource(diagnostic.source)?.tool ?? "lsp",
1135
- };
1136
- }
1137
- function projectDiagnosticToWidget(diagnostic, filePath) {
1138
- return {
1139
- severity: diagnostic.severity,
1140
- semantic: diagnostic.semantic,
1141
- message: diagnostic.message,
1142
- line: diagnostic.line,
1143
- col: diagnostic.column,
1144
- rule: diagnostic.rule ?? diagnostic.code,
1145
- tool: diagnostic.runner || diagnostic.tool,
1146
- uri: widgetDiagnosticUri(filePath, diagnostic.line, diagnostic.column),
1147
- };
1148
- }
1149
- // The ast-grep LSP keys its diagnostics `rule = "ast-grep:<id>"` (source:code,
1150
- // lsp-diagnostics.ts), while the napi runner — per-edit AND the project scan
1151
- // (#308) — emits the bare `<id>`. Both run the same shipped ruleset, so the same
1152
- // violation must collapse to one finding in mode=full's merge regardless of which
1153
- // engine produced it. Strip the `ast-grep:` source prefix and the `-js` language
1154
- // suffix (the napi runner already treats `<id>` / `<id>-js` as one rule) so the
1155
- // LSP sweep and the napi scan don't double-report the same line. The
1156
- // normalization itself now lives in `clients/dispatch/rule-id-normalize.ts` so the
1157
- // inline suppression parser, the project rule-policy matcher, and dedup all apply
1158
- // the same canonical form.
1159
- function diagnosticDedupKey(filePath, diagnostic) {
1160
- const ruleId = normalizeRuleId(diagnostic.rule ?? diagnostic.tool ?? "");
1161
- return [path.resolve(filePath), diagnostic.line ?? "?", ruleId].join(":");
1162
- }
1163
- /**
1164
- * Which project runner produced a retained row (#2154). ONE derivation, used by
1165
- * both the retirement filter and the record that counts what it retired — v2's
1166
- * N2 was two verbatim copies of this expression drifting apart.
1167
- */
1168
- function runnerIdOf(diagnostic) {
1169
- return diagnostic.tool ?? diagnostic.rule?.split(":", 1)[0] ?? "";
1170
- }
1171
- /**
1172
- * The one project-runner retirement decision. Coverage is authoritative when
1173
- * present for the runner; the older id-only arm is used only when the runner
1174
- * declared no coverage at all.
1175
- *
1176
- * #2962: an entry with an EMPTY file set is a declaration, not a missing one —
1177
- * "this producer analysed the root and proved zero files". Selecting it out by
1178
- * its own size (the pre-#2962 `entry.files.size > 0` filter) dropped it into the
1179
- * whole-root id-only arm, so one complete opengrep report with
1180
- * `paths.scanned: []` retired every retained opengrep finding in the tree even
1181
- * though no file had been scanned. The id-only arm is unchanged for the eight
1182
- * runners that declare no coverage.
1183
- */
1184
- export function runnerRetirementDecision(diagnostic, filePath, authoritativeRunnerIds, authoritativeCoverage) {
1185
- const runnerId = runnerIdOf(diagnostic);
1186
- const coverage = (authoritativeCoverage ?? []).filter((entry) => entry.runnerId === runnerId);
1187
- if (coverage.length === 0) {
1188
- return authoritativeRunnerIds?.has(runnerId) ? "retire" : "keep";
1189
- }
1190
- const realFilePath = realpathOrResolve(filePath);
1191
- for (const entry of coverage) {
1192
- const root = entry.root;
1193
- const relative = path.relative(root, realFilePath);
1194
- const underRoot = relative === "" ||
1195
- (!relative.startsWith("..") && !path.isAbsolute(relative));
1196
- if (!underRoot)
1197
- continue;
1198
- if (entry.files.has(realFilePath)) {
1199
- return "retire";
1200
- }
1201
- }
1202
- return "keep";
1203
- }
1204
- function summarizeDiagnostics(filePath, diagnostics, hasFinalSnapshot) {
1205
- let blocking = 0;
1206
- let errors = 0;
1207
- let warnings = 0;
1208
- let advisories = 0;
1209
- for (const diagnostic of diagnostics) {
1210
- // #1641: a demoted past-EOF entry keeps its severity for display but
1211
- // drops out of the tallies — same reasoning as widget-state's `isBlocking`.
1212
- if (diagnostic.stale)
1213
- continue;
1214
- if (diagnostic.semantic === "blocking")
1215
- blocking++;
1216
- // #2414: severity → tier classification is shared with
1217
- // `countDiagnostics`/`countAdvisories` in `clients/widget-state.ts` via
1218
- // `classifyDiagnosticTier` — mode=all reads that store's tally and
1219
- // mode=full reads this one, so the two must agree. `hint`/`info` no
1220
- // longer inflate `warnings` (they used to, matching a pre-#2414
1221
- // `countDiagnostics`, so a hint-only file scored 0/0/0 there too and the
1222
- // `withIssues` filter below dropped it — that gap is now closed via
1223
- // `advisories`, not by re-folding hints into `warnings`).
1224
- const tier = classifyDiagnosticTier(diagnostic);
1225
- if (tier === "error")
1226
- errors++;
1227
- else if (tier === "warning")
1228
- warnings++;
1229
- else if (tier === "advisory")
1230
- advisories++;
1231
- }
1232
- return {
1233
- filePath,
1234
- blocking,
1235
- errors,
1236
- warnings,
1237
- advisories,
1238
- hasFinalSnapshot,
1239
- diagnostics,
1240
- };
1241
- }
1242
- // #1618: sentence form used when exactly one reason accounts for every
1243
- // unconfirmed file — mirrors the pre-#1618 two-case ternary's phrasing so
1244
- // existing single-reason callers see unchanged text.
1245
- const UNCONFIRMED_REASON_SENTENCE = {
1246
- budget: "check didn't complete within budget",
1247
- inconclusive: "check was inconclusive (notify-write or diagnostics wait timed out)",
1248
- coverage_gap: "an auxiliary scanner coverage gap",
1249
- service_destroyed: "the LSP service was reset mid-sweep",
1250
- error: "check errored",
1251
- // #1618 review round 2: the LSP layer considered this touch CONFIRMED —
1252
- // the file's content changed under it (stale binding), which is a
1253
- // completely different failure from a timeout. Must never collapse into
1254
- // "within budget", the exact string this PR exists to stop misrendering.
1255
- binding_mismatch: "the file changed on disk since this result was computed",
1256
- // #2052: no server was asked. Say WHY and say it is permanent for this
1257
- // path, so the reader does not read an empty result as "clean" or retry it.
1258
- outside_project_root: "the file is outside every initialized session project root, so no language server was asked",
1259
- };
1260
- // Short per-reason label used only when MORE than one reason is present, so
1261
- // the note can still say "N timed out, M errored" instead of collapsing to
1262
- // one sentence that can't carry two counts.
1263
- const UNCONFIRMED_REASON_COUNT_LABEL = {
1264
- budget: "timed out",
1265
- inconclusive: "inconclusive",
1266
- coverage_gap: "coverage gap",
1267
- service_destroyed: "service reset mid-sweep",
1268
- error: "errored",
1269
- binding_mismatch: "stale binding",
1270
- outside_project_root: "outside project root",
1271
- };
1272
- /**
1273
- * #1618: a result predating the `unconfirmedReason` field (a legacy/test
1274
- * double, or `LSPWorkspaceDiagnosticResult`'s own pre-#1618 shape) is
1275
- * classified from its OTHER fields exactly the way the dead
1276
- * `unconfirmedErrored = length - unconfirmedTimedOut` subtraction meant to,
1277
- * before that subtraction was structurally always 0 (the sweep's catch
1278
- * block sets both `error` and `timedOut`, so `unconfirmedErrored` never had
1279
- * a way to become nonzero). An explicit `result.error` now wins even when
1280
- * `unconfirmedReason` is absent, so an errored file is never rendered as
1281
- * "within budget".
1282
- *
1283
- * #1618 review round 2: `mismatched` MUST be checked first. A binding
1284
- * mismatch is discovered HERE, after the sweep already returned the result
1285
- * as confirmed (`!timedOut`, no `.error`, no `.unconfirmedReason`) — the
1286
- * `?? "budget"` fallback below would otherwise silently claim a
1287
- * stale-binding file as "within budget", the exact string this PR exists to
1288
- * stop misrendering.
1289
- */
1290
- function classifyUnconfirmedReason(result, mismatched) {
1291
- if (mismatched.has(result))
1292
- return "binding_mismatch";
1293
- return result.unconfirmedReason ?? (result.error ? "error" : "budget");
1294
- }
1295
- /**
1296
- * #1618: replaces the `unconfirmedTimedOut`/`unconfirmedErrored` dead
1297
- * subtraction with a real per-reason tally read off each result's own
1298
- * `unconfirmedReason` (falling back to `classifyUnconfirmedReason` for a
1299
- * legacy result that predates the field). `mismatched` is threaded through
1300
- * so a stale-binding file is never counted under a fallback reason it
1301
- * doesn't have (review round 2).
1302
- */
1303
- function tallyUnconfirmedReasons(results, mismatched) {
1304
- const tally = new Map();
1305
- for (const result of results) {
1306
- const reason = classifyUnconfirmedReason(result, mismatched);
1307
- tally.set(reason, (tally.get(reason) ?? 0) + 1);
1308
- }
1309
- return tally;
1310
- }
1311
- /** Render `tallyUnconfirmedReasons`' output as the clause inside the
1312
- * "unconfirmed (...)" note — a single sentence when one reason accounts for
1313
- * everything, else a comma-joined "N label" breakdown. */
1314
- function formatUnconfirmedReasonClause(tally) {
1315
- const entries = [...tally.entries()].filter(([, count]) => count > 0);
1316
- if (entries.length === 0)
1317
- return "";
1318
- if (entries.length === 1) {
1319
- return UNCONFIRMED_REASON_SENTENCE[entries[0][0]];
1320
- }
1321
- return entries
1322
- .map(([reason, count]) => `${count} ${UNCONFIRMED_REASON_COUNT_LABEL[reason]}`)
1323
- .join(", ");
1324
- }
1325
- /**
1326
- * #646: break the confirmed/unconfirmed LSP-sweep tally down PER primary
1327
- * server id (typescript, pyright, marksman, ...) instead of one flat
1328
- * aggregate count. Mirrors `tools/lsp-diagnostics.ts`'s `primaryServerId`/
1329
- * `tallyConfirmation` split — both tools now use the SAME
1330
- * `primaryServerId` (clients/lsp/config.ts) so a file's classification as
1331
- * "primary language server" vs "auxiliary scanner" agrees between them.
1332
- *
1333
- * Motivating case (#646): a real sweep came back 34/155 unconfirmed, and
1334
- * diagnosing WHICH server was responsible required grepping
1335
- * `~/.pi-lens/latency.log` by hand — it turned out to be 100% one push-only
1336
- * server (marksman). This tally makes that visible directly: `{ marksman:
1337
- * { confirmed: 0, total: 34 }, typescript: { confirmed: 121, total: 121 } }`.
1338
- *
1339
- * Purely an additional reporting dimension — does NOT change what counts as
1340
- * confirmed/unconfirmed (the existing `confirmedLspResults`/
1341
- * `unconfirmedLspResults` partition in `formatFullMode` is unchanged and
1342
- * still the source of truth this reads from).
1343
- */
1344
- function tallyLspByPrimaryServer(confirmed, unconfirmed) {
1345
- const byServer = new Map();
1346
- const bump = (filePath, wasConfirmed) => {
1347
- const id = primaryServerId(filePath) ?? "unknown";
1348
- const entry = byServer.get(id) ?? { confirmed: 0, total: 0 };
1349
- entry.total += 1;
1350
- if (wasConfirmed)
1351
- entry.confirmed += 1;
1352
- byServer.set(id, entry);
1353
- };
1354
- for (const result of confirmed)
1355
- bump(result.filePath, true);
1356
- for (const result of unconfirmed)
1357
- bump(result.filePath, false);
1358
- return byServer;
1359
- }
1360
- /**
1361
- * Render the per-server tally from `tallyLspByPrimaryServer` as a short
1362
- * clause, e.g. "typescript: 121/121, marksman: 0/34" — sorted so the
1363
- * worst-confirmed server (the one most likely to be why a sweep looks
1364
- * unconfirmed) is called out first.
1365
- */
1366
- function formatLspServerBreakdown(byServer) {
1367
- return [...byServer.entries()]
1368
- .sort((a, b) => {
1369
- const rateA = a[1].confirmed / a[1].total;
1370
- const rateB = b[1].confirmed / b[1].total;
1371
- return rateA - rateB;
1372
- })
1373
- .map(([id, { confirmed, total }]) => `${id}: ${confirmed}/${total}`)
1374
- .join(", ");
1375
- }
1376
- /**
1377
- * #646: split a raw LSP-sweep result set's diagnostics into "primary" (the
1378
- * real language server, e.g. typescript/pyright) vs "auxiliary" (cross-
1379
- * cutting scanners attached via clientScope "all" — ast-grep, opengrep,
1380
- * zizmor, typos, marksman, ...), the same separation `lsp_diagnostics`
1381
- * already applies to its own findings rendering (see
1382
- * `tools/lsp-diagnostics.ts`'s `collectBatchDiagnostics`). Computed from the
1383
- * raw per-file LSP results (not the merged widget-state summaries further
1384
- * down `formatFullMode`) so it doesn't disturb the existing merge/dedup
1385
- * logic those summaries feed into.
1386
- */
1387
- function tallyLspPrimaryVsAuxiliary(results) {
1388
- let primary = 0;
1389
- let auxiliary = 0;
1390
- for (const result of results) {
1391
- const primaryId = primaryServerId(result.filePath);
1392
- for (const diagnostic of result.diagnostics ?? []) {
1393
- if (diagnostic.serverId === primaryId)
1394
- primary += 1;
1395
- else
1396
- auxiliary += 1;
1397
- }
1398
- }
1399
- return { primary, auxiliary };
1400
- }
1401
- function mergeDiagnosticsWithWidgetSummaries(widgetSummaries, lspResults, projectSnapshot, projectDelta,
1402
- /**
1403
- * #1993: resolved paths covered by a CONFIRMED, fully-covered LSP result
1404
- * in `lspResults`. The fresh sweep is AUTHORITATIVE for these files - any
1405
- * widget-store diagnostics seeded for them predate the sweep and must not
1406
- * survive it (an additive-only merge let mid-edit stale 🔴 entries render
1407
- * forever beside a clean sweep). Files absent from this set keep today's
1408
- * additive behavior (fail-open: unconfirmed/timed-out sweeps never retire
1409
- * widget state they did not actually re-check).
1410
- */
1411
- authoritativeLspFiles, authoritativeRunnerIds, authoritativeRunnerCoverage) {
1412
- const byFile = new Map();
1413
- const seen = new Set();
1414
- for (const summary of widgetSummaries) {
1415
- const filePath = path.resolve(summary.filePath);
1416
- // NOTE (#1993 review): dispositions recorded against a CACHED copy's
1417
- // message text may not match this file's fresh sweep copy (weak
1418
- // anchors key on message). Both copies derive from the same LSP
1419
- // diagnostic via convertLspDiagnostics, so identity is stable in
1420
- // practice; inline pi-lens-ignore comments are the primary
1421
- // suppression mechanism.
1422
- if (authoritativeLspFiles?.has(filePath))
1423
- continue;
1424
- const retained = summary.diagnostics ?? [];
1425
- const diagnostics = retained
1426
- .filter((diagnostic) => runnerRetirementDecision(diagnostic, filePath, authoritativeRunnerIds, authoritativeRunnerCoverage) !== "retire")
1427
- .map((d) => ({ ...d }));
1428
- byFile.set(filePath,
1429
- // #2154 v4: when the filter retires rows, the stored tallies describe
1430
- // diagnostics that are no longer delivered — full mode rendered
1431
- // `main.go 1W … 1 warning` with no row under it. Recompute from what
1432
- // survived, and only then (an untouched file keeps its own counts).
1433
- diagnostics.length === retained.length
1434
- ? { ...summary, filePath, diagnostics }
1435
- : summarizeDiagnostics(filePath, diagnostics, summary.hasFinalSnapshot ?? true));
1436
- for (const diagnostic of diagnostics) {
1437
- seen.add(diagnosticDedupKey(filePath, diagnostic));
1438
- }
1439
- }
1440
- const addDiagnostic = (filePath, widgetDiagnostic) => {
1441
- const existing = byFile.get(filePath);
1442
- const diagnostics = existing ? [...existing.diagnostics] : [];
1443
- const key = diagnosticDedupKey(filePath, widgetDiagnostic);
1444
- if (seen.has(key))
1445
- return;
1446
- seen.add(key);
1447
- diagnostics.push(widgetDiagnostic);
1448
- byFile.set(filePath, summarizeDiagnostics(filePath, diagnostics, existing?.hasFinalSnapshot ?? true));
1449
- };
1450
- for (const result of lspResults) {
1451
- const filePath = path.resolve(result.filePath);
1452
- for (const diagnostic of result.diagnostics ?? []) {
1453
- addDiagnostic(filePath, lspDiagnosticToWidget(diagnostic));
1454
- }
1455
- }
1456
- for (const diagnostic of projectSnapshot?.diagnostics ?? []) {
1457
- addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic, diagnostic.filePath));
1458
- }
1459
- for (const diagnostic of projectDelta?.diagnostics ?? []) {
1460
- addDiagnostic(path.resolve(diagnostic.filePath), projectDiagnosticToWidget(diagnostic, diagnostic.filePath));
1461
- }
1462
- return [...byFile.values()];
1463
- }
1464
- /**
1465
- * Apply inline `pi-lens-ignore` suppression to the merged mode=full summaries so
1466
- * the project-wide sweep honors the same comments as the per-edit path (#442) —
1467
- * without it, a site cleanly suppressed in mode=all reappears as blocking here.
1468
- * Reads each flagged file once (bounded to files that actually have diagnostics);
1469
- * a read failure is fail-safe (keep the diagnostics rather than hide a finding on
1470
- * an I/O error). Re-summarizes so the blocking/error/warning counts reflect the
1471
- * suppression.
1472
- *
1473
- * Also applies the project's `.pi-lens.json` `rules.<id>.disable`/`select`
1474
- * policy AFTER inline suppression / disposition so the same set of findings
1475
- * the per-edit `dispatcher.ts` filters is what's rendered here. The policy
1476
- * map is passed in (computed once per `formatFullMode` call) rather than
1477
- * re-deriving per file — see `loadProjectRulePolicyMap(cwd)`.
1478
- */
1479
- async function applyInlineSuppressionsToSummaries(summaries, cwd, policyMap) {
1480
- return Promise.all(summaries.map(async (summary) => {
1481
- if (!summary.diagnostics.length)
1482
- return summary;
1483
- let content;
1484
- try {
1485
- content = await fs.readFile(summary.filePath, "utf8");
1486
- }
1487
- catch {
1488
- // Inline suppression/disposition remains fail-open when the file cannot
1489
- // be read, but project policy does not depend on content and must still
1490
- // be applied so a fresh full-mode result cannot bypass `disable`/`select`.
1491
- const policyKept = applyRulePolicy(summary.diagnostics, policyMap);
1492
- return policyKept.length === summary.diagnostics.length
1493
- ? summary
1494
- : summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
1495
- }
1496
- // #690/#3088: inline `pi-lens-ignore` → the same false-positive/
1497
- // suppress/defer disposition filter the per-edit dispatch path applies
1498
- // (dispatcher.ts) → the project's `.pi-lens.json` rule policy. mode=full
1499
- // merges in diagnostics from a fresh LSP sweep/project scan that never
1500
- // went through the dispatch path, so without this a disposed finding
1501
- // reappears here. #3088 folded the three calls onto the shared
1502
- // `applyFindingPolicy` seam, which the `source=lsp` probe lane now calls
1503
- // too — one stack, one order, for every model-facing surface.
1504
- const { kept: policyKept } = applyFindingPolicy(summary.diagnostics, {
1505
- cwd,
1506
- filePath: summary.filePath,
1507
- content,
1508
- policyMap,
1509
- });
1510
- // Tag `flagged` diagnostics for the render loop (formatAllMode). Content
1511
- // is already in hand here (unlike mode=all/delta's cache-only path), so
1512
- // this is the one place the tag can be computed without adding I/O to
1513
- // the "instant" modes.
1514
- for (const d of policyKept) {
1515
- // flagged is weak-anchored (module doc, diagnostic-dispositions.ts) so
1516
- // the tag survives incidental edits to the flagged line itself.
1517
- const { weak } = anchorsForDiagnostic(cwd, summary.filePath, d, content);
1518
- if (getDisposition(cwd, weak)?.disposition === "flagged") {
1519
- d.flagged = true;
1520
- }
1521
- }
1522
- if (policyKept.length === summary.diagnostics.length)
1523
- return summary;
1524
- return summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
1525
- }));
1526
- }
1527
- function shouldUseCachedProjectDiagnostics(value) {
1528
- return value === "cached";
1529
- }
1530
- function shouldRefreshProjectDiagnostics(value) {
1531
- return value === "cheap" || value === "all";
1532
- }
1533
- /** True when full mode should include project-runner state at all (any non-none refreshRunners). */
1534
- function shouldIncludeProjectRunners(value) {
1535
- return shouldRefreshProjectDiagnostics(value) || value === "cached";
1536
- }
1537
- /**
1538
- * Merge cache-derived diagnostics from the analyzer extractors (jscpd, madge,
1539
- * gitleaks, knip …) into the scanned project snapshot: append to the existing
1540
- * one (recording the runners), or synthesize a minimal snapshot when there was
1541
- * no in-process scan. Returns the snapshot unchanged when there is nothing extra.
1542
- */
1543
- function foldExtraDiagnosticsIntoSnapshot(snapshot, extra, runners, cwd) {
1544
- if (extra.length === 0)
1545
- return snapshot;
1546
- if (snapshot) {
1547
- const merged = new Set([...snapshot.runners, ...runners]);
1548
- return {
1549
- ...snapshot,
1550
- diagnostics: [...snapshot.diagnostics, ...extra],
1551
- runners: [...merged],
1552
- };
1553
- }
1554
- return {
1555
- version: PROJECT_DIAGNOSTICS_CACHE_VERSION,
1556
- cwd,
1557
- tier: "all",
1558
- scannedAt: new Date().toISOString(),
1559
- diagnostics: extra,
1560
- filesScanned: 0,
1561
- runners,
1562
- };
1563
- }
1564
- async function getProjectDiagnosticsSnapshotForFullMode(cwd, options) {
1565
- if (shouldRefreshProjectDiagnostics(options.refreshRunners)) {
1566
- return scanProjectDiagnostics({
1567
- cwd,
1568
- tier: "cheap",
1569
- maxFiles: options.maxProjectFiles,
1570
- signal: options.signal,
1571
- files: options.files,
1572
- includeGenerated: options.includeGenerated,
1573
- });
1574
- }
1575
- if (shouldUseCachedProjectDiagnostics(options.refreshRunners)) {
1576
- // The cached snapshot is a cross-session cache; drop diagnostics for files
1577
- // edited/deleted since the scan so a stale entry isn't replayed (#298). A
1578
- // fresh scan (above) is current by construction and needs no reconcile.
1579
- const cached = loadProjectDiagnosticsSnapshot(cwd);
1580
- if (!cached)
1581
- return undefined;
1582
- const reconciled = reconcileProjectDiagnosticsSnapshot(cached);
1583
- // #2154: what this gate DROPS was invisible — the count was computed and
1584
- // thrown away, so a session that silently retired another session's rows
1585
- // (the whole point of the content axis added this round) left no record
1586
- // of having done so. Bounded by construction: at most one row per
1587
- // mode=full call, and only when rows were actually retired — the shape
1588
- // `lsp_authoritative_widget_retire` uses for the sibling arm.
1589
- if (reconciled.staleDropped > 0) {
1590
- logLatency({
1591
- type: "phase",
1592
- toolName: "lens_diagnostics",
1593
- filePath: cwd,
1594
- phase: "project_snapshot_rows_retired",
1595
- durationMs: 0,
1596
- metadata: {
1597
- files: reconciled.staleDropped,
1598
- rows: cached.diagnostics.length - reconciled.snapshot.diagnostics.length,
1599
- scannedAt: cached.scannedAt,
1600
- },
1601
- });
1602
- }
1603
- return reconciled.snapshot;
1604
- }
1605
- return undefined;
1606
- }
1607
- // @delivery-surface: lens-diagnostics:mode-full
1608
- async function formatFullMode(cwd, severity, lspService, cacheManager, options = {}) {
1609
- const runWorkspaceDiagnostics = lspService.runWorkspaceDiagnostics;
1610
- if (typeof runWorkspaceDiagnostics !== "function") {
1611
- return {
1612
- content: [
1613
- {
1614
- type: "text",
1615
- text: "LSP service does not support project-wide workspace diagnostics.",
1616
- },
1617
- ],
1618
- details: { mode: "full", filesChecked: 0, lspUnavailable: true },
1619
- };
1620
- }
1621
- const { signal, pathsScope, nextWriteIndex } = options;
1622
- // #2535: advisories name the delivery host's callable tools; pi callers
1623
- // omit host and keep the pi spelling.
1624
- const host = options.host ?? "pi";
1625
- const includeFile = createScopedFileFilter(cwd, pathsScope);
1626
- // `paths` (#461): route the active scans at exactly the requested files
1627
- // instead of walking the whole project. Three cases:
1628
- // - files only → pass them as the explicit list (skips the walk). An EMPTY
1629
- // list (every requested file was missing) still passes through — both
1630
- // seams treat [] as "scan nothing", where undefined would trigger a full
1631
- // project walk that includeFile then filters to nothing (expensive no-op).
1632
- // - any directory entry → fall back to the walk, narrowed by includeFile.
1633
- // Passing only the file entries would silently skip everything under the
1634
- // requested directories — an under-scan the caller can't detect.
1635
- // - no paths → undefined, today's full walk.
1636
- const explicitFiles = pathsScope && !pathsScope.hasDirectoryEntries
1637
- ? pathsScope.existingEntries
1638
- : undefined;
1639
- // #613: the heavyweight-analyzer fresh-fetch (below) used to be `await`ed
1640
- // AFTER this Promise.all had already fully resolved — sequentially eating
1641
- // into the SAME wall-clock ceiling (`FULL_SCAN_WALL_CLOCK_MS`) the LSP sweep
1642
- // already spent, rather than sharing it concurrently as the old comment here
1643
- // claimed. On a real project the LSP sweep alone can take 100+s, leaving the
1644
- // analyzers almost no budget before the shared abort signal fires — killing
1645
- // ALL of them (even unconditional ones like knip/jscpd/madge) before any
1646
- // could complete. Building its promise here, alongside the sweep and cheap
1647
- // scan, makes it genuinely concurrent — all three phases now race the SAME
1648
- // signal from the same starting point instead of stacking.
1649
- const projectRunnersRequested = shouldIncludeProjectRunners(options.refreshRunners);
1650
- const analyzersPromise = projectRunnersRequested
1651
- ? loadBootstrapClients().then((clients) => fetchFreshProjectDiagnostics(cacheManager, cwd, clients, signal, {
1652
- runtime: options.runtime,
1653
- analysisRoot: options.analysisRoot,
1654
- }))
1655
- : Promise.resolve({
1656
- diagnostics: [],
1657
- runners: [],
1658
- analyzed: [],
1659
- authoritativeCoverage: [],
1660
- // #1623: every heavyweight analyzer is ELIGIBLE for this project but
1661
- // this call never asked for it (refreshRunners wasn't cheap/all/
1662
- // cached) — the expensive fetch below deliberately never runs in
1663
- // that case (see the comment above), but rendering total silence
1664
- // here reads exactly as "ran clean", the false-empty #1623 exists to
1665
- // close. `ANALYZER_IDS` (fresh-fetch.ts) is the single source of
1666
- // truth for which lanes this covers — reuse it rather than
1667
- // hand-listing ids here.
1668
- cold: [...ANALYZER_IDS],
1669
- coldReasons: Object.fromEntries(ANALYZER_IDS.map((id) => [id, notRequestedReason(host)])),
1670
- failed: [],
1671
- timings: {},
1672
- });
1673
- const [rawLspResults, rawProjectSnapshot, extracted] = await Promise.all([
1674
- runWorkspaceDiagnostics.call(lspService, cwd, {
1675
- maxFiles: options.maxLspFiles,
1676
- signal,
1677
- onProgress: options.onProgress,
1678
- onServerReady: options.onServerReady,
1679
- nextWriteIndex: options.nextWriteIndex,
1680
- files: explicitFiles,
1681
- }),
1682
- getProjectDiagnosticsSnapshotForFullMode(cwd, {
1683
- ...options,
1684
- files: explicitFiles,
1685
- }),
1686
- analyzersPromise,
1687
- ]);
1688
- if (extracted.analysisRootError) {
1689
- return {
1690
- content: [{ type: "text", text: extracted.analysisRootError }],
1691
- isError: true,
1692
- details: {
1693
- mode: "full",
1694
- analysisRootError: extracted.analysisRootError,
1695
- analysisRootValidation: extracted.analysisRootValidation,
1696
- },
1697
- };
1698
- }
1699
- const aborted = signal?.aborted ?? false;
1700
- // #1640: before ANY consumer sees them — the footer reconcile, the widget
1701
- // merge, the rendered counts — demote TypeScript errors on files tsserver
1702
- // checked in its INFERRED project. Applied once, here, so a single seam
1703
- // governs the whole sweep instead of each renderer learning the rule.
1704
- const lspResults = await demoteInferredProjectSweepResults(rawLspResults.filter((result) => includeFile(result.filePath)), cwd, lspService,
1705
- // #1645 review F1: the sweep that produced these results is signal-bounded,
1706
- // so the probe loop that post-processes them must be too.
1707
- signal);
1708
- // A result bound to a different document must not replace the current
1709
- // widget state, even when it contains real diagnostics. Pull results may carry
1710
- // a content hash without the push binding verdict, so verify that hash against
1711
- // disk through the bounded async fallback above; unreadable files remain
1712
- // unknown and retain the existing fail-open policy.
1713
- const mismatchedLspResults = await findFullScanBindingMismatches(lspResults);
1714
- // #630: `timedOut`/`error` means the per-file check never actually
1715
- // completed or was inconclusive (#570's `touchFile().inconclusive`, or
1716
- // this sweep's own outer per-file deadline/throw — see
1717
- // `LSPWorkspaceDiagnosticResult`'s doc comment) — `diagnostics` is a
1718
- // default-EMPTY placeholder in that case, not a confirmed clean. Partition
1719
- // once here so BOTH the footer write (below, #571) and the merge into
1720
- // `summaries` (further down) share the same confirmed/unconfirmed split.
1721
- // Previously only the footer write excluded these; the merge still fed
1722
- // the unfiltered `lspResults` in, so a timed-out file's placeholder `[]`
1723
- // read as "0 diagnostics" — false-clean — in the rendered/`details`
1724
- // output (#630).
1725
- const confirmedLspResults = lspResults.filter((result) => !result.timedOut && !result.error && !mismatchedLspResults.has(result));
1726
- const fullyCoveredLspResults = confirmedLspResults.filter((result) => (result.unconfirmedServerIds?.length ?? 0) === 0);
1727
- const partiallyCoveredLspResults = confirmedLspResults.filter((result) => (result.unconfirmedServerIds?.length ?? 0) > 0);
1728
- const unconfirmedLspResults = lspResults.filter((result) => result.timedOut || result.error || mismatchedLspResults.has(result));
1729
- const authoritativeLspFiles = new Set();
1730
- const unreconciledFiles = new Set();
1731
- // #571: reconcile this scan's fresh, CONFIRMED per-file results into the
1732
- // footer cache. A footer write is never allowed to fail the tool call, so
1733
- // any unexpected throw is swallowed.
1734
- for (const result of fullyCoveredLspResults) {
1735
- try {
1736
- // #692: provenance label ONLY — must never affect `rule`/identity (see
1737
- // `ConvertLspDiagnosticsOptions.scanOrigin`'s doc comment).
1738
- const diagnostics = convertLspDiagnostics(result.diagnostics, result.filePath, { scanOrigin: "lens_diagnostics_full" });
1739
- // #692: re-tag aux-sourced findings (ast-grep, opengrep, zizmor, typos)
1740
- // with their real tool id + semantic policy — the same treatment the
1741
- // per-edit dispatch runner gives them — so a scan-reconciled entry no
1742
- // longer keeps tool "lsp". No file content is read here (this loop
1743
- // only has `result.diagnostics`/`result.filePath` from the sweep, not
1744
- // the file body) — `detectFileRole` still classifies test files
1745
- // correctly from the path alone (its content-based checks only refine
1746
- // generated/stub detection), and native inline-suppression comments
1747
- // were already applied upstream by `runWorkspaceDiagnostics`
1748
- // (`applyAuxiliarySuppressions`), so an empty content string here is a
1749
- // safe no-op re-check rather than a behavior gap.
1750
- const retagged = retagAuxiliaryDiagnostics(diagnostics, result.diagnostics, "", { cwd, fileRole: detectFileRole(result.filePath) });
1751
- const retired = reconcileScanDiagnostics(result.filePath, retagged, true,
1752
- // The service reserves this token when the file enters the scan. The
1753
- // fallback preserves compatibility with older test doubles/services.
1754
- result.writeIndex ?? nextWriteIndex?.(),
1755
- // #1093: `observedAt` is set only when this result was served from
1756
- // the workspace-diagnostics cache (a replay of an older scan) —
1757
- // stamp `touchedAt` with when it was scanned, not now(), so a
1758
- // mode=full that only re-serves the cache can't keep a resolved
1759
- // finding on screen by re-arming the mtime gate. Undefined for
1760
- // freshly-touched results (observed now).
1761
- result.observedAt);
1762
- // A result rejected by the shared ordering guard is not authoritative
1763
- // for delivery. Otherwise full mode hides the old widget row while
1764
- // mode=all still serves it, creating a false clean/full disagreement.
1765
- // Strict `=== true`: `undefined` (a double that predates the boolean
1766
- // return) takes the unreconciled arm, never the retiring one.
1767
- if (retired === true) {
1768
- authoritativeLspFiles.add(path.resolve(result.filePath));
1769
- }
1770
- else {
1771
- unreconciledFiles.add(path.resolve(result.filePath));
1772
- recordDegradationOnce({
1773
- kind: "diagnostic-retained-unreconciled",
1774
- subject: `${path.resolve(result.filePath)}:lsp`,
1775
- reason: "confirmed diagnostic result could not replace retained widget state",
1776
- });
1777
- }
1778
- }
1779
- catch {
1780
- // Never let a footer-reconciliation hiccup fail the scan itself.
1781
- unreconciledFiles.add(path.resolve(result.filePath));
1782
- recordDegradationOnce({
1783
- kind: "diagnostic-retained-unreconciled",
1784
- subject: `${path.resolve(result.filePath)}:lsp`,
1785
- reason: "confirmed diagnostic result failed during widget reconciliation",
1786
- });
1787
- }
1788
- }
1789
- // Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`) —
1790
- // loaded once per mode=full call, BEFORE `projectSnapshot`/`projectDelta`
1791
- // are built below, so `.diagnostics.length` on both (which feeds
1792
- // `details.projectDiagnostics`/`details.projectDiagnosticsDelta` as well
1793
- // as the merge into `summaries`) is the POST-policy count. Threaded into
1794
- // `applyInlineSuppressionsToSummaries` too (idempotent re-apply there) so
1795
- // the merged summaries honor the same policy the per-edit dispatch path
1796
- // applies. A project config read failure is treated as "no policy"
1797
- // (defensive, matches the `applyInlineSuppressionsToSummaries` read-error
1798
- // discipline).
1799
- const policyMap = loadProjectRulePolicyMap(cwd);
1800
- const scannedSnapshot = filterProjectDiagnosticsSnapshot(rawProjectSnapshot, includeFile);
1801
- // Heavyweight-analyzer findings (jscpd, madge, gitleaks, knip, govulncheck,
1802
- // trivy, dead-code) — a FRESH run of each (#585), not a cache-only read:
1803
- // mode=full is the "authoritative full picture" call, so a session_start-only
1804
- // snapshot that can be hours stale in a long session is no longer good
1805
- // enough. Safe to run concurrently with the LSP sweep: every one of these
1806
- // analyzers has its own in-flight de-dupe guard (KnipClient/JscpdClient/
1807
- // DeadCodeClient's `inFlight` map, SecurityScanClient.dedupeScan for
1808
- // gitleaks/govulncheck/trivy — see fresh-fetch.ts's header), so a fresh
1809
- // fetch here racing a concurrent session_start/turn_end pass over the same
1810
- // tool JOINS that run instead of double-spawning it. `extracted` was already
1811
- // computed above, in the SAME `Promise.all` as the LSP sweep (#613) — only
1812
- // when the caller opted into project-runner state (otherwise it's the
1813
- // `Promise.resolve({...})` stub from `analyzersPromise` above).
1814
- // #2154: only ids whose client said it parsed a scan of this root this call
1815
- // (`FreshProjectDiagnosticsResult.analyzed`). Every exclusion — a skipped
1816
- // runner, a crash that still reported success, the cache-read test-runner
1817
- // lane — is decided at the record site in fresh-fetch.ts, not re-derived
1818
- // here; a second list of "which ids don't count" would be the mirror this
1819
- // repo's single-source-of-truth rule forbids.
1820
- const authoritativeRunnerIds = new Set(extracted.analyzed ?? []);
1821
- const authoritativeRunnerCoverage = extracted.authoritativeCoverage ?? [];
1822
- const foldedProjectSnapshot = foldExtraDiagnosticsIntoSnapshot(scannedSnapshot, extracted.diagnostics.filter((d) => includeFile(d.filePath)), extracted.runners, cwd);
1823
- const projectSnapshot = applyProjectRulePolicy(foldedProjectSnapshot, policyMap);
1824
- const projectDelta = applyProjectRulePolicy(filterProjectDiagnosticsDeltaReport(loadProjectDiagnosticsDeltaReport(cwd), includeFile), policyMap);
1825
- // #630: only the CONFIRMED LSP results contribute diagnostics to the merge
1826
- // — an unconfirmed (timed-out/errored) file's placeholder `[]` must not be
1827
- // read as "0 issues, clean" via its LSP contribution. It can still
1828
- // legitimately show diagnostics from widgetSummaries/project-runner state
1829
- // below if those independently have entries for it.
1830
- // #1993: files with a CONFIRMED, fully-covered LSP result are authoritative
1831
- // - the fresh sweep replaces their widget-store state instead of merging
1832
- // additively beside it.
1833
- // #1993 review: retirement must be observable - if a future regression
1834
- // makes the set falsely authoritative, findings would vanish silently.
1835
- const authoritativeRetiredCount = getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath) &&
1836
- authoritativeLspFiles.has(path.resolve(summary.filePath)) &&
1837
- (summary.diagnostics?.length ?? 0) > 0).length;
1838
- if (authoritativeRetiredCount > 0) {
1839
- logLatency({
1840
- type: "phase",
1841
- phase: "lsp_authoritative_widget_retire",
1842
- filePath: "",
1843
- durationMs: 0,
1844
- metadata: { files: authoritativeRetiredCount },
1845
- });
1846
- }
1847
- // #2154 v4 F2: the same rule for the runner arm, in the same shape as its
1848
- // LSP sibling above — a retirement nobody can see is how a regression
1849
- // deletes findings silently. Bounded by construction: at most one row per
1850
- // mode=full call, and only when rows were actually retired.
1851
- const retiredRunnerCounts = new Map();
1852
- const runnerRetiredRows = getFileDiagnosticSummaries()
1853
- .filter((summary) => includeFile(summary.filePath))
1854
- .reduce((total, summary) => total +
1855
- (summary.diagnostics ?? []).filter((diagnostic) => {
1856
- const retired = runnerRetirementDecision(diagnostic, summary.filePath, authoritativeRunnerIds, authoritativeRunnerCoverage) === "retire";
1857
- if (retired) {
1858
- const runnerId = runnerIdOf(diagnostic);
1859
- retiredRunnerCounts.set(runnerId, (retiredRunnerCounts.get(runnerId) ?? 0) + 1);
1860
- }
1861
- return retired;
1862
- }).length, 0);
1863
- if (runnerRetiredRows > 0) {
1864
- logLatency({
1865
- type: "phase",
1866
- phase: "runner_authoritative_widget_retire",
1867
- filePath: "",
1868
- durationMs: 0,
1869
- metadata: {
1870
- rows: runnerRetiredRows,
1871
- runners: [...retiredRunnerCounts.keys()].join(","),
1872
- coverage: authoritativeRunnerCoverage.map((entry) => ({
1873
- runnerId: entry.runnerId,
1874
- root: entry.root,
1875
- files: entry.files.size,
1876
- })),
1877
- },
1878
- });
1879
- }
1880
- for (const entry of authoritativeRunnerCoverage) {
1881
- // #2962: a zero-file declaration retires nothing, so it must neither
1882
- // write this row (a `runner_coverage_retired` row with count 0 would
1883
- // report the opposite of what happened) nor BURN the once-per-session
1884
- // claim — the session's first real coverage row would then be suppressed
1885
- // by the call that proved no coverage. Its own observation is the
1886
- // `runner-coverage-empty` ledger row raised at the fresh-fetch seam.
1887
- if (entry.files.size === 0)
1888
- continue;
1889
- if (!claimPhaseOncePerSession("runner_coverage_retired", entry.runnerId)) {
1890
- continue;
1891
- }
1892
- logLatency({
1893
- type: "phase",
1894
- phase: "runner_coverage_retired",
1895
- filePath: "",
1896
- durationMs: 0,
1897
- metadata: {
1898
- runnerId: entry.runnerId,
1899
- count: retiredRunnerCounts.get(entry.runnerId) ?? 0,
1900
- },
1901
- });
1902
- }
1903
- let summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta, authoritativeLspFiles, authoritativeRunnerIds, authoritativeRunnerCoverage), cwd, policyMap);
1904
- if (unreconciledFiles.size > 0) {
1905
- summaries = summaries.map((summary) => unreconciledFiles.has(path.resolve(summary.filePath))
1906
- ? summarizeDiagnostics(summary.filePath, markUnreconciledFindings(summary.diagnostics), summary.hasFinalSnapshot)
1907
- : summary);
1908
- }
1909
- // #1888: the full-mode summary above is the first seam where every
1910
- // producing lane is correlated. The earlier footer loop intentionally writes
1911
- // only CONFIRMED LSP results, so an ast-grep backpressure failure left
1912
- // project-scan (`ast-grep-napi`) findings visible to lens_diagnostics but
1913
- // absent from the widget. Commit this same post-policy, post-suppression set
1914
- // to the widget so its human-facing count cannot silently become single-lane.
1915
- const parsedProjectScanObservedAt = projectSnapshot?.scannedAt
1916
- ? Date.parse(projectSnapshot.scannedAt)
1917
- : undefined;
1918
- const projectScanObservedAt = parsedProjectScanObservedAt !== undefined &&
1919
- Number.isFinite(parsedProjectScanObservedAt)
1920
- ? parsedProjectScanObservedAt
1921
- : undefined;
1922
- for (const summary of summaries) {
1923
- reconcileCorrelatedScanDiagnostics(summary.filePath, summary.diagnostics, nextWriteIndex?.(), projectScanObservedAt);
1924
- }
1925
- const result = await formatAllMode(cwd, severity, summaries, {
1926
- mode: "full",
1927
- lspFilesChecked: rawLspResults.length,
1928
- partial: aborted,
1929
- projectDiagnostics: projectSnapshot === undefined
1930
- ? undefined
1931
- : {
1932
- tier: projectSnapshot.tier,
1933
- filesScanned: projectSnapshot.filesScanned,
1934
- diagnostics: projectSnapshot.diagnostics.length,
1935
- runners: projectSnapshot.runners,
1936
- },
1937
- projectDiagnosticsDelta: projectDelta === undefined
1938
- ? undefined
1939
- : {
1940
- diagnostics: projectDelta.diagnostics.length,
1941
- sources: projectDelta.sources,
1942
- turnIndex: projectDelta.turnIndex,
1943
- },
1944
- }, 0, pathsScope);
1945
- const missingNote = pathsScopeMissingNote(pathsScope);
1946
- // #630: mirrors `tools/lsp-diagnostics.ts`'s `unconfirmedReasonClause`/
1947
- // `tallyConfirmation` semantic guarantee (never silently render
1948
- // unconfirmed as clean), adapted to this tool's own aggregate-summary
1949
- // shape rather than its per-file result shape. `confirmedLspResults` were
1950
- // already excluded from the merge above, so an agent reading the summary
1951
- // alone would otherwise see nothing for these files and could read that
1952
- // as "0 diagnostics" — say explicitly which files the LSP sweep could not
1953
- // confirm and why, distinguishing a hard error from a soft timeout the
1954
- // same way #570 does upstream.
1955
- const unconfirmedByReason = tallyUnconfirmedReasons(unconfirmedLspResults, mismatchedLspResults);
1956
- // #646: per-primary-server breakdown of the same confirmed/unconfirmed
1957
- // tally — lets an agent see AT A GLANCE which server is responsible for
1958
- // any unconfirmed files (e.g. "marksman: 0/34") instead of having to
1959
- // cross-reference latency.log by hand. Computed unconditionally so it's
1960
- // always available in `details` even on an all-confirmed sweep; only
1961
- // rendered in the text note when more than one server is involved (with
1962
- // a single server the breakdown is redundant with the totals already
1963
- // stated).
1964
- const lspServerBreakdown = tallyLspByPrimaryServer(confirmedLspResults, unconfirmedLspResults);
1965
- const serverBreakdownClause = lspServerBreakdown.size > 1
1966
- ? ` — by server: ${formatLspServerBreakdown(lspServerBreakdown)}.`
1967
- : "";
1968
- const unconfirmedLspNote = unconfirmedLspResults.length > 0
1969
- ? `\n\n⚠ LSP sweep: ${confirmedLspResults.length} file(s) confirmed via LSP, ` +
1970
- `${unconfirmedLspResults.length} unconfirmed (${formatUnconfirmedReasonClause(unconfirmedByReason)})${serverBreakdownClause} — NOT the same as 0 diagnostics for: ${unconfirmedLspResults
1971
- .slice(0, 20)
1972
- .map((result) => result.filePath)
1973
- .join(", ")}${unconfirmedLspResults.length > 20 ? ", …" : ""}. These files' LSP contribution is excluded from this result; re-run mode=full to retry them (they may still show findings above from cached/project-runner state).`
1974
- : "";
1975
- // Code-unit comparator (#1883): this list ships as the
1976
- // `unconfirmedLspServerIds` structured field and as the lane names in the
1977
- // agent-visible coverage note, so its order must be deterministic across
1978
- // locales — localeCompare is deliberately avoided.
1979
- const uncoveredScannerIds = [
1980
- ...new Set(partiallyCoveredLspResults.flatMap((result) => result.unconfirmedServerIds ?? [])),
1981
- ].sort((a, b) => Number(a > b) - Number(a < b));
1982
- const auxiliaryCoverageNote = uncoveredScannerIds.length > 0
1983
- ? `\n\n⚠ Auxiliary coverage incomplete: ${uncoveredScannerIds.join(", ")} did not answer for ${partiallyCoveredLspResults.length} file(s). Findings from answering servers are included; this is not a clean verdict for the named scanner lane(s).`
1984
- : "";
1985
- // #646: primary-vs-auxiliary split of the raw LSP-sweep findings (before
1986
- // they're merged into the widget-state summaries below), mirroring
1987
- // `lsp_diagnostics`' "Primary findings"/"Auxiliary findings" separation —
1988
- // so a page of ast-grep/opengrep/marksman noise never buries whether the
1989
- // real language server itself found anything in this sweep.
1990
- const lspPrimaryVsAuxiliary = tallyLspPrimaryVsAuxiliary(confirmedLspResults);
1991
- logExtension({
1992
- subsystem: "lsp-diagnostics",
1993
- message: "lens_diagnostics verdict",
1994
- metadata: {
1995
- server: [
1996
- ...new Set(confirmedLspResults.map((result) => primaryServerId(result.filePath) ?? "unknown")),
1997
- ].join(","),
1998
- primary: lspPrimaryVsAuxiliary.primary,
1999
- auxiliary: lspPrimaryVsAuxiliary.auxiliary,
2000
- total: lspPrimaryVsAuxiliary.primary + lspPrimaryVsAuxiliary.auxiliary,
2001
- sources: [
2002
- ...new Set(confirmedLspResults.flatMap((result) => (result.diagnostics ?? []).map((diagnostic) => diagnostic.source ?? "unknown"))),
2003
- ].slice(0, 5),
2004
- },
2005
- });
2006
- const lspPrimaryVsAuxiliaryNote = lspPrimaryVsAuxiliary.primary + lspPrimaryVsAuxiliary.auxiliary > 0
2007
- ? `\n\nLSP sweep findings: ${lspPrimaryVsAuxiliary.primary} primary (language server), ` +
2008
- `${lspPrimaryVsAuxiliary.auxiliary} auxiliary (ast-grep/opengrep/zizmor/typos/marksman/...).`
2009
- : "";
2010
- // #533/#585: a heavyweight analyzer (knip/jscpd/madge/gitleaks/govulncheck/
2011
- // trivy/dead-code) that contributed nothing this run is either COLD (not
2012
- // applicable to this project / tool unavailable — see fresh-fetch.ts's
2013
- // per-analyzer gates) or genuinely clean; `cold` only ever lists the
2014
- // former. Silently folding "not applicable" into "no issues found" would be
2015
- // exactly the false-empty #533 is about. Named per #511/#514's
2016
- // actionable-warning shape: say what's cold and what would warm it, only
2017
- // when the caller actually asked for runner state (extracted.cold is
2018
- // always [] otherwise).
2019
- // #585: an aborted fresh-fetch (Escape / the mode=full wall-clock ceiling)
2020
- // reports its still-in-flight analyzers separately from a genuine
2021
- // not-applicable/unavailable skip — "stopped mid-scan, unknown" is a
2022
- // different, more honest reason than "not applicable to this project", and
2023
- // conflating them would suggest re-running mode=full is pointless when it
2024
- // isn't (a re-run may well complete for that analyzer).
2025
- const abortedIds = new Set(extracted.abortedIds ?? []);
2026
- const genuinelyColdIds = extracted.cold.filter((id) => !abortedIds.has(id));
2027
- const partialIds = extracted.partial ?? [];
2028
- // #747: an unsafe analysis root (cwd at/above $HOME) skips ALL heavyweight
2029
- // analyzers before anything spawns — rendering that as the per-analyzer
2030
- // "not applicable / unavailable" list would send the caller chasing seven
2031
- // wrong reasons. One note with the real one instead.
2032
- // #1623: prefer the SPECIFIC reason captured at each gate
2033
- // (`extracted.coldReasons`) over a generic guess — `formatNotRunEntry`
2034
- // falls back to `warmTriggerFor` only for an id this call's `coldReasons`
2035
- // doesn't cover (e.g. a caller-supplied `cold` list from an older cache
2036
- // shape). Single formatter (extractors.ts) so this note's wording can't
2037
- // drift from any other caller that renders the same `cold` list.
2038
- // #1623 fix-round F5: the "not requested" (quick-mode) batch shares ONE
2039
- // reason (`notRequestedReason`) across every id — `formatNotRunEntry`
2040
- // repeating that same ~130-char sentence per id makes the note nearly
2041
- // unreadable, and the "not applicable / unavailable this run" header
2042
- // below is a dishonest label for it: these lanes ARE applicable and
2043
- // available, this call just never asked for them. Render that batch as
2044
- // its own compact note — bare ids, the shared reason stated once — and
2045
- // keep the detailed per-id format for genuinely cold lanes, where each
2046
- // id's reason really does differ (not a git repo vs binary unavailable
2047
- // vs retry cooldown, ...).
2048
- const coldNote = extracted.unsafeRoot
2049
- ? `\n\nheavyweight analyzers skipped: the analysis root resolves at or above the home directory, so a fresh knip/jscpd/madge/gitleaks/govulncheck/trivy/dead-code scan would walk every unrelated tree under it. Supply a project directory below the home ceiling. Absence of their findings is NOT a clean verdict.`
2050
- : extracted.analysisRootError
2051
- ? `\n\nheavyweight analyzers skipped: ${extracted.analysisRootError}. Supply an existing project directory below the home ceiling. Absence of their findings is NOT a clean verdict.`
2052
- : !projectRunnersRequested && genuinelyColdIds.length > 0
2053
- ? `\n\nnot run this call (quick mode): ${genuinelyColdIds.join(", ")}. ${notRequestedReason(host)}. Absence of their findings is NOT a clean verdict.`
2054
- : genuinelyColdIds.length > 0
2055
- ? `\n\ncold (not applicable / unavailable this run): ${genuinelyColdIds
2056
- .map((id) => formatNotRunEntry(id, extracted.coldReasons))
2057
- .join(", ")}. These analyzers have not contributed to this result — absence of their findings is NOT a clean verdict.`
2058
- : "";
2059
- const partialNote = partialIds.length > 0
2060
- ? `\n\n⚠ partial coverage (findings included): ${partialIds
2061
- .map((id) => `${id} — ${extracted.partialReasons?.[id] ?? "coverage is incomplete"}`)
2062
- .join(", ")}. Coverage is incomplete, so absence of findings is NOT a clean verdict.`
2063
- : "";
2064
- // #1004: unlike every other analyzer above (knip/jscpd/madge/gitleaks/
2065
- // govulncheck/opengrep/trivy/dead-code all run a FRESH whole-project scan
2066
- // per the fresh-fetch.ts header, so "clean" there really does mean "the
2067
- // whole project was checked this call"), test-runner has no whole-project
2068
- // run to trigger: its cache only ever reflects the (targeted,
2069
- // cascade-aware) test file(s) touched by the MOST RECENT edit's turn_end
2070
- // fire (`runtime-turn.ts`). Folding it into `runners`/`diagnostics` with no
2071
- // distinguishing note — as opposed to when it's cold, which already gets
2072
- // `coldNote` above — would let a caller mistake "test-runner ran
2073
- // project-wide and is clean" for "it only ever saw the 2 files edited an
2074
- // hour ago", exactly the false-empty/false-confidence #533 this whole
2075
- // module exists to prevent. Fires whenever test-runner CONTRIBUTED
2076
- // (`runners`), independent of whether it also has cold/failed entries this
2077
- // call — a caller needs this caveat on every mode=full response where
2078
- // test-runner findings are present, not only some.
2079
- const testRunnerEditScopedNote = extracted.runners.includes("test-runner")
2080
- ? `\n\ntest-runner coverage is edit-scoped, NOT a full-project run: its findings only reflect the test file(s) touched by the most recent edit's turn_end fire, not a fresh whole-project test suite run (mode=full never re-runs the suite — see fresh-fetch.ts). Absence of test-runner findings elsewhere in the project is NOT a clean verdict, and presence here does not mean "just checked" — it may be from several turns ago.`
2081
- : "";
2082
- const failedAnalyzers = extracted.failed ?? [];
2083
- const failedNote = failedAnalyzers.length > 0
2084
- ? `\n\nfailed (ran, but no trustworthy result): ${failedAnalyzers
2085
- .map(({ id, summary }) => `${id} — ${summary}`)
2086
- .join(", ")}. These analyzers were not cached and will be retried; absence of their findings is NOT a clean verdict.`
2087
- : "";
2088
- // #1617: the #1616 suppressed-bucket rule applied to mode=full — a finding
2089
- // an agent/user marked false-positive/won't-fix now drops out of
2090
- // `diagnostics` (fresh-fetch.ts's `record()`), but that drop must stay
2091
- // visible as a count rather than reading as "nothing was wrong here".
2092
- // Review-round F4 (#1625): per-lane attribution — "gitleaks 2, knip 1" says
2093
- // WHICH analyzer's marks are doing the suppressing, not just a bare total.
2094
- // A lane that's 100% suppressed still won't appear in `runners`/`cold` as
2095
- // distinct from "ran clean" — that gap is #1623's lane-status territory,
2096
- // not fixed here.
2097
- const dispositionSuppressedByLaneText = Object.entries(extracted.dispositionSuppressedByLane ?? {})
2098
- .map(([id, count]) => `${id} ${count}`)
2099
- .join(", ");
2100
- const dispositionSuppressedNote = (extracted.dispositionSuppressed ?? 0) > 0
2101
- ? `\n\nsuppressed by disposition: ${extracted.dispositionSuppressed} finding(s) dropped from this result because they're marked false-positive or won't-fix (${dispositionSuppressedByLaneText}). Not a clean verdict for those locations — they were found, then intentionally hidden.`
2102
- : "";
2103
- // #747/#250: the cheap project-diagnostics scan (scanProjectDiagnostics) and
2104
- // the LSP workspace sweep (collectWorkspaceDiagnosticFiles) both refuse to
2105
- // WALK from a cwd at/above $HOME — from there, walking would enumerate every
2106
- // unrelated tree under home. An explicit `paths` file list (explicitFiles)
2107
- // bypasses both walks, so it is never unsafe. Their empty results in that
2108
- // case are "walked nothing", not "clean" — say so, matching the fresh-fetch
2109
- // unsafeRoot note above.
2110
- const walkUnsafeRoot = explicitFiles === undefined && isAtOrAboveHomeDir(cwd);
2111
- const walkUnsafeRootNote = walkUnsafeRoot
2112
- ? `\n\nproject file walk skipped: the working directory resolves at or above the home directory, so the cheap project scan and the LSP workspace sweep would each enumerate every unrelated tree under it. Re-run from inside a project directory. Absence of their findings is NOT a clean verdict.`
2113
- : "";
2114
- // #784: the cheap project scan's `scanTruncated` flag (#760) reached this
2115
- // seam already but nothing rendered it, so a capped scan read as a
2116
- // complete clean sweep. Say so explicitly, same wording the MCP
2117
- // `pilens_project_scan` tool uses.
2118
- const scanTruncatedNoticeText = projectSnapshot
2119
- ? scanTruncationNotice(projectSnapshot)
2120
- : undefined;
2121
- const scanTruncatedNote = scanTruncatedNoticeText
2122
- ? `\n\n${scanTruncatedNoticeText}`
2123
- : "";
2124
- // #1107 phase 2: same "reached the seam, nothing rendered it" gap as #784
2125
- // above, for the generated-name/dir skip counters.
2126
- const generatedSkipNoticeText = projectSnapshot
2127
- ? generatedSkipNotice(projectSnapshot, host)
2128
- : undefined;
2129
- const generatedSkipNote = generatedSkipNoticeText
2130
- ? `\n\n${generatedSkipNoticeText}`
2131
- : "";
2132
- // #1623: the cheap in-process project scan (tree-sitter/fact-rules/
2133
- // ast-grep) is a THIRD lane `coldNote` never covers (it isn't one of
2134
- // `ANALYZER_IDS` — that list is fetch-fetch.ts's heavyweight analyzers
2135
- // only). It has three distinct states, all of which must render — the
2136
- // fix-round F2 finding: with `refreshRunners=cached` and no snapshot ever
2137
- // written yet, BOTH of the two notes below used to stay empty (one gated
2138
- // on `scannedAt` being present, the other on the "not requested" branch
2139
- // this call isn't in), so the original #1623 silence survived in exactly
2140
- // this mode. `cheapScanScannedAtMs` centralizes the one bit every branch
2141
- // needs: whether the stored snapshot has a usable timestamp (#1623
2142
- // fix-round F4 — a corrupt/missing `scannedAt` must not compute a NaN
2143
- // age, mirroring `./cache.ts`'s `Number.isFinite` guard).
2144
- const cheapScanScannedAtMs = rawProjectSnapshot?.scannedAt
2145
- ? Date.parse(rawProjectSnapshot.scannedAt)
2146
- : undefined;
2147
- const cheapScanStatusNote = !projectRunnersRequested
2148
- ? `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): not run this call (${notRequestedReason(host)}).`
2149
- : options.refreshRunners === "cached"
2150
- ? cheapScanScannedAtMs !== undefined &&
2151
- Number.isFinite(cheapScanScannedAtMs)
2152
- ? `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): served from cache, ${formatCacheAgeOld(Date.now() - cheapScanScannedAtMs)} — not re-run this call.`
2153
- : `\n\ncheap project scan (tree-sitter/fact-rules/ast-grep): not run (no cached scan; refresh with refreshRunners=cheap/all to populate).`
2154
- : "";
2155
- const abortedNote = abortedIds.size > 0
2156
- ? `\n\nstopped mid-scan (still running in the background, not reflected in this result): ${[
2157
- ...abortedIds,
2158
- ].join(", ")}. Not a clean verdict for these — re-run mode=full to pick up their result once it's cached.`
2159
- : "";
2160
- // #585: mode=full now fetches these analyzers fresh rather than reading a
2161
- // possibly-stale session_start cache — say so honestly, with per-analyzer
2162
- // elapsed time, since this can legitimately take a while (trivy's own
2163
- // ~180s ceiling).
2164
- // #1623: `extracted.cachedAgeMs` marks ids that are a cache-read BY DESIGN
2165
- // (today, only test-runner — see fresh-fetch.ts) rather than a fresh
2166
- // execution this call. Exclude those from "fetched fresh" below — folding
2167
- // a cache replay into the same list as a genuine run is exactly the
2168
- // misread the second #1623 dogfood pass reported (a cached result read as
2169
- // "just ran"). They get their own note with an actual age instead.
2170
- const cachedAgeMs = extracted.cachedAgeMs ?? {};
2171
- const timingEntries = Object.entries(extracted.timings ?? {}).filter(([id]) => !(id in cachedAgeMs));
2172
- const freshNote = timingEntries.length > 0
2173
- ? `\n\nfetched fresh this call: ${timingEntries
2174
- .map(([id, ms]) => `${id} (${Math.round(ms)}ms)`)
2175
- .join(", ")}.`
2176
- : "";
2177
- const cachedAgeEntries = Object.entries(cachedAgeMs);
2178
- const cachedAgeNote = cachedAgeEntries.length > 0
2179
- ? `\n\nserved from cache this call (not re-run): ${cachedAgeEntries
2180
- .map(([id, ms]) => `${id} (${formatCacheAgeOld(ms)})`)
2181
- .join(", ")}.`
2182
- : "";
2183
- // coldRunners always lands in details (even when empty) so a caller can
2184
- // reliably check "were any extractors cold" without a presence check.
2185
- // analyzerTimingsMs surfaces the #585 fresh-fetch elapsed time per
2186
- // analyzer that actually ran this call (empty when refreshRunners opted
2187
- // out of runner state entirely).
2188
- const resultWithCold = {
2189
- ...result,
2190
- details: {
2191
- ...result.details,
2192
- coldRunners: extracted.cold,
2193
- partialRunners: partialIds,
2194
- // #1623: the specific reason each cold id was skipped (single source
2195
- // of truth: extractors.ts's `formatNotRunEntry` renders the same map
2196
- // into the text note above) — lets a caller check WHY without parsing
2197
- // text.
2198
- coldReasons: extracted.coldReasons ?? {},
2199
- failedAnalyzers,
2200
- analyzerTimingsMs: extracted.timings,
2201
- dispositionSuppressed: extracted.dispositionSuppressed ?? 0,
2202
- dispositionSuppressedByLane: extracted.dispositionSuppressedByLane ?? {},
2203
- // #1623: ms-old each cache-read-by-design lane's data was (today, only
2204
- // test-runner) — lets a caller distinguish "just ran" from "served
2205
- // from cache" without parsing the text note.
2206
- analyzersCachedAgeMs: cachedAgeMs,
2207
- analyzersAborted: extracted.aborted ?? false,
2208
- analyzersAbortedIds: extracted.abortedIds ?? [],
2209
- // #747: true when the fresh fetch refused an at-or-above-$HOME root —
2210
- // lets a caller distinguish "skipped for safety" from per-analyzer
2211
- // cold reasons without parsing the text note.
2212
- // This is the fetch seam's explicit-root policy. The cwd walk below is
2213
- // intentionally separate: it answers a different root question.
2214
- analyzersUnsafeRoot: extracted.analysisRootValidation?.state === "unsafe" ||
2215
- extracted.unsafeRoot === true,
2216
- analysisRootValidation: extracted.analysisRootValidation,
2217
- // #747: true when the cwd resolved at/above $HOME so the cheap project
2218
- // scan and the LSP workspace sweep both refused to walk — lets a caller
2219
- // distinguish "walked nothing for safety" from a genuinely clean sweep.
2220
- projectWalkUnsafeRoot: walkUnsafeRoot,
2221
- // #630: confirmed/unconfirmed LSP-sweep tally, mirroring
2222
- // `lsp_diagnostics`' confirmation state — lets a caller check "were
2223
- // any files unconfirmed" without re-deriving it from the text.
2224
- lspFilesConfirmed: confirmedLspResults.length,
2225
- lspFilesUnconfirmed: unconfirmedLspResults.length,
2226
- lspFilesPartiallyCovered: partiallyCoveredLspResults.length,
2227
- unconfirmedLspServerIds: uncoveredScannerIds,
2228
- unconfirmedLspFiles: unconfirmedLspResults.map((result) => result.filePath),
2229
- // #646: per-primary-server breakdown of the same tally, plus the
2230
- // primary/auxiliary findings split — mirrors `lsp_diagnostics`'
2231
- // per-server reporting granularity (always present, even when every
2232
- // server confirmed cleanly, so a caller can check it unconditionally).
2233
- lspServerBreakdown: Object.fromEntries(lspServerBreakdown),
2234
- lspPrimaryDiagnosticsCount: lspPrimaryVsAuxiliary.primary,
2235
- lspAuxiliaryDiagnosticsCount: lspPrimaryVsAuxiliary.auxiliary,
2236
- // #784: lets a caller check "was the cheap project scan truncated"
2237
- // without parsing the text note.
2238
- projectScanTruncated: projectSnapshot?.scanTruncated ?? false,
2239
- // #1107 phase 2: same machine-readable convention, for the
2240
- // generated-name/dir skip counters.
2241
- ...(projectSnapshot?.generatedFileSkips
2242
- ? { projectGeneratedFileSkips: projectSnapshot.generatedFileSkips }
2243
- : {}),
2244
- ...(projectSnapshot?.generatedNameOnlySkips
2245
- ? {
2246
- projectGeneratedNameOnlySkips: projectSnapshot.generatedNameOnlySkips,
2247
- }
2248
- : {}),
2249
- ...(projectSnapshot?.generatedDirSkips
2250
- ? { projectGeneratedDirSkips: projectSnapshot.generatedDirSkips }
2251
- : {}),
2252
- },
2253
- };
2254
- // Stopped mid-scan: the results above are whatever completed before the abort.
2255
- // Tell the agent so it doesn't read a partial sweep as "clean" (#341). The
2256
- // abort is either a user/turn cancel (Escape) or the wall-clock ceiling firing
2257
- // (AbortSignal.timeout → TimeoutError), which guarantees the scan can't hang
2258
- // indefinitely — distinguish them so the agent knows whether to just re-run.
2259
- if (aborted) {
2260
- const timedOut = signal
2261
- ?.reason?.name === "TimeoutError";
2262
- const note = timedOut
2263
- ? `\n\n⚠ Scan exceeded its ${Math.round((options.wallClockMs ?? 0) / 1000)}s time budget and was stopped — results are partial. ` +
2264
- "Narrow it with maxLspFiles, or raise PI_LENS_LENS_DIAGNOSTICS_FULL_TIMEOUT_MS."
2265
- : "\n\n⚠ Scan cancelled before completion — results are partial. " +
2266
- "Re-run with a smaller maxLspFiles to finish within budget.";
2267
- return {
2268
- content: [
2269
- {
2270
- type: "text",
2271
- text: result.content[0].text +
2272
- note +
2273
- unconfirmedLspNote +
2274
- auxiliaryCoverageNote +
2275
- lspPrimaryVsAuxiliaryNote +
2276
- partialNote +
2277
- coldNote +
2278
- testRunnerEditScopedNote +
2279
- failedNote +
2280
- dispositionSuppressedNote +
2281
- walkUnsafeRootNote +
2282
- scanTruncatedNote +
2283
- generatedSkipNote +
2284
- abortedNote +
2285
- freshNote +
2286
- cachedAgeNote +
2287
- cheapScanStatusNote +
2288
- missingNote,
2289
- },
2290
- ],
2291
- details: { ...resultWithCold.details, timedOut },
2292
- };
2293
- }
2294
- if (missingNote ||
2295
- coldNote ||
2296
- partialNote ||
2297
- testRunnerEditScopedNote ||
2298
- failedNote ||
2299
- dispositionSuppressedNote ||
2300
- walkUnsafeRootNote ||
2301
- scanTruncatedNote ||
2302
- generatedSkipNote ||
2303
- abortedNote ||
2304
- freshNote ||
2305
- cachedAgeNote ||
2306
- cheapScanStatusNote ||
2307
- unconfirmedLspNote ||
2308
- auxiliaryCoverageNote ||
2309
- lspPrimaryVsAuxiliaryNote) {
2310
- return {
2311
- content: [
2312
- {
2313
- type: "text",
2314
- text: result.content[0].text +
2315
- unconfirmedLspNote +
2316
- auxiliaryCoverageNote +
2317
- lspPrimaryVsAuxiliaryNote +
2318
- partialNote +
2319
- coldNote +
2320
- testRunnerEditScopedNote +
2321
- failedNote +
2322
- dispositionSuppressedNote +
2323
- walkUnsafeRootNote +
2324
- scanTruncatedNote +
2325
- generatedSkipNote +
2326
- abortedNote +
2327
- freshNote +
2328
- cachedAgeNote +
2329
- cheapScanStatusNote +
2330
- missingNote,
2331
- },
2332
- ],
2333
- details: resultWithCold.details,
2334
- };
2335
- }
2336
- return resultWithCold;
2337
- }
2338
- async function projectResolvedCwd(summary, cwd) {
2339
- // #2777 O1: the whole three-way lookup lives on the LSP seam now. A file
2340
- // with no primary LSP server is `undefined` here by construction, and
2341
- // formatAllMode renders that row without a cwd term (N1) — the literal
2342
- // `cwd=undefined` cannot be produced.
2343
- const resolvedCwd = await resolveLspCwdForFile(summary.filePath, cwd);
2344
- return {
2345
- ...summary,
2346
- ...(resolvedCwd === undefined ? {} : { resolvedCwd }),
2347
- diagnostics: summary.diagnostics ?? [],
2348
- };
2349
- }
2350
- // @delivery-surface: lens-diagnostics:mode-all
2351
- async function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(), detailOverrides = { mode: "all" }, staleDropped = 0, pathsScope, dependencyDemoted = 0) {
2352
- // #2275 review F2: the widget footer stops DRAWING a dependency-drift
2353
- // demotion once it hits `DEPENDENCY_DRIFT_MAX_DELIVERIES` unconfirmed
2354
- // deliveries, but the record stays here (dropping it would make an
2355
- // unconfirmed LSP error read as clean). Say so, through the same note
2356
- // channel as the demotion itself, so the agent knows why the footer went
2357
- // quiet and that a re-run can still confirm the finding.
2358
- const footerCapped = summaries.reduce((n, s) => n + (s.diagnostics ?? []).filter((d) => d.footerRetired === true).length, 0);
2359
- // Files changed/deleted since their diagnostics were recorded have already
2360
- // been dropped by reconcileStaleWidgetFiles; note them so the agent knows
2361
- // those aren't "clean", just un-rescanned (use mode=full to refresh).
2362
- const staleNote = (staleDropped > 0
2363
- ? ` (${staleDropped} changed file${staleDropped === 1 ? "" : "s"} omitted as stale — use mode=full to rescan)`
2364
- : "") +
2365
- (dependencyDemoted > 0
2366
- ? ` (${dependencyDemoted} blocking finding${dependencyDemoted === 1 ? "" : "s"} demoted to [stale] — an imported file changed since; re-run to confirm)`
2367
- : "") +
2368
- (footerCapped > 0
2369
- ? ` (${footerCapped} demoted finding${footerCapped === 1 ? "" : "s"} capped after ${DEPENDENCY_DRIFT_MAX_DELIVERIES} unconfirmed deliveries — no longer shown in the pi-lens footer, still listed here; re-run to confirm)`
2370
- : "");
2371
- // mode=full already actively scanned exactly the requested paths, so a zero
2372
- // result there IS a legitimate clean read — the cache-only note only applies
2373
- // to delta/all, which is why this is gated on detailOverrides.mode !== "full".
2374
- const isFullMode = detailOverrides.mode === "full";
2375
- const includeFile = createScopedFileFilter(cwd, pathsScope);
2376
- // Cached summaries predate marks. Every cache-only surface re-applies
2377
- // dispositions through the shared applyCachedDispositions seam — strict
2378
- // false-positive anchors against current content when the file is
2379
- // unchanged since observation, weak-only fallback otherwise.
2380
- const policyMap = loadProjectRulePolicyMap(cwd);
2381
- const dispositioned = isFullMode
2382
- ? summaries
2383
- : summaries.map((s) => {
2384
- const diagnostics = s.diagnostics ?? [];
2385
- const kept = applyCachedDispositions(diagnostics, cwd, s.filePath);
2386
- const policyKept = applyRulePolicy(kept, policyMap);
2387
- if (policyKept.length === kept.length &&
2388
- kept.length === diagnostics.length)
2389
- return s;
2390
- return summarizeDiagnostics(s.filePath, policyKept, s.hasFinalSnapshot);
2391
- });
2392
- // #1641: the single chokepoint both mode=all (cache-only `summaries`) and
2393
- // mode=full (widget cache merged with a FRESH LSP sweep, above) converge
2394
- // through — a fresh sweep's server can still be citing its own stale
2395
- // in-memory document, so gating only the cached half would miss exactly
2396
- // the live incident this issue reports. Runs AFTER dispositions/rule
2397
- // policy (not before): a finding a mark or a `.pi-lens.json` rule policy
2398
- // already dropped is not being served, so it must never trigger a resync
2399
- // or a `diagnostic_past_eof` telemetry record on this call.
2400
- const eofGated = dispositioned.map((s) => {
2401
- const { diagnostics, demotedCount } = demotePastEofDiagnostics({
2402
- store: "lens_diagnostics",
2403
- cwd,
2404
- filePath: s.filePath,
2405
- diagnostics: s.diagnostics ?? [],
2406
- resync: resyncDocumentOnPastEof,
2407
- });
2408
- if (demotedCount === 0)
2409
- return s;
2410
- return summarizeDiagnostics(s.filePath, diagnostics, s.hasFinalSnapshot);
2411
- });
2412
- const visibleSummaries = eofGated.filter((s) => includeFile(s.filePath));
2413
- // Filter to files with actual issues. A file whose only findings were
2414
- // demoted by the #1641 past-EOF gate has blocking/errors/warnings at 0 —
2415
- // counting it as "issue-free" would tell the agent the file is CLEAN when
2416
- // it actually has an unconfirmed finding waiting on a rescan. Surface it
2417
- // under `severity: "all"` (the gate's own advisory tier) so it stays
2418
- // visible; a narrower `error`/`warning` filter still excludes it, matching
2419
- // how those filters already treat any other non-matching severity.
2420
- const withIssues = visibleSummaries.filter((s) => {
2421
- if (severity === "error")
2422
- return s.blocking > 0 || s.errors > 0;
2423
- // #2414: a "warning" filter must not admit hint/info — that is exactly
2424
- // the "present hints as warnings" defect this issue exists to close.
2425
- if (severity === "warning")
2426
- return s.blocking > 0 || s.errors > 0 || s.warnings > 0;
2427
- // severity: "all" — a hint/info-only file (`advisories > 0`,
2428
- // warnings === 0) must still surface here, or the #2414 fix that stops
2429
- // hints inflating `warnings` would silently drop that file from the
2430
- // detailed listing instead of just from the compact warning tally.
2431
- return (s.blocking > 0 ||
2432
- s.errors > 0 ||
2433
- s.warnings > 0 ||
2434
- s.advisories > 0 ||
2435
- (s.diagnostics ?? []).some((d) => d.stale));
2436
- });
2437
- const pathsNote = isFullMode ? "" : pathsScopeCacheOnlyNote(pathsScope);
2438
- if (withIssues.length === 0) {
2439
- const text = (visibleSummaries.length === 0
2440
- ? "No files diagnosed yet this session."
2441
- : `No ${severity === "all" ? "" : severity + " "}issues across ${visibleSummaries.length} file${visibleSummaries.length === 1 ? "" : "s"} diagnosed this session. ✓`) +
2442
- staleNote +
2443
- pathsNote;
2444
- return {
2445
- content: [{ type: "text", text }],
2446
- details: {
2447
- ...detailOverrides,
2448
- filesChecked: visibleSummaries.length,
2449
- staleDropped,
2450
- },
2451
- };
2452
- }
2453
- // Sort: blocking first, then errors, then warnings
2454
- const sorted = withIssues.sort((a, b) => b.blocking - a.blocking || b.errors - a.errors || b.warnings - a.warnings);
2455
- // #2777 O2: only rendered rows resolve their root. The projection sits
2456
- // below the withIssues filter, so a 500-file session pays the cwd
2457
- // resolution for the handful of rows listed here, not for every summary
2458
- // the cache holds.
2459
- const rendered = await Promise.all(sorted.map((summary) => projectResolvedCwd(summary, cwd)));
2460
- const lines = [];
2461
- let totalBlocking = 0;
2462
- let totalErrors = 0;
2463
- let totalWarnings = 0;
2464
- let totalAdvisories = 0;
2465
- for (const s of rendered) {
2466
- const rel = path.relative(cwd, s.filePath);
2467
- const parts = [];
2468
- if (s.blocking > 0)
2469
- parts.push(`🔴 ${s.blocking} blocking`);
2470
- if (s.errors > 0 && s.blocking === 0)
2471
- parts.push(`${s.errors}E`);
2472
- if (s.warnings > 0)
2473
- parts.push(`${s.warnings}W`);
2474
- // #2414: hint/info never inflate `warnings`, but a hint-only row still
2475
- // needs a reason to be listed — otherwise it shows a bare filename with
2476
- // no count at all.
2477
- if (s.advisories > 0)
2478
- parts.push(`${s.advisories} hint${s.advisories === 1 ? "" : "s"}`);
2479
- if (!s.hasFinalSnapshot)
2480
- parts.push(`(pending)`);
2481
- const staleCount = (s.diagnostics ?? []).filter((d) => d.stale).length;
2482
- if (staleCount > 0) {
2483
- parts.push(`${staleCount} stale — re-run to confirm`);
2484
- }
2485
- // #2777 N1: files with no primary LSP server have no resolvedCwd —
2486
- // omit the term rather than rendering the literal `cwd=undefined`.
2487
- if (s.resolvedCwd !== undefined)
2488
- parts.push(`cwd=${s.resolvedCwd}`);
2489
- lines.push(`${rel} ${parts.join(" ")}`);
2490
- // List the actual diagnostics (not just counts) so the agent can act on
2491
- // them without re-running anything — same "L<line>: <message>" shape as the
2492
- // inline blocker output. The widget state now exposes the FULL set (not the
2493
- // TUI's 12-cap); the tool applies its own generous per-file budget purely to
2494
- // avoid flooding context on a pathologically broken file.
2495
- const matching = (s.diagnostics ?? [])
2496
- .filter((d) => matchesSeverity(d, severity))
2497
- .sort(bySeverityThenLine);
2498
- const shown = matching.slice(0, MAX_DIAGNOSTICS_PER_FILE);
2499
- for (const d of shown) {
2500
- const marker = d.stale
2501
- ? ""
2502
- : isErrorLike(d)
2503
- ? d.semantic === "blocking"
2504
- ? "🔴 "
2505
- : ""
2506
- : "";
2507
- const label = d.rule ?? d.tool;
2508
- const tag = label ? ` [${label}]` : "";
2509
- const flaggedTag = d.flagged ? " 📌 flagged-to-fix" : "";
2510
- const msg = d.message.replace(/\s+/g, " ").trim();
2511
- // #1641: a demoted past-EOF entry no longer has a trustworthy line —
2512
- // show the marker instead of a coordinate that doesn't exist in the
2513
- // current file, matching the #1622/#1627 stale-line render convention.
2514
- // Past-EOF demotions replace the untrustworthy coordinate; other
2515
- // demotions (#1631 drift) keep their in-bounds line and append the
2516
- // shared stale marker instead.
2517
- const pastEof = d.stale && (d.staleReason ?? "past-eof") === "past-eof";
2518
- const loc = pastEof ? PAST_EOF_STALE_MARKER : `L${d.line ?? "?"}`;
2519
- const staleTag = d.stale && !pastEof ? ` ${STALE_LINE_MARKER}` : "";
2520
- lines.push(` ${marker}${loc}: ${msg}${tag}${flaggedTag}${staleTag}`);
2521
- }
2522
- if (matching.length > shown.length) {
2523
- lines.push(` … ${matching.length - shown.length} more in this file (showing ${shown.length} of ${matching.length})`);
2524
- }
2525
- totalBlocking += s.blocking;
2526
- totalErrors += s.errors;
2527
- totalWarnings += s.warnings;
2528
- totalAdvisories += s.advisories;
2529
- }
2530
- // #1799: `totalBlocking` and `totalErrors` count the same findings UNLESS a
2531
- // dependency-drift demotion (#1631) has revoked blocking authority from an
2532
- // error while leaving it in the error tally (widget-state.ts `isBlocking`
2533
- // vs `countDiagnostics`) — that's a real, live disagreement between the two
2534
- // totals, not double-counting. So `errors` renders only when
2535
- // `blocking === 0`, same guard as the per-file row above: it shows the
2536
- // drift-demoted findings the blocking line can't, without ever printing the
2537
- // same findings twice under two labels when blocking > 0.
2538
- const summary = [
2539
- `\nSummary (${visibleSummaries.length} files diagnosed this session):`,
2540
- totalBlocking > 0
2541
- ? ` 🔴 ${totalBlocking} blocking error${totalBlocking === 1 ? "" : "s"}`
2542
- : null,
2543
- totalBlocking === 0 && totalErrors > 0
2544
- ? ` ${totalErrors} error${totalErrors === 1 ? "" : "s"}`
2545
- : null,
2546
- totalWarnings > 0
2547
- ? ` ${totalWarnings} warning${totalWarnings === 1 ? "" : "s"}`
2548
- : null,
2549
- // #2414: reported separately from `warnings` — hint/info are style
2550
- // opinions, not defects, and must not read as unresolved code issues.
2551
- totalAdvisories > 0
2552
- ? ` ${totalAdvisories} hint/info (style — not counted as warnings)`
2553
- : null,
2554
- ]
2555
- .filter(Boolean)
2556
- .join("\n");
2557
- return {
2558
- content: [
2559
- { type: "text", text: lines.join("\n") + summary + staleNote },
2560
- ],
2561
- details: {
2562
- ...detailOverrides,
2563
- filesWithIssues: withIssues.length,
2564
- totalBlocking,
2565
- totalErrors,
2566
- totalWarnings,
2567
- totalAdvisories,
2568
- staleDropped,
2569
- },
2570
- };
2571
- }