pi-lens 4.3.0 → 4.4.1

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 (659) hide show
  1. package/CHANGELOG.md +1159 -0
  2. package/README.md +116 -25
  3. package/config/dependency-cruiser-eager-allowlist.json +6 -2
  4. package/dist/chunk-2BEMIWEW.js +51 -0
  5. package/dist/chunk-2EECBNC5.js +89 -0
  6. package/dist/chunk-2FQ62SPM.js +83 -0
  7. package/dist/chunk-2UMYMRI3.js +118 -0
  8. package/dist/chunk-34OCDWOS.js +48 -0
  9. package/dist/chunk-3FERWMCH.js +144 -0
  10. package/dist/chunk-3JL6IFD7.js +97 -0
  11. package/dist/chunk-3JSEWUWI.js +327 -0
  12. package/dist/chunk-3MED75HT.js +37 -0
  13. package/dist/chunk-3MRXIJ6I.js +79 -0
  14. package/dist/chunk-3P4PF2BX.js +2250 -0
  15. package/dist/chunk-3PNFWVHT.js +201 -0
  16. package/dist/chunk-3TTPKMEF.js +162 -0
  17. package/dist/chunk-5JHXUETW.js +326 -0
  18. package/dist/chunk-5W3R6LFT.js +24 -0
  19. package/dist/chunk-64LU2OVH.js +341 -0
  20. package/dist/chunk-6B5I6ICN.js +44 -0
  21. package/dist/chunk-6P54TMWL.js +274 -0
  22. package/dist/chunk-6UYA642J.js +145 -0
  23. package/dist/chunk-6XX5HCLK.js +398 -0
  24. package/dist/chunk-7AXO3ZDF.js +30 -0
  25. package/dist/chunk-7BX5DWSA.js +1907 -0
  26. package/dist/chunk-7ELY2CSM.js +902 -0
  27. package/dist/chunk-7M4T5N3V.js +38 -0
  28. package/dist/chunk-7U2IDNXN.js +37 -0
  29. package/dist/chunk-ABBOA7UD.js +157 -0
  30. package/dist/chunk-AFZYMAIV.js +756 -0
  31. package/dist/chunk-ALQRYH6O.js +112 -0
  32. package/dist/chunk-AXKPQ6ZI.js +1610 -0
  33. package/dist/chunk-BHWHXMQ2.js +62 -0
  34. package/dist/chunk-BJF25SUG.js +22 -0
  35. package/dist/chunk-BNKLYOOO.js +18 -0
  36. package/dist/chunk-BW5KFIQC.js +59 -0
  37. package/dist/chunk-CBD2B5CP.js +599 -0
  38. package/dist/chunk-CFYISHUW.js +42 -0
  39. package/dist/chunk-CMU4RMJB.js +30 -0
  40. package/dist/chunk-CULQYFLU.js +14 -0
  41. package/dist/chunk-D65GOCJB.js +289 -0
  42. package/dist/chunk-D6YF5HKX.js +59 -0
  43. package/dist/chunk-DMS52BEP.js +342 -0
  44. package/dist/chunk-ELVWJYRV.js +211 -0
  45. package/dist/chunk-EUT3B22Y.js +27 -0
  46. package/dist/chunk-F5VLZHUW.js +146 -0
  47. package/dist/chunk-FC4RP4I2.js +12 -0
  48. package/dist/chunk-FGALODG3.js +55 -0
  49. package/dist/chunk-G3RLJYA2.js +2358 -0
  50. package/dist/chunk-GAYGGICG.js +32 -0
  51. package/dist/chunk-H2W2AXD3.js +419 -0
  52. package/dist/chunk-H5EV3VMF.js +114 -0
  53. package/dist/chunk-HREZQCT3.js +106 -0
  54. package/dist/chunk-HSIRMK2D.js +517 -0
  55. package/dist/chunk-IMSQR4WO.js +184 -0
  56. package/dist/chunk-IQZXOZ7S.js +4816 -0
  57. package/dist/chunk-IR6XFA5P.js +37 -0
  58. package/dist/chunk-IUKPRZER.js +78 -0
  59. package/dist/chunk-IUR76K6A.js +262 -0
  60. package/dist/chunk-J3FZR3YY.js +98 -0
  61. package/dist/chunk-J6DFZDFA.js +562 -0
  62. package/dist/chunk-J7WJZ6W7.js +50 -0
  63. package/dist/chunk-JONGANYH.js +202 -0
  64. package/dist/chunk-KJCWXUUS.js +425 -0
  65. package/dist/chunk-KJN4IOEJ.js +17 -0
  66. package/dist/chunk-KMGPUKFD.js +25 -0
  67. package/dist/chunk-LD7WFTRX.js +4533 -0
  68. package/dist/chunk-LJDODBBZ.js +78 -0
  69. package/dist/chunk-LNOMA3MP.js +169 -0
  70. package/dist/chunk-MGIWSROG.js +2334 -0
  71. package/dist/chunk-MQA2YBAH.js +308 -0
  72. package/dist/chunk-MTDRLSI7.js +21720 -0
  73. package/dist/chunk-N2IRDBZI.js +2355 -0
  74. package/dist/chunk-N77WFUL7.js +50 -0
  75. package/dist/chunk-N7YNJE5R.js +466 -0
  76. package/dist/chunk-NABLC7W3.js +148 -0
  77. package/dist/chunk-NFXP47RV.js +999 -0
  78. package/dist/chunk-NJF2X5XF.js +45 -0
  79. package/dist/chunk-NXL4FFQQ.js +47 -0
  80. package/dist/chunk-O7HZX7CA.js +309 -0
  81. package/dist/chunk-OC7LYAU5.js +708 -0
  82. package/dist/chunk-OEX24ECI.js +348 -0
  83. package/dist/chunk-OYOB3ZFG.js +78 -0
  84. package/dist/chunk-OYTMZXRF.js +41 -0
  85. package/dist/chunk-OZJINN4O.js +84 -0
  86. package/dist/chunk-PCHDGUKT.js +23 -0
  87. package/dist/chunk-QOZXJ4NA.js +3364 -0
  88. package/dist/chunk-QYF5S6P6.js +45 -0
  89. package/dist/chunk-RJFED73B.js +79 -0
  90. package/dist/chunk-RTQNRM5X.js +27 -0
  91. package/dist/chunk-S6AMWKPC.js +189 -0
  92. package/dist/chunk-T4QZ6FEH.js +308 -0
  93. package/dist/chunk-T54VJRAD.js +5456 -0
  94. package/dist/chunk-TA5L5KWQ.js +167 -0
  95. package/dist/chunk-TQFBZVCG.js +27 -0
  96. package/dist/chunk-U52SAGRM.js +62 -0
  97. package/dist/chunk-UEAHAMEI.js +5814 -0
  98. package/dist/chunk-UGSMCQ4D.js +327 -0
  99. package/dist/chunk-UOPVJQ4N.js +103 -0
  100. package/dist/chunk-USHG4DLH.js +459 -0
  101. package/dist/chunk-UXVNL3D7.js +23 -0
  102. package/dist/chunk-V5MEBST3.js +4139 -0
  103. package/dist/chunk-VGEVL5EQ.js +29 -0
  104. package/dist/chunk-VPWKF3GN.js +87 -0
  105. package/dist/chunk-VSMJK3ZJ.js +1250 -0
  106. package/dist/chunk-VYKKMYVT.js +560 -0
  107. package/dist/chunk-VZHJS6DR.js +442 -0
  108. package/dist/chunk-WATC3IQR.js +213 -0
  109. package/dist/chunk-WPE5FPGR.js +54 -0
  110. package/dist/chunk-WSEWKCXU.js +188 -0
  111. package/dist/chunk-WW5BLB74.js +659 -0
  112. package/dist/chunk-XGUL4SLQ.js +406 -0
  113. package/dist/chunk-XHSCVSOL.js +170 -0
  114. package/dist/chunk-XIMYZ3JA.js +27 -0
  115. package/dist/chunk-YLN4K6WD.js +385 -0
  116. package/dist/chunk-Z3CCVDUB.js +76 -0
  117. package/dist/chunk-Z54ZSOBZ.js +61 -0
  118. package/dist/chunk-Z6BB4JLQ.js +478 -0
  119. package/dist/chunk-Z7QQA2KT.js +27 -0
  120. package/dist/chunk-ZETYB3XG.js +2626 -0
  121. package/dist/chunk-ZMOMPX2Z.js +87 -0
  122. package/dist/chunk-ZOEYVXSV.js +149 -0
  123. package/dist/chunk-ZZUFZUQZ.js +12120 -0
  124. package/dist/index.js +39712 -29876
  125. package/dist/mcp/analyze-cli.js +186 -232
  126. package/dist/mcp/cli.js +137 -98
  127. package/dist/mcp/server.js +22688 -1573
  128. package/dist/mcp/worker.js +97 -38
  129. package/dist/probes/installer.js +135 -0
  130. package/dist/workers/project-snapshot-persist-worker.js +22 -0
  131. package/dist/workers/review-graph-persist-worker.js +15 -0
  132. package/docs/agent-guide.md +13 -4
  133. package/docs/agent-tools.md +42 -0
  134. package/docs/ast-grep_rules_catalog.md +5 -4
  135. package/docs/configuration.md +28 -2
  136. package/docs/custom-rules.md +25 -2
  137. package/docs/environment-variables.md +86 -8
  138. package/docs/features.md +45 -5
  139. package/docs/globalconfig.md +2 -1
  140. package/docs/io-bridge-v2-migration.md +94 -0
  141. package/docs/lsp-capability-matrix.md +37 -4
  142. package/docs/lsp-idle-eviction.md +124 -0
  143. package/docs/mcp.md +21 -5
  144. package/docs/merge-queue-rollout.md +77 -0
  145. package/docs/pi-lens-fixer.md +160 -88
  146. package/docs/pi-lens-investigator.md +74 -26
  147. package/docs/pi-lens-merge-policy.md +736 -0
  148. package/docs/pi-lens-monitor.md +75 -7
  149. package/docs/pi-lens-retro.md +6 -4
  150. package/docs/pi-lens-reviewer.md +189 -48
  151. package/docs/pi-lens-subagent.md +246 -45
  152. package/docs/pi-lens-warden.md +18 -7
  153. package/docs/public-api-stability.md +55 -0
  154. package/docs/release-qa-baseline.md +84 -17
  155. package/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  156. package/docs/schema/pi-lens-config-v1.json +530 -0
  157. package/docs/servercapabilities.md +56 -49
  158. package/docs/settings.md +1 -0
  159. package/docs/usage.md +5 -1
  160. package/docs/word-index.md +6 -0
  161. package/grammars/tree-sitter-bash.wasm +0 -0
  162. package/grammars/tree-sitter-bash.wasm.json +3 -3
  163. package/package.json +19 -7
  164. package/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  165. package/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
  166. package/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
  167. package/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
  168. package/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  169. package/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
  170. package/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
  171. package/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
  172. package/scripts/analyze-pi-lens-logs.mjs +1275 -62
  173. package/scripts/download-grammars.js +5 -0
  174. package/scripts/grammars.lock.json +6 -1
  175. package/scripts/install-selftest.mjs +33 -34
  176. package/scripts/lib/host-provided-deps.mjs +13 -4
  177. package/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  178. package/dist/clients/actionable-warnings-logger.js +0 -35
  179. package/dist/clients/actionable-warnings.js +0 -1610
  180. package/dist/clients/advisory-provenance.js +0 -593
  181. package/dist/clients/agent-behavior-client.js +0 -147
  182. package/dist/clients/agent-nudge.js +0 -325
  183. package/dist/clients/analysed-root.js +0 -1
  184. package/dist/clients/ast-grep-client.js +0 -731
  185. package/dist/clients/ast-grep-rule-manager.js +0 -146
  186. package/dist/clients/ast-grep-tool-logger.js +0 -118
  187. package/dist/clients/ast-grep-types.js +0 -10
  188. package/dist/clients/ast-grep-yaml-synth.js +0 -154
  189. package/dist/clients/atomic-write-staging.js +0 -75
  190. package/dist/clients/atomic-write.js +0 -189
  191. package/dist/clients/availability-probe-flight.js +0 -12
  192. package/dist/clients/bash-file-access.js +0 -1056
  193. package/dist/clients/biome-client.js +0 -357
  194. package/dist/clients/blocker-freshness.js +0 -837
  195. package/dist/clients/blocker-past-eof.js +0 -129
  196. package/dist/clients/bootstrap.js +0 -569
  197. package/dist/clients/bounded-cache.js +0 -190
  198. package/dist/clients/bounded-pid-file-lock.js +0 -202
  199. package/dist/clients/bounded-telemetry.js +0 -295
  200. package/dist/clients/build-identity.js +0 -95
  201. package/dist/clients/bundled-resource-health.js +0 -113
  202. package/dist/clients/bus-events-logger.js +0 -105
  203. package/dist/clients/bus-publish.js +0 -171
  204. package/dist/clients/cache/rule-cache.js +0 -190
  205. package/dist/clients/cache-manager.js +0 -524
  206. package/dist/clients/cache-observability.js +0 -1313
  207. package/dist/clients/call-graph.js +0 -852
  208. package/dist/clients/cargo-manifest.js +0 -422
  209. package/dist/clients/cascade-budget.js +0 -196
  210. package/dist/clients/cascade-format.js +0 -315
  211. package/dist/clients/cascade-logger.js +0 -31
  212. package/dist/clients/cascade-types.js +0 -9
  213. package/dist/clients/child-unref.js +0 -124
  214. package/dist/clients/code-quality-warnings.js +0 -286
  215. package/dist/clients/codebase-model.js +0 -173
  216. package/dist/clients/collateral-test-role.js +0 -40
  217. package/dist/clients/complexity-client.js +0 -462
  218. package/dist/clients/config-core/deny.js +0 -221
  219. package/dist/clients/config-core/index.js +0 -50
  220. package/dist/clients/config-core/merge.js +0 -357
  221. package/dist/clients/config-core/normalize.js +0 -340
  222. package/dist/clients/config-core/process-spec.js +0 -248
  223. package/dist/clients/config-core/provenance.js +0 -164
  224. package/dist/clients/config-core/records.js +0 -218
  225. package/dist/clients/config-core/resolve.js +0 -125
  226. package/dist/clients/config-core/safe-object.js +0 -78
  227. package/dist/clients/config-core/schema.js +0 -165
  228. package/dist/clients/config-diagnostic-codes.js +0 -285
  229. package/dist/clients/config-locations.js +0 -388
  230. package/dist/clients/config-resolve.js +0 -798
  231. package/dist/clients/config-schema.js +0 -251
  232. package/dist/clients/config-warn.js +0 -407
  233. package/dist/clients/console-guard-install.js +0 -26
  234. package/dist/clients/cooperative-budget.js +0 -42
  235. package/dist/clients/dead-code-client.js +0 -510
  236. package/dist/clients/dead-code-logger.js +0 -30
  237. package/dist/clients/deadline-utils.js +0 -280
  238. package/dist/clients/debug-handles.js +0 -211
  239. package/dist/clients/debug-heap.js +0 -174
  240. package/dist/clients/deferred-lsp-work.js +0 -106
  241. package/dist/clients/degradation-ledger.js +0 -426
  242. package/dist/clients/demoted-finding-render.js +0 -169
  243. package/dist/clients/dependency-checker.js +0 -885
  244. package/dist/clients/deps/ast-grep-napi.js +0 -22
  245. package/dist/clients/deps/js-yaml.js +0 -9
  246. package/dist/clients/deps/minimatch.js +0 -4
  247. package/dist/clients/deps/pi-tui.js +0 -14
  248. package/dist/clients/deps/typebox.js +0 -14
  249. package/dist/clients/deps/vscode-jsonrpc.js +0 -1
  250. package/dist/clients/deps/web-tree-sitter.js +0 -24
  251. package/dist/clients/diagnostic-dispositions.js +0 -630
  252. package/dist/clients/diagnostic-line-freshness.js +0 -305
  253. package/dist/clients/diagnostic-logger.js +0 -73
  254. package/dist/clients/diagnostic-tracker.js +0 -105
  255. package/dist/clients/diagnostics-publish.js +0 -230
  256. package/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  257. package/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  258. package/dist/clients/dispatch/collect-later-tier.js +0 -35
  259. package/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  260. package/dist/clients/dispatch/dispatcher.js +0 -1269
  261. package/dist/clients/dispatch/fact-provider-types.js +0 -1
  262. package/dist/clients/dispatch/fact-rule-runner.js +0 -17
  263. package/dist/clients/dispatch/fact-runner.js +0 -30
  264. package/dist/clients/dispatch/fact-scheduler.js +0 -68
  265. package/dist/clients/dispatch/fact-store.js +0 -377
  266. package/dist/clients/dispatch/facts/comment-facts.js +0 -22
  267. package/dist/clients/dispatch/facts/file-content.js +0 -19
  268. package/dist/clients/dispatch/facts/function-facts.js +0 -348
  269. package/dist/clients/dispatch/facts/import-facts.js +0 -204
  270. package/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  271. package/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  272. package/dist/clients/dispatch/finding-policy.js +0 -339
  273. package/dist/clients/dispatch/indent-detect.js +0 -417
  274. package/dist/clients/dispatch/inline-suppressions.js +0 -89
  275. package/dist/clients/dispatch/integration.js +0 -2377
  276. package/dist/clients/dispatch/lazy.js +0 -11
  277. package/dist/clients/dispatch/pending-runner-findings.js +0 -87
  278. package/dist/clients/dispatch/plan.js +0 -278
  279. package/dist/clients/dispatch/priorities.js +0 -20
  280. package/dist/clients/dispatch/rule-id-normalize.js +0 -50
  281. package/dist/clients/dispatch/rule-ignores.js +0 -69
  282. package/dist/clients/dispatch/rule-policy.js +0 -135
  283. package/dist/clients/dispatch/rules/async-noise.js +0 -40
  284. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  285. package/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  286. package/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  287. package/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  288. package/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  289. package/dist/clients/dispatch/rules/high-complexity.js +0 -72
  290. package/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  291. package/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  292. package/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  293. package/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  294. package/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  295. package/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  296. package/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  297. package/dist/clients/dispatch/runner-context.js +0 -52
  298. package/dist/clients/dispatch/runners/actionlint.js +0 -111
  299. package/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  300. package/dist/clients/dispatch/runners/biome-check.js +0 -296
  301. package/dist/clients/dispatch/runners/cpp-check.js +0 -219
  302. package/dist/clients/dispatch/runners/credo.js +0 -82
  303. package/dist/clients/dispatch/runners/cue-vet.js +0 -416
  304. package/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  305. package/dist/clients/dispatch/runners/detekt.js +0 -176
  306. package/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  307. package/dist/clients/dispatch/runners/elixir-check.js +0 -195
  308. package/dist/clients/dispatch/runners/eslint.js +0 -137
  309. package/dist/clients/dispatch/runners/fact-rules.js +0 -29
  310. package/dist/clients/dispatch/runners/fish-indent.js +0 -69
  311. package/dist/clients/dispatch/runners/gleam-check.js +0 -167
  312. package/dist/clients/dispatch/runners/go-vet.js +0 -104
  313. package/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  314. package/dist/clients/dispatch/runners/hadolint.js +0 -91
  315. package/dist/clients/dispatch/runners/helm-lint.js +0 -171
  316. package/dist/clients/dispatch/runners/helm-render.js +0 -940
  317. package/dist/clients/dispatch/runners/htmlhint.js +0 -117
  318. package/dist/clients/dispatch/runners/index.js +0 -108
  319. package/dist/clients/dispatch/runners/javac.js +0 -99
  320. package/dist/clients/dispatch/runners/ktlint.js +0 -110
  321. package/dist/clients/dispatch/runners/lsp.js +0 -379
  322. package/dist/clients/dispatch/runners/markdownlint.js +0 -167
  323. package/dist/clients/dispatch/runners/mypy.js +0 -102
  324. package/dist/clients/dispatch/runners/oxlint.js +0 -414
  325. package/dist/clients/dispatch/runners/php-lint.js +0 -85
  326. package/dist/clients/dispatch/runners/phpstan.js +0 -120
  327. package/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  328. package/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  329. package/dist/clients/dispatch/runners/pyright.js +0 -169
  330. package/dist/clients/dispatch/runners/rubocop.js +0 -99
  331. package/dist/clients/dispatch/runners/ruff.js +0 -88
  332. package/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  333. package/dist/clients/dispatch/runners/shellcheck.js +0 -173
  334. package/dist/clients/dispatch/runners/shfmt.js +0 -102
  335. package/dist/clients/dispatch/runners/spellcheck.js +0 -128
  336. package/dist/clients/dispatch/runners/spotbugs.js +0 -259
  337. package/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  338. package/dist/clients/dispatch/runners/stylelint.js +0 -166
  339. package/dist/clients/dispatch/runners/swiftlint.js +0 -163
  340. package/dist/clients/dispatch/runners/taplo.js +0 -174
  341. package/dist/clients/dispatch/runners/terragrunt.js +0 -168
  342. package/dist/clients/dispatch/runners/tflint.js +0 -125
  343. package/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  344. package/dist/clients/dispatch/runners/trivy-config.js +0 -253
  345. package/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  346. package/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  347. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  348. package/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  349. package/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  350. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  351. package/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  352. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  353. package/dist/clients/dispatch/runners/utils.js +0 -15
  354. package/dist/clients/dispatch/runners/vale.js +0 -128
  355. package/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  356. package/dist/clients/dispatch/runners/yamllint.js +0 -95
  357. package/dist/clients/dispatch/runners/zig-check.js +0 -79
  358. package/dist/clients/dispatch/suppress-writer.js +0 -79
  359. package/dist/clients/dispatch/tool-profile.js +0 -30
  360. package/dist/clients/dispatch/types.js +0 -21
  361. package/dist/clients/dispatch/utils/format-utils.js +0 -45
  362. package/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  363. package/dist/clients/disposition-logger.js +0 -32
  364. package/dist/clients/disposition-publish.js +0 -124
  365. package/dist/clients/durable-store.js +0 -97
  366. package/dist/clients/effective-config.js +0 -415
  367. package/dist/clients/env-utils.js +0 -83
  368. package/dist/clients/error-class.js +0 -23
  369. package/dist/clients/eval-timestamp.js +0 -17
  370. package/dist/clients/event-loop-hold.js +0 -274
  371. package/dist/clients/event-loop-monitor.js +0 -250
  372. package/dist/clients/extension-log.js +0 -504
  373. package/dist/clients/extension-mode.js +0 -73
  374. package/dist/clients/feature-hints.js +0 -103
  375. package/dist/clients/file-kinds.js +0 -412
  376. package/dist/clients/file-role.js +0 -134
  377. package/dist/clients/file-time.js +0 -160
  378. package/dist/clients/file-utils.js +0 -1032
  379. package/dist/clients/finding-delivery-gate.js +0 -494
  380. package/dist/clients/finding-identity.js +0 -88
  381. package/dist/clients/fix-worklog.js +0 -103
  382. package/dist/clients/format-events-publish.js +0 -343
  383. package/dist/clients/format-service.js +0 -222
  384. package/dist/clients/formatters-lazy.js +0 -9
  385. package/dist/clients/formatters.js +0 -2032
  386. package/dist/clients/freshness-cadence.js +0 -17
  387. package/dist/clients/freshness.js +0 -38
  388. package/dist/clients/generated-artifacts.js +0 -394
  389. package/dist/clients/generation-guard.js +0 -215
  390. package/dist/clients/git-guard.js +0 -1136
  391. package/dist/clients/git-tracked-ignore.js +0 -243
  392. package/dist/clients/gitleaks-client.js +0 -509
  393. package/dist/clients/go-client.js +0 -104
  394. package/dist/clients/govulncheck-client.js +0 -605
  395. package/dist/clients/gradle-ktfmt-style.js +0 -348
  396. package/dist/clients/grammar-source.js +0 -465
  397. package/dist/clients/gzip-stage-write.js +0 -135
  398. package/dist/clients/hashline-anchor.js +0 -424
  399. package/dist/clients/hook-budgets.js +0 -76
  400. package/dist/clients/host-edit-normalize.js +0 -140
  401. package/dist/clients/host-ports.js +0 -39
  402. package/dist/clients/indent-retarget.js +0 -154
  403. package/dist/clients/inline-blocker-dispositions.js +0 -186
  404. package/dist/clients/install-diagnostics.js +0 -156
  405. package/dist/clients/installer/index.js +0 -5416
  406. package/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  407. package/dist/clients/installer/managed-tool-refresh.js +0 -783
  408. package/dist/clients/instance-reaper-state.js +0 -7
  409. package/dist/clients/instance-reaper.js +0 -1137
  410. package/dist/clients/instance-registry-lock.js +0 -145
  411. package/dist/clients/instance-registry.js +0 -773
  412. package/dist/clients/jscpd-client.js +0 -378
  413. package/dist/clients/json-cache-read.js +0 -65
  414. package/dist/clients/knip-client.js +0 -788
  415. package/dist/clients/language-policy.js +0 -252
  416. package/dist/clients/language-profile.js +0 -361
  417. package/dist/clients/language-registry.js +0 -603
  418. package/dist/clients/latency-logger.js +0 -727
  419. package/dist/clients/lazy-import.js +0 -21
  420. package/dist/clients/ledger-bounds.js +0 -54
  421. package/dist/clients/lens-config.js +0 -404
  422. package/dist/clients/lens-engine.js +0 -358
  423. package/dist/clients/lens-events.js +0 -125
  424. package/dist/clients/lens-flag-registry.js +0 -467
  425. package/dist/clients/lens-map.js +0 -1156
  426. package/dist/clients/live-bus-emitter.js +0 -74
  427. package/dist/clients/log-cleanup.js +0 -222
  428. package/dist/clients/lsp/aggregation.js +0 -178
  429. package/dist/clients/lsp/cascade-tier.js +0 -421
  430. package/dist/clients/lsp/client.js +0 -4593
  431. package/dist/clients/lsp/config.js +0 -691
  432. package/dist/clients/lsp/content-limits.js +0 -41
  433. package/dist/clients/lsp/diagnostic-binding.js +0 -225
  434. package/dist/clients/lsp/document-drift.js +0 -467
  435. package/dist/clients/lsp/edits.js +0 -1219
  436. package/dist/clients/lsp/index.js +0 -8305
  437. package/dist/clients/lsp/inferred-project.js +0 -216
  438. package/dist/clients/lsp/jvm-runtime.js +0 -298
  439. package/dist/clients/lsp/language.js +0 -63
  440. package/dist/clients/lsp/launch.js +0 -746
  441. package/dist/clients/lsp/lombok.js +0 -158
  442. package/dist/clients/lsp/path-utils.js +0 -5
  443. package/dist/clients/lsp/pending-aux-coverage.js +0 -251
  444. package/dist/clients/lsp/position-encoding.js +0 -61
  445. package/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  446. package/dist/clients/lsp/server.js +0 -3290
  447. package/dist/clients/lsp/session-roots.js +0 -192
  448. package/dist/clients/lsp/spawn-history.js +0 -17
  449. package/dist/clients/lsp/sync-kind.js +0 -56
  450. package/dist/clients/lsp/tsserver-sync.js +0 -332
  451. package/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  452. package/dist/clients/lsp/wait-policy/classification.js +0 -85
  453. package/dist/clients/lsp/wait-policy/index.js +0 -10
  454. package/dist/clients/lsp/wait-policy/strategies.js +0 -392
  455. package/dist/clients/lsp/watch-queue.js +0 -61
  456. package/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  457. package/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  458. package/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  459. package/dist/clients/lsp-budget.js +0 -186
  460. package/dist/clients/lsp-document-symbols.js +0 -110
  461. package/dist/clients/lsp-lazy.js +0 -9
  462. package/dist/clients/lsp-mutation.js +0 -396
  463. package/dist/clients/lsp-status.js +0 -59
  464. package/dist/clients/map-with-concurrency.js +0 -40
  465. package/dist/clients/mcp/analyze.js +0 -420
  466. package/dist/clients/mcp/host-shim.js +0 -31
  467. package/dist/clients/mcp/ipc.js +0 -523
  468. package/dist/clients/mcp/review.js +0 -197
  469. package/dist/clients/mcp/session.js +0 -412
  470. package/dist/clients/memory-sampler.js +0 -208
  471. package/dist/clients/message-end-attribution.js +0 -30
  472. package/dist/clients/metrics-client.js +0 -107
  473. package/dist/clients/metrics-history.js +0 -327
  474. package/dist/clients/middle-man-analysis.js +0 -263
  475. package/dist/clients/model-provider.js +0 -53
  476. package/dist/clients/module-report-lsp.js +0 -189
  477. package/dist/clients/module-report.js +0 -2150
  478. package/dist/clients/mutating-tool.js +0 -651
  479. package/dist/clients/mutation-attribution.js +0 -368
  480. package/dist/clients/mutation-bridge.js +0 -240
  481. package/dist/clients/ndjson-logger.js +0 -734
  482. package/dist/clients/observed-mutation-sources.js +0 -101
  483. package/dist/clients/observed-mutation.js +0 -1253
  484. package/dist/clients/oldtext-autopatch.js +0 -128
  485. package/dist/clients/opaque-mutation-scan.js +0 -496
  486. package/dist/clients/opengrep-client.js +0 -333
  487. package/dist/clients/opengrep-config.js +0 -76
  488. package/dist/clients/package-manager.js +0 -607
  489. package/dist/clients/package-root.js +0 -38
  490. package/dist/clients/partial-edit-apply.js +0 -439
  491. package/dist/clients/path-attribution-telemetry.js +0 -43
  492. package/dist/clients/path-keyed-map.js +0 -108
  493. package/dist/clients/path-utils.js +0 -1244
  494. package/dist/clients/performance-report.js +0 -331
  495. package/dist/clients/persist-debounce.js +0 -70
  496. package/dist/clients/persistent-reverify.js +0 -271
  497. package/dist/clients/php-cs-fixer-config.js +0 -119
  498. package/dist/clients/pipeline.js +0 -1480
  499. package/dist/clients/post-autofix-notice.js +0 -37
  500. package/dist/clients/print-mode.js +0 -21
  501. package/dist/clients/probe-home-state.js +0 -228
  502. package/dist/clients/process-bridge.js +0 -66
  503. package/dist/clients/process-singletons.js +0 -210
  504. package/dist/clients/process-snapshot.js +0 -68
  505. package/dist/clients/project-changes.js +0 -181
  506. package/dist/clients/project-conventions.js +0 -171
  507. package/dist/clients/project-diagnostics/cache.js +0 -137
  508. package/dist/clients/project-diagnostics/extractors.js +0 -109
  509. package/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  510. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  511. package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  512. package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  513. package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  514. package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  515. package/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  516. package/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  517. package/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  518. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  519. package/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  520. package/dist/clients/project-diagnostics/scanner.js +0 -520
  521. package/dist/clients/project-diagnostics/types.js +0 -1
  522. package/dist/clients/project-lens-config.js +0 -807
  523. package/dist/clients/project-report.js +0 -701
  524. package/dist/clients/project-scale.js +0 -237
  525. package/dist/clients/project-scan-policy.js +0 -11
  526. package/dist/clients/project-snapshot-fingerprint.js +0 -48
  527. package/dist/clients/project-snapshot-persist-worker.js +0 -20
  528. package/dist/clients/project-snapshot.js +0 -1653
  529. package/dist/clients/project-trust.js +0 -181
  530. package/dist/clients/python-environment.js +0 -217
  531. package/dist/clients/python-provenance.js +0 -639
  532. package/dist/clients/quiet-window-config.js +0 -47
  533. package/dist/clients/quiet-window.js +0 -236
  534. package/dist/clients/read-bridge.js +0 -139
  535. package/dist/clients/read-expansion.js +0 -266
  536. package/dist/clients/read-guard-logger.js +0 -251
  537. package/dist/clients/read-guard-tool-lines.js +0 -1306
  538. package/dist/clients/read-guard.js +0 -1561
  539. package/dist/clients/recent-touches.js +0 -240
  540. package/dist/clients/redact/secrets.js +0 -449
  541. package/dist/clients/resource-sampler.js +0 -628
  542. package/dist/clients/reverse-deps.js +0 -270
  543. package/dist/clients/review-graph/builder.js +0 -5126
  544. package/dist/clients/review-graph/format.js +0 -33
  545. package/dist/clients/review-graph/git-identity.js +0 -163
  546. package/dist/clients/review-graph/import-resolvers.js +0 -481
  547. package/dist/clients/review-graph/persist-worker.js +0 -10
  548. package/dist/clients/review-graph/query.js +0 -244
  549. package/dist/clients/review-graph/revision-drift.js +0 -21
  550. package/dist/clients/review-graph/service.js +0 -96
  551. package/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  552. package/dist/clients/review-graph/symbol-id.js +0 -103
  553. package/dist/clients/review-graph/tsconfig-paths.js +0 -299
  554. package/dist/clients/review-graph/types.js +0 -1
  555. package/dist/clients/review-graph/workspace-modules.js +0 -449
  556. package/dist/clients/review-graph-logger.js +0 -81
  557. package/dist/clients/ruff-client.js +0 -179
  558. package/dist/clients/rules-scanner.js +0 -131
  559. package/dist/clients/run-duration.js +0 -55
  560. package/dist/clients/runtime-agent-end.js +0 -746
  561. package/dist/clients/runtime-config.js +0 -77
  562. package/dist/clients/runtime-context.js +0 -211
  563. package/dist/clients/runtime-coordinator.js +0 -1324
  564. package/dist/clients/runtime-session.js +0 -2485
  565. package/dist/clients/runtime-tool-call.js +0 -1295
  566. package/dist/clients/runtime-tool-result.js +0 -2049
  567. package/dist/clients/runtime-turn.js +0 -3767
  568. package/dist/clients/rust-client.js +0 -97
  569. package/dist/clients/safe-spawn.js +0 -2357
  570. package/dist/clients/sanitize.js +0 -286
  571. package/dist/clients/scan-utils.js +0 -25
  572. package/dist/clients/scratch-tree-policy.js +0 -165
  573. package/dist/clients/search-read-registration.js +0 -94
  574. package/dist/clients/secret-findings.js +0 -114
  575. package/dist/clients/security-scan-client.js +0 -352
  576. package/dist/clients/session-event-guard.js +0 -297
  577. package/dist/clients/session-lifecycle.js +0 -484
  578. package/dist/clients/session-start-observability.js +0 -94
  579. package/dist/clients/session-state-store.js +0 -101
  580. package/dist/clients/session-summary.js +0 -15
  581. package/dist/clients/sessionstart-logger.js +0 -27
  582. package/dist/clients/sg-runner.js +0 -876
  583. package/dist/clients/sgconfig.js +0 -347
  584. package/dist/clients/shared-checkout-guard.js +0 -338
  585. package/dist/clients/single-flight.js +0 -158
  586. package/dist/clients/situational-tool-telemetry.js +0 -108
  587. package/dist/clients/skills-resolver.js +0 -141
  588. package/dist/clients/slow-fs.js +0 -138
  589. package/dist/clients/smells-rollup.js +0 -231
  590. package/dist/clients/source-filter.js +0 -596
  591. package/dist/clients/source-walker.js +0 -211
  592. package/dist/clients/spawn-output-cap.js +0 -120
  593. package/dist/clients/spawn-timeout-cooldown.js +0 -110
  594. package/dist/clients/stale-marker.js +0 -17
  595. package/dist/clients/startup-scan.js +0 -400
  596. package/dist/clients/startup-timing.js +0 -130
  597. package/dist/clients/string-utils.js +0 -33
  598. package/dist/clients/subagent-mode.js +0 -111
  599. package/dist/clients/symbol-containment.js +0 -24
  600. package/dist/clients/symbol-types.js +0 -5
  601. package/dist/clients/test-runner-client.js +0 -2507
  602. package/dist/clients/test-runner-delivery.js +0 -287
  603. package/dist/clients/todo-scanner.js +0 -229
  604. package/dist/clients/tool-agreement.js +0 -423
  605. package/dist/clients/tool-config.js +0 -249
  606. package/dist/clients/tool-cwd.js +0 -270
  607. package/dist/clients/tool-definition.js +0 -47
  608. package/dist/clients/tool-event.js +0 -105
  609. package/dist/clients/tool-policy.js +0 -2755
  610. package/dist/clients/tool-probe.js +0 -53
  611. package/dist/clients/tool-render.js +0 -156
  612. package/dist/clients/tool-set-policy.js +0 -118
  613. package/dist/clients/tree-sitter-cache.js +0 -364
  614. package/dist/clients/tree-sitter-client.js +0 -4195
  615. package/dist/clients/tree-sitter-logger.js +0 -100
  616. package/dist/clients/tree-sitter-navigator.js +0 -269
  617. package/dist/clients/tree-sitter-query-loader.js +0 -569
  618. package/dist/clients/tree-sitter-shared.js +0 -158
  619. package/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  620. package/dist/clients/trivy-client.js +0 -467
  621. package/dist/clients/tui-fit.js +0 -50
  622. package/dist/clients/turn-context.js +0 -52
  623. package/dist/clients/turn-end/lane.js +0 -51
  624. package/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  625. package/dist/clients/turn-end/lanes/secrets.js +0 -204
  626. package/dist/clients/turn-summary-render.js +0 -72
  627. package/dist/clients/turn-summary.js +0 -132
  628. package/dist/clients/typos-config.js +0 -29
  629. package/dist/clients/user-notify.js +0 -51
  630. package/dist/clients/vanished-instance-marker.js +0 -57
  631. package/dist/clients/warm-attach.js +0 -305
  632. package/dist/clients/widget-state.js +0 -1808
  633. package/dist/clients/word-index-logger.js +0 -49
  634. package/dist/clients/word-index-store.js +0 -459
  635. package/dist/clients/word-index.js +0 -2080
  636. package/dist/clients/workspace-topology.js +0 -443
  637. package/dist/clients/write-ordering-guard.js +0 -65
  638. package/dist/clients/zizmor-config.js +0 -340
  639. package/dist/i18n.js +0 -69
  640. package/dist/mcp/build-staleness.js +0 -124
  641. package/dist/scripts/lib/process-scan.mjs +0 -583
  642. package/dist/scripts/lib/skills-predicate.mjs +0 -129
  643. package/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  644. package/dist/tools/activate-tools.js +0 -87
  645. package/dist/tools/ast-grep-outline.js +0 -137
  646. package/dist/tools/ast-grep-replace.js +0 -244
  647. package/dist/tools/ast-grep-search.js +0 -762
  648. package/dist/tools/effective-config.js +0 -90
  649. package/dist/tools/lens-diagnostic-mark.js +0 -375
  650. package/dist/tools/lens-diagnostics.js +0 -2571
  651. package/dist/tools/lsp-diagnostics.js +0 -1745
  652. package/dist/tools/lsp-navigation.js +0 -1367
  653. package/dist/tools/lsp-structured-output.js +0 -294
  654. package/dist/tools/module-report.js +0 -382
  655. package/dist/tools/project-report.js +0 -86
  656. package/dist/tools/render-compact.js +0 -374
  657. package/dist/tools/scan-progress.js +0 -53
  658. package/dist/tools/shared.js +0 -29
  659. package/dist/tools/symbol-search.js +0 -155
@@ -1,83 +0,0 @@
1
- /**
2
- * Environment / config input helpers.
3
- *
4
- * Small, dependency-free utilities used by logger modules and runtime tuning
5
- * knobs to keep duplicated env-handling expressions in a single place.
6
- */
7
- /**
8
- * True when pi-lens should suppress side-effecting log writes — e.g. inside
9
- * the vitest test runner, or when callers explicitly set `PI_LENS_TEST_MODE=1`.
10
- *
11
- * Resolution:
12
- * - `PI_LENS_TEST_MODE === "1"` → true (explicit opt-in)
13
- * - `VITEST` set and `PI_LENS_TEST_MODE !== "0"` → true (vitest default, with explicit opt-out)
14
- * - otherwise false
15
- *
16
- * Replaces the boolean previously duplicated verbatim in ~10 logger modules.
17
- */
18
- export function isTestMode() {
19
- if (process.env.PI_LENS_TEST_MODE === "1")
20
- return true;
21
- if (process.env.VITEST && process.env.PI_LENS_TEST_MODE !== "0")
22
- return true;
23
- return false;
24
- }
25
- /**
26
- * Coerce an arbitrary input to a non-negative finite number, or 0 otherwise.
27
- *
28
- * Use this to gate config / env values before they flow into `Math.max` /
29
- * `Math.min` / `setTimeout`. `Number(undefined)` is `NaN`, and a single NaN
30
- * argument makes `Math.max` return NaN, which `setTimeout` silently treats
31
- * as 0 — see the runner-timeout-floor regression caught in PR #109.
32
- *
33
- * @example
34
- * ```ts
35
- * const floor = Math.max(
36
- * toPositiveFinite(process.env.PI_LENS_KNOB_MS),
37
- * toPositiveFinite(loadedConfig?.knobMs),
38
- * 0,
39
- * );
40
- * ```
41
- */
42
- export function toPositiveFinite(value) {
43
- const num = typeof value === "number" ? value : Number(value);
44
- return Number.isFinite(num) && num > 0 ? num : 0;
45
- }
46
- /**
47
- * Factory for the lazy-memoized "positive-finite-number env override, else a
48
- * default" getter pattern repeated across `startup-scan.ts` (#699, #758) and
49
- * originally documented as house style in `runtime-config.ts` / `slow-fs.ts` /
50
- * `subagent-mode.ts` (#763).
51
- *
52
- * `get()` reads `process.env[envName]` through {@link toPositiveFinite} the
53
- * first time it's called, memoizes the result (or `fallback` when the env var
54
- * is unset/non-finite/non-positive), and returns the memo on every subsequent
55
- * call. `_resetForTests()` clears the memo so a test can flip the env var and
56
- * observe a fresh read (matching the existing `_resetForTests` convention).
57
- *
58
- * Deliberately does NOT touch `process.env` until `get()` is first called —
59
- * that's the entire point of the lazy pattern: importing a module must never
60
- * have a side effect on process env at load time.
61
- *
62
- * @example
63
- * ```ts
64
- * const _ttl = lazyEnvNumber("PI_LENS_STARTUP_SCAN_VERDICT_TTL_MS", DEFAULT_TTL_MS);
65
- * export const getStartupScanVerdictTtlMs = _ttl.get;
66
- * export const _resetStartupScanVerdictTtlForTests = _ttl._resetForTests;
67
- * ```
68
- */
69
- export function lazyEnvNumber(envName, fallback) {
70
- let memo;
71
- return {
72
- get() {
73
- if (memo !== undefined)
74
- return memo;
75
- const envValue = toPositiveFinite(process.env[envName]);
76
- memo = envValue > 0 ? envValue : fallback;
77
- return memo;
78
- },
79
- _resetForTests() {
80
- memo = undefined;
81
- },
82
- };
83
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * The class name of a caught error, and NOTHING else — never the message,
3
- * which for some callers could quote content they do not want to risk
4
- * surfacing (a config-core bug thrown while walking the raw, untrusted
5
- * parsed document; a whole-resolution catch wrapping several stages at
6
- * once). One leaf so that guarantee does not grow a third, fourth, and
7
- * fifth hand-rolled `error instanceof Error ? error.name : "unknown error"`
8
- * (#2451 — `clients/config-warn.ts`'s `normalizeParseErrorReason` (the
9
- * `classOnly` option), `clients/config-core/normalize.ts`,
10
- * `clients/config-core/resolve.ts`, and `clients/lens-config.ts` all call
11
- * this instead of inlining their own copy).
12
- *
13
- * Zero imports of its own, deliberately: `config-core/` must never import a
14
- * sink (`clients/config-warn.ts` -> the degradation ledger) — that exact
15
- * import closed seven cycles when #2426 removed it, and importing
16
- * `normalizeParseErrorReason` from inside `config-core/` to get this one
17
- * value back would reopen them. This leaf is how `config-warn.ts` and
18
- * `config-core/*.ts` share ONE implementation without either importing the
19
- * other.
20
- */
21
- export function errorClassName(error) {
22
- return error instanceof Error ? error.name : "unknown error";
23
- }
@@ -1,17 +0,0 @@
1
- import { performance } from "node:perf_hooks";
2
- /**
3
- * The moment pi-lens's module graph started evaluating, captured as this
4
- * module's first statement (#1374 host-boot vs extension-eval split).
5
- *
6
- * This module is otherwise side-effect-free by design (#1434 S3d review): it
7
- * does nothing but capture a timestamp. `clients/console-guard-install.ts`
8
- * imports it as its FIRST import so the capture happens before the guard's
9
- * own install work, and `clients/startup-timing.ts` imports it too, for the
10
- * constant alone. Before this split, `startup-timing.ts` imported the
11
- * constant FROM `console-guard-install.ts` — which meant importing
12
- * startup-timing anywhere (e.g. a test measuring load time) silently
13
- * installed the console guard as a side effect. Splitting the constant out
14
- * means importing this module, or `startup-timing.ts`, never installs
15
- * anything.
16
- */
17
- export const PI_LENS_EVAL_STARTED_MS = performance.now();
@@ -1,274 +0,0 @@
1
- /**
2
- * #2507: hold the event loop for the lifetime of in-flight work that would
3
- * otherwise be kept alive by NOTHING.
4
- *
5
- * Every handle pi-lens owns on the way to an LSP answer is deliberately
6
- * unref'd. `clients/lsp/launch.ts#unrefLspProcessHandles` unrefs the language
7
- * server child AND its three stdio pipes so a settled one-shot `pi --print`
8
- * can exit without waiting for a lingering server (the #1097/#1110 class);
9
- * `clients/child-unref.ts` does the same for every fire-and-forget probe
10
- * spawn; `bounded()` and several LSP waits unref their timers for the same
11
- * reason. That is correct for an IDLE process and wrong for a BUSY one: in a
12
- * headless child (`pi --mode json -p --no-extensions`, stdin ignored, no TUI,
13
- * no other extension holding a socket or timer) an in-flight `lsp_diagnostics`
14
- * call reaches a point where the only thing it is waiting on is an unref'd
15
- * timer or an unref'd pipe, libuv finds no referenced handle, and Node exits 0
16
- * IN THE MIDDLE of the tool call — no error, no result, no `turn_end`. The
17
- * reporter measured it against pyright; the drain site the repro lands on is
18
- * `LSPService.ensureWarmForSweep`'s warm-up retry backoff (an explicitly
19
- * `.unref()`'d `setTimeout`), but it is one of many: any await whose only
20
- * pending handle is unref'd has the same shape.
21
- *
22
- * The fix is NOT to stop unref'ing — that would leak a lingering server into
23
- * every settled one-shot process, which is the reason the unref exists. It is
24
- * to hold ONE referenced handle for exactly as long as something is in
25
- * flight: a counted keep-alive, armed while at least one hold is taken and
26
- * disarmed the moment the last one is released. Idle behaviour is therefore
27
- * byte-identical to before — no hold, no timer, nothing referenced.
28
- *
29
- * Shape 15's screen (AGENTS.md), which this follows deliberately:
30
- * - a COUNTER of tokens, not a boolean, so overlapping tool calls each
31
- * release independently and a throwing/aborted caller still releases
32
- * (callers release from `finally`);
33
- * - a bounded lifetime PER HOLD, because a leaked hold is the INVERSE defect
34
- * (a process that can never exit). Each token gets {@link
35
- * getEventLoopHoldMaxMs} measured from its OWN acquire — derived from the
36
- * longest legitimate operation's ceiling (`getWorkspaceSweepMaxHoldAgeMs`,
37
- * the full-scan wall clock plus its safety margin) rather than a second,
38
- * independently-drifting literal. The keep-alive is armed for the SOONEST
39
- * outstanding deadline; when it fires, only tokens that are actually stale
40
- * are released (with a log record naming them), and it re-arms for
41
- * whatever remains. An epoch-anchored version of this — one timer from the
42
- * first hold, clearing every token when it fired — was #2649 review F1: a
43
- * call issued late in a legitimate 300s `lens_diagnostics mode=full` was
44
- * force-released seconds after starting, i.e. #2507's drain reproduced BY
45
- * the fix.
46
- *
47
- * There is deliberately NO session-boundary clear here, unlike
48
- * `clients/lsp/workspace-sweep-hold.ts`. A `session_start` can land mid-turn
49
- * (see `clients/bootstrap.ts`), so clearing holds on a session boundary would
50
- * un-hold a tool call that is still running and reintroduce this exact defect
51
- * for it. The hold is scoped to one call's try/finally, not to a session.
52
- */
53
- import { claimPhaseOncePerSession, logLatency, releaseOncePerSessionPhase, } from "./latency-logger.js";
54
- import { getWorkspaceSweepMaxHoldAgeMs } from "./lsp/workspace-sweep-hold.js";
55
- import { getProcessSingleton } from "./process-singletons.js";
56
- /** The one row per session proving the keep-alive was taken (#2649 F2). */
57
- const EVENT_LOOP_HOLD_ARMED_PHASE = "event_loop_hold_armed";
58
- const EVENT_LOOP_HOLD_FAMILY = "event-loop-hold";
59
- const EVENT_LOOP_HOLD_VERSION = 1;
60
- /**
61
- * Process-scoped on purpose: pi evaluates the pi-lens module graph more than
62
- * once in one process (see `clients/process-singletons.ts`), and what is being
63
- * counted here is a property of the PROCESS's event loop, not of one module
64
- * evaluation. Two copies would arm two timers for one loop.
65
- */
66
- function state() {
67
- return getProcessSingleton(EVENT_LOOP_HOLD_FAMILY, EVENT_LOOP_HOLD_VERSION, () => ({ holds: new Map(), nextHoldId: 1, timer: undefined }));
68
- }
69
- /**
70
- * Upper bound on how long ONE hold — each hold, measured from its OWN
71
- * acquire — may keep this process alive. Derived from `lens_diagnostics
72
- * mode=full`'s own wall-clock ceiling plus the shared
73
- * `SWEEP_IDLE_SAFETY_MARGIN_MS` — the same derivation the workspace-sweep
74
- * hold's max-hold-age failsafe uses, so the two cannot drift apart into a
75
- * relationship where the longest legitimate tool call outlives the bound that
76
- * is supposed to only catch bugs.
77
- */
78
- export function getEventLoopHoldMaxMs() {
79
- return getWorkspaceSweepMaxHoldAgeMs();
80
- }
81
- /**
82
- * Arm the keep-alive for the SOONEST deadline any outstanding hold has — the
83
- * earliest moment a token can become stale — never for a fixed epoch from the
84
- * first hold (#2649 review F1).
85
- *
86
- * Round 1 armed once on 0→1 and cleared every token when it fired, so a call
87
- * that started while an older one was still running inherited whatever was
88
- * left of the FIRST call's bound: a `lens_diagnostics mode=full` legitimately
89
- * running to its 300s ceiling force-released a healthy `lsp_diagnostics`
90
- * issued at t=305s, which is #2507's own drain produced by the fix. The
91
- * reviewer reproduced it in a real child with the bound shortened: both calls
92
- * unsettled, `active=[]`, `EXIT code=0`.
93
- *
94
- * Soonest-deadline, not the youngest survivor's: the youngest would let an
95
- * older survivor overstay its own max age by the whole age gap. Each token is
96
- * released when IT is stale and never before.
97
- */
98
- function armForNextDeadline(current) {
99
- if (current.timer !== undefined)
100
- return;
101
- if (current.holds.size === 0)
102
- return;
103
- const maxMs = getEventLoopHoldMaxMs();
104
- let soonest = Number.POSITIVE_INFINITY;
105
- for (const entry of current.holds.values()) {
106
- soonest = Math.min(soonest, entry.acquiredAt + maxMs);
107
- }
108
- // A past-due deadline is left negative on purpose: Node clamps any delay
109
- // below 1ms to 1ms, so it fires on a later turn of the loop either way, and
110
- // a hand-written floor here would be a guard naming a recurrence that
111
- // cannot happen (#2649 verify F4 — probed: 5 re-arms at delay -5000
112
- // completed in 5ms, and no test reds without the floor).
113
- const delayMs = soonest - Date.now();
114
- // NOT `unref()`'d — referencing the loop is this timer's entire job. It is
115
- // also the failsafe: when it fires, whatever is stale is over either way.
116
- current.timer = setTimeout(() => reapStaleHolds(), delayMs);
117
- }
118
- function disarmIfIdle(current) {
119
- if (current.holds.size > 0)
120
- return;
121
- if (current.timer === undefined)
122
- return;
123
- clearTimeout(current.timer);
124
- current.timer = undefined;
125
- }
126
- /**
127
- * Release every hold that has outlived {@link getEventLoopHoldMaxMs} ON ITS
128
- * OWN CLOCK, log what was released, and re-arm for whatever is still
129
- * outstanding. Same shape as `clients/lsp/workspace-sweep-hold.ts`'s
130
- * `reapStaleHolds` — deliberately a second, independent implementation rather
131
- * than a shared ledger (that consolidation is tracked separately).
132
- */
133
- function reapStaleHolds() {
134
- const current = state();
135
- current.timer = undefined;
136
- try {
137
- const maxMs = getEventLoopHoldMaxMs();
138
- const now = Date.now();
139
- const released = [];
140
- for (const [holdId, entry] of current.holds) {
141
- if (now - entry.acquiredAt <= maxMs)
142
- continue;
143
- current.holds.delete(holdId);
144
- released.push(entry);
145
- }
146
- if (released.length > 0) {
147
- recordForceReleased(released, current.holds.size, maxMs, now);
148
- }
149
- }
150
- finally {
151
- // A survivor is held by nothing until this re-arms — the drain — and
152
- // forever if it never does — the leak. In a `finally` because this runs
153
- // inside a timer callback, where anything that escapes is an uncaught
154
- // exception, and because the round-2 code put the log between the reap
155
- // and the re-arm: a throwing sink stranded the survivor (#2649 verify
156
- // F3). The map is already consistent by here — entries are deleted
157
- // before anything is logged.
158
- armForNextDeadline(current);
159
- }
160
- }
161
- /**
162
- * The force-release record, and its own absorption. Telemetry may not break
163
- * the guard it observes — the house rule spelled at
164
- * `clients/runtime-tool-call.ts`'s blocked-attribution cleanup: a guard that
165
- * fails because its telemetry broke is worse than no guard. Nothing here has
166
- * a fallback sink to escalate to; the keep-alive's correctness does not
167
- * depend on the row being written.
168
- */
169
- function recordForceReleased(released, stillHeld, maxMs, now) {
170
- try {
171
- const oldestAcquiredAt = Math.min(...released.map((entry) => entry.acquiredAt));
172
- logLatency({
173
- type: "phase",
174
- phase: "event_loop_hold_force_released",
175
- filePath: "",
176
- durationMs: now - oldestAcquiredAt,
177
- metadata: {
178
- maxHoldMs: maxMs,
179
- releasedHolds: released.length,
180
- stillHeld,
181
- labels: [...new Set(released.map((entry) => entry.label))].slice(0, 8),
182
- },
183
- });
184
- }
185
- catch {
186
- // see the doc comment — a broken sink must not reach the reap path
187
- }
188
- }
189
- /**
190
- * ONE row per session saying the keep-alive was actually taken (#2649 review
191
- * F2). Without it the only record this module ever writes is the failsafe, so
192
- * a silent log cannot distinguish "working as designed" from "never wired at
193
- * all" — the #2526 gap shape. Claimed through the logger's own
194
- * once-per-session mechanism rather than a private latch, so the "one row per
195
- * session" a reader counts against is the same property `config_resolved`
196
- * has.
197
- */
198
- function recordFirstArm(entry, maxMs) {
199
- try {
200
- if (!claimPhaseOncePerSession(EVENT_LOOP_HOLD_ARMED_PHASE, "process")) {
201
- return;
202
- }
203
- logLatency({
204
- type: "phase",
205
- phase: EVENT_LOOP_HOLD_ARMED_PHASE,
206
- filePath: "",
207
- durationMs: 0,
208
- metadata: { label: entry.label, maxHoldMs: maxMs },
209
- });
210
- }
211
- catch {
212
- // This runs on the ACQUIRE path, and `normalizeToolDefinition` takes the
213
- // hold outside its own try — so a throw here rejected the tool call with
214
- // the sink's error instead of returning the tool's result (#2649 verify
215
- // F3). Same house rule as `recordForceReleased` above: the hold is
216
- // already taken and armed by this point, so absorbing the throw loses a
217
- // log row and nothing else.
218
- }
219
- }
220
- /**
221
- * Take one hold for the lifetime of a piece of in-flight work, and return its
222
- * release. Callers MUST release from a `finally` block: a throw, a rejection
223
- * or an abort has to release exactly like a normal return, or the hold leaks
224
- * and the process stops being able to exit.
225
- *
226
- * The release is identity-guarded and idempotent — calling it twice, or after
227
- * the max-age failsafe already force-released it, is a no-op and never
228
- * disarms another caller's hold.
229
- */
230
- export function acquireEventLoopHold(label) {
231
- const current = state();
232
- const holdId = current.nextHoldId++;
233
- const entry = { acquiredAt: Date.now(), label };
234
- current.holds.set(holdId, entry);
235
- const wasArmed = current.timer !== undefined;
236
- armForNextDeadline(current);
237
- if (!wasArmed)
238
- recordFirstArm(entry, getEventLoopHoldMaxMs());
239
- return () => {
240
- const now = state();
241
- if (!now.holds.delete(holdId))
242
- return;
243
- disarmIfIdle(now);
244
- };
245
- }
246
- /** Test-only: how many holds are outstanding right now. */
247
- export function _eventLoopHoldCountForTests() {
248
- return state().holds.size;
249
- }
250
- /**
251
- * Test-only: the keep-alive handle's own state. `hasRef` is the property the
252
- * whole fix rests on — a handle that is armed but unref'd holds nothing — so
253
- * it is read from the live `Timeout` (`hasRef()`), not asserted from the
254
- * source text.
255
- */
256
- export function _eventLoopKeepAliveForTests() {
257
- const timer = state().timer;
258
- return {
259
- armed: timer !== undefined,
260
- hasRef: timer?.hasRef?.() === true,
261
- };
262
- }
263
- /** Test-only: drop every hold and disarm, between tests. */
264
- export function _resetEventLoopHoldForTests() {
265
- // This module's own phase only — never a blanket clear of a structure other
266
- // producers claim in (`releaseOncePerSessionPhase`'s doc comment).
267
- releaseOncePerSessionPhase(EVENT_LOOP_HOLD_ARMED_PHASE);
268
- const current = state();
269
- current.holds.clear();
270
- if (current.timer !== undefined)
271
- clearTimeout(current.timer);
272
- current.timer = undefined;
273
- current.nextHoldId = 1;
274
- }
@@ -1,250 +0,0 @@
1
- /**
2
- * Production event-loop occupancy monitor (#192 Phase 2).
3
- *
4
- * pi-lens runs on pi's TUI event loop; a long synchronous block freezes
5
- * keystrokes. Our telemetry historically logged phase *durations*, which can't
6
- * distinguish a TUI-freezing synchronous burst from harmless async/subprocess
7
- * time — that blind spot let a ~1.5s enumeration freeze through (#188/#191).
8
- *
9
- * This wraps Node's native `perf_hooks.monitorEventLoopDelay()` — a histogram
10
- * of how late the loop services its own timer, i.e. how long it was blocked —
11
- * with **no per-event JS overhead**. `max` ≈ the worst synchronous block since
12
- * the last reset.
13
- *
14
- * ## System-stall contamination (#1122 / #1123)
15
- *
16
- * `monitorEventLoopDelay` measures timer lag with the monotonic clock
17
- * (`uv_hrtime`, backed by `QueryPerformanceCounter` on Windows). When the whole
18
- * process is frozen or descheduled — machine sleep / Modern Standby, or paging
19
- * thrash under commit-charge exhaustion — its next timer fires late by the
20
- * *entire* wall-clock gap, and the histogram records that gap as a "block".
21
- * Two distinct system stalls were confirmed against the Windows System event
22
- * log: (1) a 290,179 ms block that lined up exactly with a 14:33:05Z→14:37:55Z
23
- * Modern Standby window (Kernel-Power 506/507), reported byte-identical by two
24
- * independent pids because the HDR histogram quantizes ~290 s into one bucket;
25
- * (2) a later silent host exit with zero sleep events but twelve
26
- * Resource-Exhaustion-Detector (id 2004) events at 97% commit charge — the
27
- * process was paging, not sleeping. Both are machine artifacts, not pi-lens
28
- * work; latency.log also held multi-*hour* "blocks" that can only be overnight
29
- * sleep.
30
- *
31
- * Comparing a wall clock to a monotonic clock does NOT catch these: on Windows
32
- * both advance across Modern Standby (the histogram's monotonic delta already
33
- * equalled the wall gap). The reliable discriminator is **CPU consumption**: a
34
- * genuine synchronous block of D ms burns ≈ D ms of main-thread CPU, so the
35
- * window that contains it must have consumed at least ~D ms of CPU. A frozen or
36
- * thrashing process consumes ~0 CPU across the gap, so when the worst block
37
- * exceeds all the CPU the window could account for, it was a stall, not work.
38
- * We window per turn (so the CPU accounting is bounded and each block is
39
- * attributable to its turn) and tag system-stall-suspected samples instead of
40
- * letting them poison the "worst real block" high-water.
41
- */
42
- import { logExtension } from "./extension-log.js";
43
- import { monitorEventLoopDelay } from "node:perf_hooks";
44
- const NS_PER_MS = 1e6;
45
- const US_PER_MS = 1e3;
46
- let histogram;
47
- let monitorUnavailable = false;
48
- // Per-window (per-turn) baselines for CPU-vs-wall accounting. Captured when the
49
- // monitor starts and re-captured on every reset, so each window's CPU budget is
50
- // measured against exactly the histogram window it will be compared to.
51
- let windowStartWallMs = 0;
52
- let windowStartCpuMs = 0;
53
- const cpuTotalMs = () => {
54
- const c = process.cpuUsage();
55
- return (c.user + c.system) / US_PER_MS;
56
- };
57
- /**
58
- * Start the monitor (idempotent). Call once, as early as possible, so startup
59
- * blocks are captured. Cheap — the sampling is native; nothing runs per event.
60
- *
61
- * Purely observational: if the runtime doesn't implement
62
- * `perf_hooks.monitorEventLoopDelay` (e.g. Bun < 1.3, which throws
63
- * `ERR_NOT_IMPLEMENTED` on the call), we degrade to "no stats" rather than let
64
- * the throw abort extension load. `getEventLoopStats()` already tolerates an
65
- * absent histogram, so every caller keeps working without telemetry.
66
- */
67
- export function startEventLoopMonitor(resolutionMs = 20) {
68
- if (histogram || monitorUnavailable)
69
- return;
70
- try {
71
- const h = monitorEventLoopDelay({ resolution: resolutionMs });
72
- h.enable();
73
- histogram = h;
74
- windowStartWallMs = Date.now();
75
- windowStartCpuMs = cpuTotalMs();
76
- }
77
- catch (err) {
78
- monitorUnavailable = true;
79
- logExtension({
80
- subsystem: "event-loop-monitor",
81
- message: `event-loop occupancy telemetry disabled (runtime lacks monitorEventLoopDelay): ${err?.message ?? String(err)}`,
82
- });
83
- }
84
- }
85
- /**
86
- * Below this, a block is not classified at all (`below-floor`). #1980 mined
87
- * the 5 s+ tier; 1 s is a deliberately conservative floor that still covers
88
- * every block that tier cares about while keeping sub-second jitter — where
89
- * measurement slop rivals the signal — out of the verdict.
90
- */
91
- const STALL_CLASSIFY_FLOOR_MS = 1000;
92
- /**
93
- * Slack allowed to the CPU budget before a block counts as unaccounted.
94
- * `process.cpuUsage()` and the histogram are sampled at slightly different
95
- * instants and the histogram quantizes, so a block whose CPU coverage is
96
- * within this much of exact is called `cpu-accounted`, not a stall.
97
- */
98
- const STALL_CLASSIFY_SLOP_MS = 250;
99
- /**
100
- * Split a block into compute-vs-stall from the CPU it could possibly have
101
- * burned (#1980). Pure, so the discrimination is testable without the native
102
- * histogram or a real machine stall — the same reason
103
- * {@link isSuspendSuspectedBlock} is pure.
104
- *
105
- * ## Why this is sound in exactly one direction
106
- *
107
- * `windowCpuMs` is the CPU the WHOLE process consumed across the WHOLE window
108
- * — every thread, every phase, not just this block. So it is an UPPER BOUND on
109
- * the CPU this one block could have burned. A synchronous compute block of D ms
110
- * needs ≈ D ms of main-thread CPU. Therefore:
111
- *
112
- * - `windowCpuMs + slop < maxMs` PROVES the block was not compute. Even if the
113
- * block had been the only thing running, there was not enough CPU in the
114
- * whole window to cover it. This is the `non-cpu-stall` verdict and it is a
115
- * real inference, not a heuristic.
116
- * - The converse proves nothing. A window can burn 30 s of CPU in other phases
117
- * and still contain a 5 s I/O park. That is why the label is
118
- * `cpu-accounted` ("the budget covers it") rather than "compute": it reports
119
- * the absence of proof, not the presence of compute.
120
- *
121
- * The existing 20 s suspend verdict is not re-derived here; this delegates to
122
- * {@link isSuspendSuspectedBlock} so there is one definition of a suspend, and
123
- * `system-stall` is checked BEFORE `non-cpu-stall` because a suspend is the
124
- * strictly more specific case of the same evidence.
125
- *
126
- * ## Known ambiguity, carried forward not silenced
127
- *
128
- * `isSuspendSuspectedBlock`'s own doc records that a >20 s synchronous syscall
129
- * stall is CPU-indistinguishable from a suspend. Nothing here fixes that. What
130
- * this adds is the tier BELOW that floor: a 5-20 s non-CPU block, which used
131
- * to read as ordinary compute and now reads as `non-cpu-stall` with the
132
- * `inFlightPhase` attribution beside it.
133
- */
134
- export function classifyLoopBlock(maxMs, windowCpuMs, options = {}) {
135
- const { floorMs = STALL_CLASSIFY_FLOOR_MS, slopMs = STALL_CLASSIFY_SLOP_MS, suspendFloorMs, suspendSlopMs, } = options;
136
- const cpuCoverageRatio = maxMs > 0 ? Math.round((windowCpuMs / maxMs) * 100) / 100 : undefined;
137
- if (maxMs < floorMs)
138
- return { stallClass: "below-floor", cpuCoverageRatio };
139
- if (isSuspendSuspectedBlock(maxMs, windowCpuMs, suspendFloorMs, suspendSlopMs))
140
- return { stallClass: "system-stall", cpuCoverageRatio };
141
- if (windowCpuMs + slopMs < maxMs)
142
- return { stallClass: "non-cpu-stall", cpuCoverageRatio };
143
- return { stallClass: "cpu-accounted", cpuCoverageRatio };
144
- }
145
- const safeMs = (ns) => Number.isFinite(ns) ? Math.round((ns / NS_PER_MS) * 10) / 10 : 0;
146
- /**
147
- * Classify a worst-block sample as genuine CPU work or a suspend/freeze
148
- * artifact. Pure so the discrimination is testable without the (vitest-flaky)
149
- * native histogram or a real machine sleep.
150
- *
151
- * A block is system-stall-suspected only when it is both (a) larger than any
152
- * plausible synchronous pi-lens stall (`floorMs`, default 20 s — the real tier
153
- * observed in latency.log tops out ~15 s) and (b) unaccounted for by the
154
- * window's CPU budget (`windowCpuMs + slopMs < maxMs`). The floor keeps a real
155
- * multi-second I/O-bound block (low CPU, but genuine) from being mislabeled,
156
- * while a frozen or paging process — ~0 CPU across a minutes-to-hours gap —
157
- * always trips. Sub-floor blocks are never auto-tagged, but the logged
158
- * `windowCpuMs`/`windowWallMs` still expose the CPU-vs-wall ratio so a reviewer
159
- * can spot a shorter paging stall by hand.
160
- *
161
- * KNOWN AMBIGUITY (honest, not fully resolvable from CPU alone): a genuine
162
- * pi-lens block that is BLOCKED IN A SYSCALL for >20 s — e.g. a `readdirSync` /
163
- * `statSync` stalled on a OneDrive/cloud-backed path fetching a dehydrated file,
164
- * or an antivirus-throttled read — also consumes ~0 CPU while wall time
165
- * advances, so it is CPU-indistinguishable from a suspend and WILL be tagged
166
- * `suspectSystemStall` and excluded from the health high-waters. That is the
167
- * conservative choice (a >20 s synchronous FS stall is itself a P0-worthy bug we
168
- * do NOT want silently counted as normal), and it is not silenced: such a sample
169
- * still re-logs every turn with its `lastPhase` attribution, which is the
170
- * forensic breadcrumb for exactly this class. Sharper corroboration (a magnitude
171
- * ceiling, a `maxMs`-vs-`windowWallMs` ratio) is tracked as a follow-up note on
172
- * #1123, not decided here.
173
- */
174
- export function isSuspendSuspectedBlock(maxMs, windowCpuMs, floorMs = 20000, slopMs = 1000) {
175
- if (maxMs < floorMs)
176
- return false;
177
- return windowCpuMs + slopMs < maxMs;
178
- }
179
- /** Current occupancy stats, or undefined if the monitor was never started. */
180
- export function getEventLoopStats() {
181
- if (!histogram)
182
- return undefined;
183
- const maxMs = safeMs(histogram.max);
184
- const windowWallMs = Math.max(0, Date.now() - windowStartWallMs);
185
- const windowCpuMs = Math.max(0, cpuTotalMs() - windowStartCpuMs);
186
- // One classification pass feeds both fields, so `suspectSystemStall` and
187
- // `stallClass` can never disagree about the same sample (#1980).
188
- const classification = classifyLoopBlock(maxMs, windowCpuMs);
189
- return {
190
- maxMs,
191
- p99Ms: safeMs(histogram.percentile(99)),
192
- meanMs: safeMs(histogram.mean),
193
- windowWallMs: Math.round(windowWallMs),
194
- windowCpuMs: Math.round(windowCpuMs),
195
- suspectSystemStall: classification.stallClass === "system-stall",
196
- stallClass: classification.stallClass,
197
- cpuCoverageRatio: classification.cpuCoverageRatio,
198
- };
199
- }
200
- /**
201
- * Reset the histogram and re-baseline the CPU/wall window — called at turn
202
- * boundaries so each window's worst block is attributable to that turn and its
203
- * CPU budget is measured over the same span (#192 intent, wired in #1122).
204
- */
205
- export function resetEventLoopMonitor() {
206
- histogram?.reset();
207
- windowStartWallMs = Date.now();
208
- windowStartCpuMs = cpuTotalMs();
209
- }
210
- /**
211
- * Decide whether THIS block beats the session's running high-water. Pure so
212
- * the threshold logic is testable without the (vitest-flaky) native
213
- * histogram. Requires `maxMs > lastLoggedMs + deltaMs` above a floor
214
- * (`minMs`) so noise near the current max doesn't keep re-triggering the
215
- * high-water bookkeeping.
216
- *
217
- * NOTE (#1723): this is no longer the logging gate — see
218
- * `shouldLogLoopBlock` for that. This function now answers a narrower
219
- * question ("is this a new session worst?", used for the `worstSoFar`
220
- * metadata flag and for deciding whether to advance the high-water), because
221
- * gating the LOG on it hid every sub-maximum block after a session's first
222
- * large one, which made loop_block-vs-pull-timeout correlation undecidable.
223
- */
224
- export function shouldLogWorstBlock(maxMs, lastLoggedMs, minMs = 60, deltaMs = 25) {
225
- return maxMs >= minMs && maxMs > lastLoggedMs + deltaMs;
226
- }
227
- /**
228
- * Decide whether a block is worth persisting to `latency.log` AT ALL (#1723).
229
- * Every block at or above the floor (`minMs`) qualifies — not only a new
230
- * session worst — because a session's later blocks can be smaller than an
231
- * early spike yet still be exactly the ones that starved an LSP pull
232
- * (#1549/#1713) and are otherwise invisible to the correlation.
233
- *
234
- * Volume is bounded by CALL CADENCE, not by this gate: the `turn_end` caller
235
- * invokes this at most once per turn (the histogram window is reset every
236
- * turn), so the natural cap is one `loop_block` record per turn — a jittery
237
- * session cannot flood the log because there is nowhere for a second sample
238
- * to come from within the same turn.
239
- */
240
- export function shouldLogLoopBlock(maxMs, minMs = 60) {
241
- return maxMs >= minMs;
242
- }
243
- /** Test-only: stop and clear the monitor so cases don't leak into each other. */
244
- export function _stopEventLoopMonitorForTest() {
245
- histogram?.disable();
246
- histogram = undefined;
247
- monitorUnavailable = false;
248
- windowStartWallMs = 0;
249
- windowStartCpuMs = 0;
250
- }