@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,192 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
6
+ function ownerPidIsLive(pid) {
7
+ if (!Number.isSafeInteger(pid) || pid <= 0)
8
+ return false;
9
+ try {
10
+ process.kill(pid, 0);
11
+ return true;
12
+ }
13
+ catch (error) {
14
+ return error.code === "EPERM";
15
+ }
16
+ }
17
+ function quarantinePath(lockPath, token) {
18
+ return `${lockPath}.quarantine-${process.pid}-${token}`;
19
+ }
20
+ async function restoreQuarantinedLock(lockPath, quarantined) {
21
+ try {
22
+ await fsp.rename(quarantined, lockPath);
23
+ }
24
+ catch {
25
+ // A replacement owner may already hold the canonical name. Never overwrite it.
26
+ }
27
+ }
28
+ async function releaseQuarantineLock(lockPath, token) {
29
+ const quarantined = quarantinePath(lockPath, `release-${token}`);
30
+ let renamed = false;
31
+ for (let attempt = 0; attempt < 3; attempt += 1) {
32
+ try {
33
+ await fsp.rename(lockPath, quarantined);
34
+ renamed = true;
35
+ break;
36
+ }
37
+ catch (error) {
38
+ if (error.code !== "ENOENT" ||
39
+ attempt === 2)
40
+ return;
41
+ await new Promise((resolve) => setImmediate(resolve));
42
+ }
43
+ }
44
+ if (!renamed)
45
+ return;
46
+ try {
47
+ const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
48
+ if (owner.token === token) {
49
+ await fsp.rm(quarantined, { recursive: true, force: true });
50
+ }
51
+ else {
52
+ await restoreQuarantinedLock(lockPath, quarantined);
53
+ }
54
+ }
55
+ catch {
56
+ await restoreQuarantinedLock(lockPath, quarantined);
57
+ }
58
+ }
59
+ function quarantineOwnerIsStale(owner, staleMs) {
60
+ return Number.isInteger(owner.pid) && owner.pid > 0 &&
61
+ Number.isFinite(owner.createdAt) &&
62
+ (!ownerPidIsLive(owner.pid) || Date.now() - owner.createdAt > staleMs);
63
+ }
64
+ async function reclaimQuarantineLock(lockPath, staleMs) {
65
+ const quarantined = quarantinePath(lockPath, `reclaim-${Date.now()}-${randomUUID()}`);
66
+ try {
67
+ await fsp.rename(lockPath, quarantined);
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ let stale = false;
73
+ try {
74
+ const owner = JSON.parse(await fsp.readFile(path.join(quarantined, "owner.json"), "utf8"));
75
+ stale = quarantineOwnerIsStale(owner, staleMs);
76
+ }
77
+ catch {
78
+ try {
79
+ stale = Date.now() - (await fsp.stat(quarantined)).mtimeMs > staleMs;
80
+ }
81
+ catch {
82
+ stale = false;
83
+ }
84
+ }
85
+ if (stale) {
86
+ await fsp.rm(quarantined, { recursive: true, force: true });
87
+ return true;
88
+ }
89
+ await restoreQuarantinedLock(lockPath, quarantined);
90
+ return false;
91
+ }
92
+ async function tryAcquireQuarantineLock(lockPath, staleMs) {
93
+ const owner = {
94
+ pid: process.pid,
95
+ createdAt: Date.now(),
96
+ token: `${process.pid}-${Date.now()}-${randomUUID()}`,
97
+ };
98
+ for (let attempt = 0; attempt < 2; attempt += 1) {
99
+ try {
100
+ await fsp.mkdir(lockPath);
101
+ try {
102
+ await fsp.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), "utf8");
103
+ }
104
+ catch (error) {
105
+ await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => { });
106
+ throw error;
107
+ }
108
+ return () => releaseQuarantineLock(lockPath, owner.token);
109
+ }
110
+ catch (error) {
111
+ if (error.code !== "EEXIST")
112
+ throw error;
113
+ }
114
+ let stale;
115
+ try {
116
+ const existing = JSON.parse(await fsp.readFile(path.join(lockPath, "owner.json"), "utf8"));
117
+ stale = quarantineOwnerIsStale(existing, staleMs);
118
+ }
119
+ catch {
120
+ try {
121
+ stale = Date.now() - (await fsp.stat(lockPath)).mtimeMs > staleMs;
122
+ }
123
+ catch {
124
+ stale = true;
125
+ }
126
+ }
127
+ if (!stale || !(await reclaimQuarantineLock(lockPath, staleMs)))
128
+ return null;
129
+ }
130
+ return null;
131
+ }
132
+ export async function acquireQuarantinePidFileLock(lockPath, options) {
133
+ const deadline = Date.now() + options.waitMs;
134
+ for (;;) {
135
+ const release = await tryAcquireQuarantineLock(lockPath, options.staleMs);
136
+ if (release)
137
+ return release;
138
+ const remaining = deadline - Date.now();
139
+ if (remaining <= 0) {
140
+ if (options.onContention === "skip-log") {
141
+ options.logContention();
142
+ return null;
143
+ }
144
+ throw new Error(options.timeoutMessage);
145
+ }
146
+ await new Promise((resolve) => setTimeout(resolve, Math.min(options.retryMs, remaining)));
147
+ }
148
+ }
149
+ export function acquireBoundedPidFileLock(lockPath, options) {
150
+ const token = `${process.pid}:${Date.now()}:${randomUUID()}`;
151
+ const deadline = Date.now() + options.waitMs;
152
+ for (;;) {
153
+ try {
154
+ const fd = fs.openSync(lockPath, "wx");
155
+ fs.writeFileSync(fd, token, "utf8");
156
+ fs.closeSync(fd);
157
+ return () => {
158
+ try {
159
+ if (fs.readFileSync(lockPath, "utf8") === token) {
160
+ fs.unlinkSync(lockPath);
161
+ }
162
+ }
163
+ catch {
164
+ // Protected write completed; cleanup is best-effort.
165
+ }
166
+ };
167
+ }
168
+ catch (error) {
169
+ if (error.code !== "EEXIST")
170
+ throw error;
171
+ try {
172
+ const [pidText] = fs.readFileSync(lockPath, "utf8").split(":", 1);
173
+ if (!ownerPidIsLive(Number.parseInt(pidText ?? "", 10))) {
174
+ fs.unlinkSync(lockPath);
175
+ continue;
176
+ }
177
+ }
178
+ catch (lockError) {
179
+ if (lockError.code === "ENOENT")
180
+ continue;
181
+ }
182
+ if (Date.now() >= deadline) {
183
+ if (options.onContention === "skip-log") {
184
+ options.logContention();
185
+ return null;
186
+ }
187
+ throw new Error(options.timeoutMessage);
188
+ }
189
+ Atomics.wait(waitArray, 0, 0, options.retryMs);
190
+ }
191
+ }
192
+ }
@@ -20,9 +20,10 @@
20
20
  import { logBusEvent } from "./bus-events-logger.js";
21
21
  import { normalizeFilePath } from "./path-utils.js";
22
22
  import { appendRecentTouches } from "./recent-touches.js";
23
+ import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
23
24
  export const BUS_FILES_TOUCHED_EVENT = "pilens:files:touched";
24
25
  export const BUS_FILES_TOUCHED_VERSION = 1;
25
- let busEmit;
26
+ const liveEmitter = createLiveBusEmitter();
26
27
  let hasLoggedFailure = false;
27
28
  // Log-once-per-process guards for the two structural-no-op outcomes below
28
29
  // (see bus-events-logger.ts's module doc for why these aren't logged on
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
38
39
  * no `pi.events`).
39
40
  */
40
41
  export function wireBusEmitter(emitFn) {
41
- busEmit = emitFn;
42
+ liveEmitter.wire(emitFn);
43
+ }
44
+ export function wireBusEmitterGetter(getter) {
45
+ liveEmitter.wireGetter(getter);
42
46
  }
43
47
  /** Test-only: reset module state between test files. */
44
48
  export function _resetForTests() {
45
- busEmit = undefined;
49
+ liveEmitter.reset();
46
50
  hasLoggedFailure = false;
47
51
  hasLoggedUnwired = false;
48
52
  hasLoggedDisabled = false;
@@ -102,6 +106,7 @@ export function publishFilesTouched(args) {
102
106
  }).catch((err) => {
103
107
  args.dbg?.(`bus-publish: recent-touches append failed: ${err}`);
104
108
  });
109
+ const busEmit = liveEmitter.get();
105
110
  if (!busEmit) {
106
111
  if (!hasLoggedUnwired) {
107
112
  hasLoggedUnwired = true;
@@ -129,6 +134,7 @@ export function publishFilesTouched(args) {
129
134
  }));
130
135
  }
131
136
  busEmit(BUS_FILES_TOUCHED_EVENT, payload);
137
+ hasLoggedFailure = false;
132
138
  logBusEvent({
133
139
  event: BUS_FILES_TOUCHED_EVENT,
134
140
  outcome: "emitted",
@@ -147,6 +153,7 @@ export function publishFilesTouched(args) {
147
153
  });
148
154
  if (!hasLoggedFailure) {
149
155
  hasLoggedFailure = true;
156
+ recordStaleBusFailure(BUS_FILES_TOUCHED_EVENT, err);
150
157
  args.dbg?.(`bus-publish: pilens:files:touched emit failed (further failures suppressed): ${err}`);
151
158
  }
152
159
  }
@@ -9,6 +9,8 @@ import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
10
  import { getProjectDataDir } from "../file-utils.js";
11
11
  import { readJsonCache } from "../json-cache-read.js";
12
+ import { resolvePackagePath } from "../package-root.js";
13
+ import { writeFileAtomic } from "../atomic-write.js";
12
14
  // v4: cache skip_test_files + fix_action — v3 entries silently dropped them,
13
15
  // and ruleHash (rule-file mtimes) never invalidates on a code-only fix.
14
16
  // v5 (#675): rule SELECTION changed in code — javascript no longer inherits the
@@ -20,7 +22,37 @@ import { readJsonCache } from "../json-cache-read.js";
20
22
  // the language's own. A v5 tsx entry was hashed over tsx files only, so a
21
23
  // typescript-rule edit never invalidated it; v5 entries self-miss anyway (the
22
24
  // hash input set changed), the bump just makes the semantics break explicit.
23
- export const CACHE_VERSION = "v6";
25
+ // v7 (#1118): computeRuleHash now CONTENT-hashes project-local rule files
26
+ // instead of trusting mtime+size alone. mtime+size is the review-graph
27
+ // first-filter, not the full gold standard (size:mtimeMs + confirmContentChanged)
28
+ // — a rule-file edit that preserves both (git checkout timestamp restoration, a
29
+ // same-length tweak, a formatter that preserves mtime) replayed a stale
30
+ // compiled set from disk and re-persisted it under the still-matching
31
+ // fingerprint, poisoning every future process. The bump forces every v6 entry
32
+ // (hashed under the old metadata-only formula) to miss once on upgrade, so a
33
+ // silently-poisoned v6 entry can't be trusted by the new code.
34
+ export const CACHE_VERSION = "v7";
35
+ /**
36
+ * Bundled rule files ship with the extension and are immutable within a
37
+ * process — they can't change mid-session, so the cheap mtime+size
38
+ * fingerprint (v6's whole-set formula) is sufficient for them, and there are
39
+ * ~705 of them across all languages, so unconditionally reading+hashing their
40
+ * bytes on every edit would be the unconditional hot-path hashing the
41
+ * event-loop discipline forbids (RuleCache.get runs on the per-edit
42
+ * tree-sitter runner hot path).
43
+ *
44
+ * Project-local rule files (under `<project>/rules/tree-sitter-queries/`,
45
+ * see `ruleFilesForLanguage`) are the opposite: mutable, and a SMALL set (the
46
+ * handful a developer actually maintains) — cheap to content-hash. Splitting
47
+ * on this prefix mirrors the split `yaml-rule-parser.ts`/`ast-grep-napi.ts`
48
+ * already use for the ast-grep side of the same class (#1105): project-origin
49
+ * trees get a content-hash CONFIRM, bundled trees stay mtime-cheap.
50
+ */
51
+ const BUNDLED_RULES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
52
+ function isBundledRuleFile(resolvedFile) {
53
+ return (resolvedFile === BUNDLED_RULES_ROOT ||
54
+ resolvedFile.startsWith(BUNDLED_RULES_ROOT + path.sep));
55
+ }
24
56
  export class RuleCache {
25
57
  cacheFile;
26
58
  cacheDir;
@@ -38,9 +70,19 @@ export class RuleCache {
38
70
  computeRuleHash(ruleFiles) {
39
71
  const hash = crypto.createHash("sha256");
40
72
  for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
41
- if (fs.existsSync(file)) {
42
- const stat = fs.statSync(file);
43
- hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
73
+ const resolved = path.resolve(file);
74
+ if (!fs.existsSync(resolved))
75
+ continue;
76
+ const stat = fs.statSync(resolved);
77
+ hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
78
+ // Content-CONFIRM only the project-local, mutable subset (a handful of
79
+ // files) — mtime+size alone is a first filter, not proof of freshness,
80
+ // and a preserved-mtime+size edit here would otherwise replay (and
81
+ // re-persist) a stale compiled set. Bundled files (~705, immutable
82
+ // within a process) skip this: the metadata fingerprint stays their
83
+ // whole story, keeping the common no-project-rules case as cheap as v6.
84
+ if (!isBundledRuleFile(resolved)) {
85
+ hash.update(fs.readFileSync(resolved));
44
86
  }
45
87
  }
46
88
  return hash.digest("hex").slice(0, 16);
@@ -74,7 +116,7 @@ export class RuleCache {
74
116
  ruleHash: this.computeRuleHash(ruleFiles),
75
117
  queries,
76
118
  };
77
- fs.writeFileSync(this.cacheFile, JSON.stringify(entry, null, 2));
119
+ writeFileAtomic(this.cacheFile, JSON.stringify(entry, null, 2));
78
120
  this.pruneStaleVersions();
79
121
  }
80
122
  catch {
@@ -8,9 +8,11 @@
8
8
  *
9
9
  * All paths are relative to project root (process.cwd()).
10
10
  */
11
+ import { createSubsystemLogger } from "./extension-log.js";
11
12
  import * as fs from "node:fs";
12
13
  import * as path from "node:path";
13
14
  import { getProjectDataDir } from "./file-utils.js";
15
+ import { writeFileAtomic } from "./atomic-write.js";
14
16
  import { readJsonCache } from "./json-cache-read.js";
15
17
  import { normalizeMapKey } from "./path-utils.js";
16
18
  // --- Defaults ---
@@ -21,6 +23,8 @@ const DEFAULT_TURN_STATE = {
21
23
  maxCycles: 3,
22
24
  lastUpdated: "",
23
25
  };
26
+ export const MCP_TURN_STATE_OWNER_ID = `mcp-${process.pid}`;
27
+ const TURN_OWNER_STALE_MS = 30 * 60 * 1000;
24
28
  // --- Helpers ---
25
29
  function getLensDir(cwd) {
26
30
  return getProjectDataDir(cwd);
@@ -36,7 +40,7 @@ export class CacheManager {
36
40
  log;
37
41
  constructor(verbose = false) {
38
42
  this.log = verbose
39
- ? (msg) => console.error(`[cache] ${msg}`)
43
+ ? createSubsystemLogger("cache")
40
44
  : () => { };
41
45
  }
42
46
  /**
@@ -107,10 +111,45 @@ export class CacheManager {
107
111
  timestamp: new Date().toISOString(),
108
112
  ...extraMeta,
109
113
  };
110
- fs.writeFileSync(cachePath, JSON.stringify(data, null, 2));
111
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
114
+ writeFileAtomic(cachePath, JSON.stringify(data, null, 2), {
115
+ bestEffort: false,
116
+ });
117
+ writeFileAtomic(metaPath, JSON.stringify(meta, null, 2), {
118
+ bestEffort: false,
119
+ });
112
120
  this.log(`Cache written: ${scanner}`);
113
121
  }
122
+ /** Inspect a cache without changing the behavior of readCache consumers. */
123
+ inspectCache(scanner, cwd, maxAgeMs = DEFAULT_MAX_AGE_MS) {
124
+ const cachePath = path.join(getCacheDir(cwd), `${scanner}.json`);
125
+ const metaPath = path.join(getCacheDir(cwd), `${scanner}.meta.json`);
126
+ for (const cachePathname of [cachePath, metaPath]) {
127
+ try {
128
+ fs.statSync(cachePathname);
129
+ }
130
+ catch (err) {
131
+ if (err.code === "ENOENT")
132
+ return "missing";
133
+ return "unreadable";
134
+ }
135
+ }
136
+ try {
137
+ const meta = readJsonCache(metaPath, (parsed) => parsed);
138
+ if (!meta || typeof meta.timestamp !== "string")
139
+ return "malformed";
140
+ const timestamp = new Date(meta.timestamp).getTime();
141
+ if (!Number.isFinite(timestamp))
142
+ return "malformed";
143
+ const age = Date.now() - timestamp;
144
+ if (age < 0 || age > maxAgeMs)
145
+ return age < 0 ? "malformed" : "stale";
146
+ const data = readJsonCache(cachePath, (parsed) => parsed);
147
+ return data === undefined ? "malformed" : "fresh";
148
+ }
149
+ catch {
150
+ return "unreadable";
151
+ }
152
+ }
114
153
  /**
115
154
  * Check if a cache entry is fresh (exists and not expired).
116
155
  */
@@ -175,16 +214,57 @@ export class CacheManager {
175
214
  fs.mkdirSync(lensDir, { recursive: true });
176
215
  const statePath = getTurnStatePath(cwd);
177
216
  state.lastUpdated = new Date().toISOString();
178
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
217
+ writeFileAtomic(statePath, JSON.stringify(state, null, 2));
218
+ }
219
+ /** Return whether a writer may read/write this workspace worklist. */
220
+ getTurnStateAccess(cwd, owner) {
221
+ const state = this.readTurnState(cwd);
222
+ if (!state.owner) {
223
+ if (!state.sessionId || state.sessionId === owner.id)
224
+ return "owned";
225
+ // Pre-owner files have no liveness information. Preserve the existing
226
+ // stale-session eviction behavior for this legacy shape.
227
+ return "available";
228
+ }
229
+ if (state.owner.kind === owner.kind &&
230
+ state.owner.id === owner.id) {
231
+ return "owned";
232
+ }
233
+ return this.isTurnStateOwnerStale(state.owner) ? "available" : "foreign-live";
234
+ }
235
+ isTurnStateOwnerStale(owner) {
236
+ if (owner.pid > 0 && owner.pid !== process.pid) {
237
+ try {
238
+ process.kill(owner.pid, 0);
239
+ return false;
240
+ }
241
+ catch {
242
+ return true;
243
+ }
244
+ }
245
+ const lastSeen = Date.parse(owner.lastSeen);
246
+ return !Number.isFinite(lastSeen) || Date.now() - lastSeen > TURN_OWNER_STALE_MS;
179
247
  }
180
248
  /**
181
249
  * Add or update a file's modified ranges in turn state.
182
- * Merges overlapping ranges.
250
+ * Merges overlapping ranges. `sessionId:null` deliberately preserves the
251
+ * current owner; callers must provide an explicit owner id to claim a stale
252
+ * worklist.
183
253
  */
184
- addModifiedRange(filePath, range, importsChanged, cwd, sessionId) {
254
+ addModifiedRange(filePath, range, importsChanged, cwd, sessionId, ownerKind = "pi") {
185
255
  const state = this.readTurnState(cwd);
186
- if (sessionId)
256
+ if (sessionId) {
257
+ const owner = {
258
+ kind: ownerKind,
259
+ id: sessionId,
260
+ pid: process.pid,
261
+ lastSeen: new Date().toISOString(),
262
+ };
263
+ if (this.getTurnStateAccess(cwd, owner) === "foreign-live")
264
+ return state;
187
265
  state.sessionId = sessionId;
266
+ state.owner = owner;
267
+ }
188
268
  const normalizedPath = this.toTurnStateKey(filePath, cwd);
189
269
  const existing = state.files[normalizedPath];
190
270
  if (existing) {
@@ -209,19 +289,27 @@ export class CacheManager {
209
289
  /**
210
290
  * Clear turn state (after turn_end processes it).
211
291
  */
212
- clearTurnState(cwd) {
292
+ clearTurnState(cwd, owner) {
293
+ const currentState = this.readTurnState(cwd);
294
+ const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
295
+ if (!isCurrentOwner && process.pid !== currentState.owner?.pid)
296
+ return false;
213
297
  const state = {
214
298
  ...DEFAULT_TURN_STATE,
215
299
  files: {}, // fresh object — DEFAULT_TURN_STATE.files can be polluted by addModifiedRange
216
300
  lastUpdated: new Date().toISOString(),
217
301
  };
218
302
  this.writeTurnState(state, cwd);
303
+ return true;
219
304
  }
220
305
  /**
221
306
  * Increment turn cycle counter.
222
307
  */
223
- incrementTurnCycle(cwd) {
308
+ incrementTurnCycle(cwd, owner) {
224
309
  const state = this.readTurnState(cwd);
310
+ const isCurrentOwner = this.getTurnStateAccess(cwd, owner) !== "foreign-live";
311
+ if (!isCurrentOwner && process.pid !== state.owner?.pid)
312
+ return state;
225
313
  state.turnCycles++;
226
314
  this.writeTurnState(state, cwd);
227
315
  return state;