@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
@@ -16,6 +16,326 @@ All notable changes to pi-lens will be documented in this file.
16
16
 
17
17
  ### Security
18
18
 
19
+ ## [4.1.3] - 2026-08-28
20
+
21
+ ### Added
22
+
23
+ - **Record build identity at session start (refs #1775)** — `sessionstart.log` now logs one bounded line per session start with the serving checkout's commit hash, entry-file mtime, and package version, derived from the running build's own files rather than `process.cwd()`. The dirty flag is deferred (would require a spawn on the session-start hot path); see the issue for the remainder.
24
+
25
+ - **Rerun externally killed CI Unit tests once (refs #2042)** — a completed CI run now classifies the failed Unit-tests log and reruns only failed jobs when the verdict is `infra-kill`. A per-head-SHA marker and the workflow run-attempt gate prevent rerun loops, while classification errors produce an explicit PR comment.
26
+
27
+ - **Recover starved CI runs and merge train-approved PRs (closes #2184, closes #2185)** — the merge-train warden classifies every open PR head as runs-concluded-normally, starved-run, absent-run, in-progress, or unknown, re-runs a starved run once, and comments when GitHub drops a dispatch; a new label-gated merge lane lands a `train:approved` PR only when both required checks conclude success on its exact current head.
28
+
29
+ - **Enable Kotlin structural linting (refs #2198)** — Add eight VTCode-derived Kotlin ast-grep rules (`kotlin-no-lateinit`, `kotlin-no-nullable-boolean`, `kotlin-no-println`, `kotlin-no-unnecessary-let`, `kotlin-no-unsafe-cast`, `kotlin-no-var`, `kotlin-prefer-data-class`, `kotlin-prefer-is-empty`). Kotlin delivers through the ast-grep CLI/LSP lane, because `@ast-grep/napi` 0.45.1 ships no Kotlin grammar.
30
+
31
+ - **Enable C/C++ memory and I/O idiom rules (refs #2201)** — Add three VTCode-derived rules: `c-no-malloc-free`, `cpp-no-malloc-free`, `cpp-no-printf`. All detection-only, at `severity: warning`, and distinct from CodeRabbit's existing C/C++ security rules.
32
+
33
+ - **Classify a red Unit tests run as infra-kill, infra-net, or real (refs #2103)** — `scripts/classify-ci-failure.mjs` reads a failed job's log, tells a SIGKILL/exit-137 kill (with or without the `with-memory-watch.mjs` wrapper surviving to report it) and a DNS/network failure apart from a genuine assertion failure, posts one sticky PR comment naming the verdict, and reruns the failed jobs once per SHA when the verdict is infra. Every recognized real-failure shape (a FAIL block, an inline test-failure marker, a file-level collection error, or the overall failed-test tally) wins over any infra signal in the same log, and an unrecognized shape defaults to real rather than infra — the classifier is only as good as the shapes it recognizes, so treat "real" as the safe default, not an absolute guarantee against every possible log shape. The "once per SHA" rerun guard is once per SEQUENTIAL invocation, not across concurrency: concurrent invocations for the same SHA may each attempt a rerun (GitHub answers a duplicate with 403, which the marker records as `failed:403` and a later invocation can retry).
34
+
35
+ - **Port five codemod gallery candidates (refs #2195)** - add Python `Optional` detection, generator-expression, Ruby symbol-to-proc, Rust character-index, and constrained JSX SVG-attribute rules. Semantic rewrites remain detection-only; guarded Python and Ruby rewrites expose fixes.
36
+
37
+ - **Enable Java structural linting (refs #2197)** — Add four VTCode-derived Java rules to the ast-grep CLI/LSP lane. The NAPI runner stays limited to its bundled grammars, because `@ast-grep/napi` 0.45.1 ships no Java grammar.
38
+
39
+ - **Port VTCode's `no-important` CSS rule (closes #2199)** — flags `!important` declarations as a detection-only warning; a safe fix would need to know which selector should win, which the rule cannot infer. CSS and HTML roots are scoped in the napi fallback so language-tagged rules do not scan unrelated parsed roots.
40
+
41
+ - **Port 5 VTCode Go idiom rules (closes #2200)** — `go-no-fmt-println`, `go-no-panic-in-lib`, `go-no-underscore-func-name`, `go-prefer-errors-is`, and `go-prefer-string-builder` flag `fmt.Println`-family calls, library `panic()`, snake_case function names, direct error comparison, and repeated string concatenation. All five ship detection-only at their triaged severity (warning for `no-fmt-println`/`no-panic-in-lib`, hint for the rest) — each rewrite needs project or call-site context ast-grep cannot verify, so no `fix:` ships. The `+=` arm of `go-prefer-string-builder` is limited to string-literal operands because ast-grep has no type information. `go-no-global-variable` is excluded per the #2195 triage: valid package globals (constants, synchronization primitives) make it too noisy without project-specific policy.
42
+
43
+ - **Guard a shared checkout against branch switches that destroy another session's work (refs #2007)** — a new opt-in `--lens-checkout-guard` (`guard.sharedCheckout=true`) declines `git checkout`, `switch`, `restore`, `reset --hard`, `stash`, `clean`, `merge`, `rebase`, `pull`, `cherry-pick`, and `revert` when the checkout has uncommitted work and another live pi-lens session is registered on the same root. The refusal names the peer pids and tells you to commit or take a dedicated worktree, rather than moving anyone's work behind their back. Command classification reuses the existing git-guard shell analysis; peer liveness reuses the instance registry.
44
+
45
+ > Refs #2007
46
+
47
+ ### Changed
48
+
49
+ - **Split `loop_block` into compute and non-CPU stalls, and name the sweep that caused one (refs #1980, #1723)** — every `loop_block` record now carries `stallClass` (`below-floor`, `cpu-accounted`, `non-cpu-stall`, `system-stall`) and `cpuCoverageRatio`, derived from the `windowCpuMs` that has sat unread beside `durationMs` since #1122. Nine of sixteen genuine 5s+ blocks in a 23h window burned less CPU than the block lasted; those now read as parked, not as work. `suspectSystemStall` is unchanged. The LSP workspace-diagnostics sweep's per-file touch is also bracketed now, so a block inside it names `lsp_workspace_diagnostics_touch` instead of arriving anonymous.
50
+
51
+ - **Cut the word-index per-edit seam's longest synchronous stretch (refs #2067)** — the cascade and MCP-analyze seams now replace a document through the cooperative primitive, serialized through the index's own operation queue, instead of holding the event loop for the whole replacement. On a 2,829-document, 2.38M-posting corpus the longest synchronous stretch on a large document drops about 3x. Cooperative removal staging now filters each token's postings with the same packed primitive the synchronous path uses, dropping a clock read per posting element. Search results and BM25 scores are unchanged.
52
+
53
+ - **Bound memory accounting (refs #2132, #2114)** — Add measured byte estimates for review-graph and dispatch-cache residency to `memory_sample`, and keep the word-index persistence hook visible in sampler coverage. #2132 criterion 3 remains undelivered; the `runtime.wordIndex` to `memory_sample` seam can still report `wordIndex:null` and remains a named follow-up.
54
+
55
+ - **Classify merge-train pagination truncation (closes #2134)** — the warden reports GraphQL pull-request pagination truncation instead of treating a partial population as complete.
56
+
57
+ - **Gate word-index arena recompaction on a share of the vocabulary (refs #2246)** — the flat 64-store recompaction threshold was crossed by every edit, because one document replacement raises the backing-store estimate by the edited document's distinct-token count. The gate is now 10% of the live vocabulary above a 64-store floor, so fragmentation accumulates proportionally to the index. On a 2,844-document, 2.4M-posting corpus, 300 sequential edits recompact 14 times instead of 299, the per-edit cooperative block drops from mean 32.0 ms to 12.4 ms, and peak resident memory is unchanged (the recompaction share of per-edit CPU falls from 23% to 4%). Small corpora keep the pre-change behaviour: the floor governs any index whose vocabulary is under 640 tokens.
58
+
59
+ - **Convert the word-index per-edit occupancy guard to a load-invariant work count (refs #2254)** — the per-edit seam's occupancy bound was a wall-clock max-block over a 401-document fixture with `retry: 2`, so it was both flaky and a noisy neighbour in the timing-sensitive test lane. It now asserts the seam's cooperative replacement reads the clock a number of times bounded by the old document's distinct tokens, not by the posting elements it walks, which is deterministic and invariant to runner load. The guard leaves the timing-sensitive lane, and that lane returns to `maxWorkers: 2` now that its one heavy neighbour is gone. The shared `countClockReads` helper moves to `tests/support/perf-harness.ts` so the incremental test and the seam test read one implementation.
60
+
61
+ - **Weekly stale-issue report now flags missing priority labels (refs #1676)** — the scheduled detector's report on #1323 adds a Priority label coverage section, listing open issues with zero `priority:*` labels and open issues with more than one. Advisory only; the job still never edits an issue.
62
+
63
+ - **Enforce client dependency boundaries and cycles in CI (refs #1844)** — dependency-cruiser now rejects client import cycles, declared leaf imports, and unapproved additions to the session-start eager graph.
64
+
65
+ - **Add six test-authoring screens to AGENTS.md (refs #1829)** — Contributor doctrine now names the six ways a green test can assert nothing, each with its screen, a real PR example, and a detection step.
66
+
67
+ - **Reorganize AGENTS.md for retrieval (refs #1829)** — a task-type index and reading modes up front, the scattered invariant paragraphs pooled into a grouped "Standing invariants" section, placement rules that end top-prepend and tail-append conflicts, the stale version section dissolved (CHANGELOG.md owns versions), and a small HISTORY.md for decision-inert records. Every invariant paragraph preserved verbatim.
68
+
69
+ - **Contributor and reviewer discipline harvested from six external skill sets plus the 2026-08-26 arc** — AGENTS.md gains the minimalism ladder (climb before writing: exists → reuse → stdlib → dep → one line → minimum), a review materiality bar (no stylistic/hypothetical/line-count findings, nothing a required CI check already fails on, spec-vs-standards axes reported separately, restructure insights route to issues), the deletion test on consolidation verdicts, the exit-137 judgment recipe, and the mechanical-versus-intent rule for maintainer trailing commits; the investigator playbook gains a loop-first diagnosis discipline; the fixer playbook gains commit-before-checkout-proofs and the verbatim-evidence rule; the reviewer playbook gains the quoted-evidence audit and treats fix rounds as fresh PRs (refs #2128).
70
+
71
+ - **PRs touching tests now carry a Test assessment, and the authoring screens grow to ten** — per touched test file, state what it uniquely pins and what became redundant; removal requires a named surviving test to red on the same mutations. The PR body lint enforces the section (advisory) when the PR touches `tests/`; candidates not deleted in-PR go to the corpus value ledger (#2123). AGENTS.md's test-authoring screens gain four classic vacuity shapes: all-mocks, not-throw-as-sole-assertion, implementation mirror, and snapshot-as-behavior (with the characterization-baseline carve-out).
72
+
73
+ - **Name and pin the files-touched bus seam (closes #1966)** — Document the two publisher modules, the agent-nudge subscriber, payload and delivery contract, and outcome-level observability; add a source conformance test that fails when the bus surface changes without updating the declaration.
74
+
75
+ - **Unify the pinned npm version across workflows (#2051)** — ci.yml and release.yml both pin npm 11.18.0 so a single lockfile-writer version governs CI and release installs.
76
+
77
+ - **Single-flight availability probes (refs #2131)** — concurrent consumers now share one tool/root probe, joined decisions are observable, and budget overruns enter the bounded degradation ledger.
78
+
79
+ - **Concise zero-read recovery (closes #2335)** — The read guard now delivers its edit-without-read instruction as a single paragraph instead of a three-paragraph block. The 🔄 RETRYABLE marker and the "in this conversation" scope are unchanged.
80
+
81
+ - **Memoize compiled gitignore globs (closes #1976)** — Reuse compiled ignore patterns for each matcher instance, reducing the measured compile count from 500 to ≤6.
82
+
83
+ - **Persist word-index changes incrementally off the hot path (refs #2068)** — packed posting lanes now reuse cached wire segments for untouched documents, so a per-edit persist rebuilds only dirty document contributions. Dirty files resolve through one wire-slot map, and each affected token lane flattens once per persist while the existing snapshot worker handles stringify and gzip.
84
+
85
+ - **Shrink the word index's resident footprint by 4.6x (closes #2069)** — Postings and the forward index now live in packed `Int32Array` lanes over a dense file-id space instead of one boxed `{ file, line }` object per posting. On pi-lens's own 2,682-document tree the index measures 32.7 MB, down from 151.6 MB, and 15.1 bytes per posting entry, down from 70.2. Against the 186.6 MB the issue records before posting interning landed, the reduction is 5.7x. Search results, the persisted snapshot format, and incremental refresh behaviour are unchanged. The `full_rebuild`, `incremental_refresh`, `cold_build`, and `persist_succeeded` records in `word-index.log` gained a `residentBytes` field, and `memory_sample` gained `postingEntries` and `residentBytes`, so a heap census can be reconciled from the logs.
86
+
87
+ - **Review-graph one-file rebuild (refs #2074)** — the incremental rebuild now reads the graph's own adjacency indexes instead of rescanning every edge. Restoring preserved incoming edges and reading a changed file's import targets both became bucket lookups, and the derived indexes stay live through the update instead of being rebuilt at the end. On a 1,600-file fixture a one-file rebuild drops from 48.0 ms to 33.6 ms median, with edge-metadata comparisons down from 9,600 to 4 and edge visits down from 19,200 to 8. The same fix makes `existedBefore` report the truth, which unblocks reverse-dependency index reuse on every incremental build.
88
+
89
+ ### Fixed
90
+
91
+ - **Identity-guard seven more in-flight promise caches against ABA eviction (refs #1968)** — `SecurityScanClient.dedupeScan` (gitleaks/trivy/govulncheck), `JscpdClient`, `DependencyChecker`'s per-file and per-project caches, the MCP Stop-hook's `runTurnEndForIpc`, `initLSPConfig`, and `ensureTool`'s per-tool install map all cleared their in-flight entry with a bare delete-by-key, same shape as the dead-code/knip sites #1968 already fixed. A late-settling run could evict a live successor a second writer registered under the same key, causing the next caller to start a duplicate scan/check/install. All seven now delete only when the map still holds their own promise. One sibling, `ast-grep`'s shared availability probe (`runner-helpers.ts`), was reachable TODAY (not latent): a session-boundary reset is itself the second writer, so the bug could fire in production, not only after a future code change.
92
+
93
+ - **Stop the CI kill record from blaming memory it never ran out of (refs #2042)** — the `[mem-watch]` verdict asserted "the OS reclaimed memory" for every exit-137, including three real kills whose own low-water mark showed 13.0-13.3 GB of 16 GB still available. It now classifies from its numbers, emits a distinct `KILLED WITH HEADROOM` verdict when the box had room, and names the pid it was watching. A new failure-gated CI step captures the kernel's own `dmesg` and `systemd-oomd` records, so the next occurrence names the signal's sender.
94
+
95
+ - **Re-resolve a runner-detection alias probed before its symlink existed (closes #2077)** — `TestRunnerClient` no longer memoizes a project root whose canonicalization failed, so an alias first probed while it was still missing now converges to the real root's runner verdict on the next probe instead of serving the fallback key's stale "no runner" answer for the client's whole life. Resolved spellings are memoized exactly as before, so the hot path is unchanged.
96
+
97
+ - **Recompact churned word-index arenas (refs #2117)** — incremental replacements now recompact the posting arena once churn passes 64 backing stores. The per-edit gate is O(1) and the cooperative copy is serialized through the index's async queue and safe against a concurrent edit, so it cannot corrupt postings or stall an edit. Resident-byte estimates include abandoned arena slack, and the bounded refresh record carries before-and-after bytes and store counts.
98
+
99
+ - **Dedupe review-graph edges after deferred symbol resolution (closes #2127)** — same-batch rebuilds no longer retain duplicate calls or references edges when placeholders converge on real symbols.
100
+
101
+ - **Report every root a host serves in `pilens_health` (refs #2130)** — the resource footprint projected one scalar `projectRoot` per instance, so a host also serving a subagent's temp worktree read as single-rooted there while `instances.json` listed both. Each `resourceFootprint.perInstance` entry now carries `projectRoots`, resolved through `getInstanceRoots` (the single reader the shared-checkout guard already uses), with the pinned primary still in `projectRoot` for existing consumers.
102
+
103
+ - **Count every LSP client, and stop leaking a subagent's temp root (refs #2130)** — `memory_sample.subsystems.lsp.clients` now counts clients across every module evaluation, so a host serving a secondary root no longer reports `clients: 1` beside `lspChildCount: 2`. `deregisterInstanceRoot` shares the registry mutation tail, so a short-lived subagent can no longer remove its root before its own queued add lands and leave the temp root in `instances.json` for the rest of the host's life. A host entry synthesized from an `lsp-fallback` guess now yields its primary slot to the session's real root instead of pinning the guess forever.
104
+
105
+ - **Stamp classifiedBy on ok-path availability decisions (refs #2131)** — seven `logAvailabilityDecision` success-arm calls (biome, dead-code, formatters, jvm-runtime, package-manager, zizmor) omitted `classifiedBy`, so a `cause: "ok"` row could not be attributed to a probe or a caller-asserted repair from the log alone. All seven now stamp `"probe"` for a direct probe success or `"caller"` for an install/join-repaired one, matching their sibling failure arms. A sweep test pins every `cause: "ok"` emit site so the gap cannot recur unnoticed.
106
+
107
+ - **Normalize review-graph.log's cwd to one form (closes #2141)** — `logReviewGraph` and `logWordIndex` now normalize `cwd` at their single emit seam, so the same project root no longer appears as both `C:\...\pi-free` and `C:/.../pi-free` in the same log.
108
+
109
+ - **Repair escaped-newline flattened PR bodies in body lint (refs #2145)** — a body can arrive with the literal two-character sequence `\n` (or `\r\n`) standing in for a real line break, the sibling of the space-flattening shape #2149 already repairs. `detectEscapedNewlineBody`/`repairEscapedNewlineBody` restore it losslessly, but refuse outright when the body carries a fenced code block: a flattened fence's own delimiters can no longer be told apart from genuine content, so fence-safe repair stays deferred as recorded on the issue.
110
+
111
+ - **Stop duplicate merge-train warden actions (closes #2150)** — the warden stops on a non-advancing GraphQL cursor and deduplicates PR records before applying actions.
112
+
113
+ - **Bound language-server verification output (refs #2169)** — verification retains at most 64 KiB of child output with head-and-tail retention, so a late transport-required line can rescue a valid LSP probe. A truncated verification records one bounded degradation.
114
+
115
+ - **Match the merge lane to live repository state (refs #2185)** — the lane updates a green branch that is behind instead of attempting a merge master protection would refuse, reads the `(advisory)` name suffix this repository actually uses, resolves duplicate check names to the newest run, requires the `train:approved` label to come from an approver, and deduplicates its merge-failure comment.
116
+
117
+ - **Stop a routine paging repeat from reddening the merge-train warden (refs #2192)** — the warden's PR reader orders by `UPDATED_AT`, so a PR updated mid-pagination lands on the next page too. That boundary repeat was recorded as a fatal error, once per repeated node, so a fully shifted window could emit up to 200 identical red lines in one 10-minute run. It is now one record per page naming the count, classified by cursor: a repeat with an advancing cursor is benign, a repeat with a stalled cursor stays fatal because it is real truncation. `fetchOpenPullRequests` returns `{ message, benign }` records, so the warden and the merge lane read one classification instead of each deciding for itself.
118
+
119
+ - **Correct the Java rule set's accumulator and raw-type detection (refs #2197)** — `no-string-concat-in-loop` now requires the accumulator to be a String, so it stops firing on numeric accumulation and starts catching `s += x`. `no-raw-types` skips `instanceof` and `.class`, which have no parameterized form, and reports raw types nested in type arguments.
120
+
121
+ - **Deflake the output-cap tail-retention test (refs #2197)** — Let the child ignore SIGTERM so the cap's kill cannot settle it before it emits its last line. On POSIX a child's pipe writes are asynchronous, so the cap kill could destroy queued output the assertion needed.
122
+
123
+ - **Move the dirty-fraction word-index guard off wall clock (refs #2202)** — the incremental word-index occupancy test asserted a same-run timing ratio (`dirty=750 ms < fullMs * 1.5`). Review replicated it 20x under 57-94% CPU load and measured a 0.436x-3.863x spread, wider than the 2x regression the guard exists to catch, and found `retry: 2` only escaped a calibrated 2x-work injection about two-thirds of the time. `serializeWordIndex` now records, per call, how many tokens it re-flattened and whether it took the incremental or full-rebuild path (`getLastWordIndexSerializeWork`, test-only). The guard asserts on that count directly: no timer, no retry, no runner-load dependence, and it fails deterministically on a synthetic work-doubling regression.
124
+
125
+ - **Fix `playground-verify-rule.mjs` matching the wrong source (closes #2208)** — the harness wrote a caller's `--code` into the upstream ast-grep playground's `query` field, which only feeds its Pattern mode. Config mode (what this harness always uses) matches against `state.source` instead, so the URL hash carried no source and the playground silently fell back to its own hardcoded sample. Every run graded that fixed sample, not the caller's code, so `matches` never reflected the fixture under test. `source` now carries the code; a rule + known-matching snippet reports a real match count instead of a silent 0. Also adds a scrape-side sentinel that catches the same failure mode recurring through upstream schema drift alone (e.g. a future `state.source` rename), and fixes the reported match `lines` clamping to the match count instead of the source's own line count.
126
+
127
+ - **Run the ast-grep in-process fallback on every JS/TS module extension, and record the languages it cannot serve (closes #2215)** — `.mjs`, `.cjs`, `.mts`, and `.cts` files reached the napi fallback and were dropped without running a single rule, because its extension allowlist was hand-maintained beside three other lists that described the same thing. All four now derive from one language matrix, so the ~470-rule catalog reaches those files. The twelve catalog languages `@ast-grep/napi` bundles no grammar for (python, java, go, ruby, cpp, rust, csharp, c, kotlin, swift, php, scala — 247 of the 470 rules) are recorded as ast-grep LSP/CLI-only rather than silently skipped, the skip telemetry now names each language's delivery route, and a file admitted for a grammar the loaded addon turns out not to have leaves an `ast-grep-napi-language-unavailable` degradation record instead of nothing.
128
+
129
+ - **Derive the project scan's ast-grep kind per file instead of hard-coding jsts (closes #2217)** — The scanner passed the literal `"jsts"` to `evaluateAstGrepRules` for every napi-evaluated file, including `.css` and `.html`, so `suppressLinterOverlap` (and any future kind-gated ast-grep policy) evaluated non-JS files under a JS/TS linter-overlap decision. It also diverged from the per-edit dispatch path, which derives kind from `detectFileKind` — the same file could see two different kinds depending on which path evaluated it. The scan now calls `detectFileKind` (the shared `KIND_EXTENSIONS` resolver), matching the dispatch path exactly.
130
+
131
+ - **Normalize the remaining NDJSON logger `filePath` fields (refs #2219, the #2141 class)** — `cascade.log`, `latency.log`, `read-guard.log`, `tree-sitter.log`, and `actionable-warnings.log` now normalize `filePath` once at each logger's single emit seam, so the same file no longer appears in two path forms across a log. Non-path sentinels (`"<quiet-window>"`, `"<tree-sitter>"`, shell commands, coarse labels) mixed into the same field are left untouched instead of being resolved against the process cwd.
132
+
133
+ - **Remove three real-timer races from tests that only pass under CPU contention (refs #2225, #2235, #2182)** — `safe-spawn.ts`'s cap-then-timeout/cap-then-abort tests raced a real child's stdout against a real 300ms timer (5/8 failures under 8 concurrent runs); they now mock the child so the cap trips before the timeout by construction, in a new `safe-spawn-cap-race.test.ts`. `spawn-timeout-cooldown.test.ts`'s markdownlint-fix guard paired real filesystem I/O with a heavy dynamic import inside vitest's 5000ms default, reproduced as a hard timeout under synthetic load; it now carries a 20s budget. `managed-tool-refresh-strategies.test.ts` had more instances of the dangling-promise-contamination shape #2216 first fixed for one test — a file-wide `vi.setConfig({ testTimeout: 20_000 })` replaces per-test patching. A residual shared-mutable-state race in the same file, independent of any timeout, is left open on #2182.
134
+
135
+ - **Make a stale compiled `.js` beside a `tests/**/*.ts` file fail loudly (refs #2232)** — `tests/` is excluded from `npm run build`, so any `.js` sibling of a test-support `.ts` file is leftover residue from an earlier, differently configured local compile. Vitest's import specifiers end in `.js`, so that residue silently wins module resolution over the real source, running stale code with no warning. The build-freshness `globalSetup` now also flags this residue and aborts the run naming the file.
136
+
137
+ - **Make the spawn-timeout cooldown regression test load-invariant (closes #2235)** — load the real pipeline during test-module setup so the 5000ms test budget measures cooldown behavior instead of contention during a heavy dynamic import.
138
+
139
+ - **Bound the dispatch fact store (refs #2240)** — `FactStore`'s per-file records are now capped at 1024 with LRU eviction, so a several-hundred-file batch no longer retains one entry per distinct path until the heap is exhausted. The files being dispatched are pinned, so a background project walk cannot evict facts a live dispatch is still reading.
140
+
141
+ - **Harden the dispatch fact-store pin against eviction (refs #2240)** — the debounced ast-grep warning scan is a dispatch entry point, but it never pinned its file or re-derived content, so a project walk in its 2-second window could evict `file.content` and make inline `pi-lens-ignore` suppressions silently stop applying. The scan now pins and re-derives like every other dispatch caller, via a new `beginDispatchFor` that pins WITHOUT clearing the file's already-fresh facts (the earlier clear-and-re-derive cost ~51ms of redundant re-parsing for a file already re-derived by the inline dispatch 2 seconds earlier). The pin is also released at dispatch completion, so the pin set tracks dispatches actually in flight rather than the last 16 files touched. Capacity-eviction telemetry is now labeled per store instead of one shared subject across all six production `FactStore` instances, so a session-start review-graph walk can no longer consume the dispatch store's once-per-session degradation record before a real dispatch runs.
142
+
143
+ - **Bound retained FactStore content bytes (closes #2247)** — file-fact stores now evict least-recently-used records after retaining 64 MiB of UTF-8 `file.content`, as well as after 1,024 records. In-flight dispatch records remain pinned until settlement. A count-axis and a byte-axis eviction on the same store each get their own degradation record instead of sharing one dedupe key, and a pin whose content alone exceeds the byte budget stops evicting unpinned inserts instead of silently discarding every one of them, recording that state through its own degradation kind.
144
+
145
+ - **Napi runner css/html admission (#2248)** — Route CSS and HTML files to the napi ast-grep runner when its bundled grammars support them.
146
+
147
+ - **Anchor `.gitignore`'s scratch `test-*` patterns to the repo root (refs #2250)** — the unanchored `test-*.ts`/`.js`/`.py`/`.md`/`.sh`/`.mjs` rules matched the basename of any tracked file at any depth, silently hiding `clients/test-runner-client.ts` and several `tests/` files from `rg` and other ignore-respecting search tools. `git status` never flagged it because git still tracks the files. The patterns now only match scratch files dropped at the repo root, their original intent.
148
+
149
+ - **Baseline the git-config contamination guard against pre-suite identity (refs #2251)** — the test-teardown guard no longer fails every local run for a maintainer whose real git identity happens to equal a fixture value (`user.name=t`, `user.email=t@t.local`). It snapshots the config before any test runs and flags only fixture values that appear during the run, so real contamination still fails loudly.
150
+
151
+ - **Scope the playground source sentinel (refs #2253)** — the verifier now checks the caller's fixture inside the source pane's Monaco editor. A matching rule note in the config pane no longer masks upstream source drift.
152
+
153
+ - **Bound every in-memory review-graph retention site (refs #2255, #2240)** — the live `ReviewGraph` grew with project size and had no size guard; only the persisted snapshot was capped. On a large repository it was the second unbounded dispatch store behind the multi-gigabyte OOM abort (`FactStore.fileFacts` was the first, bounded in #2243). Two sites retained a graph for the life of the process: the workspace cache and `session.reviewGraph` on a caller's FactStore, two of which are module-scope. Both now go through one memoized retention seam capped by estimated resident bytes (`PI_LENS_GRAPH_MAX_IN_MEMORY_BYTES`, default 512 MiB), evicting with the same centrality-ranked selection the snapshot uses. `memory_sample` counts every retention site instead of the cache alone, deduplicated by object identity, so the sample can no longer read clean while a full graph is resident. A trim never yields an empty graph, and a graph trimmed for size is now distinguished from one whose source walk was truncated: both report as partial, but only the size-trimmed one stays usable as an incremental base, so an over-budget repository is not forced into a full project walk every turn. That marker is process-local and never persisted. Each trim emits one bounded degradation record per workspace.
154
+
155
+ - **Ast-grep YAML rule caches detect edited and nested files (closes #2262)** — the in-memory rules cache snapshots every YAML file with `mtimeMs` and size, re-checked at most once per 2-second cadence window. Editing an existing rule or adding a nested rule invalidates the cache even when the rule directory mtime stays unchanged; pickup lag is bounded by one cadence window.
156
+
157
+ - **Topology-derived startup scan and language-profile memos re-arm with the workspace marker index at session start (closes #2263)** — `resetWorkspaceTopology()` walks one registered downstream-cache reset list, clearing `startupScanContextCache`, `languageProfileCache`, and tsconfig-path caches with the source index. Caches are cleared only at session start via the topology-reset registry; mid-session edits are not detected.
158
+
159
+ - **Move three Java rules off an inert `files:` negation glob (fixes #2280)** — `no-raw-types`, `no-string-concat-in-loop`, and `no-system-out-println` used `files: ["**/*.java", "!**/test/**"]` to carve out test files, but ast-grep 0.45.1's `files:` field has no negation support, so the exclusion silently did nothing. All three now use the real `ignores:` field ast-grep's engine honors natively, restoring the test-file carve-out.
160
+
161
+ - **Preserve latency logger exports in test mocks (refs #2281)**
162
+
163
+ - **Detect different-size project-snapshot rewrites inside one mtime bucket (closes #2285)** — The authoritative in-process cache now validates both body mtime and size before serving its snapshot, so a hot project root cannot mask a different-length external write indefinitely. Same-size, same-mtime rewrites remain outside this metadata-only hot-path guarantee.
164
+
165
+ - **Merge-train duplicate classification (#2289)** — The warden now treats a PR number repeated within one GraphQL page as malformed data and a fatal error. Genuine cross-page window slides remain benign notes.
166
+
167
+ - **Add a size axis to five mtime-only content memos (closes #2300)** — The MCP warm-build staleness gate, the LSP diagnostic-binding content-hash memo, the workspace-diagnostics per-file freshness cache, the cross-process recent-touches watermark, and the installer's tool-path probe cache now all validate byte size alongside mtime, so an external rewrite landing in the same coarse-granularity mtime bucket with a different length is no longer served as unchanged. Every fix reuses a stat call already being made — no added per-edit I/O.
168
+
169
+ - **Remove the redundant markdownlint autofix cooldown guard (closes #2301)** — `detectFileChangedAfterCommand` remains the single source of truth for autofix cooldowns, and its direct test owns that contract.
170
+
171
+ - **ast-grep findings survive a silent auxiliary LSP (closes #2324)** — the napi fallback stays active until the ast-grep LSP completes its first diagnostic publication for the SPECIFIC FILE (not just anywhere on its client), closing the race where a sibling file's publication wrongly satisfied the gate. The fallback and the late-auxiliary delivery lane no longer both fire for the same file: a napi run consumes that file's pending late-auxiliary pair. The remaining narrow case — a server that published once but goes silent on a later touch of the same file — is now a bounded `aux-runner-findings-lost` degradation record instead of a silent drop.
172
+
173
+ - Auto-repair clearly flattened pull request bodies before advisory lint validation. Repair only splits inline headings; duplicate or otherwise structurally inconsistent template headings refuse repair and preserve the original lint errors.
174
+
175
+ - **Correct the vulture exit-code comment (closes #1765)** — The comment in `clients/dead-code-client.ts` now states the verified vulture 2.16 exit codes: 0 on a clean run, 3 with findings on stdout when dead code is found, and 1 with an error on stderr for invalid input or parse errors.
176
+
177
+ - **Read-guard eviction paths now leave a trace (#1918)** — Whole-file eviction (file cap, idle timeout, external-delete cleanup) and the per-file edits-cap trim each emit a bounded, always-on `read-guard.log` record naming the file and which path evicted it, closing the gap #1915 left after fixing the record-cap path.
178
+
179
+ - **Record formatter cache hits (closes #1940)** — Emit `formatter_selected` with `outcome: "hit"`, `reason: "cache"`, and `cached: true` on formatter detection cache hits, providing hit-rate observability with a single denominator in `latency.log`.
180
+
181
+ - **Cap dependency-drift blocker re-serves (refs #1950)** — A demoted-but-confirmable inline blocker (`clients/blocker-freshness.ts`) now retires after 3 degraded deliveries with no re-run, instead of re-serving indefinitely. The retirement note says the record can still be confirmed by a fresh dispatch, distinct from #1944's past-EOF retirement, which means the finding is provably unconfirmable.
182
+
183
+ - **Record message_end cache_usage attribution loss (closes #1956)** — A stale extension ctx now records a bounded `cache-usage-attribution-stale` ledger entry instead of silently writing the `cache_usage` row unattributed; the row still writes so provider token and cost data is never dropped.
184
+
185
+ - **Gate nested LSP requests (closes #1971)** — Skip `workspace/willRenameFiles`, `workspace/didRenameFiles`, and `codeAction/resolve` unless the server registered them, matching each registration's scheme/glob/file-kind filters at the send boundary while preserving supported edits and unresolved-action fallbacks.
186
+
187
+ - **Stop re-canonicalizing already-normalized dispatch paths (refs #2016)** — the dispatch context's `filePath`, `cwd`, and `projectRoot` are normalized once at construction, and seven downstream sites no longer pay a redundant `realpathSync` per dispatch. Scanner ids and the relative baseline key move to the cheap syntactic normalizer, which also fixes a key that resolved against the process working directory on Windows and stayed relative on Linux.
188
+
189
+ - **Budget CI test workers against real memory, and name an OOM kill (refs #2042)** — Fork concurrency and the per-fork heap ceiling now come from one resolver that sizes them against the host's memory instead of two constants tuned on a dev host, and the CI suite runs under a memory watch that reports the low-water mark so exit 137 no longer reads as unattributable infrastructure noise.
190
+
191
+ - **Retire deleted failed-first test targets (closes #2044)** — Test selection retires only confirmed-missing canonical paths with bounded work, then continues with valid failures or normal discovery.
192
+
193
+ - **Bound markdownlint verification and preserve typed spawn failures (closes #2045)** — markdownlint-cli2 now verifies through its stdin mode without scanning project files, and verifier logs retain the effective check command and typed timeout or spawn failure kind.
194
+
195
+ - **Canonicalize test-runner cache roots (refs #2048)** — Test-runner availability and Vitest glob caches now share project identity across path aliases, while positive runner verdicts expire when their supporting config disappears.
196
+
197
+ - **Honor LSP file-operation filters (closes #2049)** — Send file rename requests and notifications only to servers whose validated scheme, glob, file/folder, and case filters match the renamed resource.
198
+
199
+ - **Decline LSP roots outside every session project root (refs #2052)** — a file outside every initialized session cwd now receives an explicit outside-project-root skip and one bounded record per foreign root, instead of diagnostics computed under the wrong project context. The full sweep carries the skip into the unconfirmed lane, so a declined file is never reported or cached as clean. A process that initializes several project roots keeps serving all of them.
200
+
201
+ > Refs #2052
202
+
203
+ - **Cover the whole failed-Git-integration family.** `git pull`, `git revert`, and `git am` now join merge, rebase, and cherry-pick when opaque recovery drops clean incoming index paths. Truncated `git status` output fails closed, and an undocumented porcelain status pair keeps its path instead of voiding recovery for every other file in the command.
204
+
205
+ - **Make LSP spawn records count truthfully (closes #2064)** — `lsp_client_selected` reported `cold-spawn` for every caller that merely joined another caller's in-flight spawn, so the one metric that looked like a spawn count over-counted 3.0x in a 21.8 h field window, and one 29.3 s TypeScript spawn read as 39 spawns in 2 ms. The record now names the starter (`cold-spawn`, `spawn-failure`) apart from the joiners (`cold-spawn-joined`, `spawn-failure-joined`), on the same record with the same denominator. A new `lsp_server_spawned` latency record fires once per language-server process start for every server, so `latency.log` finally counts TypeScript spawns, which `lsp_launch_candidate_success` never covered.
206
+
207
+ - **A crashed LSP client no longer pins its retained text forever (refs #2065)** — the client-count Set backing the incremental-text-retention telemetry only deregistered on a graceful shutdown; a crash (connection error, connection close, or an unexpected process exit) never removed the client, so its retained text stayed counted, and reachable, for the rest of the process lifetime. Deregistration now happens in the one place every death path already converges on, so a crash is cleaned up the same way a graceful shutdown is. The eviction scan that runs on every `didChange` past the retention cap no longer spreads and scans the full per-path map; it tracks text-bearing paths in their own recency-ordered auxiliary set instead, so the scan cost stays proportional to the 128-entry cap rather than to the number of open documents. That auxiliary set is now also cleared on `didClose`, closing a second, smaller instance of the same unbounded-per-path-store class: without it, a closed document that was never evicted by the cap left a stale entry behind on every open/close cycle. `pullGenerations`, a per-path pull-fan-out counter, is deliberately left uncleared on close, for the same reason the codebase already leaves `pullRequestSequences` uncleared: resetting it would let a stale in-flight pull's captured generation match the counter again after a close/reopen and be wrongly accepted.
208
+
209
+ - **Incremental LSP document text is bounded and released on close (refs #2065)** — full text retained for incremental synchronization is capped at 128 paths and 64 MiB of UTF-16 data, evicts least-recently-sent paths, and is removed with the other per-document state on `didClose`. Periodic `memory_sample` records now expose retained entries and bytes.
210
+
211
+ - **Per-send LSP document sync makes one JavaScript pass over the previous document instead of two, on `didChange` (refs #2066)** — the newline count behind `lsp_document_send`'s `contentLineCount` and the last-line position behind an Incremental `didChange` range now come from a single scan whose result rides on the existing per-path content-binding entry, so a change no longer splits the previous document into one substring per line to read one of them. Line-count semantics are unchanged: `contentLineCount` still counts `\n` only, while the range still treats `\r\n`, lone `\r`, and `\n` as terminators. The remainder of #2066 is its "one pass total" target: the #1095 sha256 content binding is a second, native full-document pass and still runs on every send.
212
+
213
+ - **Intern word-index posting files (refs #2067)** — Per-edit document replacement now compares shared file identities instead of re-normalizing every posting element; build/refresh telemetry records posting-entry counts and per-edit replacement cost. This is the declared prerequisite for #2069's posting representation work.
214
+
215
+ - **Nested `.gitignore` edits now refresh ignore verdicts (closes #2071)** —
216
+ agent writes invalidate every cached project matcher containing the edited
217
+ path, so its per-path memo cannot serve a verdict from an older ignore file.
218
+ Root edits rebuild each affected matcher, while nested edits preserve
219
+ compiled-glob reuse for sibling trees.
220
+
221
+ - **Give nested ignore rules and path verdicts one freshness clock (closes #2071, closes #1976)** — a nested `.gitignore` edit no longer leaves two paths under the same rule disagreeing, and the ignore matcher stats a nested source once per cadence window instead of once per file. A 2000-file walk drops from 18,064 `statSync` calls (9.03 per file) to 192 (0.10 per file), and from 1,886 ms to 85-92 ms across runs. An externally edited nested ignore file is honored within one cadence window (2 s).
222
+
223
+ - **Review-graph source-path normalization (refs #2072)** — reuse canonical paths across builds, remove redundant persist coverage normalization, and expose normalization and persist durations in telemetry.
224
+
225
+ - **Clear outgoing idle-eviction timers on cache replacement (refs #2073)** — Review-graph, reverse-dependency, and authoritative project-snapshot cache replacements now release the prior entry's timer before installing the new entry, preventing one full payload from being retained per rebuild. Regression coverage asserts one live timer after twenty replacements for each cache family.
226
+
227
+ - **Fail closed on truncated Git file lists.** Truncated `git ls-files` output now reports unavailable tracked and ignored sets instead of presenting partial sets as complete.
228
+
229
+ - **Opaque-mutation exclusion counter overstated suppression (closes #2081)** — `excludedIncomingCount` incremented for every clean-index-only entry dropped by the failed-integration filter, even when the entry's mtime fell outside the recovery window and it was never going to be dispatched. It now counts an entry only when the mtime-freshness check would otherwise have included it, so the field means what its doc comment claims: dispatches actually prevented. Added a test asserting the `opaque_mutation_status_pair_unknown` latency record's emission and phase identity through the `logLatency` seam, alongside the existing `opaque_mutation_incoming_excluded` coverage — renaming either phase string now reds a test.
230
+
231
+ - **PR-title checks now validate the live pull-request title (refs #2083)** — rerunning a failed check can recover after the title is fixed without requiring a new pull-request event.
232
+
233
+ - **PR body lint now rejects unfilled templates and recognizes the fleet's section conventions (refs #1844)** — live body lookup uses the repository API endpoint with a bounded timeout and safe fallback.
234
+
235
+ - **Read the live PR body in close-keyword syntax checks (refs #2086)** — Reruns now validate the edited PR body instead of replaying a stale event snapshot.
236
+
237
+ - **Read the live PR body in close-keyword verification (refs #2086)** — a post-merge rerun of `--verify-merged` used to relint the closed-event payload's stale body and, in production, silently kept doing so because the workflow step never carried the `GITHUB_TOKEN` env var the live fetch needs. It now sets that var and fails the check loud on any fetch problem instead of falling back to stale data, so an edit that fixes or introduces a comma-separated close list is reliably seen on rerun.
238
+
239
+ - **Close two named remainders from the #2088 sweep-floor fix (refs #2088)** — the sweep-floor meta-sweep now recognizes the `expect(x.length).toBe(0)` emptiness spelling (14 test files use it, previously invisible to the census), and `managed-tool-seam-coverage` gained a positive control on its `safeSpawnAsync(` detector so a regex that stops matching fails loud instead of reading as zero violations.
240
+
241
+ - **Closed the managed-tool seam sweep's vacuous exemption (refs #2088)** — The meta-sweep exempted `managed-tool-seam-coverage.test.ts` with a false reason; the file walks `clients/` from a cwd-relative root and asserted zero violations with no floor, so blinding its walk to an empty array still passed. It now walks from a repo-root-derived path and carries its own scanned-files and detector-signal floors. Reworded two other exemption reasons that wrongly said "not a population sweep" when both carry their own hand-rolled floors. Added the `readdir` named-import spelling to the meta-sweep's enumeration regex, closing an async-walk evasion. Recalibrated the meta-sweep's `minFlagged` to the measured population (55) after the exemption-list growth made the prior figure stale.
242
+
243
+ - **Governance sweeps now fail on empty populations (refs #2088)** — Added declared scan and finding floors, stale dynamic-runner checks, and a registered meta-sweep for hand-rolled governance scans.
244
+
245
+ - **Tests that could not run reported as passed (closes #2089)** — Thirteen test bodies bare-returned before their first assertion, so Vitest counted them green while they asserted nothing. Each now skips visibly (`it.skipIf` or `ctx.skip(reason)`), the pnpm symlink case runs unguarded on every platform, and a new sweep over the whole `tests/` tree fails on the shape.
246
+
247
+ - **Stop metrics-history's git probe from leaking stderr into the TUI (#2095)** — `getCurrentCommit()` now pipes `git rev-parse` stderr instead of inheriting it, so a failing probe reports "unknown" quietly instead of printing a raw `fatal:` line. The same fix applies to the LSP launcher's Windows registry PATH probe, the only other unguarded `execSync`/`execFileSync` call in the runtime.
248
+
249
+ - Resolve metrics commit attribution against each target file's Git repository. (closes #2099)
250
+
251
+ - **Truncated tool output is reported as truncated.** Several spawn sites (`git status` opaque recovery, oxlint, `helm lint`, `helm template`, and the shared-checkout working-tree probe) retained unbounded stdout and carried truncation guards that could never fire. Each now caps its output, and every truncation guard in the tree reads `truncatedByOutputCap` before failure or status handling. `safeSpawnAsync` now records `killedForOutputCap`, so callers handle cap termination without guessing from POSIX or Windows exit shapes. A run that hit the cap and then timed out or was interrupted still reports the timeout or abort. Also corrected the trivy scan, `sg` exec/scan, and ast-grep pattern validation. The shared runner ledger now says a tool was stopped at its output cap instead of blaming it for a kill pi-lens sent.
252
+
253
+ - **Rename the CI classifier's kill label to infra-kill and read kernel evidence (refs #2103)** — every exit-137/SIGKILL kill used to post as `infra-oom` even with memory headroom; the classifier now labels it `infra-kill` and enriches the detail with the kernel kill-evidence step's dmesg/cgroup output already in the log.
254
+
255
+ - **Fail loudly when the stale open-issues scan is truncated (closes #2104)** — the weekly detector proves exhaustion for the open-issue population, fails loudly when its safety bound is reached, and reports the scanned population. The master commit read remains a deliberately bounded recent window and reports commits beyond its detail cap.
256
+
257
+ - Serialize overlapping per-path LSP `didChange` sends so Incremental payloads use the latest confirmed document content (closes #2113).
258
+
259
+ - **Deferred runner results remain visible and actionable (refs #2122)** — Deferred runners now show a pending state at edit time, report failures at turn end, preserve freshness coverage, and bound handoff retention with degradation telemetry.
260
+
261
+ - **Key session start and the host registry by project root (closes #2129, refs #2130)** — a subagent temp worktree no longer steals the process's primary session. Root identity is now an input to session-start classification, so a start in a different directory takes the reduced path instead of resetting the host's warm LSP fleet and re-running the whole startup battery over unchanged content. Two temp roots in one host previously cost about 50 seconds of opengrep and 53 seconds of word-index rebuild each, and drove host RSS from 290MB to 1.1GB in four minutes. The host registry entry in `instances.json` now holds a set of roots with the primary pinned, instead of one scalar every session start overwrote, so warm attach and the shared-checkout guard can see a peer working under any of its roots. The shared-checkout guard now confirms a peer against every root it serves, so a session working under a secondary root can no longer be missed and have its uncommitted work discarded. `memory_sample` carries the owning root and the distinct-root count of its live LSP clients, which makes a turn index attributable on a multi-root host.
262
+
263
+ > Refs #2129, #2130
264
+
265
+ - **Scope availability probe flights per owner (refs #2131, refs #2140)** — Package-manager, dispatch, toolchain, checker, cwd, and security-scan availability owners now hold separate keyed flights, so an owner reset cannot tear down another owner’s probe. Release-managed binaries under `~/.pi-lens/bin` answer before PATH probing, and the resolved path reaches security scans.
266
+
267
+ - **Resolve LSP-managed release binaries before PATH (refs #2140)** — Every GitHub-release-managed LSP server (clojure-lsp, cue, deno, expert, gleam, marksman, opengrep, rust-analyzer, taplo, terraform-ls, typos-lsp, zizmor, zls) now checks `~/.pi-lens/bin` before walking bare PATH candidates, same as the fast path PR #2148 already gave the CLI-scan side of opengrep/gitleaks/trivy/govulncheck. A managed binary with no PATH entry no longer ENOENTs a launch candidate before the installer step finds the same binary a moment later. For rust-analyzer and deno, this means a pi-lens-managed copy is now preferred over a developer-toolchain-managed one (rustup, `deno upgrade`) when both are present; the managed copy is kept from drifting too far via the existing 7-day managed-tool refresh cadence.
268
+
269
+ - **Key process-unique state on `globalThis` so it survives a second module evaluation (refs #2146)** — pi evaluates the pi-lens module graph up to nine times per process, so the primary-session registration and the instance-registry mutation tail existed once per evaluation instead of once per process. The concurrent-session guard read an empty registration and ran the full session_start battery per subagent temp root, and concurrent read-modify-write cycles tore `instances.json`. Both now share one versioned process singleton, and `host_boot` records the evaluation ordinal. `session_shutdown` gained the same root discriminator `session_start` has, so a subagent's teardown no longer clears the shared registration and leaves the next subagent to run the full battery.
270
+
271
+ - **Distinguish clean from absent auxiliary LSP results (closes #2151)** — Turn-end late-auxiliary harvesting now recognizes published clean results and retires stuck coverage pairs with bounded, reconciled telemetry.
272
+
273
+ - **Adapt the first cold auxiliary wait to observed spawn cost (closes #2152)** — a cold auxiliary touch uses the larger of its declared wait and the server's last successful spawn duration plus a 500ms margin, capped at 8s. Warm touches retain the declared wait capped at 2s, and `PI_LENS_AUX_GRACE_MS` caps the budget (it never raises it).
274
+
275
+ - **Identity-feeding sorts now use a code-unit comparator, not `localeCompare` (closes #2155, closes #2165)** — the availability-probe cache key in `runner-helpers.ts` sorted env entries with `localeCompare`, a locale-dependent comparator. Two processes (or one process under a changed locale) could order the same env set differently and mint different keys for identical state, causing a silent probe-dedupe miss. Fixed via a new shared `compareOrdinal` helper (`clients/string-utils.ts`), applied to every identity-feeding sort found in the class sweep: dependency-cycle and madge dedupe keys, review-graph signature/hash/cache keys, the Windows spawn cache key, the bounded-hash object-key order, the rule-cache content hash, the workspace-diagnostics scope key, the turn-end-findings signature, and the formatter-config signature. `localeCompare` sorts kept for user-facing display are left untouched.
276
+
277
+ - **Share one LSP service across module evaluations (refs #2157)** — all evaluations now share one service, its generation handoff, workspace-sweep hold, and classic TypeScript repair guard through versioned process-singleton families. Incompatible live services shut down fast before replacement, and a secondary pipeline crash or a secondary session's idle-reset timer no longer tears down the primary fleet.
278
+
279
+ - **Consumed `.gitignore` files refresh at matcher lookup (closes #2159)** — pi-lens detects external edits, git restores, and changes under ignored directories with a cadence-bounded freshness probe.
280
+
281
+ - Use per-file diagnostic publication timestamps to distinguish a fresh clean primary LSP response from an absent response in the tsserver sync racer.
282
+
283
+ - **Bound the stale-findings re-arm loop and count cap-evicted pairs (closes #2167, closes #2168)** — Late-auxiliary turn-end harvesting now caps a pair that keeps returning stale findings at `MAX_LATE_AUX_REARMS` re-arms, re-arms (instead of dropping) a pair after a transient probe-cache read failure, and counts a pending-coverage cap eviction with a dedicated `capEvicted` counter folded into the turn's reconciliation sum.
284
+
285
+ - **Bound Svelte and Prisma language-server probes, and Vue's remaining dispatch-side budget (refs #2169, #2176)** — the Svelte and Prisma installer registry entries now carry their own cold-start `--version` verification ceilings (20s and 40s), matching real measured cold runs of 12.4s and up to 27.3s. Separately, Bash, JSON, Prisma, Vue, and Svelte language servers now raise the dispatch lsp-runner's 5-second cold-spawn wait floor to match their installer bounds, so a slow cold spawn cannot still read as unavailable after installer verification would have accepted it.
286
+
287
+ - PR body repair keeps distinct numbered fix-round headings distinct, so legitimate multi-round bodies can be repaired while duplicate and corrupted headings remain protected.
288
+
289
+ - **Preserve identity during concurrent instance registration (refs #2173)** — An adjacent O_EXCL lock serializes registry writers, reclaims stale ownership, and preserves session identity during child-first synthesis.
290
+
291
+ - **Bound the Vue language-server verification probe (refs #2176)** — the managed Vue registry entry now gives its cold-start `--version` probe a 30-second per-tool ceiling, while all other tools retain the 10-second installer default.
292
+
293
+ - **Migrate remaining Git-using smoke scripts to the scrubbed fixture environment (closes #2177)** — governance now detects fixture identity and path-exemption drift, and the JavaScript helper has direct wrapper coverage.
294
+
295
+ - **Bound the JSON and Bash language-server verification probes (closes #2194)** — `bash-language-server` and `vscode-json-language-server` measured 9,667ms and 11,047ms cold `--version` starts with closed stdin, close enough to the 10-second installer default that host contention alone could trip a false verification degradation. Both now carry a 20-second `verificationTimeoutMs`, delivered through the managed-local, install, and refresh paths. The refresh delivery for the other five install strategies (pip, gem, github, maven, archive) is now covered by tests too, closing the gap the issue's follow-up comment flagged.
296
+
297
+ - **Escalate workflow runs stuck queued (closes #2203)** — the merge-train warden classified a run that GitHub queued and never scheduled as `runs-in-progress` on every cycle forever, which read as healthy waiting. A tracked run that has executed zero steps for 60 minutes now classifies `stalled-run`: the warden names it in a PR comment, cancels it on the next cycle, and re-runs it once, bounded by GitHub's own `run_attempt`. A run with executed steps stays in progress however long it takes, and a cancellation the warden did not make is left alone.
298
+
299
+ - **Stamp classifiedBy on every availability_decision failure arm (refs #2131, #2209)** — 12 call sites across formatters, the JVM runtime probe, zizmor's gh-token latch, PowerShell script analysis, govulncheck, and the shared runner-helpers/ast-grep seams now record whether a probe or the call site itself classified the outcome, closing the failure-arm half of the gap #2205 fixed for successes.
300
+
301
+ - **Re-check test-runner negative verdicts instead of latching them (refs #2252)** — `TestRunnerClient.detectRunner` and `parseVitestTestGlobs` used to memoize "no runner"/"no config" for the process's whole life once probed. A config file added after the first probe (`vitest.config.ts` appearing, a project scaffolded mid-session) now converges on the same client instance instead of re-serving the earlier miss.
302
+
303
+ - **Treat a null PR body as empty (closes #2268)** — Let PR-body and close-keyword checks report their actual validation results instead of treating an empty GitHub body as a fetch failure.
304
+
305
+ - **Make the failed-target cap test load-invariant (closes #2290)** — Seed the real `TestRunnerClient` state with deterministic failed results instead of launching 33 child processes, so the cap assertion is independent of batch contention.
306
+
307
+ - **Make cross-root eviction coverage load-invariant (closes #2305)** — Seed failed-target eviction state through `TestRunnerClient.recordResult` instead of spawning child processes, while retaining global ordering, root-map reacquisition, and telemetry assertions.
308
+
309
+ - **Normalize tabs in playground-verify's sentinel comparison, and fail fast on a stable mismatch (closes #2306)** — a first source line with an internal tab (`const\tok\t= arr.indexOf(x) !== -1;`) burned the full 40s poll timeout and reported "likely upstream schema drift": Monaco doesn't render a tab as U+0009, so the raw-tab sentinel never matched, the same failure mode the existing nbsp normalization was added for. `buildScrapeExpr` now collapses every run of tab/nbsp/space to one space on BOTH sides of the comparison instead of hand-listing substitutions. The poll loop also tracks a stable, non-empty rendered-source length across polls and concludes as soon as it repeats unmatched for three consecutive polls, rather than always waiting out the full `--timeout`; the failure message now names both remaining causes (a sentinel-normalization mismatch in this harness, or upstream schema drift leaving the page's default sample rendered) instead of a bare schema-drift verdict.
310
+
311
+ - **Pin the availability-probe cooldown consult (closes #2309)** — Preserve the transient timeout decision and skip redundant probes while a command is cooling down.
312
+
313
+ - **Harden the CI classifier's automatic PR comment path (closes #2318, refs #2316)** — escape log-derived HTML comment delimiters, neutralize mentions, and accept only the final anchored classifier marker so failed test output cannot forge rerun suppression or ping users.
314
+
315
+ - Schedule ast-grep NAPI rules for CSS and HTML edits, restore four sibling structural and IaC runner-kind pairs found by the class sweep, and guard every declared pair against dispatch-plan drift. (#2323; refs #2325)
316
+
317
+ - **HTML ast-grep catalog coverage (closes #2325)** — Verify that every NAPI-routed language ships enabled rules and that HTML edits report plaintext HTTP links.
318
+
319
+ - **Avoid unrelated diagnostics after a conflicted Git integration.** Pi Lens now excludes clean incoming index paths from failed Git integration recovery (merge, rebase, cherry-pick, pull, revert, am).
320
+
321
+ - **Guard package-lock metadata during releases (#2043)** — Detect package identity, dependency, and peer-policy drift at CI, commit, install, and changelog release preflight boundaries.
322
+
323
+ - **Attribute pi-lens writes and bound review-graph path normalization (refs #2070, closes #2072)** — drift correlation records identify pi-lens writes with bounded normalized paths, while the review-graph scale regression proves warm 8,000-file walks normalize only the changed-file bound.
324
+
325
+ - **Prove idle-eviction replacements release graph memory (closes #2073)** — Add an enforced forced-GC test for twenty 2,000-node workspace graph replacements. The cache retains under 6 MiB after replacement, proving outgoing eviction timers do not retain prior graphs.
326
+
327
+ - **Pin diagnostics bus publishers and subscribers (closes #2079)** — Add source-scan conformance coverage for the `pilens:diagnostics` event surface.
328
+
329
+ - **Test-flakiness and hygiene residuals (refs #2090, closes #2139, refs #2182)** — `project-snapshot.test.ts` now restores its stubbed idle-evict env var so it cannot leak into later tests, and `project-report.test.ts`'s graph-cold assertion checks the cache stayed cold instead of a 22x-loose wall-clock bound. `session-lifecycle.test.ts`'s guard=0 reset test gets a 15s timeout, sized off a measured 5.4-7.2s honest cost under load instead of vitest's tight 5s default. `managed-tool-refresh-strategies.test.ts`'s cross-session re-arm test gets the same budget correction after reproducing its combined-run flake against the degradation-ledger suites.
330
+
331
+ - **`vi.stubEnv` leak sweep undercount (refs #2090, closes #2223)** — `reverse-deps-cache.test.ts` now unstubs `PI_LENS_REVERSE_DEPS_IDLE_EVICT_MS` in its `afterEach`, the second live instance of the #2090 leak class that the original sweep missed. `check-pr-body.test.ts`'s "renames out of tests/" test unstubbed its GitHub env stubs only after its assertion, so a failing assertion would have left them stubbed for later tests; it now unstubs in `afterEach` like every other describe block in the file.
332
+
333
+ - **Guard the warmed YAML rules cache with load-invariant work counts (closes #2292)** — assert that repeated cache hits perform no directory, metadata, or content reads inside the freshness cadence, and that the next cadence performs one metadata sweep without reading rule content.
334
+
335
+ - **Close #2298's rules-cache work-count residuals (refs #2292)** — count the warmed cache's existence and content-open probes, and verify metadata sweeps across nested rule files.
336
+
337
+ - **Isolate real-Git test fixtures and guard repository config (closes #2163)** — shared fixture environment hygiene, source governance, and a post-suite contamination guard prevent tests from mutating the checkout.
338
+
19
339
  ## [4.1.2] - 2026-08-24
20
340
 
21
341
  ### Added
@@ -0,0 +1,79 @@
1
+ [
2
+ "./clients/agent-nudge.js",
3
+ "./clients/ast-grep-client.js",
4
+ "./clients/bootstrap.js",
5
+ "./clients/bounded-telemetry.js",
6
+ "./clients/build-identity.js",
7
+ "./clients/bus-events-logger.js",
8
+ "./clients/bus-publish.js",
9
+ "./clients/cache-manager.js",
10
+ "./clients/cache-observability.js",
11
+ "./clients/cascade-format.js",
12
+ "./clients/console-guard-install.js",
13
+ "./clients/debug-handles.js",
14
+ "./clients/debug-heap.js",
15
+ "./clients/degradation-ledger.js",
16
+ "./clients/diagnostic-tracker.js",
17
+ "./clients/diagnostics-publish.js",
18
+ "./clients/dispatch/lazy.js",
19
+ "./clients/dispatch/runner-context.js",
20
+ "./clients/disposition-publish.js",
21
+ "./clients/event-loop-monitor.js",
22
+ "./clients/extension-log.js",
23
+ "./clients/extension-mode.js",
24
+ "./clients/file-utils.js",
25
+ "./clients/format-events-publish.js",
26
+ "./clients/format-service.js",
27
+ "./clients/formatters-lazy.js",
28
+ "./clients/git-guard.js",
29
+ "./clients/host-ports.js",
30
+ "./clients/installer/index.js",
31
+ "./clients/instance-reaper.js",
32
+ "./clients/instance-registry.js",
33
+ "./clients/latency-logger.js",
34
+ "./clients/lens-config.js",
35
+ "./clients/lens-events.js",
36
+ "./clients/lens-flag-registry.js",
37
+ "./clients/lsp-budget.js",
38
+ "./clients/lsp-lazy.js",
39
+ "./clients/lsp-status.js",
40
+ "./clients/lsp/cascade-tier.js",
41
+ "./clients/lsp/config.js",
42
+ "./clients/lsp/index.js",
43
+ "./clients/memory-sampler.js",
44
+ "./clients/message-end-attribution.js",
45
+ "./clients/package-root.js",
46
+ "./clients/path-attribution-telemetry.js",
47
+ "./clients/path-utils.js",
48
+ "./clients/project-lens-config.js",
49
+ "./clients/project-trust.js",
50
+ "./clients/quiet-window.js",
51
+ "./clients/read-bridge.js",
52
+ // index.ts:56 is type-only, but dependency-cruiser parses it as an edge.
53
+ "./clients/read-guard.js",
54
+ "./clients/recent-touches.js",
55
+ "./clients/runtime-agent-end.js",
56
+ "./clients/runtime-context.js",
57
+ "./clients/runtime-coordinator.js",
58
+ "./clients/runtime-session.js",
59
+ "./clients/runtime-tool-call.js",
60
+ "./clients/runtime-tool-result.js",
61
+ "./clients/runtime-turn.js",
62
+ "./clients/safe-spawn.js",
63
+ "./clients/session-event-guard.js",
64
+ "./clients/session-lifecycle.js",
65
+ "./clients/session-start-observability.js",
66
+ "./clients/session-state-store.js",
67
+ "./clients/sessionstart-logger.js",
68
+ "./clients/smells-rollup.js",
69
+ "./clients/startup-timing.js",
70
+ "./clients/tool-definition.js",
71
+ "./clients/tool-render.js",
72
+ "./clients/tool-set-policy.js",
73
+ "./clients/turn-summary-render.js",
74
+ "./clients/turn-summary.js",
75
+ "./clients/user-notify.js",
76
+ "./clients/vanished-instance-marker.js",
77
+ "./clients/warm-attach.js",
78
+ "./clients/widget-state.js"
79
+ ]
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
+ import { normalizeFilePath } from "./path-utils.js";
5
6
  const AW_LOG_DIR = getGlobalPiLensDir();
6
7
  const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
7
8
  const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
@@ -12,11 +13,25 @@ const writer = createNdjsonLogger({
12
13
  maxBytes: MAX_LOG_BYTES,
13
14
  backupPath: AW_LOG_BACKUP_FILE,
14
15
  });
16
+ /**
17
+ * #2219 (the #2141 class): `filePath` reaches here from
18
+ * `actionable-warnings.ts`'s raw `path.resolve(cwd, file)` — the file
19
+ * imports `normalizeMapKey` and uses it for map lookups on this same value,
20
+ * but never normalizes what gets logged. `filePath` is optional (several
21
+ * events, e.g. `report_started`/`report_complete`, carry none), so only
22
+ * normalize when present.
23
+ */
15
24
  export function logActionableWarningsEvent(entry) {
16
25
  if (isTestMode()) {
17
26
  return;
18
27
  }
19
- writer.log({ ts: new Date().toISOString(), ...entry });
28
+ writer.log({
29
+ ts: new Date().toISOString(),
30
+ ...entry,
31
+ ...(entry.filePath !== undefined
32
+ ? { filePath: normalizeFilePath(entry.filePath) }
33
+ : {}),
34
+ });
20
35
  }
21
36
  export function getActionableWarningsLogPath() {
22
37
  return AW_LOG_FILE;
@@ -13,6 +13,7 @@ import * as os from "node:os";
13
13
  import * as path from "node:path";
14
14
  import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
15
15
  import { resolvePackagePath } from "./package-root.js";
16
+ import { truncatedByOutputCap } from "./spawn-output-cap.js";
16
17
  import { SgRunner, } from "./sg-runner.js";
17
18
  // --- Client ---
18
19
  function extractDebugAst(raw) {
@@ -263,6 +264,18 @@ export class AstGrepClient {
263
264
  const result = await this.runner.execRaw(args, 10_000, options);
264
265
  const stderr = result.stderr.trim();
265
266
  const stdout = result.stdout.trim();
267
+ // FIRST (#2100): hitting `execRaw`'s cap SIGTERMs ast-grep, so a
268
+ // truncated run also carries the kill's error message. Read after the
269
+ // two checks below, this guard could only ever describe a run that
270
+ // exited before the signal reached it. `execRaw` re-spells `failure`
271
+ // in its own vocabulary but keeps the timeout/aborted spellings, so
272
+ // those runs still fall through to the error branch below.
273
+ if (truncatedByOutputCap(result)) {
274
+ return {
275
+ valid: false,
276
+ error: "ast-grep validation output was truncated",
277
+ };
278
+ }
266
279
  if (result.error)
267
280
  return { valid: false, error: result.error };
268
281
  if (result.status !== 0 && !(result.status === 1 && !stderr)) {
@@ -272,12 +285,6 @@ export class AstGrepClient {
272
285
  `ast-grep validation failed with exit code ${result.status}`,
273
286
  };
274
287
  }
275
- if (result.outputTruncated) {
276
- return {
277
- valid: false,
278
- error: "ast-grep validation output was truncated",
279
- };
280
- }
281
288
  if (stderrHasError(stderr))
282
289
  return { valid: false, error: stderr };
283
290
  const warning = stderr || stdout || undefined;
@@ -0,0 +1,12 @@
1
+ import { createSingleFlight } from "./single-flight.js";
2
+ /** Create a flight registry whose lifetime belongs to the caller. */
3
+ export function createAvailabilityProbeFlight(options = {}) {
4
+ const flights = createSingleFlight(options);
5
+ return {
6
+ run(key, probe) {
7
+ const joined = flights.has(key);
8
+ return { promise: flights.run(key, probe), joined };
9
+ },
10
+ clear: () => flights.clear(),
11
+ };
12
+ }
@@ -179,6 +179,7 @@ export class BiomeClient {
179
179
  latched: true,
180
180
  hostStallMs,
181
181
  budgetMs: PROBE_TIMEOUT_MS,
182
+ classifiedBy: "probe",
182
183
  });
183
184
  return { outcome: "success", value: true };
184
185
  }
@@ -225,6 +226,12 @@ export class BiomeClient {
225
226
  latched: true,
226
227
  hostStallMs: this.lastProbeHostStallMs,
227
228
  budgetMs: PROBE_TIMEOUT_MS,
229
+ classifiedBy: "caller",
230
+ evidence: {
231
+ install: "succeeded",
232
+ binary: "biome",
233
+ source: "managed-dir",
234
+ },
228
235
  });
229
236
  }
230
237
  return true;