@herbertgao/pi-extensions 2026.10.1 → 2026.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (707) hide show
  1. package/README.md +13 -9
  2. package/THIRD_PARTY_NOTICES.md +2 -2
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  9. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  10. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  11. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  12. package/node_modules/@dietrichgebert/ponytail/README.md +68 -72
  13. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +7 -0
  14. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  15. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  16. package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
  17. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  18. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -32
  19. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  20. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +17 -16
  21. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +9 -10
  22. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -57
  23. package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
  24. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  25. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  26. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  32. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  33. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  34. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  35. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  36. package/node_modules/@tifan/pi-recap/package.json +1 -1
  37. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  38. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  39. package/node_modules/pi-antigravity/README.md +23 -2
  40. package/node_modules/pi-antigravity/package.json +5 -3
  41. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  42. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  43. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  44. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  45. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  46. package/node_modules/pi-antigravity/src/index.ts +52 -137
  47. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  48. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  49. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  50. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  51. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  52. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  53. package/node_modules/pi-lens/CHANGELOG.md +1064 -0
  54. package/node_modules/pi-lens/README.md +115 -25
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  56. package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
  57. package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
  58. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  59. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  60. package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
  61. package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
  62. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  63. package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
  64. package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
  65. package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
  66. package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
  67. package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
  68. package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
  69. package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
  70. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  71. package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
  72. package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
  74. package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
  75. package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
  76. package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
  77. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  79. package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
  80. package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
  81. package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
  82. package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
  83. package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
  84. package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
  85. package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
  86. package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
  87. package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
  88. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  89. package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
  90. package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
  91. package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
  92. package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
  93. package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
  94. package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
  95. package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
  96. package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
  97. package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
  98. package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
  99. package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
  100. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  101. package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
  102. package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
  103. package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
  104. package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
  105. package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
  106. package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
  107. package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
  108. package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
  109. package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
  110. package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
  111. package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
  112. package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
  113. package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
  114. package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
  115. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  116. package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
  117. package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
  118. package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
  119. package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
  120. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  121. package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
  122. package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
  123. package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
  124. package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
  125. package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
  126. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  127. package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
  128. package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
  129. package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
  130. package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
  131. package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
  132. package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
  134. package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
  135. package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
  136. package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
  137. package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
  138. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  139. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  140. package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
  141. package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
  142. package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
  143. package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
  144. package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
  145. package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
  146. package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
  147. package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
  148. package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
  149. package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
  150. package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
  151. package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
  152. package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
  153. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  154. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  155. package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
  156. package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
  157. package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
  158. package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
  159. package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
  160. package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
  161. package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
  162. package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
  163. package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
  164. package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
  165. package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
  166. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  167. package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
  168. package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
  169. package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
  170. package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
  171. package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
  172. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  173. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  174. package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
  175. package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
  176. package/node_modules/pi-lens/dist/index.js +16456 -8335
  177. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  178. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  179. package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
  180. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  181. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  182. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  183. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  184. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  185. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  186. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
  187. package/node_modules/pi-lens/docs/configuration.md +20 -1
  188. package/node_modules/pi-lens/docs/environment-variables.md +52 -2
  189. package/node_modules/pi-lens/docs/features.md +8 -4
  190. package/node_modules/pi-lens/docs/globalconfig.md +1 -0
  191. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  192. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
  193. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  194. package/node_modules/pi-lens/docs/mcp.md +21 -5
  195. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  196. package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
  197. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  198. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
  199. package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
  200. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  201. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
  202. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  203. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  204. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  205. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  206. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  207. package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
  208. package/node_modules/pi-lens/docs/settings.md +1 -0
  209. package/node_modules/pi-lens/docs/usage.md +5 -1
  210. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  211. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  212. package/node_modules/pi-lens/package.json +18 -6
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  215. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  216. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  217. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  218. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  219. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  220. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  221. package/package.json +15 -10
  222. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  223. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  224. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  225. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  226. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  227. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  228. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  229. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  230. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  231. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  232. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  233. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  234. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  235. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  236. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  237. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  238. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  239. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  240. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  241. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  242. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  243. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  244. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  245. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  246. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  247. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  248. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  249. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  250. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  251. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  252. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  253. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  254. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  255. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  256. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  257. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  258. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  259. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  260. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  261. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  262. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  263. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  264. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  265. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  266. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  267. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  268. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  269. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  270. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  271. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  272. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  273. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  274. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  275. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  276. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  277. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  278. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  279. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  280. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  281. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  282. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  283. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  284. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  285. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  286. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  287. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  288. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  289. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  290. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  291. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  292. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  293. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  294. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  295. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  296. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  297. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  298. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  299. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  300. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  301. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  302. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  303. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  304. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  305. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  306. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  307. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  308. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  309. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  310. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  311. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  312. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  313. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  314. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  315. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  316. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  317. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  318. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  319. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  320. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  321. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  322. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  323. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  324. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  325. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  326. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  327. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  328. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  329. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  330. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  331. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  332. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  333. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  334. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  335. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  336. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  337. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  338. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  339. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  340. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  341. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  342. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  343. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  344. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  345. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  346. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  347. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  348. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  349. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  350. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  351. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  352. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  353. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  354. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  355. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  356. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  357. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  358. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  359. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  360. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  361. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  362. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  363. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  364. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  365. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  366. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  367. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  368. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  369. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  370. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  371. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  372. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  373. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  374. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  375. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  376. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  377. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  378. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  379. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  380. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  381. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  382. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  383. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  384. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  385. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  386. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  406. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  407. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  408. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  409. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  410. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  411. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  412. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  413. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  414. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  415. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  416. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  417. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  418. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  419. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  420. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  421. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  422. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  423. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  424. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  425. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  426. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  427. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  428. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  429. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  430. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  431. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  432. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  433. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  434. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  435. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  436. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  437. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  438. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  439. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  440. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  441. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  442. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  443. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  444. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  445. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  446. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  447. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  448. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  449. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  450. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  451. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  452. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  453. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  454. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  455. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  456. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  457. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  458. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  459. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  460. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  461. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  462. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  463. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  464. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  465. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  466. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  467. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  468. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  469. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  470. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  471. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  472. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  473. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  474. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  475. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  476. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  477. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  478. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  479. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  480. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  481. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  482. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  483. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  484. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  485. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  486. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  487. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  488. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  489. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  490. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  491. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  492. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  493. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  494. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  495. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  496. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  497. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  498. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  499. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  500. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  501. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  502. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  503. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  504. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  505. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  506. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  507. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  508. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  509. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  510. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  511. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  512. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  513. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  514. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  515. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  516. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  517. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  518. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  519. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  520. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  521. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  522. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  523. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  524. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  525. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  526. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  527. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  528. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  529. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  530. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  531. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  532. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  533. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  534. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  535. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  536. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  537. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  538. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  539. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  540. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  541. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  542. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  543. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  544. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  545. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  546. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  547. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  548. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  549. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  550. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  551. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  552. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  553. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  554. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  555. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  556. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  557. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  558. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  559. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  560. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  561. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  562. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  563. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  564. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  565. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  566. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  567. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  568. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  569. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  570. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  571. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  572. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  573. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  574. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  575. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  576. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  577. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  578. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  579. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  580. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  581. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  582. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  583. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  584. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  585. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  586. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  587. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  588. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  589. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  590. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  591. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  592. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  593. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  594. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  595. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  596. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  597. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  598. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  599. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  600. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  601. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  602. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  603. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  604. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  605. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  606. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  607. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  608. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  609. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  610. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  611. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  612. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  613. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  614. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  615. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  616. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  617. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  618. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  619. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  620. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  621. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  622. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  623. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  624. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  625. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  626. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  627. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  628. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  629. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  630. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  631. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  632. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  633. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  634. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  635. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  636. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  637. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  638. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  639. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  640. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  641. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  642. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  643. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  644. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  645. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  646. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  647. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  648. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  649. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  650. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  651. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  652. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  653. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  654. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  655. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  656. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  657. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  658. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  659. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  660. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  661. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  662. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  663. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  664. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  665. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  666. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  667. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  668. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  669. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  670. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  671. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  672. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  673. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  674. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  675. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  676. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  677. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  678. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  679. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  680. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  681. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  682. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  683. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  684. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  685. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  686. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  687. package/node_modules/pi-lens/dist/i18n.js +0 -69
  688. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  689. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  690. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  691. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  692. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  693. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  694. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  695. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  696. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  697. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  698. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  699. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  700. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  701. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  702. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  703. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  704. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  705. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  706. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  707. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
@@ -79,6 +79,7 @@ async function main() {
79
79
  analyzeActionableWarnings(files.actionableWarnings, state),
80
80
  analyzeAstGrepTools(files.astGrepTools, state),
81
81
  analyzeWorklog(files.worklog, state),
82
+ analyzeExtension(files.extension, state),
82
83
  ]);
83
84
 
84
85
  const report = buildReport(state);
@@ -165,18 +166,34 @@ function discoverLogFiles(logRoot, archived) {
165
166
  });
166
167
 
167
168
  return {
168
- latency: byPrefix("latency"),
169
- sessionStart: byPrefix("sessionstart"),
170
- cascade: byPrefix("cascade"),
171
- readGuard: byPrefix("read-guard"),
172
- treeSitter: byPrefix("tree-sitter"),
173
- actionableWarnings: byPrefix("actionable-warnings"),
174
- astGrepTools: byPrefix("ast-grep-tools"),
169
+ latency: chronologicalFiles(byPrefix("latency")),
170
+ sessionStart: chronologicalFiles(byPrefix("sessionstart")),
171
+ cascade: chronologicalFiles(byPrefix("cascade")),
172
+ readGuard: chronologicalFiles(byPrefix("read-guard")),
173
+ treeSitter: chronologicalFiles(byPrefix("tree-sitter")),
174
+ actionableWarnings: chronologicalFiles(byPrefix("actionable-warnings")),
175
+ astGrepTools: chronologicalFiles(byPrefix("ast-grep-tools")),
176
+ extension: chronologicalFiles(byPrefix("extension")),
175
177
  diagnostics: dailyLogs,
176
178
  worklog: worklogs,
177
179
  };
178
180
  }
179
181
 
182
+ /**
183
+ * Rotated siblings (`x.log.1`) carry older rows than the active `x.log`. The
184
+ * run-splitting detectors (D3/D4/D5) and the read-guard evidence walk (D10)
185
+ * only read correctly in chronological order, so order every multi-file stream
186
+ * oldest-first with the active file last.
187
+ */
188
+ function chronologicalFiles(files) {
189
+ return [...files].sort((a, b) => {
190
+ const activeA = path.basename(a).endsWith(".log");
191
+ const activeB = path.basename(b).endsWith(".log");
192
+ if (activeA !== activeB) return activeA ? 1 : -1;
193
+ return a.localeCompare(b);
194
+ });
195
+ }
196
+
180
197
  function safeReaddir(dir) {
181
198
  try {
182
199
  return fs.readdirSync(dir);
@@ -205,6 +222,45 @@ function createState(files) {
205
222
  toolResults: counter(),
206
223
  phaseCounts: counter(),
207
224
  phaseTimeouts: counter(),
225
+ // D1: every latency row timestamp (ms) in window, for the coverage-gap
226
+ // scan. Sorted after all files are read.
227
+ rowTs: [],
228
+ cascadeTs: [],
229
+ // D2: real-log pollution — rows whose filePath points at a test
230
+ // home, grouped by pid.
231
+ scratchRows: new Map(),
232
+ // D5: test_runner_delivery outcomes per session, joined with the
233
+ // sessionstart firing/stale text into one delivery-health verdict.
234
+ testRunnerDelivery: new Map(),
235
+ // D6: `phase: knip` executed rows per pid, for the drift and cost rules.
236
+ knip: new Map(),
237
+ // D7: hook-await-exceeded overruns plus the per-pid degradation census.
238
+ hookAwaitExceeded: [],
239
+ degradationKinds: new Map(),
240
+ // D8: turn_end `tool_result` summaries per pid, and the retained-state text.
241
+ turnEndTools: new Map(),
242
+ retainingNewerTurn: [],
243
+ // D9: empty-candidate LSP waits, edit touches that saw no client, and
244
+ // warm-reuse selections, joined into the wait/contradiction verdict.
245
+ lspWaitEmpty: new Map(),
246
+ lspTouchEdit: new Map(),
247
+ lspTouchNoClients: [],
248
+ lspClientSelectedWarm: [],
249
+ // D11: read-set carry outcomes at a restart boundary.
250
+ carryRestart: [],
251
+ // D12: nudges and scope transitions, joined by pid + wall clock.
252
+ agentNudges: [],
253
+ sessionScopeTransitions: [],
254
+ // D13: deferred runner failures joined to their delivery row by (pid, turnId).
255
+ deferredRunnerFailed: [],
256
+ lateRunnerFindings: [],
257
+ // D14: auxiliary stuck pair (pid, filePath, serverId) recurrence.
258
+ auxStuck: new Map(),
259
+ // D15: advisory provenance decisions with unknown provenance.
260
+ advisoryProvenance: [],
261
+ // D8/D15: first latency row timestamp per pid (lifetime base).
262
+ pidFirstTs: new Map(),
263
+ pidLastTs: new Map(),
208
264
  workspace: {
209
265
  started: 0,
210
266
  completed: 0,
@@ -280,6 +336,24 @@ function createState(files) {
280
336
  staleRanges: [],
281
337
  zeroReads: [],
282
338
  unavailableSnapshots: [],
339
+ // E2: the block/warn split. These rows do not carry a host-side
340
+ // decision, so the analyzer must not invent one.
341
+ blocks: [],
342
+ warns: [],
343
+ blockByKind: counter(),
344
+ bypassedMismatch: 0,
345
+ // D10: per-(session,file) evidence that a read or a committed edit
346
+ // happened earlier in the session, so a later `zero_read` block is a
347
+ // lost read-set rather than a genuine cold edit.
348
+ fileEvidence: new Map(),
349
+ stateLost: [],
350
+ genuineZeroRead: [],
351
+ },
352
+ extension: {
353
+ // D2: extension.log rows whose payload names a test home
354
+ // directory, grouped by pid, plus the warn/error census.
355
+ scratchRows: new Map(),
356
+ warnErrorGroups: counter(),
283
357
  },
284
358
  treeSitter: {
285
359
  phases: counter(),
@@ -292,12 +366,26 @@ function createState(files) {
292
366
  session: {
293
367
  starts: 0,
294
368
  cwds: counter(),
369
+ // E4: per-start build identity (`commit=`), so every rate can be split
370
+ // by build. Keyed by commit, with the start timestamps that carried it.
371
+ commits: counter(),
295
372
  slowStarts: [],
296
373
  slowTasks: [],
297
374
  toolNoise: counter(),
298
375
  lspNoise: counter(),
299
376
  errors: [],
300
- rotations: counter(),
377
+ // D1: every sessionstart line timestamp (ms) in window.
378
+ rowTs: [],
379
+ // D16: `pi-lens loaded: Nms` lines, with their wall clock so a short
380
+ // lived pid can be marked.
381
+ slowLoads: [],
382
+ // D16: the wall clock of every `session_start fired`, so a load with no
383
+ // session start within 60 s (a short-lived pid) is visible.
384
+ firedTs: [],
385
+ // D3/D4/D5/D8: one run per `session_start fired`. Run splitting is the
386
+ // only way to attribute a turn-end test decision to the session that made it.
387
+ runs: [],
388
+ currentRun: newRun(),
301
389
  },
302
390
  actionable: {
303
391
  events: counter(),
@@ -329,13 +417,26 @@ function createState(files) {
329
417
  };
330
418
  }
331
419
 
420
+ /**
421
+ * The failure kind of a `failed` or `crashed` runner row. `metadata.failureKind`
422
+ * is exact: inline rows carry it (#3781) and collect-later rows too (#3796).
423
+ * Rows written by released versions never carry it, so the count heuristic
424
+ * (a failure with diagnostics is found-errors) stays only as that fallback.
425
+ */
426
+ function runnerFailureKind(entry) {
427
+ if (entry.metadata?.failureKind) return entry.metadata.failureKind;
428
+ if (entry.status === "crashed") return "crashed";
429
+ return (entry.diagnosticCount ?? 0) > 0 ? "blocking_diagnostics" : "unknown";
430
+ }
431
+
332
432
  async function analyzeLatency(files, state) {
333
433
  for (const file of files) {
334
434
  await forEachJsonLine(file, "latency", state, (entry) => {
335
435
  const ts = dateOf(entry.ts);
336
436
  if (!inWindow(ts)) return;
337
437
  state.seen.inc("latency");
338
- trackProject(state, entry.filePath);
438
+ trackLatencyProject(state, entry);
439
+ trackLatencySignals(state, entry, ts);
339
440
 
340
441
  if (entry.type === "runner") {
341
442
  const status = entry.status ?? "unknown";
@@ -344,15 +445,8 @@ async function analyzeLatency(files, state) {
344
445
  if (status === "failed" || status === "crashed") {
345
446
  // Separate a genuine runner breakage from "the check ran and found
346
447
  // blocking issues" (e.g. the LSP runner reports status:failed when a
347
- // file has type errors). Prefer the logged failureKind; fall back to
348
- // the heuristic that a failure carrying diagnostics is found-errors.
349
- const kind =
350
- entry.metadata?.failureKind ??
351
- (status === "crashed"
352
- ? "crashed"
353
- : (entry.diagnosticCount ?? 0) > 0
354
- ? "blocking_diagnostics"
355
- : "unknown");
448
+ // file has type errors).
449
+ const kind = runnerFailureKind(entry);
356
450
  state.latency.runnerFailureKinds.inc(`${runner}:${kind}`);
357
451
  if (kind === "blocking_diagnostics") {
358
452
  state.latency.runnerBlockingFindings.inc(runner);
@@ -543,6 +637,7 @@ async function analyzeCascade(files, state) {
543
637
  const ts = dateOf(entry.ts);
544
638
  if (!inWindow(ts)) return;
545
639
  state.seen.inc("cascade");
640
+ if (ts) state.latency.cascadeTs.push(ts.getTime());
546
641
  trackProject(state, entry.filePath);
547
642
  const phase = entry.phase ?? "unknown";
548
643
  state.cascade.phases.inc(phase);
@@ -589,24 +684,76 @@ async function analyzeReadGuard(files, state) {
589
684
  for (const file of files) {
590
685
  await forEachJsonLine(file, "read-guard", state, (entry) => {
591
686
  const ts = dateOf(entry.ts);
592
- if (!inWindow(ts)) return;
687
+ if (!inWindow(ts)) {
688
+ // D10 needs evidence that may precede a live-monitor window.
689
+ // Keep this lookback state private; outside rows never affect counts.
690
+ if (hasReadEvidence(entry)) {
691
+ const key = `${readGuardSessionOf(entry) ?? "?"}\u0000${entry.filePath ?? "?"}`;
692
+ state.readGuard.fileEvidence.set(key, true);
693
+ }
694
+ return;
695
+ }
593
696
  state.seen.inc("read-guard");
594
697
  trackProject(state, entry.filePath);
595
698
  const event = entry.event ?? "unknown";
699
+ const md = entry.metadata ?? {};
596
700
  state.readGuard.events.inc(event);
597
701
  state.readGuard.byFile.inc(shortPath(entry.filePath));
598
- const reasonKind = entry.metadata?.reasonKind;
702
+ const reasonKind = md.reasonKind;
599
703
  if (reasonKind) state.readGuard.byReason.inc(reasonKind);
704
+
705
+ // D10: evidence that this file was read or edited earlier in the
706
+ // SAME session. Read before this row's own evidence is recorded, so a
707
+ // later `zero_read` block sees only the prior rows. An
708
+ // `edit_batch_summary` carries no sessionId, so its session is the
709
+ // turnId prefix before the last `:`.
710
+ const evidenceKey = `${readGuardSessionOf(entry) ?? "?"}\u0000${entry.filePath ?? "?"}`;
711
+ const hadEvidence =
712
+ state.readGuard.fileEvidence.get(evidenceKey) === true;
713
+
600
714
  if (event === "edit_preflight_blocked") {
601
715
  state.readGuard.preflightReasons.inc(reasonKind ?? "unknown");
716
+ registerBlock(state, entry, "preflight");
717
+ }
718
+ if (event === "edit_blocked") {
719
+ const summary = registerBlock(state, entry, "blocked");
720
+ if (reasonKind === "zero_read") {
721
+ pushTop(state.readGuard.zeroReads, summary, limit * 3, byLine);
722
+ if (hadEvidence) {
723
+ pushTop(
724
+ state.readGuard.stateLost,
725
+ { ...summary, classification: "state-lost" },
726
+ limit * 3,
727
+ byLine,
728
+ );
729
+ } else {
730
+ pushTop(
731
+ state.readGuard.genuineZeroRead,
732
+ { ...summary, classification: "genuine" },
733
+ limit * 3,
734
+ byLine,
735
+ );
736
+ }
737
+ }
738
+ }
739
+ if (event === "edit_warned") {
740
+ pushTop(
741
+ state.readGuard.warns,
742
+ summarizeReadGuard(entry),
743
+ limit * 3,
744
+ byLine,
745
+ );
602
746
  }
603
747
  if (event === "range_snapshot_validation") {
604
- const status = entry.metadata?.status ?? "unknown";
748
+ const status = md.status ?? "unknown";
749
+ const outcome = String(md.outcome ?? "");
605
750
  state.readGuard.snapshotStatus.inc(status);
606
751
  state.readGuard.snapshotEnforcement.inc(
607
- entry.metadata?.enforced ? "enforced" : "not_enforced",
752
+ md.enforced ? "enforced" : "not_enforced",
608
753
  );
609
- if (status === "mismatch") {
754
+ // E3: `bypassed-content-match` is an allowed edit, not a stale
755
+ // read. Only a mismatch the caller did NOT bypass is a smell.
756
+ if (status === "mismatch" && !outcome.startsWith("bypassed")) {
610
757
  state.smellTotals.inc("read-guard-stale-ranges");
611
758
  pushTop(
612
759
  state.readGuard.staleRanges,
@@ -614,6 +761,8 @@ async function analyzeReadGuard(files, state) {
614
761
  limit * 3,
615
762
  byLine,
616
763
  );
764
+ } else if (status === "mismatch") {
765
+ state.readGuard.bypassedMismatch += 1;
617
766
  } else if (status === "unavailable") {
618
767
  pushTop(
619
768
  state.readGuard.unavailableSnapshots,
@@ -623,21 +772,11 @@ async function analyzeReadGuard(files, state) {
623
772
  );
624
773
  }
625
774
  }
626
- if (event === "edit_blocked" || event === "edit_warned") {
627
- state.smellTotals.inc("read-guard-friction");
628
- const summary = summarizeReadGuard(entry);
629
- pushTop(state.readGuard.blocked, summary, limit * 3, byLine);
630
- if (reasonKind === "zero_read") {
631
- pushTop(state.readGuard.zeroReads, summary, limit * 3, byLine);
632
- }
633
- }
634
775
  if (
635
776
  event === "oldtext_not_found" ||
636
777
  event === "oldtext_duplicate" ||
637
- event === "touched_lines_missing" ||
638
- event === "edit_preflight_blocked"
778
+ event === "touched_lines_missing"
639
779
  ) {
640
- state.smellTotals.inc("read-guard-friction");
641
780
  pushTop(
642
781
  state.readGuard.oldTextIssues,
643
782
  summarizeReadGuard(entry),
@@ -645,10 +784,48 @@ async function analyzeReadGuard(files, state) {
645
784
  byLine,
646
785
  );
647
786
  }
787
+
788
+ if (hasReadEvidence(entry))
789
+ state.readGuard.fileEvidence.set(evidenceKey, true);
648
790
  });
649
791
  }
650
792
  }
651
793
 
794
+ /** D10: a row proving the file was read or edited earlier in its session. */
795
+ function hasReadEvidence(entry) {
796
+ const md = entry.metadata ?? {};
797
+ return (
798
+ entry.event === "edit_batch_summary" ||
799
+ (entry.event === "range_snapshot_validation" &&
800
+ Number(md.candidateReadCount ?? 0) > 0) ||
801
+ (entry.event === "edit_warned" && Number(md.readCount ?? 0) > 0)
802
+ );
803
+ }
804
+
805
+ /**
806
+ * D10: the session a read-guard row belongs to. `edit_batch_summary` rows carry
807
+ * no `sessionId`, so fall back to the turnId prefix before the last `:` (the
808
+ * report's own join instruction).
809
+ */
810
+ function readGuardSessionOf(entry) {
811
+ if (typeof entry.sessionId === "string" && entry.sessionId)
812
+ return entry.sessionId;
813
+ const turnId = entry.turnId;
814
+ if (typeof turnId === "string" && turnId.includes(":"))
815
+ return turnId.slice(0, turnId.lastIndexOf(":"));
816
+ return null;
817
+ }
818
+
819
+ /** E2: one blocked edit. Host/model provenance is not present on this row. */
820
+ function registerBlock(state, entry, source) {
821
+ const summary = summarizeReadGuard(entry);
822
+ pushTop(state.readGuard.blocks, summary, limit * 3, byLine);
823
+ state.readGuard.blockByKind.inc(
824
+ `${source}:${entry.metadata?.reasonKind ?? "unknown"}`,
825
+ );
826
+ return summary;
827
+ }
828
+
652
829
  async function analyzeTreeSitter(files, state) {
653
830
  for (const file of files) {
654
831
  await forEachJsonLine(file, "tree-sitter", state, (entry) => {
@@ -704,21 +881,48 @@ async function analyzeSessionStart(files, state) {
704
881
  const match = lineRe.exec(line);
705
882
  if (!match) return;
706
883
  const ts = dateOf(match[1]);
707
- if (!inWindow(ts)) return;
708
884
  const message = match[2];
885
+ if (!inWindow(ts)) {
886
+ // A --since window can begin after the session_start that owns its
887
+ // rows. Carry only that run anchor into the visible window.
888
+ if (message.startsWith("session_start fired")) {
889
+ state.session.currentRun = newRun();
890
+ state.session.currentRun.startTs = iso(ts);
891
+ }
892
+ return;
893
+ }
709
894
  if (isExcludedText(message)) {
710
895
  state.excludedRows++;
711
896
  return;
712
897
  }
713
898
  state.seen.inc("sessionstart");
899
+ if (ts) state.session.rowTs.push(ts.getTime());
900
+
901
+ // E4: starts are counted from `session_start fired`, which every mode
902
+ // writes, instead of the dead `session_start cwd:` full-mode line. The
903
+ // same line opens a D3/D4/D5/D8 run.
904
+ if (message.startsWith("session_start fired")) {
905
+ state.session.starts++;
906
+ if (ts) state.session.firedTs.push(ts.getTime());
907
+ state.session.runs.push(state.session.currentRun);
908
+ state.session.currentRun = newRun();
909
+ state.session.currentRun.startTs = iso(ts);
910
+ }
714
911
 
715
912
  const cwd = /session_start cwd:\s*(.*)$/.exec(message)?.[1];
716
913
  if (cwd) {
717
- state.session.starts++;
718
914
  state.session.cwds.inc(projectOf(cwd));
719
915
  trackProject(state, cwd);
720
916
  }
721
917
 
918
+ // E4: build identity for the session that just started.
919
+ const buildIdentity =
920
+ /session_start: build identity [—-] commit=(\w+)/.exec(message);
921
+ if (buildIdentity) {
922
+ state.session.commits.inc(buildIdentity[1]);
923
+ state.session.currentRun.commit = buildIdentity[1];
924
+ }
925
+
722
926
  // #2526 R3 S1: this session's config-resolution PENDING mark, published
723
927
  // by `loadLSPConfig` itself at the instant a resolution is actually
724
928
  // attempted — never a start-line prediction from `no-lsp`/subagent/
@@ -780,8 +984,49 @@ async function analyzeSessionStart(files, state) {
780
984
  );
781
985
  }
782
986
 
783
- if (message.includes("log_cleanup: rotated"))
784
- state.session.rotations.inc(message.replace(/^log_cleanup:\s*/, ""));
987
+ // D16: extension load time is invisible to `slow-session-start`, which
988
+ // reads `session_start total:` (a quick-mode total of tens of ms).
989
+ const load = /pi-lens loaded: (\d+)ms after process start/.exec(message);
990
+ if (load) {
991
+ state.session.slowLoads.push({
992
+ ts: iso(ts),
993
+ ms: ts ? ts.getTime() : null,
994
+ durationMs: Number(load[1]),
995
+ message,
996
+ });
997
+ }
998
+
999
+ const run = state.session.currentRun;
1000
+ const modified = /turn_end: (\d+) file\(s\) modified/.exec(message);
1001
+ if (modified) run.edits += 1;
1002
+ if (message.includes("excluded by the built-in turn-end policy")) {
1003
+ run.excluded += 1;
1004
+ const excludedPath = /turn_end:\s*(\S+)\s+→ test target excluded/.exec(
1005
+ message,
1006
+ )?.[1];
1007
+ if (excludedPath) run.excludedTestFiles.push(excludedPath);
1008
+ }
1009
+ if (message.includes("→ no test file found")) run.noTestFile += 1;
1010
+ const vitest =
1011
+ /^turn_end:\s+(.+?)\s+→\s+test vitest\s+(\S+)\s+\(([^)]+)\)/.exec(
1012
+ message,
1013
+ );
1014
+ if (vitest) {
1015
+ run.ran += 1;
1016
+ const [, src, tgt, mode] = vitest;
1017
+ if (mode.includes("failed-first")) {
1018
+ run.failedFirst += 1;
1019
+ const a = checkoutOf(src);
1020
+ const b = checkoutOf(tgt);
1021
+ if (a && b && a !== b) run.crossCheckout += 1;
1022
+ }
1023
+ }
1024
+ if (/turn_end: firing \d+ test target\(s\) async/.test(message))
1025
+ run.testFirings += 1;
1026
+ if (/\(stale\s*[—-]\s*turn advanced while tests ran\)/.test(message))
1027
+ run.testStale += 1;
1028
+ if (/turn_end: retaining newer turn state/.test(message))
1029
+ run.retainingNewerTurn += 1;
785
1030
 
786
1031
  const lower = message.toLowerCase();
787
1032
  if (
@@ -792,8 +1037,16 @@ async function analyzeSessionStart(files, state) {
792
1037
  state.smellTotals.inc("tool-install-noise");
793
1038
  state.session.toolNoise.inc(normalizeSessionNoise(message));
794
1039
  }
1040
+ // E1: anchor to the production failure emitters so a worktree name
1041
+ // such as `468-wait-timeout` inside a `cwd=`/`command=` token cannot
1042
+ // match (the 20 false positives in the live session). Every emitter
1043
+ // puts the failure word before any `key=` token: `lsp spawn <id>:
1044
+ // unavailable|failed` (clients/lsp/index.ts) and `lsp launch
1045
+ // candidate|managed|bundle|tree-bin failed` (clients/lsp/server.ts).
1046
+ // `lsp read warm unavailable` is informational, and `lsp process
1047
+ // <cmd>:` only ever logs `spawn-error` or `closed` (clients/lsp/launch.ts).
795
1048
  if (
796
- /lsp .*?(unavailable|failed|timeout|skipped_broken|exited immediately|binary not found)/i.test(
1049
+ /^lsp (?:spawn [^:]+: (?:unavailable|failed)|launch (?:candidate|managed|bundle|tree-bin) failed)/.test(
797
1050
  message,
798
1051
  )
799
1052
  ) {
@@ -814,6 +1067,9 @@ async function analyzeSessionStart(files, state) {
814
1067
  }
815
1068
  });
816
1069
  }
1070
+ // Close the final run so the last `session_start fired` block is analyzed.
1071
+ if (state.session.currentRun.startTs)
1072
+ state.session.runs.push(state.session.currentRun);
817
1073
  }
818
1074
 
819
1075
  async function analyzeActionableWarnings(files, state) {
@@ -984,12 +1240,18 @@ function pathValues(value, key = "") {
984
1240
  }
985
1241
 
986
1242
  function isExcludedText(text) {
1243
+ // E4: the old match only ever saw the dead `session_start cwd:` line. The
1244
+ // live cwd/root carriers are the pending-resolution mark and the spawn lines.
1245
+ const values = [];
987
1246
  const cwd = /session_start cwd:\s*(.*)$/.exec(text)?.[1];
988
- return cwd
989
- ? excludeGlobs.some((glob) =>
990
- minimatch(normalizePath(cwd), glob, { nocase: true, dot: true }),
991
- )
992
- : false;
1247
+ if (cwd) values.push(cwd);
1248
+ for (const match of text.matchAll(/\b(?:cwd|root)=(\S+)/g))
1249
+ values.push(match[1]);
1250
+ return values.some((value) =>
1251
+ excludeGlobs.some((glob) =>
1252
+ minimatch(normalizePath(value), glob, { nocase: true, dot: true }),
1253
+ ),
1254
+ );
993
1255
  }
994
1256
 
995
1257
  async function forEachLine(file, visitor) {
@@ -1200,19 +1462,18 @@ function summarizeCascade(entry) {
1200
1462
  }
1201
1463
 
1202
1464
  function summarizeReadGuard(entry) {
1465
+ const md = entry.metadata ?? {};
1466
+ // R2: the old offset/limit/symbol fields were undefined on 100% of rows.
1467
+ // `touchedLines[0]`/`range[0]` are the real line identity the rows carry.
1468
+ const touched = Array.isArray(md.touchedLines) ? md.touchedLines : [];
1469
+ const range = Array.isArray(md.range) ? md.range : [];
1203
1470
  return {
1204
1471
  ts: entry.ts,
1205
1472
  event: entry.event,
1206
1473
  filePath: shortPath(entry.filePath),
1207
1474
  project: projectOf(entry.filePath),
1208
- requestedOffset: entry.requestedOffset,
1209
- requestedLimit: entry.requestedLimit,
1210
- effectiveOffset: entry.effectiveOffset,
1211
- effectiveLimit: entry.effectiveLimit,
1212
- symbol: entry.symbol,
1213
- symbolKind: entry.symbolKind,
1214
- line: entry.symbolStartLine,
1215
- metadata: entry.metadata,
1475
+ line: touched[0] ?? range[0],
1476
+ metadata: md,
1216
1477
  };
1217
1478
  }
1218
1479
 
@@ -1295,9 +1556,613 @@ function normalizePath(filePath) {
1295
1556
  return String(filePath).replace(/\\/g, "/");
1296
1557
  }
1297
1558
 
1559
+ /** One D3/D4/D5/D8 run, opened by a `session_start fired` line. */
1560
+ function newRun() {
1561
+ return {
1562
+ startTs: null,
1563
+ commit: null,
1564
+ edits: 0,
1565
+ ran: 0,
1566
+ failedFirst: 0,
1567
+ crossCheckout: 0,
1568
+ excluded: 0,
1569
+ excludedTestFiles: [],
1570
+ noTestFile: 0,
1571
+ retainingNewerTurn: 0,
1572
+ testFirings: 0,
1573
+ testStale: 0,
1574
+ };
1575
+ }
1576
+
1577
+ /** The checkout a turn-end path belongs to (`.worktrees/<id>/` or the main `src/`). */
1578
+ function checkoutOf(p) {
1579
+ const m = /\.worktrees\/([^/]+)\//.exec(p);
1580
+ if (m) return m[1];
1581
+ if (/^(?:src|tests?)\//.test(p)) return "main";
1582
+ return null;
1583
+ }
1584
+
1585
+ /**
1586
+ * D2: the markers only a test home carries: the #3521 fork-tree witness home
1587
+ * and the suite's `pi-lens-test-*` temp dirs. `pi-lens-worktrees`,
1588
+ * `pi-lens-orchestrator/tmp` and `.probe-home/` are not markers: real
1589
+ * sessions edit there (the orchestrator writes its task files under
1590
+ * `.probe-home/orchestration/`).
1591
+ */
1592
+ const SCRATCH_PATH_RE = /(witness-home|pi-lens-test-)/;
1593
+
1594
+ /**
1595
+ * E5/D2: the `opaque_mutation_*` phases log the bash command as `filePath`
1596
+ * (clients/runtime-tool-call.ts, clients/runtime-tool-result.ts), except
1597
+ * `opaque_mutation_recovered`, which logs the recovered paths.
1598
+ */
1599
+ function isCommandTextRow(entry) {
1600
+ const phase = String(entry.phase ?? "");
1601
+ return (
1602
+ phase.startsWith("opaque_mutation_") &&
1603
+ phase !== "opaque_mutation_recovered"
1604
+ );
1605
+ }
1606
+
1607
+ /** E5: track a latency row's project unless its `filePath` is a shell command. */
1608
+ function trackLatencyProject(state, entry) {
1609
+ const filePath = entry?.filePath;
1610
+ if (typeof filePath !== "string" || filePath.length === 0) return;
1611
+ if (isCommandTextRow(entry)) return;
1612
+ // The `<pi-lens>` sentinel is never a project path.
1613
+ if (filePath.startsWith("<")) return;
1614
+ state.projects.inc(projectOf(filePath));
1615
+ }
1616
+
1617
+ function trackLatencyPollution(state, entry) {
1618
+ if (isCommandTextRow(entry)) return;
1619
+ const hit = pathValues(entry).some((value) => SCRATCH_PATH_RE.test(value));
1620
+ if (!hit) return;
1621
+ const pid = String(entry.pid ?? "unknown");
1622
+ state.latency.scratchRows.set(
1623
+ pid,
1624
+ (state.latency.scratchRows.get(pid) ?? 0) + 1,
1625
+ );
1626
+ }
1627
+
1628
+ /**
1629
+ * Every latency signal the D5/D6/D7/D9/D11-D15 detectors read. Kept in one
1630
+ * function so the streaming visitor stays a thin fan-out.
1631
+ */
1632
+ function trackLatencySignals(state, entry, ts) {
1633
+ const ms = ts instanceof Date ? ts.getTime() : null;
1634
+ if (ms != null && Number.isFinite(ms)) state.latency.rowTs.push(ms);
1635
+ const pid = String(entry.pid ?? "unknown");
1636
+ if (ms != null && Number.isFinite(ms)) {
1637
+ const first = state.latency.pidFirstTs.get(pid);
1638
+ if (first == null || ms < first) state.latency.pidFirstTs.set(pid, ms);
1639
+ const last = state.latency.pidLastTs.get(pid);
1640
+ if (last == null || ms > last) state.latency.pidLastTs.set(pid, ms);
1641
+ }
1642
+ trackLatencyPollution(state, entry);
1643
+
1644
+ if (entry.type === "runner") {
1645
+ const md = entry.metadata ?? {};
1646
+ if (
1647
+ entry.status === "failed" &&
1648
+ md.tier === "collect-later" &&
1649
+ Number(entry.diagnosticCount ?? 0) > 0 &&
1650
+ runnerFailureKind(entry) === "blocking_diagnostics"
1651
+ ) {
1652
+ state.latency.deferredRunnerFailed.push({
1653
+ ts: entry.ts,
1654
+ ms,
1655
+ pid,
1656
+ turnId: entry.turnId,
1657
+ runnerId: entry.runnerId,
1658
+ diagnosticCount: Number(entry.diagnosticCount ?? 0),
1659
+ filePath: entry.filePath,
1660
+ });
1661
+ }
1662
+ return;
1663
+ }
1664
+
1665
+ if (entry.type === "tool_result") {
1666
+ // D8: the turn_end summary row carries `blockerSections`; the existing
1667
+ // tool_result branch only counts `result`, so it never saw this duration.
1668
+ if (entry.metadata && entry.metadata.blockerSections != null) {
1669
+ const rows = state.latency.turnEndTools.get(pid) ?? [];
1670
+ rows.push({
1671
+ ts: entry.ts,
1672
+ ms,
1673
+ durationMs: Number(entry.durationMs ?? 0),
1674
+ });
1675
+ state.latency.turnEndTools.set(pid, rows);
1676
+ }
1677
+ return;
1678
+ }
1679
+ if (entry.type !== "phase") return;
1680
+
1681
+ const phase = entry.phase ?? "unknown";
1682
+ const md = entry.metadata ?? {};
1683
+ const nowIso = entry.ts;
1684
+
1685
+ if (phase === "test_runner_delivery") {
1686
+ const sid = md.sessionId ?? pid;
1687
+ const c = state.latency.testRunnerDelivery.get(sid) ?? counter();
1688
+ c.inc(md.outcome ?? "unknown");
1689
+ state.latency.testRunnerDelivery.set(sid, c);
1690
+ } else if (phase === "knip") {
1691
+ if (
1692
+ md.execution === "executed" &&
1693
+ Number.isFinite(Number(md.totalIssues))
1694
+ ) {
1695
+ const rows = state.latency.knip.get(pid) ?? [];
1696
+ rows.push({
1697
+ ts: nowIso,
1698
+ ms,
1699
+ durationMs: Number(entry.durationMs ?? 0),
1700
+ totalIssues: Number(md.totalIssues),
1701
+ });
1702
+ state.latency.knip.set(pid, rows);
1703
+ }
1704
+ } else if (phase === "degradation_ledger") {
1705
+ const kind = md.kind ?? "unknown";
1706
+ const perPid = state.latency.degradationKinds.get(pid) ?? new Map();
1707
+ perPid.set(kind, Math.max(perPid.get(kind) ?? 0, Number(md.count ?? 0)));
1708
+ state.latency.degradationKinds.set(pid, perPid);
1709
+ if (kind === "hook-await-exceeded") {
1710
+ const budgetMs = Number(md.budgetMs);
1711
+ const elapsedMs = Number(md.elapsedMs);
1712
+ state.latency.hookAwaitExceeded.push({
1713
+ ts: nowIso,
1714
+ pid,
1715
+ hook: md.hook,
1716
+ label: md.label,
1717
+ budgetMs,
1718
+ elapsedMs,
1719
+ cause: md.cause,
1720
+ ratio: budgetMs > 0 ? elapsedMs / budgetMs : 0,
1721
+ });
1722
+ }
1723
+ } else if (phase === "lsp_client_wait_timeout") {
1724
+ const serverIds = Array.isArray(md.serverIds) ? md.serverIds : [];
1725
+ if (serverIds.length === 0) {
1726
+ const cur = state.latency.lspWaitEmpty.get(pid) ?? { ms: 0, rows: 0 };
1727
+ cur.ms += Number(entry.durationMs ?? 0);
1728
+ cur.rows += 1;
1729
+ state.latency.lspWaitEmpty.set(pid, cur);
1730
+ }
1731
+ } else if (phase === "lsp_touch_file") {
1732
+ const cur = state.latency.lspTouchEdit.get(pid) ?? {
1733
+ rows: 0,
1734
+ noClients: 0,
1735
+ };
1736
+ if (md.source === "tool_call:edit") {
1737
+ cur.rows += 1;
1738
+ if (md.failureKind === "no_clients_none_spawning") cur.noClients += 1;
1739
+ }
1740
+ state.latency.lspTouchEdit.set(pid, cur);
1741
+ if (
1742
+ md.source === "tool_call:edit" &&
1743
+ md.failureKind === "no_clients_none_spawning"
1744
+ ) {
1745
+ state.latency.lspTouchNoClients.push({
1746
+ ts: nowIso,
1747
+ ms,
1748
+ pid,
1749
+ filePath: entry.filePath,
1750
+ });
1751
+ }
1752
+ } else if (phase === "lsp_client_selected") {
1753
+ if (md.outcome === "warm-reuse") {
1754
+ state.latency.lspClientSelectedWarm.push({
1755
+ ts: nowIso,
1756
+ ms,
1757
+ pid,
1758
+ filePath: entry.filePath,
1759
+ });
1760
+ }
1761
+ } else if (phase === "read_guard_branch_retained") {
1762
+ state.latency.carryRestart.push({
1763
+ ts: nowIso,
1764
+ pid,
1765
+ trigger: md.trigger,
1766
+ source: md.source,
1767
+ kept: Number(md.kept ?? 0),
1768
+ dropped: Number(md.dropped ?? 0),
1769
+ branchToolResults: Number(md.branchToolResults ?? 0),
1770
+ branchReadable: md.branchReadable === true,
1771
+ });
1772
+ } else if (phase === "agent_nudge") {
1773
+ state.latency.agentNudges.push({
1774
+ ts: nowIso,
1775
+ ms,
1776
+ pid,
1777
+ originCrossProcess: Number(md.originCrossProcess ?? 0),
1778
+ originLocal: Number(md.originLocal ?? 0),
1779
+ reasonMix: md.reasonMix,
1780
+ });
1781
+ } else if (phase === "session_scope_transition") {
1782
+ state.latency.sessionScopeTransitions.push({
1783
+ ts: nowIso,
1784
+ ms,
1785
+ pid,
1786
+ handoffSource: md.handoffSource,
1787
+ sessionId: md.sessionId,
1788
+ });
1789
+ } else if (phase === "late_runner_findings") {
1790
+ state.latency.lateRunnerFindings.push({
1791
+ ts: nowIso,
1792
+ ms,
1793
+ pid,
1794
+ turnId: entry.turnId,
1795
+ failed: Number(md.failed ?? 0),
1796
+ delivered: Number(md.delivered ?? 0),
1797
+ dropped: Number(md.dropped ?? 0),
1798
+ pending: Number(md.pending ?? 0),
1799
+ });
1800
+ } else if (phase === "late_auxiliary_findings") {
1801
+ const pairs = Array.isArray(md.stuckPairs) ? md.stuckPairs : [];
1802
+ for (const pair of pairs) {
1803
+ if (!pair?.filePath || !pair?.serverId) continue;
1804
+ const key = `${pid}\u0000${pair.filePath}\u0000${pair.serverId}`;
1805
+ state.latency.auxStuck.set(
1806
+ key,
1807
+ (state.latency.auxStuck.get(key) ?? 0) + 1,
1808
+ );
1809
+ }
1810
+ } else if (phase === "advisory_provenance_decision") {
1811
+ const reasons = Array.isArray(md.reasons) ? md.reasons : [];
1812
+ // The malformed reason implies decision "historical": the validator
1813
+ // returns status "unknown" with it (clients/advisory-provenance.ts) and
1814
+ // the row logs every non-current status as historical
1815
+ // (clients/runtime-context.ts), so the reason alone is the rule.
1816
+ if (reasons.includes("malformed-or-legacy-provenance")) {
1817
+ state.latency.advisoryProvenance.push({
1818
+ ts: nowIso,
1819
+ ms,
1820
+ pid,
1821
+ reasons,
1822
+ provenanceStamp: md.provenanceStamp,
1823
+ advisoryKind: md.advisoryKind,
1824
+ });
1825
+ }
1826
+ }
1827
+ }
1828
+
1829
+ /** D2: extension.log rows that name a test home. */
1830
+ async function analyzeExtension(files, state) {
1831
+ for (const file of files) {
1832
+ await forEachJsonLine(file, "extension", state, (entry) => {
1833
+ const ts = dateOf(entry.ts);
1834
+ if (!inWindow(ts)) return;
1835
+ state.seen.inc("extension");
1836
+ if (SCRATCH_PATH_RE.test(JSON.stringify(entry))) {
1837
+ const pid = String(entry.pid ?? "unknown");
1838
+ state.extension.scratchRows.set(
1839
+ pid,
1840
+ (state.extension.scratchRows.get(pid) ?? 0) + 1,
1841
+ );
1842
+ }
1843
+ const level = String(entry.level ?? "").toLowerCase();
1844
+ if (level === "warn" || level === "error") {
1845
+ const message = String(entry.message ?? "").slice(0, 80);
1846
+ state.extension.warnErrorGroups.inc(
1847
+ `${entry.subsystem ?? "unknown"}: ${message}`,
1848
+ );
1849
+ }
1850
+ });
1851
+ }
1852
+ }
1853
+
1854
+ function countBetween(sorted, a, b) {
1855
+ let n = 0;
1856
+ for (const value of sorted) {
1857
+ if (value > a && value < b) n += 1;
1858
+ else if (value >= b) break;
1859
+ }
1860
+ return n;
1861
+ }
1862
+
1863
+ /** D1: latency gaps >= 10 min that a live session filled with >= 20 rows. */
1864
+ function computeCoverageGaps(state) {
1865
+ const lat = [...state.latency.rowTs].sort((a, b) => a - b);
1866
+ const ss = [...state.session.rowTs].sort((a, b) => a - b);
1867
+ const cas = [...state.latency.cascadeTs].sort((a, b) => a - b);
1868
+ const gaps = [];
1869
+ for (let i = 1; i < lat.length; i += 1) {
1870
+ const a = lat[i - 1];
1871
+ const b = lat[i];
1872
+ const minutes = (b - a) / 60000;
1873
+ if (minutes < 10) continue;
1874
+ const sessionstartRows = countBetween(ss, a, b);
1875
+ if (sessionstartRows < 20) continue;
1876
+ gaps.push({
1877
+ start: new Date(a).toISOString(),
1878
+ end: new Date(b).toISOString(),
1879
+ minutes: Math.round(minutes),
1880
+ sessionstartRows,
1881
+ cascadeRows: countBetween(cas, a, b),
1882
+ });
1883
+ }
1884
+ return gaps;
1885
+ }
1886
+
1887
+ /** D5: per-session firing/stale text and per-session delivery outcomes. */
1888
+ function computeTestRunnerHealth(state) {
1889
+ const runs = state.session.runs.map((r) => ({
1890
+ startTs: r.startTs,
1891
+ firings: r.testFirings,
1892
+ stale: r.testStale,
1893
+ }));
1894
+ const staleRuns = runs.filter(
1895
+ (r) => r.firings >= 10 && r.stale / r.firings >= 0.5,
1896
+ );
1897
+ const firings = runs.reduce((n, r) => n + r.firings, 0);
1898
+ const stale = runs.reduce((n, r) => n + r.stale, 0);
1899
+ const sessions = [];
1900
+ for (const [sessionId, c] of state.latency.testRunnerDelivery) {
1901
+ const total = c.entries().reduce((n, [, v]) => n + v, 0);
1902
+ const delivered = c.get("delivered");
1903
+ sessions.push({
1904
+ sessionId,
1905
+ total,
1906
+ staged: c.get("staged"),
1907
+ eligible: c.get("eligible"),
1908
+ delivered,
1909
+ superseded: c.get("superseded"),
1910
+ deliveredShare: total ? delivered / total : 0,
1911
+ });
1912
+ }
1913
+ const lowDelivery = sessions.filter(
1914
+ (s) => s.total >= 10 && s.deliveredShare < 0.25,
1915
+ );
1916
+ return {
1917
+ firings,
1918
+ stale,
1919
+ staleShare: firings ? stale / firings : 0,
1920
+ staleRuns,
1921
+ sessions,
1922
+ lowDelivery,
1923
+ };
1924
+ }
1925
+
1926
+ /** D6: scanner issue-count drift and turn-end knip wall cost, per pid. */
1927
+ function computeKnip(state) {
1928
+ const drift = [];
1929
+ const cost = [];
1930
+ for (const [pid, rowsRaw] of state.latency.knip) {
1931
+ const rows = [...rowsRaw]
1932
+ .filter((r) => r.ms != null)
1933
+ .sort((a, b) => a.ms - b.ms);
1934
+ if (rows.length >= 10) {
1935
+ const issues = rows.map((r) => r.totalIssues);
1936
+ let increments = 0;
1937
+ for (let i = 1; i < issues.length; i += 1)
1938
+ if (issues[i] - issues[i - 1] >= 100) increments += 1;
1939
+ const min = Math.min(...issues);
1940
+ const max = Math.max(...issues);
1941
+ const spread = min > 0 ? (max - min) / min : 0;
1942
+ if (increments >= 5 || spread >= 0.25)
1943
+ drift.push({ pid, rows: rows.length, increments, min, max, spread });
1944
+ }
1945
+ const first = state.latency.pidFirstTs.get(pid);
1946
+ const last = state.latency.pidLastTs.get(pid);
1947
+ const hours = first != null && last != null ? (last - first) / 3600000 : 0;
1948
+ const totalMs = rows.reduce((n, r) => n + r.durationMs, 0);
1949
+ const maxRow = rows.reduce((n, r) => Math.max(n, r.durationMs), 0);
1950
+ // A lifetime under an hour counts as one hour, so a short pid's few
1951
+ // seconds of knip cannot extrapolate to a large hourly rate.
1952
+ const perHour = totalMs / Math.max(hours, 1);
1953
+ if (perHour >= 30000 || maxRow >= 5000)
1954
+ cost.push({ pid, rows: rows.length, totalMs, hours, perHour, maxRow });
1955
+ }
1956
+ return { drift, cost };
1957
+ }
1958
+
1959
+ /** D7: hook deopts that overran their budget, plus the degradation census. */
1960
+ function computeHookAwait(state) {
1961
+ const rows = [...state.latency.hookAwaitExceeded].sort((a, b) =>
1962
+ String(b.ts).localeCompare(String(a.ts)),
1963
+ );
1964
+ const census = [];
1965
+ for (const [pid, kinds] of state.latency.degradationKinds) {
1966
+ census.push({
1967
+ pid,
1968
+ kinds: [...kinds.entries()]
1969
+ .sort((a, b) => b[1] - a[1])
1970
+ .slice(0, 12)
1971
+ .map(([kind, count]) => `${kind}=${count}`),
1972
+ });
1973
+ }
1974
+ return { rows, over: rows.filter((r) => r.ratio >= 1.1).length, census };
1975
+ }
1976
+
1977
+ /** D8: turn_end hook duration per pid, and the retained-turn-state runs. */
1978
+ function computeTurnEndSlow(state) {
1979
+ const flagged = [];
1980
+ for (const [pid, rows] of state.latency.turnEndTools) {
1981
+ const slow = rows.filter((r) => r.durationMs > 3000).length;
1982
+ const max = rows.reduce((n, r) => Math.max(n, r.durationMs), 0);
1983
+ const share = rows.length ? slow / rows.length : 0;
1984
+ if ((rows.length >= 20 && share >= 0.1) || max >= 8000)
1985
+ flagged.push({ pid, rows: rows.length, slow, share, max });
1986
+ }
1987
+ const retainRuns = state.session.runs
1988
+ .filter((r) => r.retainingNewerTurn >= 5)
1989
+ .map((r) => ({ start: r.startTs, count: r.retainingNewerTurn }));
1990
+ return { flagged, retainRuns };
1991
+ }
1992
+
1993
+ /** D9: empty-candidate waits, edit touches with no client, warm-reuse clashes. */
1994
+ function computeLspWait(state) {
1995
+ const empty = [];
1996
+ for (const [pid, cur] of state.latency.lspWaitEmpty)
1997
+ if (cur.ms >= 5000) empty.push({ pid, ms: cur.ms, rows: cur.rows });
1998
+ const noClients = [];
1999
+ for (const [pid, cur] of state.latency.lspTouchEdit)
2000
+ if (cur.rows >= 20 && cur.noClients / cur.rows >= 0.2)
2001
+ noClients.push({
2002
+ pid,
2003
+ rows: cur.rows,
2004
+ noClients: cur.noClients,
2005
+ share: cur.noClients / cur.rows,
2006
+ });
2007
+ const contradictions = [];
2008
+ for (const touch of state.latency.lspTouchNoClients) {
2009
+ const match = state.latency.lspClientSelectedWarm.find(
2010
+ (w) =>
2011
+ w.pid === touch.pid &&
2012
+ w.filePath === touch.filePath &&
2013
+ touch.ms != null &&
2014
+ w.ms != null &&
2015
+ Math.abs(w.ms - touch.ms) <= 500,
2016
+ );
2017
+ if (match)
2018
+ contradictions.push({
2019
+ pid: touch.pid,
2020
+ filePath: touch.filePath,
2021
+ touchTs: touch.ts,
2022
+ selectedTs: match.ts,
2023
+ });
2024
+ }
2025
+ const pids = new Set([
2026
+ ...empty.map((e) => e.pid),
2027
+ ...noClients.map((e) => e.pid),
2028
+ ...contradictions.map((c) => c.pid),
2029
+ ]);
2030
+ return { empty, noClients, contradictions, pidCount: pids.size };
2031
+ }
2032
+
2033
+ /** D11: a restart kept nothing while the branch held a populated read set. */
2034
+ function computeCarryRestart(state) {
2035
+ return state.latency.carryRestart.filter(
2036
+ (r) =>
2037
+ ["startup", "resume", "fork", "reload"].includes(r.trigger) &&
2038
+ r.source !== "none" &&
2039
+ r.kept + r.dropped === 0 &&
2040
+ r.branchToolResults >= 50 &&
2041
+ r.branchReadable,
2042
+ );
2043
+ }
2044
+
2045
+ /** D12: a cross-process nudge right after a sidecar handoff (suspect grade). */
2046
+ function computeRestartSelfNudge(state) {
2047
+ const flags = [];
2048
+ for (const nudge of state.latency.agentNudges) {
2049
+ if (nudge.originCrossProcess < 1) continue;
2050
+ const transition = state.latency.sessionScopeTransitions.find(
2051
+ (t) =>
2052
+ t.pid === nudge.pid &&
2053
+ ["own-sidecar", "parent-sidecar"].includes(t.handoffSource) &&
2054
+ nudge.ms != null &&
2055
+ t.ms != null &&
2056
+ nudge.ms >= t.ms &&
2057
+ nudge.ms - t.ms <= 300000,
2058
+ );
2059
+ if (transition) flags.push({ ...nudge, transitionTs: transition.ts });
2060
+ }
2061
+ return flags;
2062
+ }
2063
+
2064
+ /** D13: a collect-later runner failed with findings and nothing was delivered. */
2065
+ function computeDeferredRunner(state) {
2066
+ const flags = [];
2067
+ for (const runner of state.latency.deferredRunnerFailed) {
2068
+ const delivery = state.latency.lateRunnerFindings.find(
2069
+ (f) =>
2070
+ f.pid === runner.pid &&
2071
+ f.turnId === runner.turnId &&
2072
+ (runner.ms == null || f.ms == null || f.ms >= runner.ms),
2073
+ );
2074
+ if (
2075
+ delivery &&
2076
+ delivery.failed >= 1 &&
2077
+ delivery.delivered === 0 &&
2078
+ delivery.dropped === 0
2079
+ )
2080
+ flags.push({
2081
+ ...runner,
2082
+ deliveryTs: delivery.ts,
2083
+ pending: delivery.pending,
2084
+ });
2085
+ }
2086
+ return flags;
2087
+ }
2088
+
2089
+ /** D14: an auxiliary (file, server) pair stuck in two or more turn ends. */
2090
+ function computeAuxStuck(state) {
2091
+ const flagged = [];
2092
+ for (const [key, count] of state.latency.auxStuck) {
2093
+ if (count < 2) continue;
2094
+ const [pid, filePath, serverId] = key.split("\u0000");
2095
+ flagged.push({ pid, filePath, serverId, count });
2096
+ }
2097
+ flagged.sort((a, b) => b.count - a.count);
2098
+ return flagged;
2099
+ }
2100
+
2101
+ /** D15: `historical` provenance with a malformed/legacy stamp. */
2102
+ function computeAdvisoryProvenance(state) {
2103
+ return state.latency.advisoryProvenance.map((row) => {
2104
+ const first = state.latency.pidFirstTs.get(row.pid);
2105
+ const seconds =
2106
+ first != null && row.ms != null
2107
+ ? Math.round((row.ms - first) / 1000)
2108
+ : null;
2109
+ return { ...row, secondsSinceFirstRow: seconds };
2110
+ });
2111
+ }
2112
+
2113
+ /** D16: `pi-lens loaded: Nms` loads over 2 s. A load with no
2114
+ * `session_start fired` within the next 60 s is a short-lived pid. */
2115
+ function computeSlowExtensionLoad(state) {
2116
+ const fired = state.session.firedTs;
2117
+ return state.session.slowLoads
2118
+ .filter((l) => l.durationMs >= 2000)
2119
+ .map((l) => ({
2120
+ ...l,
2121
+ shortLived: !(
2122
+ Number.isFinite(l.ms) &&
2123
+ fired.some((f) => f >= l.ms && f - l.ms <= 60000)
2124
+ ),
2125
+ }));
2126
+ }
2127
+
1298
2128
  function buildReport(state) {
1299
2129
  const smells = [];
1300
2130
  const smellCount = (id) => state.smellTotals.get(id);
2131
+
2132
+ // D1-D16 detector computations. Kept out of the smell list so every value
2133
+ // they produce is assertable on its own, not only through a smell count.
2134
+ const coverageGaps = computeCoverageGaps(state);
2135
+ const testRunnerHealth = computeTestRunnerHealth(state);
2136
+ const knip = computeKnip(state);
2137
+ const hookAwait = computeHookAwait(state);
2138
+ const turnEndSlow = computeTurnEndSlow(state);
2139
+ const lspWait = computeLspWait(state);
2140
+ const carryRestart = computeCarryRestart(state);
2141
+ const restartSelfNudge = computeRestartSelfNudge(state);
2142
+ const deferredRunner = computeDeferredRunner(state);
2143
+ const auxStuck = computeAuxStuck(state);
2144
+ const advisoryProvenance = computeAdvisoryProvenance(state);
2145
+ const slowExtensionLoad = computeSlowExtensionLoad(state);
2146
+ const latencyScratch = [...state.latency.scratchRows.values()].reduce(
2147
+ (n, v) => n + v,
2148
+ 0,
2149
+ );
2150
+ const extensionScratch = [...state.extension.scratchRows.values()].reduce(
2151
+ (n, v) => n + v,
2152
+ 0,
2153
+ );
2154
+ const d3Runs = state.session.runs.filter((r) => {
2155
+ const excludedTest = r.excludedTestFiles.some((p) =>
2156
+ /\.(test|spec)\.|\/tests?\//.test(p),
2157
+ );
2158
+ return (r.excluded >= 1 && excludedTest) || (r.edits >= 10 && r.ran === 0);
2159
+ });
2160
+ const d4Cross = state.session.runs.reduce(
2161
+ (n, r) => n + (r.crossCheckout >= 3 ? r.crossCheckout : 0),
2162
+ 0,
2163
+ );
2164
+ const testRunnerSmellCount =
2165
+ testRunnerHealth.staleRuns.length + testRunnerHealth.lowDelivery.length;
1301
2166
  addSmell(
1302
2167
  smells,
1303
2168
  "diagnostic-blockers",
@@ -1407,19 +2272,17 @@ function buildReport(state) {
1407
2272
  );
1408
2273
  addSmell(
1409
2274
  smells,
1410
- "read-guard-friction",
1411
- smellCount("read-guard-friction"),
1412
- "Read-guard blocked/warned edits or exact replacement misses",
1413
- [...state.readGuard.blocked, ...state.readGuard.oldTextIssues].slice(
1414
- 0,
1415
- limit,
1416
- ),
2275
+ "read-guard-blocks",
2276
+ state.readGuard.events.get("edit_blocked") +
2277
+ state.readGuard.events.get("edit_preflight_blocked"),
2278
+ "Read-guard blocked edits; host/model provenance is not present on the block rows, and warns and exact-replacement misses are informational",
2279
+ state.readGuard.blocks.slice(0, limit),
1417
2280
  );
1418
2281
  addSmell(
1419
2282
  smells,
1420
2283
  "read-guard-stale-ranges",
1421
2284
  smellCount("read-guard-stale-ranges"),
1422
- "Covered edit ranges whose read snapshot no longer matched current content",
2285
+ `Enforced range mismatches whose read snapshot no longer matched (bypassed-content-match ${state.readGuard.bypassedMismatch} excluded as an allowed edit)`,
1423
2286
  state.readGuard.staleRanges.slice(0, limit),
1424
2287
  );
1425
2288
  addSmell(
@@ -1479,6 +2342,258 @@ function buildReport(state) {
1479
2342
  ws.sweeps.slice(0, limit),
1480
2343
  );
1481
2344
 
2345
+ // D1: a latency gap a live session filled.
2346
+ addSmell(
2347
+ smells,
2348
+ "log-coverage-gap",
2349
+ coverageGaps.length,
2350
+ "Latency stream gap >= 10 min that a live session filled with >= 20 sessionstart rows",
2351
+ coverageGaps.slice(0, limit).map((g) => ({
2352
+ ts: g.start,
2353
+ message: `${g.minutes} min gap ${g.start} -> ${g.end}; sessionstartRows=${g.sessionstartRows}; cascadeRows=${g.cascadeRows}`,
2354
+ })),
2355
+ );
2356
+ // D2: test home markers in a real log. Never added to the
2357
+ // default denylist so the pollution stays visible.
2358
+ addSmell(
2359
+ smells,
2360
+ "real-log-test-pollution",
2361
+ latencyScratch + extensionScratch,
2362
+ `Rows naming a test home (witness-home, pi-lens-test-*) (latency ${latencyScratch}, extension ${extensionScratch}); these never belong in a real log`,
2363
+ [
2364
+ ...[...state.latency.scratchRows.entries()].map(([pid, count]) => ({
2365
+ key: `latency pid ${pid}`,
2366
+ count,
2367
+ })),
2368
+ ...[...state.extension.scratchRows.entries()].map(([pid, count]) => ({
2369
+ key: `extension pid ${pid}`,
2370
+ count,
2371
+ })),
2372
+ ].slice(0, limit),
2373
+ );
2374
+ addSmell(
2375
+ smells,
2376
+ "extension-warn-errors",
2377
+ state.extension.warnErrorGroups.size,
2378
+ "extension.log warn/error groups by subsystem and message prefix",
2379
+ state.extension.warnErrorGroups.top(limit),
2380
+ );
2381
+ // D3: a run that excluded its own test files, or edited without running one.
2382
+ addSmell(
2383
+ smells,
2384
+ "turn-end-tests-excluded",
2385
+ d3Runs.length,
2386
+ "Turn-end runs that skipped test files by policy, or edited >= 10 files and ran no test",
2387
+ d3Runs.slice(0, limit).map((r) => ({
2388
+ ts: r.startTs,
2389
+ message: `edits=${r.edits} ran=${r.ran} excluded=${r.excluded} noTestFile=${r.noTestFile}${
2390
+ r.excludedTestFiles.length ? `: ${r.excludedTestFiles.join(", ")}` : ""
2391
+ }`,
2392
+ })),
2393
+ );
2394
+ // D4: a failed-first target that resolved to a different checkout (#3649).
2395
+ addSmell(
2396
+ smells,
2397
+ "test-target-cross-checkout",
2398
+ d4Cross,
2399
+ "Failed-first test targets resolved to a different checkout than the edited source (regression guard for #3649)",
2400
+ state.session.runs
2401
+ .filter((r) => r.crossCheckout >= 3)
2402
+ .slice(0, limit)
2403
+ .map((r) => ({
2404
+ ts: r.startTs,
2405
+ message: `${r.crossCheckout} cross-checkout of ${r.failedFirst} failed-first`,
2406
+ })),
2407
+ );
2408
+ // D5: sessions whose turn-end verdicts went stale, or were rarely delivered.
2409
+ addSmell(
2410
+ smells,
2411
+ "test-runner-stale-verdicts",
2412
+ testRunnerSmellCount,
2413
+ `Sessions with >= 10 test firings and >= 50% stale (${testRunnerHealth.staleRuns.length}), or >= 10 delivery rows and < 25% delivered (${testRunnerHealth.lowDelivery.length}); window firings ${testRunnerHealth.firings}, stale ${testRunnerHealth.stale}`,
2414
+ [
2415
+ ...testRunnerHealth.staleRuns.map((r) => ({
2416
+ ts: r.startTs,
2417
+ count: r.stale,
2418
+ message: `firings=${r.firings} stale=${r.stale}`,
2419
+ })),
2420
+ ...testRunnerHealth.lowDelivery.map((s) => ({
2421
+ key: s.sessionId,
2422
+ count: s.delivered,
2423
+ message: `delivered ${s.delivered}/${s.total} (${Math.round(
2424
+ s.deliveredShare * 100,
2425
+ )}%), staged ${s.staged}, eligible ${s.eligible}, superseded ${s.superseded}`,
2426
+ })),
2427
+ ].slice(0, limit),
2428
+ );
2429
+ // D6: scanner-count drift and knip wall cost.
2430
+ addSmell(
2431
+ smells,
2432
+ "scanner-count-drift",
2433
+ knip.drift.length,
2434
+ "knip totalIssues rose by >= 100 between consecutive executed runs at least 5 times, or its spread >= 25% (pids with >= 10 executed runs)",
2435
+ knip.drift.slice(0, limit).map((d) => ({
2436
+ key: `pid ${d.pid}`,
2437
+ count: d.increments,
2438
+ message: `issues ${d.min} -> ${d.max} (+${Math.round(
2439
+ d.spread * 100,
2440
+ )}%), ${d.increments} increments >= 100`,
2441
+ })),
2442
+ );
2443
+ addSmell(
2444
+ smells,
2445
+ "turn-end-knip-cost",
2446
+ knip.cost.length,
2447
+ "knip consumed >= 30 s per hour of pid lifetime (a lifetime under an hour counts as one hour), or a single run took >= 5 s",
2448
+ knip.cost.slice(0, limit).map((c) => ({
2449
+ key: `pid ${c.pid}`,
2450
+ count: Math.round(c.perHour),
2451
+ message: `${c.totalMs}ms over ${c.hours.toFixed(2)}h = ${Math.round(
2452
+ c.perHour,
2453
+ )}ms/h, max ${c.maxRow}ms`,
2454
+ })),
2455
+ );
2456
+ // D7: hook deopts that overran their budget.
2457
+ addSmell(
2458
+ smells,
2459
+ "hook-await-exceeded",
2460
+ hookAwait.rows.length,
2461
+ `Hook awaits that overran their budget (${hookAwait.over} with ratio >= 1.1)`,
2462
+ hookAwait.rows.slice(0, limit).map((r) => ({
2463
+ ts: r.ts,
2464
+ message: `${r.hook}:${r.label} ${r.elapsedMs}/${r.budgetMs}ms (${r.ratio.toFixed(
2465
+ 3,
2466
+ )}) pid=${r.pid} cause=${r.cause}`,
2467
+ })),
2468
+ );
2469
+ // D8: turn_end hook duration and retained-state churn.
2470
+ addSmell(
2471
+ smells,
2472
+ "turn-end-slow",
2473
+ turnEndSlow.flagged.length,
2474
+ "turn_end hook duration over budget (a pid with >= 10% of summaries > 3 s, or one >= 8 s)",
2475
+ turnEndSlow.flagged.slice(0, limit).map((f) => ({
2476
+ key: `pid ${f.pid}`,
2477
+ count: f.slow,
2478
+ message: `${f.slow}/${f.rows} over 3s (${Math.round(
2479
+ f.share * 100,
2480
+ )}%), max ${f.max}ms`,
2481
+ })),
2482
+ );
2483
+ addSmell(
2484
+ smells,
2485
+ "turn-end-retained-state",
2486
+ turnEndSlow.retainRuns.length,
2487
+ "Turn-end retained newer turn state at least 5 times in one session",
2488
+ turnEndSlow.retainRuns.slice(0, limit).map((r) => ({
2489
+ ts: r.start,
2490
+ count: r.count,
2491
+ })),
2492
+ );
2493
+ // D9: empty-candidate waits and no-client/edit warm-reuse contradictions.
2494
+ addSmell(
2495
+ smells,
2496
+ "lsp-wait-empty-candidates",
2497
+ lspWait.pidCount,
2498
+ `LSP touch saw no client while a warm client was selected, or waited with an empty candidate set (empty ${lspWait.empty.length}, noClients ${lspWait.noClients.length}, contradictions ${lspWait.contradictions.length})`,
2499
+ [
2500
+ ...lspWait.empty.map((e) => ({
2501
+ key: `pid ${e.pid}`,
2502
+ count: e.rows,
2503
+ message: `${e.ms}ms waiting with no candidate server`,
2504
+ })),
2505
+ ...lspWait.noClients.map((e) => ({
2506
+ key: `pid ${e.pid}`,
2507
+ count: Math.round(e.share * 100),
2508
+ message: `${e.noClients}/${e.rows} edits saw no client (${Math.round(
2509
+ e.share * 100,
2510
+ )}%)`,
2511
+ })),
2512
+ ...lspWait.contradictions.map((c) => ({
2513
+ key: `pid ${c.pid}`,
2514
+ count: 1,
2515
+ message: `${c.filePath}: no-client touch at ${c.touchTs}, warm-reuse at ${c.selectedTs}`,
2516
+ })),
2517
+ ].slice(0, limit),
2518
+ );
2519
+ // D10: a zero_read block that contradicts earlier read/edit evidence.
2520
+ addSmell(
2521
+ smells,
2522
+ "resume-state-loss",
2523
+ state.readGuard.stateLost.length,
2524
+ `zero_read edits blocked despite earlier read/edit evidence in the same session (genuine ${state.readGuard.genuineZeroRead.length})`,
2525
+ state.readGuard.stateLost.slice(0, limit),
2526
+ );
2527
+ // D11: a restart whose read-set carry was empty while the branch was populated.
2528
+ addSmell(
2529
+ smells,
2530
+ "carry-empty-restart",
2531
+ carryRestart.length,
2532
+ "Restart carry kept and dropped nothing while the branch held a populated read set",
2533
+ carryRestart.slice(0, limit).map((r) => ({
2534
+ key: `pid ${r.pid}`,
2535
+ count: r.branchToolResults,
2536
+ message: `trigger=${r.trigger} source=${r.source} kept=${r.kept} dropped=${r.dropped} branchToolResults=${r.branchToolResults}`,
2537
+ })),
2538
+ );
2539
+ // D12: a cross-process nudge right after a sidecar handoff (suspect until O7).
2540
+ addSmell(
2541
+ smells,
2542
+ "restart-self-nudge",
2543
+ restartSelfNudge.length,
2544
+ "Cross-process nudge within 300 s of a sidecar handoff (suspect-grade until the nudge carries a file id, O7)",
2545
+ restartSelfNudge.slice(0, limit).map((r) => ({
2546
+ key: `pid ${r.pid}`,
2547
+ count: r.originCrossProcess,
2548
+ message: `${JSON.stringify(r.reasonMix)} crossProcess=${r.originCrossProcess} after ${r.transitionTs}`,
2549
+ })),
2550
+ );
2551
+ // D13: a collect-later runner failed with findings and none were delivered.
2552
+ addSmell(
2553
+ smells,
2554
+ "deferred-runner-failed-undelivered",
2555
+ deferredRunner.length,
2556
+ "collect-later runner failed with findings and the delivery row shows failed>=1, delivered=0, dropped=0 (#3796 witness)",
2557
+ deferredRunner.slice(0, limit).map((r) => ({
2558
+ ts: r.ts,
2559
+ message: `${r.runnerId} pid=${r.pid} turnId=${r.turnId} diagnostics=${r.diagnosticCount} delivery=${r.deliveryTs}`,
2560
+ })),
2561
+ );
2562
+ // D14: an auxiliary (file, server) pair stuck in >= 2 turn ends.
2563
+ addSmell(
2564
+ smells,
2565
+ "aux-stuck-pair",
2566
+ auxStuck.length,
2567
+ "Auxiliary (file, server) pair stuck in >= 2 turn ends",
2568
+ auxStuck.slice(0, limit).map((p) => ({
2569
+ key: p.serverId,
2570
+ count: p.count,
2571
+ message: `${p.filePath} pid=${p.pid}`,
2572
+ })),
2573
+ );
2574
+ // D15: historical provenance with a malformed/legacy stamp.
2575
+ addSmell(
2576
+ smells,
2577
+ "advisory-provenance-unknown",
2578
+ advisoryProvenance.length,
2579
+ "Advisory provenance resolved to historical with a malformed-or-legacy stamp",
2580
+ advisoryProvenance.slice(0, limit).map((r) => ({
2581
+ ts: r.ts,
2582
+ message: `${r.provenanceStamp} (${r.advisoryKind}) ${r.secondsSinceFirstRow}s after pid start`,
2583
+ })),
2584
+ );
2585
+ // D16: extension load time over 2 s.
2586
+ addSmell(
2587
+ smells,
2588
+ "slow-extension-load",
2589
+ slowExtensionLoad.length,
2590
+ "pi-lens loaded >= 2000ms after process start (short-lived: no session_start fired within 60 s)",
2591
+ slowExtensionLoad.slice(0, limit).map((l) => ({
2592
+ ...l,
2593
+ message: `${l.message}${l.shortLived ? " [short-lived]" : ""}`,
2594
+ })),
2595
+ );
2596
+
1482
2597
  return {
1483
2598
  window: state.window,
1484
2599
  filesScanned: Object.fromEntries(
@@ -1549,6 +2664,12 @@ function buildReport(state) {
1549
2664
  0,
1550
2665
  limit,
1551
2666
  ),
2667
+ // E2/D10: the block/warn split and the lost-read-set classification.
2668
+ warns: state.readGuard.warns.slice(0, limit),
2669
+ blockByKind: state.readGuard.blockByKind.toJSON(),
2670
+ bypassedMismatch: state.readGuard.bypassedMismatch,
2671
+ stateLost: state.readGuard.stateLost.slice(0, limit),
2672
+ genuineZeroRead: state.readGuard.genuineZeroRead.slice(0, limit),
1552
2673
  },
1553
2674
  treeSitter: {
1554
2675
  phases: state.treeSitter.phases.toJSON(),
@@ -1559,7 +2680,8 @@ function buildReport(state) {
1559
2680
  session: {
1560
2681
  starts: state.session.starts,
1561
2682
  cwds: state.session.cwds.top(limit),
1562
- rotations: state.session.rotations.toJSON(),
2683
+ commits: state.session.commits.toJSON(),
2684
+ slowLoads: state.session.slowLoads.slice(0, limit),
1563
2685
  errors: state.session.errors.slice(0, limit),
1564
2686
  },
1565
2687
  // #2526: the positive-observability counters behind the
@@ -1602,6 +2724,43 @@ function buildReport(state) {
1602
2724
  .sort((a, b) => b.total - a.total)
1603
2725
  .slice(0, limit * 3),
1604
2726
  },
2727
+ // D1-D16: the per-detector values behind the smells above, so a test can
2728
+ // pin the rule (not only the rendered count) and a reader can see WHY.
2729
+ detectors: {
2730
+ logCoverageGap: { gaps: coverageGaps },
2731
+ realLogTestPollution: {
2732
+ latencyByPid: Object.fromEntries(state.latency.scratchRows),
2733
+ extensionByPid: Object.fromEntries(state.extension.scratchRows),
2734
+ latencyTotal: latencyScratch,
2735
+ extensionTotal: extensionScratch,
2736
+ },
2737
+ turnEndTestsExcluded: {
2738
+ runs: d3Runs.map((r) => ({ ...r })),
2739
+ },
2740
+ testTargetCrossCheckout: {
2741
+ runs: state.session.runs.map((r) => ({
2742
+ startTs: r.startTs,
2743
+ crossCheckout: r.crossCheckout,
2744
+ failedFirst: r.failedFirst,
2745
+ })),
2746
+ },
2747
+ testRunnerStaleVerdicts: testRunnerHealth,
2748
+ knip,
2749
+ hookAwait,
2750
+ turnEndSlow,
2751
+ lspWait,
2752
+ resumeStateLoss: {
2753
+ stateLost: state.readGuard.stateLost,
2754
+ genuine: state.readGuard.genuineZeroRead,
2755
+ },
2756
+ carryEmptyRestart: carryRestart,
2757
+ restartSelfNudge,
2758
+ deferredRunnerFailedUndelivered: deferredRunner,
2759
+ auxStuckPairs: auxStuck,
2760
+ advisoryProvenanceUnknown: advisoryProvenance,
2761
+ slowExtensionLoad,
2762
+ extensionWarnErrors: state.extension.warnErrorGroups.toJSON(),
2763
+ },
1605
2764
  };
1606
2765
  }
1607
2766
 
@@ -1649,6 +2808,60 @@ function printReport(report) {
1649
2808
  }
1650
2809
  }
1651
2810
 
2811
+ // D1/D2/D5/D6/D7: the non-smell breakdown behind the new detectors.
2812
+ const det = report.detectors ?? {};
2813
+ const gap = det.logCoverageGap;
2814
+ if (gap?.gaps?.length) {
2815
+ console.log("\nLog coverage");
2816
+ for (const g of gap.gaps ?? [])
2817
+ console.log(
2818
+ ` gap ${g.minutes}min ${g.start} → ${g.end} sessionstart=${g.sessionstartRows} cascade=${g.cascadeRows}`,
2819
+ );
2820
+ }
2821
+ const warnErrors = Object.entries(det.extensionWarnErrors ?? {});
2822
+ if (warnErrors.length) {
2823
+ console.log("\nExtension diagnostics");
2824
+ for (const [k, v] of warnErrors.slice(0, limit))
2825
+ console.log(` ${String(v).padStart(5)} ${k}`);
2826
+ }
2827
+ const deg = det.hookAwait?.census ?? [];
2828
+ if (deg.length) {
2829
+ console.log("\nDegradation ledger census (max count per pid)");
2830
+ for (const row of deg.slice(0, limit))
2831
+ console.log(` pid ${row.pid}: ${row.kinds.join(", ")}`);
2832
+ }
2833
+ const delivery = det.testRunnerStaleVerdicts;
2834
+ if (delivery) {
2835
+ console.log("\nTest-runner delivery");
2836
+ console.log(
2837
+ ` firings=${delivery.firings} stale=${delivery.stale} (${(
2838
+ delivery.staleShare * 100
2839
+ ).toFixed(1)}%)`,
2840
+ );
2841
+ for (const s of delivery.sessions ?? [])
2842
+ console.log(
2843
+ ` ${s.sessionId}: delivered ${s.delivered}/${s.total} staged=${s.staged} eligible=${s.eligible} superseded=${s.superseded}`,
2844
+ );
2845
+ }
2846
+ const knip = det.knip;
2847
+ if (knip && (knip.drift.length || knip.cost.length)) {
2848
+ console.log("\nknip drift and cost");
2849
+ for (const d of knip.drift)
2850
+ console.log(
2851
+ ` pid ${d.pid}: issues ${d.min} → ${d.max} (${d.increments} increments >= 100)`,
2852
+ );
2853
+ for (const c of knip.cost)
2854
+ console.log(
2855
+ ` pid ${c.pid}: ${Math.round(c.perHour)}ms/h, max ${c.maxRow}ms`,
2856
+ );
2857
+ }
2858
+ const extLoads = det.slowExtensionLoad ?? [];
2859
+ if (extLoads.length) {
2860
+ const shortLived = extLoads.filter((l) => l.shortLived).length;
2861
+ console.log("\nExtension loads");
2862
+ console.log(` slow=${extLoads.length} shortLived=${shortLived}`);
2863
+ }
2864
+
1652
2865
  section(
1653
2866
  "Diagnostic rules",
1654
2867
  report.diagnostics.byRule,
@@ -1705,7 +2918,7 @@ function printReport(report) {
1705
2918
  report.latency.testRunnerVerdicts ?? {},
1706
2919
  );
1707
2920
  if (verdictSessions.length) {
1708
- console.log("\nTest-runner stale verdicts per session");
2921
+ console.log("\nTest-runner stale verdicts per session (verdict rows only)");
1709
2922
  for (const [sessionId, summary] of verdictSessions) {
1710
2923
  console.log(
1711
2924
  ` ${sessionId}: ${summary.stale}/${summary.total} stale (${(summary.rate * 100).toFixed(1)}%)`,