@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
@@ -0,0 +1,248 @@
1
+ import * as path from "node:path";
2
+ import { createReadGuardEditBatchSummary, getReadGuardCorrelationId, logReadGuardEvent, } from "./read-guard-logger.js";
3
+ import { appendProjectChange, } from "./project-changes.js";
4
+ import { normalizeMapKey } from "./path-utils.js";
5
+ const MAX_SAMPLES = 100;
6
+ const MAX_SUMMARIES_PER_CONTEXT = 100;
7
+ export function newLspMutationCorrelationId(toolCallId) {
8
+ return getReadGuardCorrelationId(toolCallId ? { toolCallId } : {});
9
+ }
10
+ function allResults(options) {
11
+ return options.results ?? [];
12
+ }
13
+ function combineResults(results) {
14
+ const files = new Set();
15
+ const fileDetails = [];
16
+ const appliedIndexes = [];
17
+ const paths = [];
18
+ let requestedTotal = 0;
19
+ let appliedTotal = 0;
20
+ let textEdits = 0;
21
+ let create = 0;
22
+ let rename = 0;
23
+ let deleteCount = 0;
24
+ for (const result of results) {
25
+ requestedTotal += result.operationTotal;
26
+ appliedTotal += result.appliedOperationTotal;
27
+ textEdits += result.operationCounts.textEdits;
28
+ create += result.operationCounts.create;
29
+ rename += result.operationCounts.rename;
30
+ deleteCount += result.operationCounts.delete;
31
+ for (const index of result.appliedOperationIndexes)
32
+ appliedIndexes.push(index);
33
+ for (const file of result.files) {
34
+ if (!files.has(file)) {
35
+ files.add(file);
36
+ paths.push(file);
37
+ }
38
+ }
39
+ fileDetails.push(...result.fileDetails);
40
+ }
41
+ return {
42
+ requestedTotal,
43
+ appliedTotal,
44
+ appliedIndexes,
45
+ files: [...files],
46
+ fileDetails,
47
+ textEdits,
48
+ create,
49
+ rename,
50
+ delete: deleteCount,
51
+ paths,
52
+ };
53
+ }
54
+ function uniqueDetails(files, fileDetails) {
55
+ const byPath = new Map();
56
+ for (const detail of fileDetails) {
57
+ const key = normalizeMapKey(path.resolve(detail.filePath));
58
+ const previous = byPath.get(key);
59
+ if (!previous) {
60
+ byPath.set(key, detail);
61
+ continue;
62
+ }
63
+ byPath.set(key, {
64
+ filePath: previous.filePath,
65
+ range: previous.range && detail.range
66
+ ? {
67
+ start: Math.min(previous.range.start, detail.range.start),
68
+ end: Math.max(previous.range.end, detail.range.end),
69
+ }
70
+ : previous.range ?? detail.range,
71
+ importsChanged: previous.importsChanged || detail.importsChanged,
72
+ });
73
+ }
74
+ return files.map((filePath) => byPath.get(normalizeMapKey(path.resolve(filePath))) ?? {
75
+ filePath,
76
+ // Resource operations have no already-computed text range. A small
77
+ // range is still enough to invalidate the touched-file turn state;
78
+ // never synchronously re-read the whole file here.
79
+ range: { start: 1, end: 1 },
80
+ importsChanged: true,
81
+ });
82
+ }
83
+ function bookkeepLspMutation(context, files, fileDetails) {
84
+ const details = uniqueDetails(files, fileDetails);
85
+ for (const detail of details) {
86
+ const filePath = path.resolve(detail.filePath);
87
+ if (context.readGuard) {
88
+ try {
89
+ context.readGuard.recordWritten(filePath);
90
+ }
91
+ catch (err) {
92
+ context.dbg?.(`lsp mutation read-guard stamp failed for ${filePath}: ${err}`);
93
+ }
94
+ }
95
+ const runtime = context.runtime;
96
+ if (runtime?.bumpFileSeq) {
97
+ const { projectSeq, fileSeq } = runtime.bumpFileSeq(filePath);
98
+ try {
99
+ appendProjectChange(context.cwd, {
100
+ seq: projectSeq,
101
+ timestamp: new Date().toISOString(),
102
+ sessionId: runtime.telemetrySessionId ?? "unknown",
103
+ turnIndex: runtime.turnIndex ?? 0,
104
+ source: context.source,
105
+ filePath,
106
+ fileSeq,
107
+ changedRange: detail.range,
108
+ });
109
+ }
110
+ catch (err) {
111
+ context.dbg?.(`lsp mutation project change append failed for ${filePath}: ${err}`);
112
+ }
113
+ }
114
+ if (context.cacheManager) {
115
+ try {
116
+ context.cacheManager.addModifiedRange(filePath, detail.range ?? { start: 1, end: 1 }, detail.importsChanged ?? true, context.cwd, runtime?.telemetrySessionId);
117
+ }
118
+ catch (err) {
119
+ context.dbg?.(`lsp mutation modified-range tracking failed for ${filePath}: ${err}`);
120
+ }
121
+ }
122
+ if (context.recordAutofix && context.source === "autofix") {
123
+ const key = normalizeMapKey(filePath);
124
+ const seen = context.autofixRecordedPaths ?? new Set();
125
+ context.autofixRecordedPaths = seen;
126
+ if (!seen.has(key)) {
127
+ if (seen.size >= MAX_SAMPLES) {
128
+ const oldest = seen.values().next().value;
129
+ if (oldest)
130
+ seen.delete(oldest);
131
+ }
132
+ seen.add(key);
133
+ context.recordAutofix(filePath);
134
+ }
135
+ }
136
+ }
137
+ if (context.publishFilesTouched && files.length > 0) {
138
+ context.publishFilesTouched(files);
139
+ }
140
+ }
141
+ function telemetryFor(context, options) {
142
+ const combined = combineResults(allResults(options));
143
+ const requestedTotal = options.requestedTotal ?? combined.requestedTotal;
144
+ const appliedTotal = combined.appliedTotal;
145
+ const requestedIndexes = Array.from({ length: Math.min(requestedTotal, MAX_SAMPLES) }, (_, index) => index);
146
+ const rejectedTotal = Math.max(0, requestedTotal - appliedTotal);
147
+ const rejectedReasons = Array.from({ length: Math.min(rejectedTotal, MAX_SAMPLES) }, (_, index) => ({ index, code: "write_failed" }));
148
+ const status = options.status ?? (appliedTotal > 0 ? "success" : "skipped");
149
+ const editBatchSummary = createReadGuardEditBatchSummary({
150
+ requestedIndexes,
151
+ requestedTotal,
152
+ resolvedIndexes: requestedIndexes,
153
+ resolvedTotal: requestedTotal,
154
+ rejectedReasons,
155
+ rejectedTotal,
156
+ appliedIndexes: combined.appliedIndexes,
157
+ appliedTotal,
158
+ participantIds: [context.correlationId],
159
+ participantTotal: 1,
160
+ commitStatus: status === "failed"
161
+ ? "failed"
162
+ : appliedTotal > 0
163
+ ? "committed"
164
+ : "no_changes",
165
+ terminalStatus: status,
166
+ });
167
+ return {
168
+ editBatchSummary,
169
+ operationCounts: {
170
+ requested: requestedTotal,
171
+ applied: appliedTotal,
172
+ textEdits: combined.textEdits,
173
+ create: combined.create,
174
+ rename: combined.rename,
175
+ delete: combined.delete,
176
+ },
177
+ sampledPaths: combined.paths.slice(0, MAX_SAMPLES),
178
+ sampledPathsTotal: combined.paths.length,
179
+ sampledPathsTruncated: combined.paths.length > MAX_SAMPLES,
180
+ considered: options.considered,
181
+ completed: options.completed,
182
+ failedCount: options.failedCount,
183
+ };
184
+ }
185
+ export function recordLspMutation(context, options = {}) {
186
+ const results = allResults(options);
187
+ const combined = combineResults(results);
188
+ if (options.bookkeep !== false && combined.files.length > 0) {
189
+ bookkeepLspMutation(context, combined.files, combined.fileDetails);
190
+ }
191
+ const telemetry = telemetryFor(context, options);
192
+ if (context.emitSummary !== false) {
193
+ const summaryCount = context.summaryCount ?? 0;
194
+ context.summaryEmitted = true;
195
+ if (summaryCount < MAX_SUMMARIES_PER_CONTEXT) {
196
+ context.summaryCount = summaryCount + 1;
197
+ logReadGuardEvent({
198
+ event: "edit_batch_summary",
199
+ correlationId: context.correlationId,
200
+ filePath: combined.files[0] ?? context.cwd,
201
+ metadata: {
202
+ tool: context.tool,
203
+ source: context.source,
204
+ outcome: telemetry.editBatchSummary.terminalStatus,
205
+ // The outer correlation identifies the soliciting tool call; the
206
+ // bounded sequence distinguishes multiple applyEdit requests within it.
207
+ summaryIndex: summaryCount,
208
+ editBatchSummary: telemetry.editBatchSummary,
209
+ operationCounts: telemetry.operationCounts,
210
+ sampledPaths: telemetry.sampledPaths,
211
+ sampledPathsTotal: telemetry.sampledPathsTotal,
212
+ sampledPathsTruncated: telemetry.sampledPathsTruncated,
213
+ considered: telemetry.considered,
214
+ completed: telemetry.completed,
215
+ failedCount: telemetry.failedCount,
216
+ },
217
+ });
218
+ }
219
+ else if (!context.summaryOverflowed) {
220
+ context.summaryOverflowed = true;
221
+ logReadGuardEvent({
222
+ event: "edit_batch_summary_overflow",
223
+ correlationId: context.correlationId,
224
+ filePath: combined.files[0] ?? context.cwd,
225
+ metadata: {
226
+ tool: context.tool,
227
+ source: context.source,
228
+ summaryLimit: MAX_SUMMARIES_PER_CONTEXT,
229
+ suppressedSummaries: "one or more",
230
+ },
231
+ });
232
+ }
233
+ }
234
+ return telemetry;
235
+ }
236
+ export function recordLspMutationBatch(context, options) {
237
+ const previous = context.emitSummary;
238
+ context.emitSummary = true;
239
+ try {
240
+ return recordLspMutation(context, options);
241
+ }
242
+ finally {
243
+ context.emitSummary = previous;
244
+ }
245
+ }
246
+ export function recordLspMutationOutcome(context, status) {
247
+ return recordLspMutation(context, { status, requestedTotal: 0 });
248
+ }
@@ -13,16 +13,19 @@
13
13
  */
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
- import { CacheManager } from "../cache-manager.js";
16
+ import { CacheManager, MCP_TURN_STATE_OWNER_ID } from "../cache-manager.js";
17
17
  import { CASCADE_GRAPH_KINDS, dispatchLintWithResult, getLatencyReports, } from "../dispatch/integration.js";
18
18
  import { FactStore } from "../dispatch/fact-store.js";
19
19
  import { detectFileKind } from "../file-kinds.js";
20
20
  import { getDiagnosticTracker } from "../diagnostic-tracker.js";
21
21
  import { getLSPService } from "../lsp/index.js";
22
+ import { PathKeyedMap } from "../path-keyed-map.js";
23
+ import { normalizeMapKey } from "../path-utils.js";
22
24
  import { loadProjectSnapshot } from "../project-snapshot.js";
23
25
  import { buildOrUpdateGraph } from "../review-graph/service.js";
24
26
  import { recordDiagnostics } from "../widget-state.js";
25
27
  import { deserializeWordIndex, removeWordIndexDocument, scheduleWordIndexPersist, updateWordIndexDocument, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
28
+ import { logWordIndex } from "../word-index-logger.js";
26
29
  import { createMcpHost } from "./host-shim.js";
27
30
  // #536: module-scoped FactStore for the warm-analyze graph seam, mirroring the
28
31
  // per-edit cascade path's own module-level `sessionFacts` singleton
@@ -43,7 +46,59 @@ const warmGraphFacts = new FactStore();
43
46
  // nothing usable" (index missing or pre-phase-2/no-forward-map), distinct from
44
47
  // "never checked" (key absent) — avoids re-attempting a snapshot load with no
45
48
  // forward index on every single analyze call.
46
- const warmWordIndexes = new Map();
49
+ const DEFAULT_WORD_INDEX_IDLE_EVICT_MS = 20 * 60_000;
50
+ const DEFAULT_WORD_INDEX_MAX_WARM_ROOTS = 8;
51
+ const warmWordIndexes = new PathKeyedMap(normalizeMapKey);
52
+ function positiveEnv(name, fallback) {
53
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
54
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
55
+ }
56
+ function idleEvictMs() {
57
+ return positiveEnv("PI_LENS_WORD_INDEX_IDLE_EVICT_MS", DEFAULT_WORD_INDEX_IDLE_EVICT_MS);
58
+ }
59
+ function maxWarmRoots() {
60
+ return positiveEnv("PI_LENS_WORD_INDEX_MAX_WARM_ROOTS", DEFAULT_WORD_INDEX_MAX_WARM_ROOTS);
61
+ }
62
+ function clearWarmWordIndexTimer(entry) {
63
+ if (entry.timer)
64
+ clearTimeout(entry.timer);
65
+ entry.timer = undefined;
66
+ }
67
+ function evictWarmWordIndex(key, entry, reason) {
68
+ if (entry.leases > 0 || warmWordIndexes.get(key) !== entry)
69
+ return false;
70
+ clearWarmWordIndexTimer(entry);
71
+ warmWordIndexes.delete(key);
72
+ logWordIndex({
73
+ phase: "warm_cache_evicted",
74
+ cwd: key,
75
+ trigger: "mcp",
76
+ reason,
77
+ });
78
+ return true;
79
+ }
80
+ function scheduleWarmWordIndexIdleEviction(key, entry) {
81
+ clearWarmWordIndexTimer(entry);
82
+ const generation = ++entry.generation;
83
+ const timer = setTimeout(() => {
84
+ entry.timer = undefined;
85
+ if (warmWordIndexes.get(key) !== entry || entry.generation !== generation)
86
+ return;
87
+ if (!evictWarmWordIndex(key, entry, "idle"))
88
+ scheduleWarmWordIndexIdleEviction(key, entry);
89
+ }, idleEvictMs());
90
+ timer.unref?.();
91
+ entry.timer = timer;
92
+ }
93
+ function enforceWarmWordIndexLruCap() {
94
+ while (warmWordIndexes.size > maxWarmRoots()) {
95
+ const victim = [...warmWordIndexes.entries()]
96
+ .filter(([, entry]) => entry.leases === 0)
97
+ .sort(([, a], [, b]) => a.lastUsedAt - b.lastUsedAt)[0];
98
+ if (!victim || !evictWarmWordIndex(victim[0], victim[1], "lru"))
99
+ return;
100
+ }
101
+ }
47
102
  /**
48
103
  * Look up (loading from the persisted snapshot on first use per cwd) the warm
49
104
  * in-memory word index this analyze facade keeps mutated in place. Exported so
@@ -52,26 +107,57 @@ const warmWordIndexes = new Map();
52
107
  * following a warm `pilens_analyze` call in the SAME process would read a
53
108
  * stale on-disk snapshot until the debounced persist (default 1500ms) flushes.
54
109
  */
55
- export function getOrLoadWarmWordIndex(cwd) {
110
+ export function acquireWarmWordIndex(cwd) {
56
111
  const key = path.resolve(cwd);
57
- if (warmWordIndexes.has(key))
58
- return warmWordIndexes.get(key);
59
- const snapshot = loadProjectSnapshot(key);
60
- const index = deserializeWordIndex(snapshot?.wordIndex) ?? undefined;
61
- // Same phase-2 rule as updateWordIndexForCascade: no forward index ⇒ no
62
- // incremental primitive available, so don't cache it as "usable" — this
63
- // call site's whole point is the incremental single-doc update.
64
- const usable = index?.forward ? index : undefined;
65
- warmWordIndexes.set(key, usable);
66
- return usable;
112
+ let entry = warmWordIndexes.get(key);
113
+ if (!entry) {
114
+ const snapshot = loadProjectSnapshot(key);
115
+ const index = deserializeWordIndex(snapshot?.wordIndex) ?? undefined;
116
+ entry = {
117
+ index: index?.forward ? index : undefined,
118
+ timer: undefined,
119
+ leases: 0,
120
+ lastUsedAt: Date.now(),
121
+ generation: 0,
122
+ };
123
+ warmWordIndexes.set(key, entry);
124
+ }
125
+ entry.leases++;
126
+ entry.lastUsedAt = Date.now();
127
+ scheduleWarmWordIndexIdleEviction(key, entry);
128
+ enforceWarmWordIndexLruCap();
129
+ let released = false;
130
+ return {
131
+ index: entry.index,
132
+ release() {
133
+ if (released)
134
+ return;
135
+ released = true;
136
+ entry.leases = Math.max(0, entry.leases - 1);
137
+ entry.lastUsedAt = Date.now();
138
+ if (warmWordIndexes.get(key) === entry) {
139
+ scheduleWarmWordIndexIdleEviction(key, entry);
140
+ enforceWarmWordIndexLruCap();
141
+ }
142
+ },
143
+ };
67
144
  }
68
145
  /**
69
146
  * Test-only reset — the module-level warm cache otherwise survives across
70
147
  * unrelated test cases in the same vitest worker.
71
148
  */
72
149
  export function _resetWarmWordIndexCacheForTests() {
150
+ for (const entry of warmWordIndexes.values())
151
+ clearWarmWordIndexTimer(entry);
73
152
  warmWordIndexes.clear();
74
153
  }
154
+ export function _getWarmWordIndexCacheStateForTests() {
155
+ return {
156
+ size: warmWordIndexes.size,
157
+ keys: [...warmWordIndexes.keys()],
158
+ timers: [...warmWordIndexes.values()].flatMap((entry) => entry.timer ? [entry.timer] : []),
159
+ };
160
+ }
75
161
  // Generous warm-up budgets: a cold language server needs to spawn AND publish
76
162
  // diagnostics. The per-edit dispatch runner caps these tightly (spawn budget +
77
163
  // 2500ms) for latency; a review tool prioritises completeness, so we pre-warm
@@ -167,11 +253,11 @@ export async function analyzeFile(filePath, cwd, options = {}) {
167
253
  }
168
254
  if (options.registerTurnState) {
169
255
  // Full-file range, importsChanged=true (conservative → dep/knip re-check
170
- // broadly). No sessionId — leaving it unset avoids turn_end's stale-session
171
- // eviction. Best-effort.
256
+ // broadly). Clear any stale pi session ID because the MCP Stop route owns
257
+ // this workspace-scoped worklist independently. Best-effort.
172
258
  try {
173
259
  const lineCount = fs.readFileSync(absPath, "utf8").split("\n").length;
174
- new CacheManager().addModifiedRange(absPath, { start: 1, end: lineCount }, true, cwd);
260
+ new CacheManager().addModifiedRange(absPath, { start: 1, end: lineCount }, true, cwd, options.ownerId ?? MCP_TURN_STATE_OWNER_ID, "mcp");
175
261
  }
176
262
  catch {
177
263
  // unreadable — skip turn-state registration
@@ -215,24 +301,30 @@ export async function analyzeFile(filePath, cwd, options = {}) {
215
301
  // INTERNALLY (PathKeyedMap), so this edit-form key and the build path's
216
302
  // walk-derived key converge on the same entry regardless of casing/
217
303
  // separator — the old duplicate-orphan hazard is gone at the map layer.
218
- const warmIndex = getOrLoadWarmWordIndex(cwd);
219
- if (warmIndex) {
220
- try {
221
- const content = fs.readFileSync(absPath, "utf8");
222
- const byteLength = Buffer.byteLength(content, "utf-8");
223
- if (byteLength > WORD_INDEX_MAX_BYTES) {
224
- removeWordIndexDocument(warmIndex, absPath);
304
+ const warmLease = acquireWarmWordIndex(cwd);
305
+ const warmIndex = warmLease.index;
306
+ try {
307
+ if (warmIndex) {
308
+ try {
309
+ const content = fs.readFileSync(absPath, "utf8");
310
+ const byteLength = Buffer.byteLength(content, "utf-8");
311
+ if (byteLength > WORD_INDEX_MAX_BYTES) {
312
+ removeWordIndexDocument(warmIndex, absPath);
313
+ }
314
+ else {
315
+ updateWordIndexDocument(warmIndex, { path: absPath, content });
316
+ }
317
+ scheduleWordIndexPersist(cwd, warmIndex);
225
318
  }
226
- else {
227
- updateWordIndexDocument(warmIndex, { path: absPath, content });
319
+ catch {
320
+ // unreadable/deleted, or an update failure — best-effort, same as the
321
+ // graph update above.
228
322
  }
229
- scheduleWordIndexPersist(cwd, warmIndex);
230
- }
231
- catch {
232
- // unreadable/deleted, or an update failure — best-effort, same as the
233
- // graph update above.
234
323
  }
235
324
  }
325
+ finally {
326
+ warmLease.release();
327
+ }
236
328
  }
237
329
  // dispatchForFile appended a latency report during the call above. Match the
238
330
  // newly-added report for this exact path; fall back to the most recent new
@@ -243,8 +335,7 @@ export async function analyzeFile(filePath, cwd, options = {}) {
243
335
  const lspRunner = latencyReport?.runners.find((runner) => runner.runnerId === "lsp");
244
336
  const lsp = lspRunner
245
337
  ? {
246
- ran: lspRunner.status !== "skipped" &&
247
- lspRunner.status !== "when_skipped",
338
+ ran: lspRunner.status !== "skipped" && lspRunner.status !== "when_skipped",
248
339
  status: lspRunner.status,
249
340
  diagnosticCount: lspRunner.diagnosticCount,
250
341
  durationMs: lspRunner.durationMs,