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,1324 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import { logCascade } from "./cascade-logger.js";
5
- import { appendProjectChange, } from "./project-changes.js";
6
- import { normalizeMapKey, pathsEqual } from "./path-utils.js";
7
- import { PathKeyedMap } from "./path-keyed-map.js";
8
- import { BoundedLruCache } from "./bounded-cache.js";
9
- import { PartialApplyRecordStore } from "./partial-edit-apply.js";
10
- import { ReadGuard } from "./read-guard.js";
11
- import { RUNTIME_CONFIG } from "./runtime-config.js";
12
- import { TurnSummaryCollector } from "./turn-summary.js";
13
- import { deriveProviderFromModelId } from "./model-provider.js";
14
- import { beginTurnContext, setTurnContextSession } from "./turn-context.js";
15
- import { recordDegradationOnce } from "./degradation-ledger.js";
16
- /** Keep deferred cascade admission bounded without dropping late findings. */
17
- export const MAX_PENDING_CASCADE_RUNS = 32;
18
- /**
19
- * Bounded receipt ring capacity (defect shape 9: bound the axis that grows —
20
- * per-mutation receipts are unbounded across a long session). Oldest receipts
21
- * evict first; eviction is counted and surfaced via
22
- * {@link RuntimeCoordinator.droppedMutationReceiptCount} so a consumer that
23
- * needs a complete window knows the answer was truncated, never silently
24
- * incomplete (#936 honesty rule).
25
- */
26
- const MAX_MUTATION_RECEIPTS = 512;
27
- /**
28
- * Bound on in-flight tool_call → tool_result correlations. A tool_result
29
- * follows its tool_call almost immediately, so this only needs to cover
30
- * calls genuinely in flight; sized generously above any realistic
31
- * in-flight-batch width so a slow paired result is never evicted before use.
32
- */
33
- const TOOL_CALL_ATTRIBUTION_CAPACITY = 256;
34
- /**
35
- * A recorded attribution older than this is treated as expired (as if it
36
- * were never recorded) rather than trusted — defense in depth against a host
37
- * that reuses a call id, or a pathologically delayed/duplicated tool_result,
38
- * outliving the LRU capacity bound. Generous relative to any real tool
39
- * round-trip; only guards against genuine identity reuse, not slow tools.
40
- */
41
- const TOOL_CALL_ATTRIBUTION_TTL_MS = 5 * 60_000;
42
- export class RuntimeCoordinator {
43
- _projectRoot = normalizeMapKey(process.cwd());
44
- _sessionGeneration = 0;
45
- _sessionStartedAt = Date.now();
46
- _errorDebtBaseline = null;
47
- _pipelineCrashCounts = new Map();
48
- _cachedExports = new Map();
49
- _startupScansInFlight = new Map();
50
- _cascadeRuns = [];
51
- _turnEndCascadeSettleStarts = new Map();
52
- _nextCascadeSettleToken = 0;
53
- // Cascade computes are kicked off unawaited by the pipeline (#450); their
54
- // promises park here until turn_end drains them via settleCascadeRuns. Each is
55
- // guaranteed non-rejecting by the pipeline's .catch.
56
- _pendingCascadeRuns = [];
57
- _cascadeSessionStats = {
58
- runs: 0,
59
- diagnosticsSurfaced: 0,
60
- coldSnapshotTouches: 0,
61
- };
62
- _complexityBaselines = new Map();
63
- _fixedThisTurn = new PathKeyedMap(normalizeMapKey);
64
- _writtenThisTurn = new PathKeyedMap(normalizeMapKey);
65
- _autofixDemotedThisTurn = new PathKeyedMap(normalizeMapKey);
66
- _reportedThisTurn = new Set();
67
- _mutationReceipts = [];
68
- _droppedMutationReceipts = 0;
69
- _projectRulesScan = {
70
- rules: [],
71
- hasCustomRules: false,
72
- };
73
- _telemetrySessionId = `lens-${Date.now().toString(36)}`;
74
- _lifecycleReason;
75
- _hasStableSessionId = false;
76
- _telemetryModel = "unknown";
77
- // Raw model/provider identity, separate from the combined `provider/model`
78
- // display string above — worklog/disposition attribution (#1448) wants the
79
- // two fields apart, blank when the host never supplied them. `_telemetryProvider`
80
- // is the explicit host value when given, else derived from the model id
81
- // (deriveProviderFromModelId, blank on ambiguity — never guessed).
82
- _telemetryModelId = "";
83
- _telemetryProvider = "";
84
- // True once a host has supplied an explicit provider this session. An
85
- // explicit provider is never downgraded by a derivation from a later
86
- // model-only call; a DERIVED provider, by contrast, is re-derived on
87
- // every model-only call so a mid-session model switch (e.g. gpt-5-mini →
88
- // claude-sonnet-4-5) doesn't leave a stale provider from the old model.
89
- _telemetryProviderIsExplicit = false;
90
- _turnIndex = 0;
91
- _writeIndex = 0;
92
- _projectSeq = 0;
93
- _turnStartProjectSeq = 0;
94
- _fileSeq = new Map();
95
- // File key → the projectSeq value at that file's most recent bump (#451). Lets
96
- // the review-graph builder ask "which files changed since I last built?" and
97
- // skip its per-build O(project) walk+stat sweep when only pi-observed edits
98
- // occurred. Keyed identically to _fileSeq (normalizeMapKey + path.resolve).
99
- _fileLastProjectSeq = new Map();
100
- _gitGuardHasBlockers = false;
101
- _gitGuardSummary = "";
102
- _gitGuardCacheUnknownReason;
103
- callGraph = null;
104
- wordIndex = null;
105
- _readGuard = null;
106
- _pendingDeferredMutations = new PathKeyedMap(normalizeMapKey);
107
- /** tool_call → tool_result path-attribution correlation (#1642). */
108
- _toolCallAttributions = new BoundedLruCache(TOOL_CALL_ATTRIBUTION_CAPACITY);
109
- _lspReadWarmState = new Map();
110
- _pendingInlineBlockers = new PathKeyedMap(normalizeMapKey);
111
- _actionableWarningsThisTurn = new Map();
112
- _codeQualityWarningsThisTurn = new Map();
113
- // #484: opt-in per-RUN summary of diagnostics/autofixes/formats,
114
- // accumulated across the run's turns and consumed once at the
115
- // agent_settled quiet window. The collector itself is always constructed
116
- // (cheap, empty Map) but callers gate recording behind the
117
- // `lens-turn-summary` flag so it's a true no-op when the feature is off.
118
- _turnSummary = new TurnSummaryCollector();
119
- // #2402/#1053: session-scoped applied-edit records for exact-retry
120
- // recognition. Both producers (partial-apply commit, full native-edit
121
- // success) write here; the preflight consults it before declaring an
122
- // oldText miss, so an identical retry never re-executes a committed write.
123
- partialApplyRecords = new PartialApplyRecordStore();
124
- resetForSession(startedAt = Date.now()) {
125
- this._sessionGeneration += 1;
126
- this._sessionStartedAt = startedAt;
127
- this._complexityBaselines.clear();
128
- this._pipelineCrashCounts.clear();
129
- this._cachedExports.clear();
130
- this.wordIndex = null;
131
- this._startupScansInFlight.clear();
132
- this._cascadeRuns = [];
133
- this._pendingCascadeRuns = [];
134
- this._turnEndCascadeSettleStarts.clear();
135
- this._cascadeSessionStats = {
136
- runs: 0,
137
- diagnosticsSurfaced: 0,
138
- coldSnapshotTouches: 0,
139
- };
140
- this._fixedThisTurn.clear();
141
- this._writtenThisTurn.clear();
142
- this._autofixDemotedThisTurn.clear();
143
- this._reportedThisTurn.clear();
144
- this._mutationReceipts = [];
145
- this._droppedMutationReceipts = 0;
146
- this._telemetrySessionId = `lens-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
147
- this._hasStableSessionId = false;
148
- this._telemetryModel = "unknown";
149
- this._telemetryModelId = "";
150
- this._telemetryProvider = "";
151
- this._telemetryProviderIsExplicit = false;
152
- this._turnIndex = 0;
153
- this._writeIndex = 0;
154
- this._projectSeq = 0;
155
- this._turnStartProjectSeq = 0;
156
- this._fileSeq.clear();
157
- this._fileLastProjectSeq.clear();
158
- this._gitGuardHasBlockers = false;
159
- this._gitGuardSummary = "";
160
- this._gitGuardCacheUnknownReason = undefined;
161
- this._readGuard = null;
162
- this._pendingDeferredMutations.clear();
163
- // #1642 F5: every sibling correlation/state map is cleared here. A
164
- // per-session-numbered host reusing tool-call ids across sessions must
165
- // not let a NEW session inherit a DEAD session's recorded skip verdict.
166
- this._toolCallAttributions.clear();
167
- this._lspReadWarmState.clear();
168
- this._pendingInlineBlockers.clear();
169
- this._actionableWarningsThisTurn.clear();
170
- this._codeQualityWarningsThisTurn.clear();
171
- this._turnSummary.clear();
172
- // #2402: an applied-edit record is a fact about the session that applied
173
- // it; a new session must re-resolve identical payloads from content.
174
- this.partialApplyRecords.clear();
175
- }
176
- get sessionStartedAt() {
177
- return this._sessionStartedAt;
178
- }
179
- get cascadeSessionStats() {
180
- return this._cascadeSessionStats;
181
- }
182
- recordCascadeRun(diagnosticsSurfaced, coldSnapshotTouches) {
183
- this._cascadeSessionStats.runs += 1;
184
- this._cascadeSessionStats.diagnosticsSurfaced += diagnosticsSurfaced;
185
- this._cascadeSessionStats.coldSnapshotTouches += coldSnapshotTouches;
186
- }
187
- /**
188
- * Record the turn-end policy verdict on every live inline-blocker record
189
- * (#3248): `true` for a file whose every blocker the policy suppressed,
190
- * `false` for every other record in the map.
191
- *
192
- * ONE writer, rewriting the WHOLE axis each turn end, so the verdict can
193
- * never outlive the pass that made it: a record demoted since, or one whose
194
- * survivors came back because the file's bytes moved under a content-bound
195
- * anchor, is cleared by the same call that sets its sibling. A fresh
196
- * dispatch replaces the record wholesale (`recordInlineBlockers`), so a NEW
197
- * blocker is never born pre-suppressed (#1198 ordering).
198
- *
199
- * @returns how many records this call CHANGED — 0 means the gate's view of
200
- * the map is already what the policy just decided.
201
- */
202
- applyInlineBlockerPolicyVerdicts(suppressedFilePaths) {
203
- const suppressed = new Set(suppressedFilePaths.map((filePath) => path.resolve(filePath)));
204
- let changed = 0;
205
- for (const [key, entry] of this._pendingInlineBlockers.entries()) {
206
- const policySuppressed = suppressed.has(path.resolve(entry.filePath));
207
- if (!!entry.policySuppressed === policySuppressed)
208
- continue;
209
- this._pendingInlineBlockers.set(key, { ...entry, policySuppressed });
210
- changed += 1;
211
- }
212
- return changed;
213
- }
214
- updateGitGuardStatus(hasBlockers, output) {
215
- // The status is an aggregate over the current per-file map. A clean B
216
- // result must not erase an unresolved A result; the pipeline records/clears
217
- // the edited file immediately before this method runs.
218
- // #3248: every live record except one whose whole blocker set the
219
- // turn-end disposition policy suppressed. The record itself stays in the
220
- // map — the policy is content-bound and re-derived from the record's
221
- // diagnostics at every turn end, so dropping it there would be silencing
222
- // rather than filtering (AGENTS.md shape 10); it just stops counting for
223
- // the gate the agent was shown nothing for.
224
- const blocking = this.getInlineBlockersSnapshot().filter((entry) => !entry.policySuppressed);
225
- this._gitGuardHasBlockers = hasBlockers || blocking.length > 0;
226
- if (!this._gitGuardHasBlockers) {
227
- this._gitGuardSummary = "";
228
- return;
229
- }
230
- const firstLine = output
231
- .split("\n")
232
- .map((line) => line.trim())
233
- .find((line) => line.length > 0);
234
- const summaries = blocking
235
- .map((entry) => entry.summary.trim())
236
- .filter(Boolean);
237
- this._gitGuardSummary = (summaries[0] ??
238
- firstLine ??
239
- "Unresolved blockers detected").slice(0, 160);
240
- }
241
- get gitGuardHasBlockers() {
242
- return this._gitGuardHasBlockers;
243
- }
244
- get gitGuardSummary() {
245
- return this._gitGuardSummary;
246
- }
247
- markGitGuardCacheUnknown(reason) {
248
- this._gitGuardCacheUnknownReason = reason;
249
- }
250
- clearGitGuardCacheUnknown() {
251
- this._gitGuardCacheUnknownReason = undefined;
252
- }
253
- get gitGuardCacheUnknownReason() {
254
- return this._gitGuardCacheUnknownReason;
255
- }
256
- beginTurn() {
257
- // #1443: runs sitting here at turn_start were appended AFTER the last
258
- // turn_end drained them (consumeCascadeRuns) — the quiet-window reconcile's
259
- // late re-injection (`onResolvedFound`, clients/lsp/cascade-tier.ts) lands
260
- // in exactly that window. Wiping them dead-ended that delivery path: the
261
- // finding was computed, formatted, appended, and then deleted before any
262
- // turn_end could render it. Carry them into THIS turn instead, exactly
263
- // once: `carriedTurns` is stamped on the way through and a run that the
264
- // next turn_end still did not consume is dropped here with a log line
265
- // rather than queued forever (a stale finding must not outlive the state
266
- // it describes, and an unbounded queue would replay it every turn).
267
- this._cascadeRuns = this._cascadeRuns.flatMap((run) => {
268
- const carriedTurns = (run.carriedTurns ?? 0) + 1;
269
- if (carriedTurns > 1) {
270
- logCascade({
271
- phase: "cascade_carry_over_drop",
272
- filePath: run.filePath,
273
- neighborCount: run.neighborCount,
274
- diagnosticCount: run.diagnosticCount,
275
- metadata: { carriedTurns, turnIndex: this._turnIndex },
276
- });
277
- return [];
278
- }
279
- return [{ ...run, carriedTurns }];
280
- });
281
- // _pendingCascadeRuns is deliberately NOT cleared here: a cascade compute
282
- // still in flight past last turn_end's settle cap (fresh graph builds have
283
- // measured up to ~19s) must surface on the NEXT turn_end, not be dropped —
284
- // pre-#450 those findings were always awaited, never lost. Session reset
285
- // still clears it.
286
- // Inline blockers are session-scoped per-file state. They are cleared only
287
- // when that file is re-analyzed clean or the session resets; a new turn must
288
- // not let a clean unrelated file erase an unresolved blocker.
289
- this._actionableWarningsThisTurn.clear();
290
- this._codeQualityWarningsThisTurn.clear();
291
- // _turnSummary is deliberately NOT cleared here (#484 rework): the
292
- // summary entry is emitted once per RUN at the agent_settled quiet
293
- // window (sendMessage during a live stream would STEER the agent, and
294
- // turn_end can fire mid-stream), so the collector must accumulate
295
- // across the run's turns. It is cleared only by consume() at emit and
296
- // by resetForSession().
297
- this._turnStartProjectSeq = this._projectSeq;
298
- this._turnIndex += 1;
299
- beginTurnContext(this._telemetrySessionId);
300
- this._writeIndex = 0;
301
- this._reportedThisTurn.clear();
302
- this._writtenThisTurn.clear();
303
- this._autofixDemotedThisTurn.clear();
304
- }
305
- /**
306
- * THE one mutation seam. Every producer of an in-project file mutation —
307
- * native write/edit, recognized bash writes, format/autofix, LSP workspace
308
- * edits, and (phase 2) opaque script-write recovery — records through here,
309
- * once: it bumps the seq store (`bumpFileSeq`), appends a bounded attributed
310
- * receipt, and optionally appends the durable change-log entry. Consumers
311
- * derive from `getFilesChangedSince` / `getMutationsSince`; no producer or
312
- * consumer may hand-roll a parallel bump+log pairing (#2000 phase 1).
313
- *
314
- * Receipt recording never throws; change-log append failures route to
315
- * `onAppendError` (default: swallowed) so telemetry cannot break dispatch.
316
- */
317
- recordProjectMutation(args) {
318
- const { projectSeq, fileSeq, key } = this.bumpFileSeq(args.filePath);
319
- if (this._mutationReceipts.length >= MAX_MUTATION_RECEIPTS) {
320
- this._mutationReceipts.shift();
321
- this._droppedMutationReceipts += 1;
322
- }
323
- this._mutationReceipts.push({
324
- seq: projectSeq,
325
- // Reuse the bump's normalized key (a realpath syscall: ~200us on
326
- // Windows, ~1.8us on POSIX since #3098) — never re-derive it here.
327
- filePath: key,
328
- source: args.source,
329
- turnIndex: this._turnIndex,
330
- ts: Date.now(),
331
- });
332
- if (args.cwd !== undefined) {
333
- try {
334
- appendProjectChange(args.cwd, {
335
- seq: projectSeq,
336
- timestamp: new Date().toISOString(),
337
- sessionId: this.telemetrySessionId,
338
- turnIndex: this.turnIndex,
339
- source: args.source,
340
- filePath: path.resolve(args.filePath),
341
- fileSeq,
342
- changedRange: args.changedRange,
343
- });
344
- }
345
- catch (err) {
346
- args.onAppendError?.(err);
347
- }
348
- }
349
- return { projectSeq, fileSeq };
350
- }
351
- /**
352
- * Attributed mutations whose seq is strictly after `seq` — the receipt-level
353
- * counterpart of {@link getFilesChangedSince}. Bounded: entries evicted by
354
- * the ring cap are gone; compare `droppedMutationReceiptCount` against zero
355
- * before treating the result as a complete window.
356
- */
357
- getMutationsSince(seq) {
358
- // Shallow copies: a consumer mutating a returned receipt must not
359
- // corrupt the ring's internal state.
360
- return this._mutationReceipts
361
- .filter((r) => r.seq > seq)
362
- .map((r) => ({ ...r }));
363
- }
364
- get droppedMutationReceiptCount() {
365
- return this._droppedMutationReceipts;
366
- }
367
- /**
368
- * Atomically records write/edit ordering before debounce can coalesce it.
369
- *
370
- * Takes the classified {@link MutationKind}, not a raw tool name (#2423):
371
- * an edit-shaped tool pi-lens does not name still demotes this file to the
372
- * deferred pass, which is the safe timing.
373
- */
374
- recordMutationToolReceipt(filePath, kind) {
375
- if (kind === "write") {
376
- this._writtenThisTurn.set(filePath, true);
377
- }
378
- else if (this._writtenThisTurn.has(filePath)) {
379
- this._autofixDemotedThisTurn.set(filePath, true);
380
- // A later edit establishes a new final state that must be eligible for
381
- // the deferred pass even if the preceding write was fixed immediately.
382
- this._fixedThisTurn.delete(filePath);
383
- }
384
- return {
385
- autofixMode: kind === "edit" || this._autofixDemotedThisTurn.has(filePath)
386
- ? "deferred"
387
- : "immediate",
388
- };
389
- }
390
- get reportedThisTurn() {
391
- return this._reportedThisTurn;
392
- }
393
- nextWriteIndex() {
394
- this._writeIndex += 1;
395
- return this._writeIndex;
396
- }
397
- peekWriteIndex() {
398
- return this._writeIndex;
399
- }
400
- setTelemetryIdentity(identity) {
401
- if (identity.sessionId && identity.sessionId.trim()) {
402
- this._telemetrySessionId = identity.sessionId.trim();
403
- setTurnContextSession(this._telemetrySessionId);
404
- }
405
- const model = identity.model?.trim();
406
- const provider = identity.provider?.trim();
407
- if (model && provider) {
408
- this._telemetryModel = `${provider}/${model}`;
409
- }
410
- else if (model) {
411
- this._telemetryModel = model;
412
- }
413
- else if (provider) {
414
- this._telemetryModel = provider;
415
- }
416
- if (model)
417
- this._telemetryModelId = model;
418
- if (provider) {
419
- this._telemetryProvider = provider;
420
- this._telemetryProviderIsExplicit = true;
421
- }
422
- else if (model && !this._telemetryProviderIsExplicit) {
423
- // No explicit provider has ever been reported this session, so the
424
- // provider is (still) a derivation — re-derive it from the CURRENT
425
- // model id every time. Without this, a stale derived provider from
426
- // an earlier model would survive a mid-session model switch (e.g.
427
- // gpt-5-mini → claude-sonnet-4-5 with no explicit provider on
428
- // either call) because the old "has any provider ever been set"
429
- // guard treated the derived value as sticky. An explicit provider,
430
- // once set, is never touched here regardless of later model calls.
431
- this._telemetryProvider = deriveProviderFromModelId(model);
432
- }
433
- }
434
- get telemetrySessionId() {
435
- return this._telemetrySessionId;
436
- }
437
- /**
438
- * Pin the session identity to pi's STABLE session id and record why this
439
- * session started (#190). Called AFTER {@link resetForSession} (which assigns
440
- * a fresh random id), so the stable id — when pi provides one via
441
- * `ctx.sessionManager.getSessionId()` — wins and survives a quit→resume.
442
- */
443
- setSessionLifecycle(args) {
444
- if (args.sessionId && args.sessionId.trim()) {
445
- this._telemetrySessionId = args.sessionId.trim();
446
- this._hasStableSessionId = true;
447
- }
448
- this._lifecycleReason = args.reason;
449
- }
450
- /** Why the current session started: new | resume | fork | reload | startup. */
451
- get sessionLifecycleReason() {
452
- return this._lifecycleReason;
453
- }
454
- /** True once a stable pi session id has been pinned (vs the random fallback). */
455
- get hasStableSessionId() {
456
- return this._hasStableSessionId;
457
- }
458
- get telemetryModel() {
459
- return this._telemetryModel;
460
- }
461
- /** Raw model id (never the combined `provider/model` display string), blank
462
- * when the host hasn't reported one this session. Worklog/disposition
463
- * attribution (#1448) reads this, not {@link telemetryModel}. */
464
- get telemetryModelId() {
465
- return this._telemetryModelId;
466
- }
467
- /** Explicit host-reported provider, or a conservative derivation from the
468
- * model id (see clients/model-provider.ts), blank when neither is known. */
469
- get telemetryProviderId() {
470
- return this._telemetryProvider;
471
- }
472
- get turnIndex() {
473
- return this._turnIndex;
474
- }
475
- get projectSeq() {
476
- return this._projectSeq;
477
- }
478
- get turnStartProjectSeq() {
479
- return this._turnStartProjectSeq;
480
- }
481
- seedProjectSequence(projectSeq, fileSeqByPath) {
482
- this._projectSeq = Math.max(0, Math.floor(projectSeq));
483
- this._turnStartProjectSeq = this._projectSeq;
484
- this._fileSeq.clear();
485
- // Seeded per-file counters carry no projectSeq provenance, so start the
486
- // changed-since map empty; the graph fast path simply won't fire until an
487
- // in-process bump records a seq-stamped change (safe: falls back to sweep).
488
- this._fileLastProjectSeq.clear();
489
- for (const [filePath, seq] of fileSeqByPath ?? []) {
490
- this._fileSeq.set(normalizeMapKey(path.resolve(filePath)), Math.max(0, seq));
491
- }
492
- }
493
- bumpFileSeq(filePath) {
494
- // normalizeMapKey costs a realpath syscall per call — ~200us on Windows,
495
- // ~1.8us on POSIX since #3098; every caller that also needs the key must
496
- // reuse this one instead of paying it twice.
497
- const key = normalizeMapKey(path.resolve(filePath));
498
- this._projectSeq += 1;
499
- const fileSeq = (this._fileSeq.get(key) ?? 0) + 1;
500
- this._fileSeq.set(key, fileSeq);
501
- this._fileLastProjectSeq.set(key, this._projectSeq);
502
- return { projectSeq: this._projectSeq, fileSeq, key };
503
- }
504
- /**
505
- * Files whose most recent bump happened AFTER `seq` — i.e. every file the
506
- * review graph would need to re-ingest to catch up from a build taken at
507
- * projectSeq `seq` (#451). Returns NORMALIZED keys (normalizeMapKey +
508
- * path.resolve), the same form the builder's fileSignatures map uses, so the
509
- * caller can compare without re-normalizing.
510
- */
511
- getFilesChangedSince(seq) {
512
- const changed = [];
513
- for (const [key, lastSeq] of this._fileLastProjectSeq) {
514
- if (lastSeq > seq)
515
- changed.push(key);
516
- }
517
- return changed;
518
- }
519
- getFileSeq(filePath) {
520
- return this._fileSeq.get(normalizeMapKey(path.resolve(filePath))) ?? 0;
521
- }
522
- getFileSeqEntries() {
523
- return [...this._fileSeq.entries()];
524
- }
525
- get sessionGeneration() {
526
- return this._sessionGeneration;
527
- }
528
- isCurrentSession(generation) {
529
- return this._sessionGeneration === generation;
530
- }
531
- markStartupScanInFlight(name, generation) {
532
- this._startupScansInFlight.set(name, generation);
533
- }
534
- clearStartupScanInFlight(name, generation) {
535
- const owner = this._startupScansInFlight.get(name);
536
- if (owner === generation) {
537
- this._startupScansInFlight.delete(name);
538
- }
539
- }
540
- isStartupScanInFlight(name) {
541
- return this._startupScansInFlight.has(name);
542
- }
543
- formatPipelineCrashNotice(filePath, err) {
544
- const key = path.resolve(filePath);
545
- const count = (this._pipelineCrashCounts.get(key) ?? 0) + 1;
546
- this._pipelineCrashCounts.set(key, count);
547
- const message = err instanceof Error ? err.message : String(err);
548
- const shortMessage = message.split("\n")[0].slice(0, 220);
549
- const shouldSurface = count <= RUNTIME_CONFIG.crashNotice.alwaysShowFirstN ||
550
- count % RUNTIME_CONFIG.crashNotice.showEveryNth === 0;
551
- if (!shouldSurface)
552
- return "";
553
- return [
554
- "⚠️ pi-lens pipeline crashed while analyzing this write.",
555
- `File: ${path.basename(filePath)} | crash count this session: ${count}`,
556
- `Error: ${shortMessage}`,
557
- "Recovery: LSP service was reset. If this repeats, rerun with --no-lsp and report the file + stack.",
558
- ].join("\n");
559
- }
560
- getCrashEntries() {
561
- return Array.from(this._pipelineCrashCounts.entries());
562
- }
563
- get projectRoot() {
564
- return this._projectRoot;
565
- }
566
- set projectRoot(value) {
567
- this._projectRoot = normalizeMapKey(value);
568
- }
569
- get errorDebtBaseline() {
570
- return this._errorDebtBaseline;
571
- }
572
- set errorDebtBaseline(value) {
573
- this._errorDebtBaseline = value;
574
- }
575
- get cachedExports() {
576
- return this._cachedExports;
577
- }
578
- appendCascadeRun(run) {
579
- this._cascadeRuns.push(run);
580
- }
581
- appendCascadePromise(p) {
582
- if (this._pendingCascadeRuns.length < MAX_PENDING_CASCADE_RUNS) {
583
- this._pendingCascadeRuns.push(p);
584
- return;
585
- }
586
- // Preserve delivery for overflow rather than growing the per-edit array.
587
- // The settled run enters the same accumulator off-hook and is therefore
588
- // visible to the next turn-end drain.
589
- recordDegradationOnce({
590
- kind: "cascade-pending-cap",
591
- subject: "runtime-coordinator",
592
- reason: `deferred cascade admission capped at ${MAX_PENDING_CASCADE_RUNS}`,
593
- });
594
- void p
595
- .then((run) => this.appendCascadeRun(run))
596
- .catch(() => {
597
- // Pipeline promises are normally non-rejecting; preserve the existing
598
- // failure sink if a caller violates that contract.
599
- });
600
- }
601
- /**
602
- * The active turn_end settle clock, or undefined outside that wait.
603
- *
604
- * #1462 review F-F: every cascade compute in flight on this coordinator
605
- * reads the SAME clock. `_turnEndCascadeSettleStarts` is keyed by settle
606
- * token so `settleCascadeRuns` can distinguish its own drain from a nested
607
- * one, but this getter always answers with the latest active window —
608
- * there is one turn_end per runtime, not one per caller. A cascade begun
609
- * moments before turn_end and one begun long before it both measure elapsed
610
- * time against the same start, which is correct: they are racing the same
611
- * settle wait and share its deadline, not separate ones.
612
- */
613
- getTurnEndCascadeSettleStart() {
614
- let latest;
615
- for (const start of this._turnEndCascadeSettleStarts.values())
616
- latest = start;
617
- return latest;
618
- }
619
- /**
620
- * Drain the deferred cascade computes kicked off this turn (#450), racing them
621
- * against a bounded wait. Fulfilled runs feed the same accumulator as inline
622
- * runs (appendCascadeRun). A promise still pending at the cap is retained so a
623
- * late-resolving compute is picked up on the next turn_end rather than lost.
624
- * The stored promises never reject (pipeline guarantees an "error" skip-run).
625
- */
626
- async settleCascadeRuns(maxWaitMs, settleOptions = {}) {
627
- const pending = this._pendingCascadeRuns;
628
- if (pending.length === 0)
629
- return { settled: 0, timedOut: 0 };
630
- this._pendingCascadeRuns = [];
631
- const settleToken = settleOptions.trackTurnEndClock
632
- ? ++this._nextCascadeSettleToken
633
- : undefined;
634
- if (settleToken !== undefined) {
635
- this._turnEndCascadeSettleStarts.set(settleToken, Date.now());
636
- }
637
- try {
638
- // Track per-promise settlement so promises still in flight at the cap can be
639
- // carried over. A settled entry records its run; an unsettled one is re-parked.
640
- const tracked = pending.map((p) => {
641
- const entry = { done: false, promise: p };
642
- entry.promise = p.then((run) => {
643
- entry.done = true;
644
- entry.run = run;
645
- return run;
646
- });
647
- return entry;
648
- });
649
- const timeout = new Promise((resolve) => {
650
- setTimeout(resolve, maxWaitMs).unref?.();
651
- });
652
- await Promise.race([
653
- Promise.allSettled(tracked.map((t) => t.promise)),
654
- timeout,
655
- ]);
656
- let settled = 0;
657
- let timedOut = 0;
658
- for (const entry of tracked) {
659
- if (entry.done && entry.run) {
660
- this.appendCascadeRun(entry.run);
661
- settled += 1;
662
- }
663
- else {
664
- this._pendingCascadeRuns.push(entry.promise);
665
- timedOut += 1;
666
- }
667
- }
668
- return { settled, timedOut };
669
- }
670
- finally {
671
- if (settleToken !== undefined) {
672
- this._turnEndCascadeSettleStarts.delete(settleToken);
673
- }
674
- }
675
- }
676
- consumeCascadeRuns() {
677
- const runs = this._cascadeRuns;
678
- this._cascadeRuns = [];
679
- return runs;
680
- }
681
- /**
682
- * R1 (#1443 follow-up): non-destructive peek used by turn_end's read-only
683
- * fast path. A carried cascade run (or one still in flight) represents a
684
- * DELIVERY OPPORTUNITY, not turn activity — an agent that answers a question
685
- * without editing anything must still get yesterday's late finding. Before
686
- * this, the files-empty early return skipped `settleCascadeRuns` /
687
- * `consumeCascadeRuns` entirely on a read-only turn, so `beginTurn`'s next
688
- * carry pass saw the run as having survived a turn_start with no offsetting
689
- * drain and dropped it — burning the one-turn carry allowance on a turn that
690
- * never had a chance to deliver.
691
- */
692
- hasCascadeRuns() {
693
- // Carried, ALREADY-BUILT runs only. Pending (still-settling) computes are
694
- // deliberately excluded: a read-only turn that fell through for a pending
695
- // run would block on the full settle cap — every turn, forever, when the
696
- // compute never resolves (re-review finding F1). A pending run loses
697
- // nothing by waiting: settleCascadeRuns re-parks it and the next turn
698
- // that actually settles it delivers it.
699
- return this._cascadeRuns.length > 0;
700
- }
701
- recordInlineBlockers(filePath, summary, writeIndex, sources, lines, contentBaseline, diagnostics) {
702
- const recordedAtMs = Date.now();
703
- this._pendingInlineBlockers.set(path.resolve(filePath), {
704
- filePath,
705
- summary,
706
- writeIndex,
707
- sources,
708
- lines,
709
- diagnostics,
710
- recordedAtMs,
711
- stale: false,
712
- ...(contentBaseline
713
- ? {
714
- recordedSize: contentBaseline.size,
715
- recordedHash: contentBaseline.sha256,
716
- }
717
- : {}),
718
- });
719
- return recordedAtMs;
720
- }
721
- clearInlineBlockers(filePath) {
722
- this._pendingInlineBlockers.delete(path.resolve(filePath));
723
- }
724
- /**
725
- * #1631: demote a cached blocker to stale without dropping it (#1419
726
- * demote-not-drop). Called by the turn-boundary freshness sweep when the
727
- * file or one of its forward imports drifted on disk after the verdict.
728
- * Idempotent: re-marking an already-stale entry (for ANY reason) is a
729
- * no-op — a `"past-eof"` demotion already took this record out of the
730
- * authoritative channel, and `sweepInlineBlockerFreshness` re-checks next
731
- * turn once that heals. Returns true only when this call transitioned the
732
- * entry to stale, so a caller can log the demotion exactly once.
733
- */
734
- markInlineBlockerStale(filePath, reason = "dependency-drift") {
735
- const key = path.resolve(filePath);
736
- const existing = this._pendingInlineBlockers.get(key);
737
- if (!existing || existing.stale)
738
- return false;
739
- this._pendingInlineBlockers.set(key, {
740
- ...existing,
741
- stale: true,
742
- staleReason: reason,
743
- });
744
- return true;
745
- }
746
- /**
747
- * #1641: re-derive the past-EOF demotion for one inline-blocker record.
748
- * Unlike {@link markInlineBlockerStale} (a one-way latch for the
749
- * dependency-drift gate), this RE-ARMS: called every turn end with the
750
- * gate's freshly-computed verdict, it can both demote (rising edge) and
751
- * heal (falling edge — a transient shrink-then-restore of the file). Never
752
- * touches a record a sibling gate demoted (`staleReason !== "past-eof"`
753
- * while `stale`) — composing with #1631's dependency-drift gate means each
754
- * gate only heals its own demotions. Returns true only on an actual
755
- * transition, so the caller logs/resyncs exactly once per edge.
756
- */
757
- setInlineBlockerPastEofStale(filePath, isPastEof) {
758
- const key = path.resolve(filePath);
759
- const existing = this._pendingInlineBlockers.get(key);
760
- if (!existing)
761
- return false;
762
- if (existing.stale && existing.staleReason !== "past-eof")
763
- return false;
764
- const currentlyPastEof = !!existing.stale && existing.staleReason === "past-eof";
765
- if (currentlyPastEof === isPastEof)
766
- return false;
767
- this._pendingInlineBlockers.set(key, {
768
- ...existing,
769
- stale: isPastEof,
770
- staleReason: isPastEof ? "past-eof" : undefined,
771
- });
772
- return true;
773
- }
774
- /**
775
- * #2982: re-derive the self-drift demotion for one inline-blocker record.
776
- *
777
- * Modelled on {@link setInlineBlockerPastEofStale}, not on
778
- * {@link markInlineBlockerStale}: it RE-ARMS rather than latching, so a
779
- * file whose bytes drift and then come back (a checkout, a revert, an
780
- * editor writing the original content) un-demotes on the next sweep
781
- * instead of staying demoted for the session. That re-arm is what lets
782
- * this axis skip the #1950 delivery cap entirely — a record that can heal
783
- * needs no bounded-noise retirement, and applying the cap here would walk
784
- * an ast-grep or tree-sitter security finding out of turn-end rendering
785
- * permanently (#2982 review).
786
- *
787
- * Composes with the sibling gates the same way they compose with each
788
- * other: it never touches a record another gate demoted, and never heals a
789
- * demotion it did not make. Returns true only on an actual transition, so
790
- * the caller counts exactly one edge.
791
- */
792
- setInlineBlockerSelfDriftStale(filePath, isSelfDrift) {
793
- const key = path.resolve(filePath);
794
- const existing = this._pendingInlineBlockers.get(key);
795
- if (!existing)
796
- return false;
797
- if (existing.stale && existing.staleReason !== "self-drift")
798
- return false;
799
- const currentlySelfDrift = !!existing.stale && existing.staleReason === "self-drift";
800
- if (currentlySelfDrift === isSelfDrift)
801
- return false;
802
- // Omit the key rather than assigning `undefined` to it: under
803
- // `exactOptionalPropertyTypes` those are different, and writing the
804
- // `undefined` is a strictness spike the ratchet counts. Healing has to
805
- // drop `staleReason` off the rebuilt record, so destructure it away
806
- // instead of letting `...existing` carry the old value through.
807
- const { staleReason: _cleared, ...rest } = existing;
808
- this._pendingInlineBlockers.set(key, {
809
- ...rest,
810
- stale: isSelfDrift,
811
- ...(isSelfDrift ? { staleReason: "self-drift" } : {}),
812
- });
813
- return true;
814
- }
815
- /**
816
- * #1944: retire a past-EOF demotion after its ONE degraded delivery.
817
- *
818
- * The past-EOF gate demotes and re-arms, but nothing ever retired the
819
- * record, so a blocker whose file shrank past the cited lines re-served on
820
- * every turn end for the rest of the session — measured live at 80+
821
- * minutes on session 01a0234c. The re-serve is unbounded, not the six
822
- * turns the first evidence window suggested.
823
- *
824
- * It is unbounded because the record cannot resolve itself. The two
825
- * clearing events (`clearInlineBlockers` on a later dispatch of the same
826
- * path, `retireInlineBlockerOnConfirmedClean` on a fresh clean verdict)
827
- * both need the file to be looked at again, and the agent has no reason to
828
- * look: the coordinates it was handed do not exist. "Re-run to confirm" is
829
- * an instruction this record makes impossible to follow.
830
- *
831
- * So the delivery surface retires it after serving it once, degraded. That
832
- * is a DROP, which #1419's demote-not-drop rule normally forbids; the
833
- * exception is narrow and stated here. The finding was already delivered
834
- * this turn with its dead coordinates annotated, and what remains is a
835
- * message pinned to a line the file does not have. `deadLines` is required
836
- * and must be non-empty, so a record stale for any OTHER reason, or
837
- * past-EOF with no identified dead line, still stands.
838
- *
839
- * @returns true when an entry was retired — the caller records it, so the
840
- * suppression is never silent (#1432 Gap 1).
841
- */
842
- retireDemotedPastEofBlocker(filePath, deadLines) {
843
- if (deadLines.length === 0)
844
- return false;
845
- const key = path.resolve(filePath);
846
- const existing = this._pendingInlineBlockers.get(key);
847
- if (!existing)
848
- return false;
849
- // Only this gate's own demotion. A dependency-drift demotion (#1631)
850
- // keeps in-bounds coordinates the agent CAN re-run against, so it stays
851
- // in the store until a fresh verdict clears it.
852
- if (!existing.stale)
853
- return false;
854
- if ((existing.staleReason ?? "past-eof") !== "past-eof")
855
- return false;
856
- this._pendingInlineBlockers.delete(key);
857
- return true;
858
- }
859
- /**
860
- * #1950 fix-round F1: read the current delivery count WITHOUT committing
861
- * an increment. `runtime-turn.ts` needs the count a delivery would reach
862
- * BEFORE it knows whether that delivery will actually reach the agent —
863
- * the turn-end signature dedupe (`turn-end-findings-last`) can suppress a
864
- * turn whose rendered content is identical to the last one, and a
865
- * suppressed turn must not advance the counter (the agent never saw it).
866
- * The caller peeks, renders the tentative body, and only calls
867
- * {@link incrementInlineBlockerStaleDelivery} once it knows the turn's
868
- * content is NOT being suppressed.
869
- */
870
- peekInlineBlockerStaleDeliveryCount(filePath) {
871
- const key = path.resolve(filePath);
872
- return this._pendingInlineBlockers.get(key)?.staleDeliveryCount ?? 0;
873
- }
874
- /**
875
- * #1950: commit one more turn end that re-served this record while
876
- * `stale`. The caller (`runtime-turn.ts`) only calls this for a
877
- * `"dependency-drift"` demotion — a `"past-eof"` demotion retires after
878
- * its own single delivery (#1944, `retireDemotedPastEofBlocker`) and
879
- * never accumulates a count — and only AFTER confirming the turn's
880
- * content will not be suppressed by the signature dedupe (see
881
- * {@link peekInlineBlockerStaleDeliveryCount}), so this counts ACTUAL
882
- * deliveries the agent saw, not turn-end loop iterations. Returns the new
883
- * count, or 0 when there is no entry to count against (already retired,
884
- * or never recorded).
885
- */
886
- incrementInlineBlockerStaleDelivery(filePath) {
887
- const key = path.resolve(filePath);
888
- const existing = this._pendingInlineBlockers.get(key);
889
- if (!existing)
890
- return 0;
891
- const next = (existing.staleDeliveryCount ?? 0) + 1;
892
- this._pendingInlineBlockers.set(key, {
893
- ...existing,
894
- staleDeliveryCount: next,
895
- });
896
- return next;
897
- }
898
- /**
899
- * #1950: retire a dependency-drift demotion once it has been delivered
900
- * `DEPENDENCY_DRIFT_MAX_DELIVERIES` times with no re-run confirming or
901
- * clearing it. Unlike {@link retireDemotedPastEofBlocker}, the record is
902
- * NOT provably unrecoverable — its coordinates are still in bounds, and a
903
- * fresh dispatch of the file could still confirm or clear it. This is a
904
- * bounded-noise cap, not a "this can never resolve" verdict, and the
905
- * caller's ledger reason must say so (`demoted-finding-retired`'s "capped,
906
- * re-run can still confirm" vs. past-EOF's "retired, unrecoverable").
907
- *
908
- * Only ever touches this gate's OWN demotion (`staleReason ===
909
- * "dependency-drift"`) — a past-EOF demotion on the same file is that
910
- * gate's own record to retire, not this one's.
911
- *
912
- * @returns true when an entry was retired — the caller records it, so the
913
- * suppression is never silent (#1432 Gap 1).
914
- */
915
- retireDemotedDependencyDriftBlocker(filePath) {
916
- const key = path.resolve(filePath);
917
- const existing = this._pendingInlineBlockers.get(key);
918
- if (!existing)
919
- return false;
920
- if (!existing.stale)
921
- return false;
922
- if (existing.staleReason !== "dependency-drift")
923
- return false;
924
- this._pendingInlineBlockers.delete(key);
925
- return true;
926
- }
927
- /**
928
- * Retire a file's inline blocker because a FRESH, content-bound diagnostic
929
- * verdict proved it gone (#1561).
930
- *
931
- * The map was invalidated by exactly two events: a later dispatch of the
932
- * SAME path returning no blockers, and that path ceasing to exist (#1245).
933
- * Neither covers the common case that produced #1561 — a blocker on file F
934
- * whose cause lives in file G. Fixing G re-dispatches G, not F, so F's
935
- * verdict was never re-taken and "Unresolved from this turn" was re-injected
936
- * for the rest of the session. In the live case the agent then ran
937
- * `lsp_diagnostics` on F, pi-lens answered "confirmed clean", and the
938
- * blocker was STILL re-served on the next three turn ends: #571 wired that
939
- * tool's confirmed result into the widget footer and stopped there.
940
- *
941
- * This is the seam #1198 invariant 4 left undecided, decided: an affirmative
942
- * clean from the authoritative current view retires the stale verdict.
943
- *
944
- * Ordering (#1198 invariants 1-2). Both stores draw from the same
945
- * `nextWriteIndex()` counter, so when both sides are stamped the retire
946
- * requires the clean verdict to be strictly NEWER. A slow old clean that
947
- * settles after a fresh dispatch found real blockers must not erase them.
948
- * When either side is unstamped the two cannot be ordered at all; the fresh
949
- * confirmed verdict wins, because it is the only one of the two backed by an
950
- * actual observation of current content. Only production's single record site
951
- * is stamped, so in practice the unstamped branch is legacy callers/tests.
952
- *
953
- * COVERAGE (#1561 F1). A blocker is retired only by a verdict that can speak
954
- * for the tools that raised it. Inline blockers come from the whole dispatch,
955
- * not just the language server: `dispatcher.ts` collects every
956
- * `semantic === "blocking"` diagnostic across all runners, so eslint,
957
- * biome-check, actionlint, elixir/gleam-check and ast-grep security rules all
958
- * land in this map. A language-server check knows nothing about any of them,
959
- * so `coveredSources` must be a SUPERSET of the recorded sources or the entry
960
- * stands. This is a commit gate: a record whose provenance is unknown (no
961
- * `sources`) fails CLOSED and is never retired, because "we don't know what
962
- * raised this" must not resolve to "an LSP check can clear it".
963
- *
964
- * @returns true when an entry was retired — the caller logs it, so an
965
- * eviction is never silent (#1432 Gap 1).
966
- */
967
- retireInlineBlockerOnConfirmedClean(filePath, confirmedAtWriteIndex, coveredSources) {
968
- const key = path.resolve(filePath);
969
- const existing = this._pendingInlineBlockers.get(key);
970
- if (!existing)
971
- return false;
972
- if (existing.writeIndex !== undefined &&
973
- confirmedAtWriteIndex !== undefined &&
974
- confirmedAtWriteIndex <= existing.writeIndex) {
975
- return false;
976
- }
977
- // Fail closed on unknown provenance, and on any source the fresh verdict
978
- // did not consult. An eslint-origin blocker outliving an LSP-only clean is
979
- // the correct outcome — the next dispatch of that file is what clears it.
980
- if (!existing.sources || existing.sources.length === 0)
981
- return false;
982
- const covered = new Set(coveredSources ?? []);
983
- if (!existing.sources.every((source) => covered.has(source)))
984
- return false;
985
- this._pendingInlineBlockers.delete(key);
986
- return true;
987
- }
988
- reconcileInlineBlockers() {
989
- // Rebuild, never delete-in-place: `PathKeyedMap.delete()` re-normalizes
990
- // the key, and `normalizeMapKey` realpaths a live file but lowercases
991
- // the tail of a deleted one — so on Windows a mixed-case filename gets
992
- // a DIFFERENT delete-time key than its set-time key and the delete
993
- // misses (#1245, verified live: `MyCase.ts` survived reconcile).
994
- // Existence-checking the display path and rebuilding survivors avoids
995
- // the key-mismatch entirely; live survivors re-set to identical keys
996
- // (both realpath), so only the stale entries are dropped.
997
- const survivors = [];
998
- for (const [displayPath, value] of this._pendingInlineBlockers.entries()) {
999
- if (fs.existsSync(displayPath))
1000
- survivors.push([displayPath, value]);
1001
- }
1002
- if (survivors.length !== this._pendingInlineBlockers.size) {
1003
- this._pendingInlineBlockers.clear();
1004
- for (const [displayPath, value] of survivors) {
1005
- this._pendingInlineBlockers.set(displayPath, value);
1006
- }
1007
- }
1008
- }
1009
- /**
1010
- * Stale-entry reconcile (#1245): a blocker recorded for a file that has
1011
- * since been deleted can never be cleared — `clearInlineBlockers` fires
1012
- * only on a LATER dispatch of the same path, which a deleted file never
1013
- * gets. Every read of the blocker map (turn_end injection, git-guard
1014
- * size/summary, `syncGitGuardRecord`) therefore drops entries whose file no
1015
- * longer exists on disk: a blocker for a deleted file is stale by
1016
- * definition (the agent cannot fix it), so it must not re-surface every
1017
- * turn or gate a commit. The map is tiny (per-turn blockers) and reads are
1018
- * bounded (once per turn_end / tool_result), so the probe cost is
1019
- * negligible.
1020
- */
1021
- getInlineBlockersSnapshot() {
1022
- this.reconcileInlineBlockers();
1023
- return [...this._pendingInlineBlockers.values()];
1024
- }
1025
- consumeInlineBlockers() {
1026
- const entries = this.getInlineBlockersSnapshot();
1027
- this._pendingInlineBlockers.clear();
1028
- return entries;
1029
- }
1030
- recordActionableWarnings(warnings) {
1031
- for (const warning of warnings) {
1032
- this._actionableWarningsThisTurn.set(warning.id, warning);
1033
- }
1034
- }
1035
- peekActionableWarnings() {
1036
- return [...this._actionableWarningsThisTurn.values()];
1037
- }
1038
- clearActionableWarnings() {
1039
- this._actionableWarningsThisTurn.clear();
1040
- }
1041
- recordCodeQualityWarnings(warnings) {
1042
- for (const warning of warnings) {
1043
- this._codeQualityWarningsThisTurn.set(warning.id, warning);
1044
- }
1045
- }
1046
- peekCodeQualityWarnings() {
1047
- return [...this._codeQualityWarningsThisTurn.values()];
1048
- }
1049
- clearCodeQualityWarnings() {
1050
- this._codeQualityWarningsThisTurn.clear();
1051
- }
1052
- /** #484: the per-run diagnostics/autofix/format collector (accumulates
1053
- * across turns; consumed once at the agent_settled quiet window). Always
1054
- * present; callers gate recording behind the `lens-turn-summary` opt-in
1055
- * flag. */
1056
- get turnSummary() {
1057
- return this._turnSummary;
1058
- }
1059
- get complexityBaselines() {
1060
- return this._complexityBaselines;
1061
- }
1062
- get fixedThisTurn() {
1063
- // Self-referencing local so chained add() returns the same facade
1064
- // instead of re-entering this getter and allocating a new one per call
1065
- // (sonar S7725).
1066
- const facade = {
1067
- add: (filePath) => {
1068
- this._fixedThisTurn.set(filePath, true);
1069
- return facade;
1070
- },
1071
- has: (filePath) => this._fixedThisTurn.has(filePath),
1072
- delete: (filePath) => this._fixedThisTurn.delete(filePath),
1073
- clear: () => this._fixedThisTurn.clear(),
1074
- };
1075
- return facade;
1076
- }
1077
- get projectRulesScan() {
1078
- return this._projectRulesScan;
1079
- }
1080
- set projectRulesScan(value) {
1081
- this._projectRulesScan = value;
1082
- }
1083
- get readGuard() {
1084
- this._readGuard ??= new ReadGuard(this._telemetrySessionId);
1085
- return this._readGuard;
1086
- }
1087
- /**
1088
- * Record the canonical target `tool_call` resolved for `toolCallId`
1089
- * (#1642). The paired `tool_result` claims this exactly once via
1090
- * {@link takeToolCallAttribution} instead of re-deriving a path from its
1091
- * own diff metadata.
1092
- */
1093
- recordToolCallAttribution(toolCallId, attribution) {
1094
- this._toolCallAttributions.set(toolCallId, {
1095
- ...attribution,
1096
- recordedAt: Date.now(),
1097
- });
1098
- }
1099
- /**
1100
- * One-shot claim of a previously recorded tool-call attribution. Removed
1101
- * on read: a given `tool_call`/`tool_result` pair correlates exactly once,
1102
- * and leaving it behind would let a later, unrelated `tool_result` that
1103
- * happens to reuse a stale id inherit a stranger's resolved path.
1104
- *
1105
- * An expired entry (older than `TOOL_CALL_ATTRIBUTION_TTL_MS`) is treated
1106
- * as a miss and removed rather than returned — see the constant's doc.
1107
- */
1108
- takeToolCallAttribution(toolCallId) {
1109
- const attribution = this._toolCallAttributions.get(toolCallId);
1110
- if (attribution === undefined)
1111
- return undefined;
1112
- this._toolCallAttributions.delete(toolCallId);
1113
- if (Date.now() - attribution.recordedAt > TOOL_CALL_ATTRIBUTION_TTL_MS) {
1114
- return undefined;
1115
- }
1116
- return attribution;
1117
- }
1118
- /**
1119
- * Queue one mutation kind for `filePath` at `agent_end`. Returns `true`
1120
- * when this call created a pending entry or added a new kind, and `false`
1121
- * for a same-kind re-touch. Callers publish each kind's first transition
1122
- * without spamming repeated edits before `agent_end`.
1123
- */
1124
- deferMutation(filePath, cwd,
1125
- /** Host tool name; any mutating tool, not just `write`/`edit` (#2423). */
1126
- toolName, turnStateCwd, kind, ownerSessionId, originCwd) {
1127
- const key = path.resolve(filePath);
1128
- const now = Date.now();
1129
- const resolvedOriginCwd = originCwd ?? turnStateCwd;
1130
- const existing = this._pendingDeferredMutations.get(key);
1131
- if (existing) {
1132
- const addedKind = !existing.kinds.has(kind);
1133
- existing.lastTouchedAt = now;
1134
- existing.cwd = cwd;
1135
- existing.turnStateCwd = turnStateCwd;
1136
- existing.toolNames.add(toolName);
1137
- existing.kinds.add(kind);
1138
- existing.queuedTurnIndex = this._turnIndex;
1139
- existing.queuedTurnId = `${this._telemetrySessionId}:${this._turnIndex}`;
1140
- existing.ownerSessionId = ownerSessionId;
1141
- existing.originCwd = resolvedOriginCwd;
1142
- return addedKind;
1143
- }
1144
- this._pendingDeferredMutations.set(key, {
1145
- filePath: key,
1146
- cwd,
1147
- turnStateCwd,
1148
- firstTouchedAt: now,
1149
- lastTouchedAt: now,
1150
- toolNames: new Set([toolName]),
1151
- kinds: new Set([kind]),
1152
- queuedTurnIndex: this._turnIndex,
1153
- queuedTurnId: `${this._telemetrySessionId}:${this._turnIndex}`,
1154
- ownerSessionId,
1155
- originCwd: resolvedOriginCwd,
1156
- });
1157
- return true;
1158
- }
1159
- deferFormat(filePath, cwd,
1160
- /** Host tool name; any mutating tool, not just `write`/`edit` (#2423). */
1161
- toolName, turnStateCwd, ownerSessionId, originCwd) {
1162
- return this.deferMutation(filePath, cwd, toolName, turnStateCwd, "format", ownerSessionId, originCwd);
1163
- }
1164
- get pendingDeferredFormatCount() {
1165
- return this._pendingDeferredMutations.size;
1166
- }
1167
- get pendingDeferredMutationCount() {
1168
- return this._pendingDeferredMutations.size;
1169
- }
1170
- /**
1171
- * Legacy unconditional drain — still exposed for any caller that
1172
- * genuinely wants "everything, no ownership check" (and for tests). New
1173
- * flush call sites should prefer {@link claimDeferredFormatFiles}.
1174
- */
1175
- consumeDeferredFormatFiles() {
1176
- const records = [...this._pendingDeferredMutations.values()];
1177
- this._pendingDeferredMutations.clear();
1178
- return records;
1179
- }
1180
- /**
1181
- * Ownership-filtered drain (#791). Claims and removes only the records
1182
- * this flush is entitled to:
1183
- * - `ownerSessionId` unset on the record, OR `currentSessionId` unset, OR
1184
- * they match → claimed as "same session" (the common case, and the
1185
- * fail-safe default when either side lacks a stable session id).
1186
- * - otherwise (both known, and they differ) the record belongs to a
1187
- * DIFFERENT session (e.g. a concurrent in-process secondary/subagent)
1188
- * and is left queued for its owner's own flush — UNLESS it has sat
1189
- * unclaimed longer than `staleAfterMs` (the owner presumably died), in
1190
- * which case this flush claims it as an orphan-recovery fallback ONLY
1191
- * when `currentOriginCwd` (this flush's own workspace/worktree) exactly
1192
- * matches the record's `originCwd` (the cwd it was actually queued
1193
- * under, #1642 F3 — NOT `turnStateCwd`, which is always the workspace
1194
- * root by design and so would match every session unconditionally). A
1195
- * mismatch means the record belongs to a DIFFERENT checkout/worktree
1196
- * than the one now running `agent_end` — claiming it across that
1197
- * boundary is exactly how a worktree's queued edit got formatted onto
1198
- * the parent checkout in the reported incident. It is left queued
1199
- * (NOT deleted) and logged instead: a legitimate crashed-session
1200
- * orphan from, say, a monorepo subdir must still be claimable later by
1201
- * a flush whose origin actually matches, or stay visible for
1202
- * observability — deleting it here would drop it forever with no
1203
- * origin ever able to reclaim it.
1204
- *
1205
- * Returns the claimed records, the left-queued mismatched-origin records,
1206
- * and per-skipped record why it was left behind — callers use this for
1207
- * `agent_end`'s latency-log provenance and for the "stale fallback
1208
- * fired"/"orphan mismatch" log lines.
1209
- */
1210
- claimDeferredFormatFiles(currentSessionId, now, staleAfterMs, currentOriginCwd) {
1211
- const claimed = [];
1212
- const staleClaimed = [];
1213
- const deferredToOwner = [];
1214
- const droppedOrphans = [];
1215
- for (const [key, record] of this._pendingDeferredMutations) {
1216
- const sameSession = record.ownerSessionId === undefined ||
1217
- currentSessionId === undefined ||
1218
- record.ownerSessionId === currentSessionId;
1219
- if (sameSession) {
1220
- claimed.push(record);
1221
- this._pendingDeferredMutations.delete(key);
1222
- continue;
1223
- }
1224
- const age = now - record.lastTouchedAt;
1225
- if (age > staleAfterMs) {
1226
- // #1642 F3: origin identity is required before an orphan-recovery
1227
- // claim, not just staleness. `currentOriginCwd === undefined`
1228
- // means the caller can't state its own origin — fail-safe as
1229
- // "can't prove different" (same posture as the sameSession check
1230
- // above), not as a free pass to claim across a KNOWN mismatch.
1231
- const originMatches = currentOriginCwd === undefined ||
1232
- pathsEqual(record.originCwd, currentOriginCwd);
1233
- if (originMatches) {
1234
- this._pendingDeferredMutations.delete(key);
1235
- staleClaimed.push(record);
1236
- }
1237
- else {
1238
- // Left queued — see the doc above for why this must not be
1239
- // deleted. Reported once per claim attempt; a genuinely
1240
- // abandoned origin will keep surfacing here, which is the
1241
- // intended, safer trade-off over silent, permanent loss.
1242
- droppedOrphans.push(record);
1243
- }
1244
- continue;
1245
- }
1246
- deferredToOwner.push(record);
1247
- }
1248
- return { claimed, staleClaimed, deferredToOwner, droppedOrphans };
1249
- }
1250
- /** Return claimed records that were never started by an aborted drain. */
1251
- requeueDeferredFormatFiles(records) {
1252
- for (const record of records) {
1253
- const key = path.resolve(record.filePath);
1254
- const existing = this._pendingDeferredMutations.get(key);
1255
- if (existing) {
1256
- for (const kind of record.kinds)
1257
- existing.kinds.add(kind);
1258
- for (const toolName of record.toolNames)
1259
- existing.toolNames.add(toolName);
1260
- continue;
1261
- }
1262
- this._pendingDeferredMutations.set(key, {
1263
- ...record,
1264
- kinds: new Set(record.kinds),
1265
- toolNames: new Set(record.toolNames),
1266
- });
1267
- }
1268
- }
1269
- claimDeferredMutations(currentSessionId, now, staleAfterMs, currentOriginCwd) {
1270
- return this.claimDeferredFormatFiles(currentSessionId, now, staleAfterMs, currentOriginCwd);
1271
- }
1272
- requeueDeferredMutations(records) {
1273
- this.requeueDeferredFormatFiles(records);
1274
- }
1275
- shouldWarmLspOnRead(filePath, maxAgeMs = 120_000) {
1276
- const state = this._lspReadWarmState.get(path.resolve(filePath));
1277
- if (!state)
1278
- return true;
1279
- if (state.status === "warming")
1280
- return false;
1281
- return Date.now() - state.ts > maxAgeMs;
1282
- }
1283
- markLspReadWarmStarted(filePath) {
1284
- this._lspReadWarmState.set(path.resolve(filePath), {
1285
- status: "warming",
1286
- ts: Date.now(),
1287
- });
1288
- }
1289
- markLspReadWarmCompleted(filePath) {
1290
- this._lspReadWarmState.set(path.resolve(filePath), {
1291
- status: "ready",
1292
- ts: Date.now(),
1293
- });
1294
- }
1295
- clearLspReadWarmState(filePath) {
1296
- this._lspReadWarmState.delete(path.resolve(filePath));
1297
- }
1298
- }
1299
- /**
1300
- * Read the live model identity off a pi extension context (#1655 item 2).
1301
- *
1302
- * `ExtensionContext.model` is `AgentSession.model`, projected through a lazy
1303
- * getter guarded by `assertActive()`
1304
- * (`@earendil-works/pi-coding-agent/dist/core/extensions/runner.js:488-491`;
1305
- * `dist/core/agent-session.js:580-582`). The value is a `Model` with `id` and
1306
- * `provider` (`@earendil-works/pi-ai/dist/types.d.ts:661-667`), or `undefined`
1307
- * when no model is selected.
1308
- *
1309
- * Because the getter is lazy and guarded, reading it on a REPLACED runner
1310
- * throws. Telemetry identity is advisory; a stale ctx must degrade to "no
1311
- * identity", never take down the event handler that happened to carry it.
1312
- */
1313
- export function readHostModelIdentity(ctx) {
1314
- try {
1315
- const model = ctx?.model;
1316
- return {
1317
- model: typeof model?.id === "string" ? model.id : undefined,
1318
- provider: typeof model?.provider === "string" ? model.provider : undefined,
1319
- };
1320
- }
1321
- catch {
1322
- return {};
1323
- }
1324
- }