@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

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 (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -6,16 +6,19 @@
6
6
  * connects to it to get LSP-complete diagnostics from the warm process instead
7
7
  * of running its own cold analysis.
8
8
  *
9
- * This module is the CLIENT + the shared path derivation only — deliberately
10
- * light (node:net + type-only result), so the bin can try the warm path WITHOUT
11
- * loading the dispatch graph. The server side lives in mcp/server.ts (which
12
- * already holds the analysis engine). If the warm path is unavailable, the
13
- * client resolves `undefined` and the caller falls back to cold local analysis.
9
+ * This module is the CLIENT + the shared path derivation + the one-shot line
10
+ * reader the server wires into its socket (deliberately light: node:net +
11
+ * type-only result, so the bin can try the warm path WITHOUT loading the
12
+ * dispatch graph). The analysis engine lives in mcp/server.ts. If the warm
13
+ * path is unavailable, the client resolves `undefined` and the caller falls
14
+ * back to cold local analysis.
14
15
  */
15
16
  import * as crypto from "node:crypto";
17
+ import * as fs from "node:fs";
16
18
  import * as net from "node:net";
17
19
  import * as os from "node:os";
18
20
  import * as path from "node:path";
21
+ import { writeFileAtomic } from "../atomic-write.js";
19
22
  export const WARM_DIAGNOSTICS_SCHEMA_VERSION = 1;
20
23
  export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
21
24
  /**
@@ -25,19 +28,23 @@ export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
25
28
  * the same project they meet. Mismatch → the client just falls back to cold.
26
29
  */
27
30
  export function ipcPathForCwd(cwd) {
28
- const root = path.resolve(cwd).toLowerCase();
29
- // sha256 (not for security — just a stable short id for the IPC socket/pipe
30
- // name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
31
- const hash = crypto
32
- .createHash("sha256")
33
- .update(root)
34
- .digest("hex")
35
- .slice(0, 16);
31
+ const hash = workspaceHash(cwd);
36
32
  if (process.platform === "win32") {
37
33
  return `\\\\.\\pipe\\pi-lens-mcp-${hash}`;
38
34
  }
39
35
  return path.join(os.tmpdir(), `pi-lens-mcp-${hash}.sock`);
40
36
  }
37
+ /**
38
+ * The single stable per-workspace id both endpoint derivations key on. Every
39
+ * per-workspace side-channel name (socket/pipe, turn-end status file) must come
40
+ * from HERE, so the hook process and the server process cannot drift apart.
41
+ */
42
+ function workspaceHash(cwd) {
43
+ const root = path.resolve(cwd).toLowerCase();
44
+ // sha256 (not for security — just a stable short id for the IPC socket/pipe
45
+ // name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
46
+ return crypto.createHash("sha256").update(root).digest("hex").slice(0, 16);
47
+ }
41
48
  /** PID-scoped endpoint used by pi sessions. The legacy MCP analyze endpoint
42
49
  * remains workspace-scoped for compatibility with the PostToolUse hook. */
43
50
  export function diagnosticsIpcPathForCwd(cwd, pid) {
@@ -50,15 +57,16 @@ export function contentHash(content) {
50
57
  return crypto.createHash("sha256").update(content).digest("hex");
51
58
  }
52
59
  /**
53
- * Shared one-shot request/response transport for the warm-attach IPC routes
54
- * (#822): connect to the incumbent's PID-scoped endpoint, write one JSON
55
- * line, read one JSON line back, classify. The per-route `validate` callback
56
- * returns a failure reason for an on-time but unusable reply (schema skew,
57
- * staleness) or `undefined` to accept it; transport failures map uniformly to
58
- * timeout/ipc-error. One transport, N routes — the diagnostics and
59
- * code-action clients cannot drift apart.
60
+ * Shared one-shot request/response transport for the tagged IPC routes (#822):
61
+ * connect to `endpoint`, write one JSON line, read one JSON line back,
62
+ * classify. The endpoint is PID-scoped for the warm-attach routes and
63
+ * workspace-scoped for the hook bin's turn-end route. The per-route `validate`
64
+ * callback returns a failure reason for an on-time but unusable reply (schema
65
+ * skew, staleness) or `undefined` to accept it; transport failures map
66
+ * uniformly to timeout/ipc-error. One transport, N routes — the clients cannot
67
+ * drift apart.
60
68
  */
61
- function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate) {
69
+ function requestOverWarmIpc(endpoint, timeoutMs, buildRequest, validate) {
62
70
  return new Promise((resolve) => {
63
71
  let settled = false;
64
72
  const finish = (value) => {
@@ -70,7 +78,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
70
78
  resolve(value);
71
79
  };
72
80
  const deadlineAt = Date.now() + timeoutMs;
73
- const socket = net.createConnection(diagnosticsIpcPathForCwd(cwd, incumbentPid));
81
+ const socket = net.createConnection(endpoint);
74
82
  socket.setEncoding("utf8");
75
83
  let buffer = "";
76
84
  const timer = setTimeout(() => finish({ available: false, reason: "timeout" }), timeoutMs);
@@ -108,7 +116,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
108
116
  }
109
117
  export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeoutMs) {
110
118
  const expectedHash = contentHash(content);
111
- return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
119
+ return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
112
120
  route: "diagnostics",
113
121
  version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
114
122
  file,
@@ -131,7 +139,7 @@ export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeout
131
139
  });
132
140
  }
133
141
  export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentHash, ranges, timeoutMs) {
134
- return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
142
+ return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
135
143
  route: "code-actions",
136
144
  version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
137
145
  file,
@@ -154,6 +162,99 @@ export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentH
154
162
  return undefined;
155
163
  });
156
164
  }
165
+ // v2 adds an explicit receipt acknowledgement. A v1 server must reject rather
166
+ // than consume a pass without the new delivery contract.
167
+ export const WARM_TURN_END_SCHEMA_VERSION = 2;
168
+ /**
169
+ * Ask the warm server to run pi-lens's real turn-end pass. Execution and
170
+ * delivery are separate: the first one-shot connection returns a capability,
171
+ * then a second one-shot connection acknowledges receipt. If the client
172
+ * deadline or either connection loses, the server leaves the durable finding
173
+ * cache untouched for a later Stop. 55 s expires inside Claude Code's 60 s hook
174
+ * timeout.
175
+ */
176
+ export async function requestWarmTurnEnd(cwd, timeoutMs = 55_000) {
177
+ const startedAt = Date.now();
178
+ const first = await requestOverWarmIpc(ipcPathForCwd(cwd), timeoutMs, () => ({
179
+ route: "turn-end",
180
+ version: WARM_TURN_END_SCHEMA_VERSION,
181
+ cwd,
182
+ }), (result) => result.route === "turn-end" &&
183
+ result.version === WARM_TURN_END_SCHEMA_VERSION
184
+ ? undefined
185
+ : "schema-mismatch");
186
+ if (!first.available || !first.response.deliveryId)
187
+ return first;
188
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
189
+ if (remainingMs <= 0)
190
+ return { available: false, reason: "timeout" };
191
+ const ack = await requestOverWarmIpc(ipcPathForCwd(cwd), remainingMs, () => ({
192
+ route: "turn-end-ack",
193
+ version: WARM_TURN_END_SCHEMA_VERSION,
194
+ cwd,
195
+ deliveryId: first.response.deliveryId,
196
+ }), (result) => result.route === "turn-end-ack" && result.acknowledged === true
197
+ ? undefined
198
+ : "ipc-error");
199
+ return ack.available
200
+ ? { available: true, response: first.response }
201
+ : { available: false, reason: ack.reason };
202
+ }
203
+ /** Per-workspace status file, keyed by the same hash as the IPC endpoint. */
204
+ export function turnEndStatusPathForCwd(cwd) {
205
+ return path.join(os.tmpdir(), `pi-lens-turn-end-${workspaceHash(cwd)}.json`);
206
+ }
207
+ export function readTurnEndStatus(cwd) {
208
+ try {
209
+ const raw = fs.readFileSync(turnEndStatusPathForCwd(cwd), "utf8");
210
+ const parsed = JSON.parse(raw);
211
+ if (!parsed || typeof parsed !== "object")
212
+ return undefined;
213
+ return {
214
+ ran: typeof parsed.ran === "number" ? parsed.ran : 0,
215
+ skipped: typeof parsed.skipped === "number" ? parsed.skipped : 0,
216
+ lastSkipReason: parsed.lastSkipReason,
217
+ lastRunAt: parsed.lastRunAt,
218
+ lastSkipAt: parsed.lastSkipAt,
219
+ };
220
+ }
221
+ catch {
222
+ // never written, unreadable, or corrupt — "no turn-end activity recorded"
223
+ return undefined;
224
+ }
225
+ }
226
+ /**
227
+ * Append one turn-end outcome from the hook process. Never throws.
228
+ *
229
+ * Read-modify-write against a shared per-workspace tmpdir file: two Stop
230
+ * hooks for the same workspace can race this concurrently (separate
231
+ * processes, no lock). `writeFileAtomic` only closes the *publication* half
232
+ * of that — it guarantees this write's own staging+rename can't torn-read
233
+ * (parse-fail and reset counters) or hit a Windows sharing violation against
234
+ * a concurrent writer's rename. It does NOT fix the read-modify-write race
235
+ * itself: two overlapping calls can still both read the same `previous` and
236
+ * publish from it, silently dropping one increment. That's accepted here —
237
+ * this is bounded best-effort telemetry (self-heals, errors already
238
+ * swallowed below), and a lock on this path isn't worth it.
239
+ */
240
+ export function recordTurnEndOutcome(cwd, outcome) {
241
+ try {
242
+ const now = new Date().toISOString();
243
+ const previous = readTurnEndStatus(cwd) ?? { ran: 0, skipped: 0 };
244
+ const next = outcome.ran
245
+ ? { ...previous, ran: previous.ran + 1, lastRunAt: now }
246
+ : {
247
+ ...previous,
248
+ skipped: previous.skipped + 1,
249
+ lastSkipReason: outcome.reason,
250
+ lastSkipAt: now,
251
+ };
252
+ writeFileAtomic(turnEndStatusPathForCwd(cwd), `${JSON.stringify(next)}\n`);
253
+ }
254
+ catch {
255
+ // telemetry only — a read-only tmpdir must not break the Stop hook
256
+ }
257
+ }
157
258
  /**
158
259
  * Ask the warm server to analyze a file. Resolves the server's result, or
159
260
  * `undefined` on ANY failure (no server, refused, stale socket, timeout, bad
@@ -200,3 +301,35 @@ export function requestWarmAnalyze(cwd, file, timeoutMs = 30_000) {
200
301
  socket.on("close", () => finish(undefined));
201
302
  });
202
303
  }
304
+ /**
305
+ * One-shot line reader for the warm IPC socket (#1219). The clients write
306
+ * exactly one newline-terminated request per connection and read one reply, so
307
+ * the server must dispatch at most one line and ignore anything after it — a
308
+ * `data` handler that keeps re-reading the same buffered line re-dispatches
309
+ * the request on stray bytes. Returns the handler to attach to the socket's
310
+ * `data` event.
311
+ */
312
+ export function createWarmIpcLineReader(onLine) {
313
+ let buffer = "";
314
+ let dispatched = false;
315
+ return (chunk) => {
316
+ if (dispatched)
317
+ return;
318
+ buffer += chunk;
319
+ const newline = buffer.indexOf("\n");
320
+ if (newline === -1)
321
+ return;
322
+ dispatched = true;
323
+ onLine(buffer.slice(0, newline));
324
+ };
325
+ }
326
+ export function createWarmIpcRequestQueue() {
327
+ let chain = Promise.resolve();
328
+ return {
329
+ enqueue(work) {
330
+ const next = chain.then(work);
331
+ chain = next.catch(() => undefined);
332
+ return next;
333
+ },
334
+ };
335
+ }
@@ -5,7 +5,7 @@
5
5
  * doesn't — session-start warms the dominant-language LSP (so warm `analyze` is
6
6
  * LSP-complete), establishes the error-debt baseline + complexity baselines, and
7
7
  * kicks off knip/jscpd/type-coverage/dep/secrets scans; turn-end runs
8
- * knip/jscpd incrementally, dep-circular, tests, cascade, and the
8
+ * knip incrementally, dep-circular, tests, cascade, and the
9
9
  * actionable/code-quality aggregation.
10
10
  *
11
11
  * The handlers don't return findings — they emit them through the same
@@ -16,15 +16,16 @@
16
16
  * persistent RuntimeCoordinator + CacheManager (so baselines/turn-state survive
17
17
  * across calls, like a real session), and the bootstrap client bundle.
18
18
  */
19
+ import * as crypto from "node:crypto";
19
20
  import * as fs from "node:fs";
20
21
  import * as path from "node:path";
21
22
  import { AstGrepClient } from "../ast-grep-client.js";
22
23
  import { loadBootstrapClients } from "../bootstrap.js";
23
- import { CacheManager } from "../cache-manager.js";
24
+ import { CacheManager, MCP_TURN_STATE_OWNER_ID, } from "../cache-manager.js";
24
25
  import { resetDispatchBaselines } from "../dispatch/integration.js";
25
26
  import { resetFormatService } from "../format-service.js";
26
27
  import { getLSPService, resetLSPService } from "../lsp/index.js";
27
- import { consumeSessionStartGuidance, consumeTestFindings, consumeTurnEndFindings, } from "../runtime-context.js";
28
+ import { consumeSessionStartGuidance, consumeTestFindings, consumeTurnEndFindings, peekTestFindings, peekTurnEndFindings, } from "../runtime-context.js";
28
29
  import { RuntimeCoordinator } from "../runtime-coordinator.js";
29
30
  import { handleSessionStart } from "../runtime-session.js";
30
31
  import { handleTurnEnd } from "../runtime-turn.js";
@@ -105,12 +106,82 @@ export async function runSessionStart(cwd) {
105
106
  aliveLspClients: getLSPService().getAliveClientCount(),
106
107
  };
107
108
  }
109
+ /**
110
+ * #1274: the delivery map used to be unbounded — every timed-out client and
111
+ * every distinct raw-cwd alias left an entry for the life of the process.
112
+ * Both bounds drop entries WITHOUT committing, which re-arms the findings in
113
+ * the cache: the safe direction, since an un-consumed finding is re-reported on
114
+ * a later Stop while a wrongly-consumed one is gone for good. The TTL is
115
+ * generous next to Claude Code's 60 s hook budget — it exists to bound the map,
116
+ * not to police clients.
117
+ */
118
+ const TURN_END_DELIVERY_TTL_MS = 10 * 60_000;
119
+ const TURN_END_DELIVERY_MAX = 8;
120
+ const TURN_END_QUEUE_WAIT_MS = 5_000;
121
+ /**
122
+ * A bounded, cancellable serial queue. A turn_end mutates shared runtime and
123
+ * cache state, so concurrent passes are unsafe; a disconnected/hung pass must
124
+ * nevertheless not accumulate an unbounded tail of Stop requests.
125
+ */
126
+ class TurnEndQueue {
127
+ running = false;
128
+ pending = [];
129
+ enqueue(work, waitMs = TURN_END_QUEUE_WAIT_MS) {
130
+ if (this.pending.length >= 1) {
131
+ return Promise.reject(new Error("turn_end queue is busy"));
132
+ }
133
+ return new Promise((resolve, reject) => {
134
+ const item = {
135
+ work,
136
+ resolve,
137
+ reject,
138
+ timer: setTimeout(() => {
139
+ const index = this.pending.indexOf(item);
140
+ if (index === -1)
141
+ return;
142
+ this.pending.splice(index, 1);
143
+ reject(new Error("turn_end queue wait timed out"));
144
+ }, waitMs),
145
+ };
146
+ item.timer.unref();
147
+ this.pending.push(item);
148
+ this.drain();
149
+ });
150
+ }
151
+ drain() {
152
+ if (this.running)
153
+ return;
154
+ const item = this.pending.shift();
155
+ if (!item)
156
+ return;
157
+ clearTimeout(item.timer);
158
+ this.running = true;
159
+ void item
160
+ .work()
161
+ .then(item.resolve, item.reject)
162
+ .finally(() => {
163
+ this.running = false;
164
+ this.drain();
165
+ });
166
+ }
167
+ reset() {
168
+ for (const item of this.pending.splice(0)) {
169
+ clearTimeout(item.timer);
170
+ item.reject(new Error("turn_end queue reset"));
171
+ }
172
+ this.running = false;
173
+ }
174
+ }
175
+ const turnEndQueue = new TurnEndQueue();
176
+ const pendingTurnEndDeliveries = new Map();
177
+ /** In-flight Stop-hook passes, keyed by cwd — see `runTurnEndForIpc` (#1274). */
178
+ const inFlightIpcTurnEnds = new Map();
108
179
  /**
109
180
  * Run pi-lens's real turn_end over the files edited this "turn". The handler
110
181
  * reads edited files from turn-state, so we register the caller-supplied files
111
182
  * first (a full-file range, importsChanged=true so dep/knip re-check broadly).
112
183
  */
113
- export async function runTurnEnd(cwd, files = []) {
184
+ async function runTurnEndNow(cwd, files = [], deferredDelivery = false) {
114
185
  const ctx = await getMcpSessionContext();
115
186
  const host = createMcpHost(undefined, cwd);
116
187
  let registered = 0;
@@ -123,9 +194,15 @@ export async function runTurnEnd(cwd, files = []) {
123
194
  catch {
124
195
  continue; // unreadable / deleted — skip
125
196
  }
126
- ctx.cacheManager.addModifiedRange(abs, { start: 1, end: lineCount }, true, cwd, ctx.runtime.telemetrySessionId);
197
+ ctx.cacheManager.addModifiedRange(abs, { start: 1, end: lineCount }, true, cwd, MCP_TURN_STATE_OWNER_ID, "mcp");
127
198
  registered++;
128
199
  }
200
+ const owner = {
201
+ kind: "mcp",
202
+ id: MCP_TURN_STATE_OWNER_ID,
203
+ pid: process.pid,
204
+ lastSeen: new Date().toISOString(),
205
+ };
129
206
  await handleTurnEnd({
130
207
  ctxCwd: cwd,
131
208
  getFlag: host.getFlag,
@@ -136,12 +213,143 @@ export async function runTurnEnd(cwd, files = []) {
136
213
  deadCodeClients: ctx.clients.deadCodeClients,
137
214
  depChecker: ctx.clients.depChecker,
138
215
  testRunnerClient: ctx.clients.testRunnerClient,
216
+ owner,
139
217
  resetLSPService,
140
218
  resetFormatService,
141
219
  });
220
+ const outcome = deferredDelivery
221
+ ? {
222
+ turnEnd: joinMessages(peekTurnEndFindings(ctx.cacheManager, cwd)),
223
+ tests: joinMessages(peekTestFindings(ctx.cacheManager, cwd)),
224
+ filesRegistered: registered,
225
+ }
226
+ : {
227
+ turnEnd: joinMessages(consumeTurnEndFindings(ctx.cacheManager, cwd)),
228
+ tests: joinMessages(consumeTestFindings(ctx.cacheManager, cwd)),
229
+ filesRegistered: registered,
230
+ };
142
231
  return {
143
- turnEnd: joinMessages(consumeTurnEndFindings(ctx.cacheManager, cwd)),
144
- tests: joinMessages(consumeTestFindings(ctx.cacheManager, cwd)),
145
- filesRegistered: registered,
232
+ outcome,
233
+ commit: () => {
234
+ if (!deferredDelivery)
235
+ return;
236
+ consumeTurnEndFindings(ctx.cacheManager, cwd);
237
+ consumeTestFindings(ctx.cacheManager, cwd);
238
+ },
146
239
  };
147
240
  }
241
+ /**
242
+ * Run a normal MCP turn_end. Its caller owns the tool response, so the
243
+ * historical consume-on-return behavior remains unchanged.
244
+ */
245
+ export function runTurnEnd(cwd, files = []) {
246
+ return turnEndQueue.enqueue(async () => (await runTurnEndNow(cwd, files)).outcome);
247
+ }
248
+ /** A result with no findings needs no durable delivery transaction. */
249
+ function hasTurnEndFindings(outcome) {
250
+ return Boolean(outcome.turnEnd || outcome.tests);
251
+ }
252
+ /** Drop expired deliveries, then the oldest ones over the cap. Never commits. */
253
+ function prunePendingTurnEndDeliveries(now = Date.now()) {
254
+ for (const [deliveryId, delivery] of pendingTurnEndDeliveries) {
255
+ if (now - delivery.createdAt >= TURN_END_DELIVERY_TTL_MS) {
256
+ pendingTurnEndDeliveries.delete(deliveryId);
257
+ }
258
+ }
259
+ // Map iteration is insertion-ordered, so the head is the oldest entry.
260
+ while (pendingTurnEndDeliveries.size > TURN_END_DELIVERY_MAX) {
261
+ const oldest = pendingTurnEndDeliveries.keys().next();
262
+ if (oldest.done)
263
+ break;
264
+ pendingTurnEndDeliveries.delete(oldest.value);
265
+ }
266
+ }
267
+ function pendingForCwd(cwd) {
268
+ for (const [deliveryId, delivery] of pendingTurnEndDeliveries) {
269
+ if (delivery.cwd === cwd)
270
+ return [deliveryId, delivery];
271
+ }
272
+ return undefined;
273
+ }
274
+ /**
275
+ * Run the Stop-hook pass as a durable delivery transaction. The result is
276
+ * acknowledged through a separate one-shot IPC request. Until that request
277
+ * arrives, the findings cache is deliberately left unconsumed; a client
278
+ * timeout, socket close, or server restart therefore leaves the result for a
279
+ * later Stop request instead of losing it between execution and reply.
280
+ */
281
+ export function runTurnEndForIpc(cwd) {
282
+ // #1274: the Stop-hook request carries no files and no sessionId, so two
283
+ // overlapping ones are byte-identical and a second pass would be pure waste
284
+ // — worse, the queue only holds ONE waiter, so a third Stop is rejected
285
+ // outright. Identical in-flight requests share the one pass and one
286
+ // deliveryId; the ack is idempotent-by-capability, so the loser of the race
287
+ // simply finds the delivery already committed.
288
+ const inFlight = inFlightIpcTurnEnds.get(cwd);
289
+ if (inFlight)
290
+ return inFlight;
291
+ const run = runTurnEndForIpcNow(cwd).finally(() => {
292
+ inFlightIpcTurnEnds.delete(cwd);
293
+ });
294
+ inFlightIpcTurnEnds.set(cwd, run);
295
+ return run;
296
+ }
297
+ function runTurnEndForIpcNow(cwd) {
298
+ return turnEndQueue.enqueue(async () => {
299
+ prunePendingTurnEndDeliveries();
300
+ const pending = pendingForCwd(cwd);
301
+ if (pending) {
302
+ return {
303
+ outcome: pending[1].transaction.outcome,
304
+ deliveryId: pending[0],
305
+ };
306
+ }
307
+ const ctx = await getMcpSessionContext();
308
+ const cachedOutcome = {
309
+ turnEnd: joinMessages(peekTurnEndFindings(ctx.cacheManager, cwd)),
310
+ tests: joinMessages(peekTestFindings(ctx.cacheManager, cwd)),
311
+ filesRegistered: 0,
312
+ };
313
+ const transaction = hasTurnEndFindings(cachedOutcome)
314
+ ? {
315
+ outcome: cachedOutcome,
316
+ commit: () => {
317
+ consumeTurnEndFindings(ctx.cacheManager, cwd);
318
+ consumeTestFindings(ctx.cacheManager, cwd);
319
+ },
320
+ }
321
+ : await runTurnEndNow(cwd, [], true);
322
+ if (!hasTurnEndFindings(transaction.outcome)) {
323
+ return { outcome: transaction.outcome };
324
+ }
325
+ const deliveryId = crypto.randomUUID();
326
+ pendingTurnEndDeliveries.set(deliveryId, {
327
+ cwd,
328
+ transaction,
329
+ createdAt: Date.now(),
330
+ });
331
+ prunePendingTurnEndDeliveries();
332
+ return { outcome: transaction.outcome, deliveryId };
333
+ });
334
+ }
335
+ /** Commit exactly one admitted delivery capability. */
336
+ export function acknowledgeTurnEnd(cwd, deliveryId) {
337
+ prunePendingTurnEndDeliveries();
338
+ const delivery = pendingTurnEndDeliveries.get(deliveryId);
339
+ if (!delivery || delivery.cwd !== cwd)
340
+ return false;
341
+ // #1274: commit BEFORE dropping the capability. Deleting first meant a
342
+ // throwing consume destroyed the only handle to the findings — precisely the
343
+ // loss this two-phase protocol exists to prevent, reintroduced on the error
344
+ // path. Leaving the entry in place on a throw keeps the delivery re-fetchable
345
+ // by a later Stop.
346
+ delivery.transaction.commit();
347
+ pendingTurnEndDeliveries.delete(deliveryId);
348
+ return true;
349
+ }
350
+ /** Test hook — drop pending requests so one failed test cannot wedge the rest. */
351
+ export function _resetTurnEndChain() {
352
+ turnEndQueue.reset();
353
+ pendingTurnEndDeliveries.clear();
354
+ inFlightIpcTurnEnds.clear();
355
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Periodic memory-attribution sample (#1123 item 2, expanded on top of item 1's
3
+ * loop_block windowing — the #1126 sizing study's "instrumentation the next
4
+ * report should add" list is this module's requirements).
5
+ *
6
+ * The #1126 sizing study diagnosed a 1.37 GB instance from code + serialized
7
+ * artifacts alone, because nothing recorded a per-subsystem trajectory over
8
+ * time — the same detection-without-attribution gap loop_block had before
9
+ * #1122/#1125. This module answers "what is resident right now, broken down
10
+ * by subsystem" as a single ndjson `memory_sample` `latency.log` line, emitted
11
+ * every `MEMORY_SAMPLE_TURN_INTERVAL` turns (cheap enough not to need finer
12
+ * throttling).
13
+ *
14
+ * HARD CONSTRAINT: every field here is an O(1) `Map`/array `.size`/`.length`
15
+ * read, or a `process.memoryUsage()` call — nothing here iterates a large
16
+ * structure's contents, and nothing takes a heap snapshot. `PI_LENS_DEBUG_HEAP`
17
+ * (item 4's memory sibling) is a SEPARATE, explicitly opt-in mechanism, out of
18
+ * scope here.
19
+ *
20
+ * WASM linear-memory byte length gap (documented, not silently dropped): the
21
+ * #1126 spec asked for `Module.wasmMemory.buffer.byteLength` off the
22
+ * web-tree-sitter parser runtime. Inspecting the installed web-tree-sitter
23
+ * 0.25.10 package (see `clients/tree-sitter-client.ts#getRuntimeStats`'s
24
+ * docstring) confirmed that value is not reachable through any public export
25
+ * — only through package-internal reflection or by overriding Emscripten's
26
+ * `wasmMemory` module option with a hand-built `WebAssembly.Memory` (a
27
+ * stability risk for an observability-only feature, given a memory-import
28
+ * mismatch would break structural analysis entirely). `process.memoryUsage()`
29
+ * `arrayBuffers` is used as the process-wide proxy instead — WASM linear
30
+ * memory backs an ArrayBuffer, so it is already included there.
31
+ */
32
+ import { getSharedTreeSitterClient } from "./tree-sitter-shared.js";
33
+ import { getReviewGraphWorkspaceCacheSnapshot } from "./review-graph/builder.js";
34
+ import { getDispatchCascadeCacheStats } from "./dispatch/integration.js";
35
+ /** Every N turns, emit one `memory_sample` latency.log line (#1123 item 2). */
36
+ export const MEMORY_SAMPLE_TURN_INTERVAL = 10;
37
+ /** True on turn 10, 20, 30, ... — never on turn 0 (nothing meaningful is
38
+ * resident yet at session start). Pure so the cadence is unit-testable
39
+ * without driving a real turn loop. */
40
+ export function shouldEmitMemorySample(turnIndex) {
41
+ return turnIndex > 0 && turnIndex % MEMORY_SAMPLE_TURN_INTERVAL === 0;
42
+ }
43
+ /** PURE: reshape Node's `process.memoryUsage()` into this module's field
44
+ * names — testable without touching the real process. */
45
+ export function toMemoryProcessUsage(mem) {
46
+ return {
47
+ rssBytes: mem.rss,
48
+ heapUsedBytes: mem.heapUsed,
49
+ heapTotalBytes: mem.heapTotal,
50
+ externalBytes: mem.external,
51
+ arrayBuffersBytes: mem.arrayBuffers,
52
+ };
53
+ }
54
+ /**
55
+ * Live O(1)/O(bounded-cache-size) subsystem counters. Impure (reads
56
+ * process-global singletons) but every individual read is a `.size`/`.length`
57
+ * access — see the module docstring's hard constraint.
58
+ */
59
+ export function collectMemorySampleSubsystems(wordIndex) {
60
+ const reviewGraph = getReviewGraphWorkspaceCacheSnapshot();
61
+ const treeSitterClient = getSharedTreeSitterClient();
62
+ const treeSitter = treeSitterClient
63
+ ? (() => {
64
+ const runtimeStats = treeSitterClient.getRuntimeStats();
65
+ const cacheStats = treeSitterClient.getParseCacheStats();
66
+ return {
67
+ ...runtimeStats,
68
+ treeCacheSize: cacheStats.size,
69
+ treeCacheMaxSize: cacheStats.maxSize,
70
+ treeCacheTotalBytes: cacheStats.totalBytes,
71
+ };
72
+ })()
73
+ : null;
74
+ const dispatchCaches = getDispatchCascadeCacheStats();
75
+ return {
76
+ reviewGraph,
77
+ wordIndex: wordIndex
78
+ ? {
79
+ docs: wordIndex.docLengths.size,
80
+ postings: wordIndex.postings.size,
81
+ forwardEntries: wordIndex.forward?.size ?? 0,
82
+ }
83
+ : null,
84
+ treeSitter,
85
+ dispatchCaches,
86
+ };
87
+ }
88
+ /** Assemble one full sample. `mem` is injectable for tests; defaults to a
89
+ * live `process.memoryUsage()` read. */
90
+ export function buildMemorySample(wordIndex, mem = process.memoryUsage()) {
91
+ return {
92
+ process: toMemoryProcessUsage(mem),
93
+ subsystems: collectMemorySampleSubsystems(wordIndex),
94
+ };
95
+ }
96
+ const toMb = (bytes) => Math.round(bytes / (1024 * 1024));
97
+ /**
98
+ * PURE: one compact `/lens-health` line reusing the same sample the periodic
99
+ * latency.log emitter builds — RSS + heap/external, plus the two subsystem
100
+ * numbers most useful for spotting an attribution outlier at a glance: the
101
+ * tree-sitter tree-cache byte total (the only subsystem counter here that is
102
+ * ALREADY in bytes, so it's directly comparable to RSS) and the review-graph
103
+ * node/edge counts (the multiple-graph-copies question #1126 called out).
104
+ * Follows #1125's health-line style (a single summary line, no per-subsystem
105
+ * breakdown table — that's what latency.log's `memory_sample` is for).
106
+ */
107
+ export function formatMemoryHealthLine(sample) {
108
+ const { process: proc, subsystems } = sample;
109
+ const treeCache = subsystems.treeSitter
110
+ ? `${toMb(subsystems.treeSitter.treeCacheTotalBytes)}MB (${subsystems.treeSitter.treeCacheSize} trees)`
111
+ : "n/a";
112
+ const graph = `${subsystems.reviewGraph.totalNodes}n/${subsystems.reviewGraph.totalEdges}e (${subsystems.reviewGraph.cacheEntries} cwd)`;
113
+ return (`Memory: RSS ${toMb(proc.rssBytes)}MB · heap ${toMb(proc.heapUsedBytes)}/${toMb(proc.heapTotalBytes)}MB` +
114
+ ` · external ${toMb(proc.externalBytes)}MB · tree-sitter cache ${treeCache} · review-graph ${graph}`);
115
+ }
@@ -10,6 +10,7 @@
10
10
  * These are observational metrics — they inform the human in session summary,
11
11
  * they don't gate or interrupt the agent mid-task.
12
12
  */
13
+ import { createSubsystemLogger } from "./extension-log.js";
13
14
  import * as fs from "node:fs";
14
15
  import * as path from "node:path";
15
16
  // --- Client ---
@@ -18,7 +19,7 @@ export class MetricsClient {
18
19
  fileBaselines = new Map();
19
20
  constructor(verbose = false) {
20
21
  this.log = verbose
21
- ? (msg) => console.error(`[metrics] ${msg}`)
22
+ ? createSubsystemLogger("metrics")
22
23
  : () => { };
23
24
  }
24
25
  /**
@@ -10,6 +10,7 @@ import { execSync } from "node:child_process";
10
10
  import * as fs from "node:fs";
11
11
  import * as path from "node:path";
12
12
  import { getProjectDataDir } from "./file-utils.js";
13
+ import { writeFileAtomic } from "./atomic-write.js";
13
14
  // --- Constants ---
14
15
  const HISTORY_FILE = "metrics-history.json";
15
16
  const MAX_HISTORY_PER_FILE = 20;
@@ -82,7 +83,7 @@ export function saveHistory(history) {
82
83
  }
83
84
  history.capturedAt = new Date().toISOString();
84
85
  const historyPath = path.join(historyDir, "metrics-history.json");
85
- fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
86
+ writeFileAtomic(historyPath, JSON.stringify(history, null, 2));
86
87
  }
87
88
  // In-memory cache to avoid loading/saving on every capture
88
89
  let pendingHistory = null;