@herbertgao/pi-extensions 2026.8.13 → 2026.8.15

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 (263) hide show
  1. package/README.md +19 -14
  2. package/THIRD_PARTY_NOTICES.md +0 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
  4. package/node_modules/@czottmann/pi-automode/README.md +5 -1
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
  11. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  12. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  13. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  14. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  16. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  19. package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
  20. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
  21. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  22. package/node_modules/@narumitw/pi-btw/README.md +125 -88
  23. package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
  24. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  25. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  26. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  27. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
  28. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  29. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  30. package/node_modules/@tifan/pi-handoff/README.md +36 -13
  31. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  32. package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
  33. package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
  34. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  35. package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
  36. package/node_modules/@tifan/pi-recap/README.md +1 -1
  37. package/node_modules/@tifan/pi-recap/package.json +2 -2
  38. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  39. package/node_modules/@tifan/pi-rename/README.md +42 -21
  40. package/node_modules/@tifan/pi-rename/package.json +1 -1
  41. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  42. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  43. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  44. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  45. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  46. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  47. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  48. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  50. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  51. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  52. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  53. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  54. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  55. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  56. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  57. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  58. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  59. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  60. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  61. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  63. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  68. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  69. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  85. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  86. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  87. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  88. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  89. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  90. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  91. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  92. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  93. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  94. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  95. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  96. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  97. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  98. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  99. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  100. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  101. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  102. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  104. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  105. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  106. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  107. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  108. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  109. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  110. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  111. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  112. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  113. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  114. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  115. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  116. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  117. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  118. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  119. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  120. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  121. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  122. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  123. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  124. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  125. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  126. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  127. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  129. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  130. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  131. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  132. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  133. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  134. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  135. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  136. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  137. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  138. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  139. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  140. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  141. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  142. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  143. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  144. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  145. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  146. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  147. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  148. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  149. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  150. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  151. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  152. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  153. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  154. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  155. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  156. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  157. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  158. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  159. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  160. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  161. package/node_modules/pi-lens/dist/index.js +11243 -6651
  162. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  163. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  164. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  165. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  166. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  167. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  168. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  169. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  170. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  171. package/node_modules/pi-lens/package.json +5 -1
  172. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  173. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  174. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  175. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  176. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  177. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  178. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  179. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  224. package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
  225. package/node_modules/pi-mcp-adapter/README.md +5 -2
  226. package/node_modules/pi-mcp-adapter/cli.js +4 -4
  227. package/node_modules/pi-mcp-adapter/commands.ts +1 -1
  228. package/node_modules/pi-mcp-adapter/config.ts +1 -0
  229. package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
  230. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  231. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
  232. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
  233. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
  234. package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
  235. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  236. package/node_modules/pi-mcp-adapter/index.ts +90 -6
  237. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
  238. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
  239. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
  240. package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
  241. package/node_modules/pi-mcp-adapter/package.json +1 -1
  242. package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
  243. package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
  244. package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
  245. package/node_modules/pi-mcp-adapter/types.ts +3 -1
  246. package/node_modules/pi-web-access/CHANGELOG.md +42 -0
  247. package/node_modules/pi-web-access/README.md +26 -10
  248. package/node_modules/pi-web-access/curator-page.ts +12 -3
  249. package/node_modules/pi-web-access/curator-server.ts +3 -1
  250. package/node_modules/pi-web-access/extract.ts +88 -13
  251. package/node_modules/pi-web-access/gemini-search.ts +10 -5
  252. package/node_modules/pi-web-access/github-extract.ts +47 -3
  253. package/node_modules/pi-web-access/index.ts +40 -40
  254. package/node_modules/pi-web-access/package.json +1 -1
  255. package/node_modules/pi-web-access/page-query.ts +61 -8
  256. package/node_modules/pi-web-access/xcrawl.ts +264 -0
  257. package/package.json +19 -22
  258. package/node_modules/@herbertgao/pi-stash/README.md +0 -34
  259. package/node_modules/@herbertgao/pi-stash/package.json +0 -51
  260. package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
  261. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  262. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  263. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -6,6 +6,7 @@ import { clearGitGuardTestFailure, mergeGitGuardTestFailure, writeGitGuardRecord
6
6
  import { cascadeSettleWaitMs } from "./cascade-budget.js";
7
7
  import { logCascade } from "./cascade-logger.js";
8
8
  import { normalizeMapKey } from "./path-utils.js";
9
+ import { compareOrdinal } from "./string-utils.js";
9
10
  import { resolveRunnerPath, toRunnerDisplayPath, } from "./dispatch/runner-context.js";
10
11
  import { getKnipIgnorePatterns } from "./file-utils.js";
11
12
  import { formatCacheAgeLabel } from "./finding-delivery-gate.js";
@@ -29,19 +30,21 @@ import { RUNTIME_CONFIG } from "./runtime-config.js";
29
30
  import { isSubagentSession } from "./subagent-mode.js";
30
31
  import { formatRunDurationMs } from "./run-duration.js";
31
32
  import { MAX_ADVISORY_AFFECTED_FILES, gateFindingsByPathFreshness, snapshotAdvisoryProvenance, } from "./advisory-provenance.js";
32
- import { sweepInlineBlockerFreshness } from "./blocker-freshness.js";
33
+ import { DEPENDENCY_DRIFT_MAX_DELIVERIES, sweepInlineBlockerFreshness, } from "./blocker-freshness.js";
33
34
  import { sweepInlineBlockerPastEof } from "./blocker-past-eof.js";
34
35
  // #2001/#2002: collect-later delivery for slow auxiliary LSP servers.
35
36
  import { getLSPService } from "./lsp/index.js";
36
- import { drainPendingAuxiliaryCoverage, isPendingAuxiliaryPastRearmTtl, markPendingAuxiliaryCoverage, } from "./lsp/pending-aux-coverage.js";
37
+ import { drainPendingAuxCapEvictedCount, drainPendingAuxiliaryCoverage, isPendingAuxiliaryPastRearmTtl, rearmPendingAuxiliaryCoverage, MAX_LATE_AUX_REARMS, pendingAuxiliaryCoverageSize, } from "./lsp/pending-aux-coverage.js";
37
38
  import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
39
+ import { drainPendingRunnerFindings, dropStaleRunnerFindings, pendingRunnerFindingsSize, } from "./dispatch/pending-runner-findings.js";
38
40
  // #1631 review V2: moved to its own leaf module so a low-level store
39
41
  // (widget-state.ts) can use the marker without importing this orchestrator —
40
42
  // see clients/stale-marker.ts's doc comment.
41
43
  import { incrementDegradationCount } from "./degradation-ledger.js";
42
- import { degradeDemotedFindingBody, formatRetirementNote, } from "./demoted-finding-render.js";
44
+ import { degradeDemotedFindingBody, formatDeliveryCapNote, formatRetirementNote, } from "./demoted-finding-render.js";
43
45
  import { STALE_LINE_MARKER } from "./stale-marker.js";
44
- import { getWidgetBlockingFilesForSweep, markWidgetFileBlockersStale, } from "./widget-state.js";
46
+ import { getActiveSessionId } from "./session-lifecycle.js";
47
+ import { getWidgetBlockingFilesForSweep, markWidgetFileBlockersStale, recordRunner, } from "./widget-state.js";
45
48
  /**
46
49
  * #1617: turn_end reads gitleaks/govulncheck/trivy straight from their
47
50
  * session-scan caches and formats them into advisory/blocker text — a
@@ -152,6 +155,9 @@ function scheduleLSPIdleReset(resetFn, delayMs, options = {}) {
152
155
  if (options.isCurrentSession && !options.isCurrentSession()) {
153
156
  return;
154
157
  }
158
+ if (options.isPrimarySession && !options.isPrimarySession()) {
159
+ return;
160
+ }
155
161
  resetFn();
156
162
  }
157
163
  catch (err) {
@@ -286,6 +292,11 @@ export async function handleTurnEnd(deps) {
286
292
  // instead of dying unrendered. `hasCascadeRuns()` is a cheap peek (no
287
293
  // pending work almost every turn), so the common read-only turn still
288
294
  // takes the early return below.
295
+ // A foreign live owner must not deliver another session's pending findings.
296
+ // A no-file turn falls through when this process has pending runner work so
297
+ // the ordinary freshness gate and delivery cache can run. Max-cycle cleanup
298
+ // below intentionally remains a terminal reset; its pending work stays in
299
+ // the bounded handoff store for the next eligible turn.
289
300
  if (files.length === 0 && !runtime.hasCascadeRuns()) {
290
301
  // A genuinely clean session must invalidate the persisted guard record.
291
302
  // Blocker records are retained only while the runtime still reports one.
@@ -310,11 +321,20 @@ export async function handleTurnEnd(deps) {
310
321
  const sessionGeneration = runtime.sessionGeneration;
311
322
  scheduleLSPIdleReset(resetLSPService, idleResetMs, {
312
323
  isCurrentSession: () => runtime.isCurrentSession(sessionGeneration),
324
+ // #2157 fix round 2: a secondary (subagent) evaluation's own timer
325
+ // must not release the primary's shared fleet — see the option's
326
+ // doc comment on `scheduleLSPIdleReset`.
327
+ isPrimarySession: () => {
328
+ const activePrimarySessionId = getActiveSessionId();
329
+ return (activePrimarySessionId === undefined ||
330
+ activePrimarySessionId === runtime.telemetrySessionId);
331
+ },
313
332
  onError: (err) => dbg(`lsp idle reset failed: ${err}`),
314
333
  });
315
334
  }
316
335
  resetFormatService();
317
- return;
336
+ if (pendingRunnerFindingsSize() === 0)
337
+ return;
318
338
  }
319
339
  // Cancel any pending idle reset since we're actively working. #1618: also
320
340
  // checks `pendingSweepRearm` — a timer deferred behind an in-flight
@@ -323,7 +343,7 @@ export async function handleTurnEnd(deps) {
323
343
  // read "nothing pending" and skip the cancel while a rearm was still
324
344
  // queued to fire the instant the sweep released its hold — resurrecting
325
345
  // idle reset on a session that had since gone back to active editing.
326
- if (lspIdleResetTimeout || pendingSweepRearm) {
346
+ if (files.length > 0 && (lspIdleResetTimeout || pendingSweepRearm)) {
327
347
  cancelLSPIdleReset();
328
348
  dbg("turn_end: cancelled pending LSP idle reset (active editing)");
329
349
  }
@@ -404,9 +424,20 @@ export async function handleTurnEnd(deps) {
404
424
  // Re-surface inline blockers from this turn that the agent didn't fix.
405
425
  // These were shown inline during write/edit but the agent moved on without resolving them.
406
426
  const unresolvedBlockers = runtime.getInlineBlockersSnapshot();
407
- /** #1944: past-EOF demotions retired after their single degraded delivery. */
427
+ /** #1944/#1950: demotions retired after their delivery limit. */
408
428
  let demotedFindingsRetired = 0;
409
- for (const { filePath: bPath, summary, stale } of unresolvedBlockers) {
429
+ /**
430
+ * #1950 fix-round F1: dependency-drift delivery-count commits, deferred
431
+ * until this turn's content is confirmed NOT suppressed by the
432
+ * `turn-end-findings-last` signature dedupe further down. That dedupe
433
+ * silences a turn whose rendered content is byte-identical to the last
434
+ * one actually delivered — the agent never sees a suppressed turn, so
435
+ * committing the counter for it would count a delivery that didn't
436
+ * happen. Each entry here is invoked only from the "not suppressed"
437
+ * branch below.
438
+ */
439
+ const pendingDependencyDriftDeliveries = [];
440
+ for (const { filePath: bPath, summary, stale, staleReason, } of unresolvedBlockers) {
410
441
  const displayPath = toRunnerDisplayPath(cwd, bPath);
411
442
  if (stale) {
412
443
  // #1631: demoted — out of the authoritative blocker channel and into the
@@ -423,6 +454,7 @@ export async function handleTurnEnd(deps) {
423
454
  const deadLines = blockerPastEof.deadLinesByPath.get(bPath) ?? [];
424
455
  const degraded = degradeDemotedFindingBody(summary, { deadLines });
425
456
  const retired = runtime.retireDemotedPastEofBlocker(bPath, deadLines);
457
+ let retirementNote;
426
458
  if (retired) {
427
459
  demotedFindingsRetired += 1;
428
460
  // Bounded by the ledger's own per-kind/subject tally, and the subject
@@ -432,10 +464,43 @@ export async function handleTurnEnd(deps) {
432
464
  subject: `inline-blocker:${displayPath}`,
433
465
  reason: `file shrank past cited line(s) ${deadLines.join(", ")}; retired after one degraded delivery`,
434
466
  });
467
+ retirementNote = formatRetirementNote(deadLines);
468
+ }
469
+ else if (staleReason === "dependency-drift") {
470
+ // #1950: a dependency-drift demotion is recoverable (its coordinates
471
+ // are still in bounds), so it does NOT retire after one delivery like
472
+ // the past-EOF case above — but nothing capped how many times the
473
+ // SAME demoted-but-unconfirmed record re-serves, and incident data
474
+ // showed repeat deliveries carrying near-zero information after the
475
+ // first. Cap it at DEPENDENCY_DRIFT_MAX_DELIVERIES instead.
476
+ //
477
+ // The count driving THIS render is a peek (fix-round F1): the actual
478
+ // increment is deferred to `pendingDependencyDriftDeliveries` below,
479
+ // committed only once this turn's content is known to reach the
480
+ // agent, so a suppressed turn's tentative render never advances the
481
+ // stored count.
482
+ const tentativeCount = runtime.peekInlineBlockerStaleDeliveryCount(bPath) + 1;
483
+ if (tentativeCount >= DEPENDENCY_DRIFT_MAX_DELIVERIES) {
484
+ retirementNote = formatDeliveryCapNote(tentativeCount);
485
+ }
486
+ pendingDependencyDriftDeliveries.push(() => {
487
+ const deliveryCount = runtime.incrementInlineBlockerStaleDelivery(bPath);
488
+ if (deliveryCount >= DEPENDENCY_DRIFT_MAX_DELIVERIES) {
489
+ const capRetired = runtime.retireDemotedDependencyDriftBlocker(bPath);
490
+ if (capRetired) {
491
+ demotedFindingsRetired += 1;
492
+ incrementDegradationCount({
493
+ kind: "demoted-finding-retired",
494
+ subject: `inline-blocker:${displayPath}`,
495
+ reason: `capped after ${deliveryCount} deliveries with no re-run; re-run can still confirm`,
496
+ });
497
+ }
498
+ }
499
+ });
435
500
  }
436
501
  // @delivery-surface: runtime-turn:unresolved-inline-blocker
437
502
  advisoryParts.push(`${STALE_LINE_MARKER} ${displayPath}:\n${degraded.body}` +
438
- (retired ? `\n${formatRetirementNote(deadLines)}` : ""));
503
+ (retirementNote ? `\n${retirementNote}` : ""));
439
504
  }
440
505
  else {
441
506
  // @delivery-surface: runtime-turn:unresolved-inline-blocker
@@ -1427,7 +1492,7 @@ export async function handleTurnEnd(deps) {
1427
1492
  }
1428
1493
  }
1429
1494
  for (const { display, abs, isNeighbor } of candidates) {
1430
- const target = testRunnerClient.getTestRunTarget(abs, cwd);
1495
+ const target = testRunnerClient.getTestRunTarget(abs, cwd, runtime.turnIndex);
1431
1496
  if (target && !seen.has(target.testFile)) {
1432
1497
  seen.add(target.testFile);
1433
1498
  targets.push(target);
@@ -1460,7 +1525,11 @@ export async function handleTurnEnd(deps) {
1460
1525
  files: provenanceFiles,
1461
1526
  });
1462
1527
  cacheManager.writeCache("test-runner-findings", { ...(priorTestCache ?? { content: "" }), testRunGeneration }, cwd);
1463
- Promise.allSettled(targets.map((t) => testRunnerClient.runTestFileAsync(t.testFile, cwd, t.runner, t.config)))
1528
+ Promise.allSettled(targets.map((t) => testRunnerClient.runTestFileAsync(t.testFile, cwd, {
1529
+ runner: t.runner,
1530
+ config: t.config,
1531
+ turnIndex: firedAtTurn,
1532
+ })))
1464
1533
  .then((results) => {
1465
1534
  const publishedAgainst = snapshotAdvisoryProvenance({
1466
1535
  cwd,
@@ -1809,6 +1878,78 @@ export async function handleTurnEnd(deps) {
1809
1878
  });
1810
1879
  }
1811
1880
  cacheManager.incrementTurnCycle(cwd, currentOwner);
1881
+ // Collect-later CLI runners continue off the write path. Their completed
1882
+ // diagnostics use the same freshness gate as late auxiliary findings and
1883
+ // enter the ordinary turn-end advisory delivery channel.
1884
+ const runnerFindingsStart = Date.now();
1885
+ // Turn-end delivery is deliberately non-blocking. Collect already-settled
1886
+ // results and requeue the rest; the edit path already paid the deferral
1887
+ // decision, so another 2s wait would charge every turn while a runner is
1888
+ // still in flight (#2122 F5).
1889
+ const pendingRunnerFindings = await drainPendingRunnerFindings(0);
1890
+ let runnerFindingsDelivered = 0;
1891
+ let runnerFindingsStale = 0;
1892
+ let runnerFindingsFailed = 0;
1893
+ let runnerFindingsDropped = 0;
1894
+ const runnerFindingsDeliveredIds = [];
1895
+ for (const pending of pendingRunnerFindings) {
1896
+ const result = pending.result;
1897
+ if (!result)
1898
+ continue;
1899
+ recordRunner(pending.filePath, pending.runnerId, result.status, result.diagnostics.length, Date.now() - pending.markedAtMs, pending.writeIndex);
1900
+ if (result.status === "failed") {
1901
+ runnerFindingsFailed += 1;
1902
+ const detail = result.failureMessage ? `: ${result.failureMessage}` : "";
1903
+ // @delivery-surface: runtime-turn:late-runner-findings
1904
+ advisoryParts.push(`❌ Deferred runner ${pending.runnerId} failed (${result.failureKind ?? "unknown"})${detail}`);
1905
+ continue;
1906
+ }
1907
+ const findings = result.diagnostics;
1908
+ if (findings.length === 0)
1909
+ continue;
1910
+ const gate = gateFindingsByPathFreshness({
1911
+ store: "late-runner-findings",
1912
+ findings,
1913
+ cwd,
1914
+ scannedAt: pending.markedAtMs,
1915
+ citedPath: (finding) => finding.filePath,
1916
+ });
1917
+ runnerFindingsStale += gate.stale.length;
1918
+ if (gate.stale.length > 0) {
1919
+ // The runner answered for bytes older than the latest edit. Do not
1920
+ // re-arm this completed answer: only a new runner query can restore
1921
+ // coverage for the refreshed bytes.
1922
+ dropStaleRunnerFindings(pending);
1923
+ runnerFindingsDropped += 1;
1924
+ }
1925
+ if (gate.live.length === 0)
1926
+ continue;
1927
+ const displayPath = toRunnerDisplayPath(cwd, pending.filePath);
1928
+ const lines = gate.live.map((finding) => ` ${displayPath}:${finding.line ?? 1}:${finding.column ?? 1} [${finding.rule ?? finding.id}] ${finding.message}`);
1929
+ runnerFindingsDelivered += gate.live.length;
1930
+ for (const finding of gate.live) {
1931
+ if (runnerFindingsDeliveredIds.length < 50) {
1932
+ runnerFindingsDeliveredIds.push(finding.id);
1933
+ }
1934
+ }
1935
+ // @delivery-surface: runtime-turn:late-runner-findings
1936
+ advisoryParts.push(`⏱️ Late runner diagnostics (${pending.runnerId} completed after the edit):\n${lines.join("\n")}`);
1937
+ }
1938
+ logLatency({
1939
+ type: "phase",
1940
+ toolName: "turn_end",
1941
+ filePath: cwd,
1942
+ phase: "late_runner_findings",
1943
+ durationMs: Date.now() - runnerFindingsStart,
1944
+ metadata: {
1945
+ pending: pendingRunnerFindings.length,
1946
+ delivered: runnerFindingsDelivered,
1947
+ stale: runnerFindingsStale,
1948
+ failed: runnerFindingsFailed,
1949
+ dropped: runnerFindingsDropped,
1950
+ deliveredIds: runnerFindingsDeliveredIds,
1951
+ },
1952
+ });
1812
1953
  // #2001/#2002: collect-later delivery for auxiliary LSP servers whose
1813
1954
  // aux-grace window expired without a publication (opengrep on Windows:
1814
1955
  // ~8s per scan against a 2s grace — the scanner's eventual findings sat
@@ -1820,12 +1961,21 @@ export async function handleTurnEnd(deps) {
1820
1961
  // client drops silently.
1821
1962
  const lateAuxStart = Date.now();
1822
1963
  const drainedPairs = drainPendingAuxiliaryCoverage();
1964
+ // #2168: cap evictions retire a pair before any drain can observe it — read
1965
+ // and reset that count here so it folds into this turn's reconciliation
1966
+ // sum instead of the pair vanishing uncounted.
1967
+ const lateAuxCapEvicted = drainPendingAuxCapEvictedCount();
1823
1968
  let lateAuxDelivered = 0;
1824
1969
  let lateAuxStale = 0;
1825
1970
  let lateAuxMissing = 0;
1826
1971
  let lateAuxRearmed = 0;
1827
1972
  let lateAuxClientGone = 0;
1828
1973
  let lateAuxProbeFailed = 0;
1974
+ let lateAuxCleanConfirmed = 0;
1975
+ let lateAuxExpired = 0;
1976
+ let lateAuxCeilingExhausted = 0;
1977
+ let lateAuxAnswered = 0;
1978
+ const lateAuxStuckPairs = [];
1829
1979
  if (drainedPairs.length > 0) {
1830
1980
  const byFile = new Map();
1831
1981
  for (const pair of drainedPairs) {
@@ -1843,23 +1993,42 @@ export async function handleTurnEnd(deps) {
1843
1993
  cached = await service.readCachedDiagnosticsForServers(lateAuxPath, new Set(pairs.map((p) => p.serverId)));
1844
1994
  }
1845
1995
  catch {
1846
- // #2027 round-1 P3-2: count dropped pairs — never silent.
1996
+ // #2027 round-1 P3-2 / #2167 R2-2: a transient probe rejection
1997
+ // tells us nothing about the pair's content, so treat it like the
1998
+ // "still scanning" branch below — re-arm under the SAME
1999
+ // ceiling/TTL bound rather than dropping the coverage outright.
2000
+ // `probeFailed` stays an honest per-turn failure count; it is
2001
+ // informational (like `stale`/`missing`), not a terminal bucket,
2002
+ // since the pair itself still resolves through rearmed/
2003
+ // ceilingExhausted/expired below.
1847
2004
  lateAuxProbeFailed += pairs.length;
1848
2005
  for (const pair of pairs) {
1849
- markPendingAuxiliaryCoverage(pair.filePath, [pair.serverId], Date.now());
2006
+ if (!isPendingAuxiliaryPastRearmTtl(pair) &&
2007
+ (pair.rearmCount ?? 0) < MAX_LATE_AUX_REARMS) {
2008
+ rearmPendingAuxiliaryCoverage(pair);
2009
+ lateAuxRearmed += 1;
2010
+ }
2011
+ else if (isPendingAuxiliaryPastRearmTtl(pair)) {
2012
+ lateAuxExpired += 1;
2013
+ }
2014
+ else {
2015
+ lateAuxCeilingExhausted += 1;
2016
+ }
1850
2017
  }
1851
2018
  continue;
1852
2019
  }
1853
2020
  const displayLateAuxPath = toRunnerDisplayPath(cwd, lateAuxPath);
1854
2021
  for (const pair of pairs) {
1855
- const rawDiags = cached.get(pair.serverId);
1856
- if (rawDiags === undefined) {
2022
+ const cachedEntry = cached.get(pair.serverId);
2023
+ if (cachedEntry === undefined) {
1857
2024
  // No live client for this server any more — best-effort probe,
1858
2025
  // drop the pair silently.
1859
2026
  lateAuxClientGone += 1;
1860
2027
  continue;
1861
2028
  }
1862
- if (rawDiags.length === 0) {
2029
+ const rawDiags = cachedEntry.diags;
2030
+ if (cachedEntry.publishedAt === undefined ||
2031
+ cachedEntry.publishedAt <= pair.markedAtMs) {
1863
2032
  // Still scanning (or published nothing yet) — keep waiting
1864
2033
  // so a scan finishing before the NEXT turn end still
1865
2034
  // delivers. Two clocks, deliberately decoupled: the
@@ -1867,17 +2036,36 @@ export async function handleTurnEnd(deps) {
1867
2036
  // the delivery gate stats against — while the re-arm TTL is
1868
2037
  // anchored on `lastRearmedAtMs`, advanced by every successful
1869
2038
  // empty probe: the scanner is demonstrably alive, just slow.
1870
- if (!isPendingAuxiliaryPastRearmTtl(pair)) {
1871
- markPendingAuxiliaryCoverage(lateAuxPath, [pair.serverId], pair.markedAtMs, Date.now());
2039
+ if (!isPendingAuxiliaryPastRearmTtl(pair) &&
2040
+ (pair.rearmCount ?? 0) < MAX_LATE_AUX_REARMS) {
2041
+ rearmPendingAuxiliaryCoverage(pair);
1872
2042
  lateAuxRearmed += 1;
2043
+ if (lateAuxStuckPairs.length < 20)
2044
+ lateAuxStuckPairs.push({
2045
+ filePath: pair.filePath,
2046
+ serverId: pair.serverId,
2047
+ });
2048
+ }
2049
+ else {
2050
+ if (isPendingAuxiliaryPastRearmTtl(pair))
2051
+ lateAuxExpired += 1;
2052
+ else
2053
+ lateAuxCeilingExhausted += 1;
1873
2054
  }
1874
2055
  continue;
1875
2056
  }
2057
+ if (rawDiags.length === 0) {
2058
+ lateAuxCleanConfirmed += 1;
2059
+ continue;
2060
+ }
1876
2061
  const converted = convertLspDiagnostics(rawDiags, lateAuxPath, {
1877
2062
  tool: "lsp",
1878
2063
  });
1879
- if (converted.length === 0)
2064
+ if (converted.length === 0) {
2065
+ lateAuxMissing += rawDiags.length;
2066
+ lateAuxAnswered += 1;
1880
2067
  continue;
2068
+ }
1881
2069
  // Freshness kernel (#1634 gated surface): stat the cited file
1882
2070
  // against the mark timestamp. Missing → drop (no remediation for
1883
2071
  // a deleted file); mtime drifted past the mark → drop too, NOT
@@ -1897,16 +2085,30 @@ export async function handleTurnEnd(deps) {
1897
2085
  lateAuxStale += gate.stale.length;
1898
2086
  lateAuxMissing +=
1899
2087
  converted.length - gate.live.length - gate.stale.length;
1900
- if (gate.stale.length > 0) {
1901
- // Stale findings mean the scan predates the last edit.
1902
- // Re-arm with a REFRESHED baseline so the next turn probes
1903
- // the newer revision (#2027 round-1 P2-1).
1904
- markPendingAuxiliaryCoverage(lateAuxPath, [pair.serverId], Date.now());
1905
- }
1906
- if (gate.live.length === 0)
2088
+ if (gate.live.length === 0) {
2089
+ if (gate.stale.length > 0) {
2090
+ // Stale findings mean the scan predates the last edit. Re-arm
2091
+ // with a refreshed baseline and carry the ceiling count.
2092
+ if (!isPendingAuxiliaryPastRearmTtl(pair) &&
2093
+ (pair.rearmCount ?? 0) < MAX_LATE_AUX_REARMS) {
2094
+ rearmPendingAuxiliaryCoverage(pair, Date.now(), true);
2095
+ lateAuxRearmed += 1;
2096
+ }
2097
+ else if (isPendingAuxiliaryPastRearmTtl(pair)) {
2098
+ lateAuxExpired += 1;
2099
+ }
2100
+ else {
2101
+ lateAuxCeilingExhausted += 1;
2102
+ }
2103
+ }
2104
+ else {
2105
+ lateAuxAnswered += 1;
2106
+ }
1907
2107
  continue;
2108
+ }
1908
2109
  const lines = gate.live.map((f) => ` ${displayLateAuxPath}:${f.line}:${f.column} [${f.rule}] ${f.message}`);
1909
2110
  lateAuxDelivered += gate.live.length;
2111
+ lateAuxAnswered += 1;
1910
2112
  // @delivery-surface: runtime-turn:late-auxiliary-findings
1911
2113
  advisoryParts.push(`🕐 Late auxiliary diagnostics (${pair.serverId} answered after its grace window):\n${lines.join("\n")}`);
1912
2114
  }
@@ -1923,12 +2125,20 @@ export async function handleTurnEnd(deps) {
1923
2125
  durationMs: Date.now() - lateAuxStart,
1924
2126
  metadata: {
1925
2127
  pending: drainedPairs.length,
2128
+ pairCreated: drainedPairs.length + lateAuxCapEvicted,
2129
+ pendingAfter: pendingAuxiliaryCoverageSize(),
1926
2130
  delivered: lateAuxDelivered,
1927
2131
  stale: lateAuxStale,
1928
2132
  missing: lateAuxMissing,
1929
2133
  rearmed: lateAuxRearmed,
1930
2134
  clientGone: lateAuxClientGone,
1931
2135
  probeFailed: lateAuxProbeFailed,
2136
+ cleanConfirmed: lateAuxCleanConfirmed,
2137
+ expired: lateAuxExpired,
2138
+ ceilingExhausted: lateAuxCeilingExhausted,
2139
+ answered: lateAuxAnswered,
2140
+ capEvicted: lateAuxCapEvicted,
2141
+ stuckPairs: lateAuxStuckPairs,
1932
2142
  },
1933
2143
  });
1934
2144
  }
@@ -1945,7 +2155,7 @@ export async function handleTurnEnd(deps) {
1945
2155
  const content = capTurnEndMessage(findingParts.join("\n\n"));
1946
2156
  const signature = `${files
1947
2157
  .slice()
1948
- .sort((a, b) => a.localeCompare(b))
2158
+ .sort((a, b) => compareOrdinal(a, b))
1949
2159
  .join("|")}::${content}`;
1950
2160
  const last = cacheManager.readCache("turn-end-findings-last", cwd);
1951
2161
  if (last?.data?.signature === signature &&
@@ -1980,6 +2190,11 @@ export async function handleTurnEnd(deps) {
1980
2190
  resetFormatService();
1981
2191
  return;
1982
2192
  }
2193
+ // #1950 fix-round F1: this turn's content is confirmed NOT suppressed —
2194
+ // it is about to reach the agent — so NOW commit the delivery-count
2195
+ // increments the per-blocker loop above only tentatively computed.
2196
+ for (const commit of pendingDependencyDriftDeliveries)
2197
+ commit();
1983
2198
  const fileSeqByPath = {};
1984
2199
  for (const [filePath, seq] of runtime.getFileSeqEntries()) {
1985
2200
  fileSeqByPath[normalizeMapKey(path.resolve(filePath))] = seq;