@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

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 (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  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 +53 -14
  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 +18 -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 +20 -4
  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/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. 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
+ }
@@ -1,13 +1,20 @@
1
1
  /**
2
2
  * Persistent NDJSON trace of `pi.events` bus publish attempts
3
3
  * (`pilens:files:touched` #482 / `pilens:diagnostics` #502 /
4
- * `pilens:format:queued` + `pilens:format:start` #673).
4
+ * `pilens:diagnostic:disposition` / `pilens:format:queued` +
5
+ * `pilens:format:start` + `pilens:autofix:start` #673/#684 /
6
+ * `pi-lens/analysis-complete` + `pi-lens/findings` + `pi-lens/turn-findings`
7
+ * #1415) — nine event names across five producers.
5
8
  *
6
- * All four producers (clients/bus-publish.ts, clients/diagnostics-publish.ts,
7
- * clients/format-events-publish.ts) are fire-and-forget: on failure or on a
8
- * structural no-op (never wired, kill switch off) they only invoke an
9
- * optional `dbg` callback, which varies by host and is a documented no-op in
10
- * the MCP host (clients/mcp/session.ts's `dbg: noop`). That leaves
9
+ * All five producers (clients/bus-publish.ts, clients/diagnostics-publish.ts,
10
+ * clients/disposition-publish.ts, clients/format-events-publish.ts,
11
+ * clients/lens-events.ts) are fire-and-forget: on failure or on a structural
12
+ * no-op (never wired, kill switch off) the four #482/#502/#673/#684
13
+ * producers only invoke an optional `dbg` callback, which varies by host and
14
+ * is a documented no-op in the MCP host (clients/mcp/session.ts's
15
+ * `dbg: noop`); `lens-events.ts` (#1415) has no `dbg` param at all — its
16
+ * events are purely observational inter-extension telemetry, so this NDJSON
17
+ * trace is its ONLY failure-visible surface. Either way, that leaves
11
18
  * bus-publish outcomes invisible in exactly the context where they matter
12
19
  * most — same failure shape as the #544 MCP session_start incident this repo
13
20
  * already fixed once.
@@ -26,6 +33,9 @@
26
33
  * spam one identical line per write batch for an entire session with zero
27
34
  * new information after the first. Both are gated log-once-per-process, the
28
35
  * same `hasLoggedFailure` shape the producers already use for emit_failed.
36
+ * `skipped_stale_session` is an info-level per-occurrence outcome: the emit
37
+ * seam intentionally declined a target whose associated ctx was confirmed
38
+ * stale, so it is neither a bus failure nor input for the smells rollup.
29
39
  * The empty-batch branch (`paths.length === 0` / `files.length === 0`) is
30
40
  * NOT logged at all: every call site already guards against invoking these
31
41
  * functions with nothing to report (see clients/pipeline.ts,
@@ -37,17 +47,63 @@ import { isTestMode } from "./env-utils.js";
37
47
  import { getGlobalPiLensDir } from "./file-utils.js";
38
48
  import { createNdjsonLogger } from "./ndjson-logger.js";
39
49
  import { getMaxLogSizeMB } from "./log-cleanup.js";
50
+ import { logLatency } from "./latency-logger.js";
40
51
  const BUS_EVENTS_LOG_FILE = path.join(getGlobalPiLensDir(), "bus-events.log");
41
52
  const writer = createNdjsonLogger({
42
53
  filePath: BUS_EVENTS_LOG_FILE,
43
54
  maxBytes: getMaxLogSizeMB() * 1024 * 1024,
44
55
  });
56
+ const eventRollupCounts = new Map();
57
+ function bumpRollupCount(event, outcome) {
58
+ const existing = eventRollupCounts.get(event) ?? {
59
+ emitted: 0,
60
+ skipped_stale_session: 0,
61
+ emit_failed: 0,
62
+ };
63
+ existing[outcome] += 1;
64
+ eventRollupCounts.set(event, existing);
65
+ }
45
66
  export function logBusEvent(entry) {
67
+ if (entry.outcome === "emitted" ||
68
+ entry.outcome === "skipped_stale_session" ||
69
+ entry.outcome === "emit_failed") {
70
+ bumpRollupCount(entry.event, entry.outcome);
71
+ }
46
72
  if (isTestMode()) {
47
73
  return;
48
74
  }
49
75
  writer.log({ ts: new Date().toISOString(), ...entry });
50
76
  }
77
+ /** Snapshot the current session's rollup, keyed by event name. Non-mutating —
78
+ * pair with {@link resetBusEventRollupCounts} at session end. */
79
+ export function getBusEventRollupCounts() {
80
+ return Object.fromEntries(eventRollupCounts);
81
+ }
82
+ /** Clear the rollup — call once the session-end snapshot has been logged
83
+ * (or from tests) so a new session starts from zero. */
84
+ export function resetBusEventRollupCounts() {
85
+ eventRollupCounts.clear();
86
+ }
87
+ /**
88
+ * Log one `session_end_bus_rollup` latency row per event NAME that had any
89
+ * activity this session, then clear the rollup. Called from index.ts's
90
+ * `session_shutdown` handler (the session-end hook this repo's other
91
+ * teardown work — LSP fleet shutdown, cache-prefix eviction — already runs
92
+ * from). A no-op (logs nothing) when nothing was ever published, matching
93
+ * `formatSmellsSessionStartLine`'s "no noise on an ordinary session" shape.
94
+ */
95
+ export function emitBusEventRollupAtSessionEnd(cwd) {
96
+ for (const [event, counts] of eventRollupCounts) {
97
+ logLatency({
98
+ type: "phase",
99
+ phase: "session_end_bus_rollup",
100
+ filePath: cwd,
101
+ durationMs: 0,
102
+ metadata: { event, ...counts },
103
+ });
104
+ }
105
+ resetBusEventRollupCounts();
106
+ }
51
107
  export function getBusEventsLogPath() {
52
108
  return BUS_EVENTS_LOG_FILE;
53
109
  }
@@ -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, resolveLiveBusEmitter, } 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,7 +106,14 @@ export function publishFilesTouched(args) {
102
106
  }).catch((err) => {
103
107
  args.dbg?.(`bus-publish: recent-touches append failed: ${err}`);
104
108
  });
105
- if (!busEmit) {
109
+ const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
110
+ event: BUS_FILES_TOUCHED_EVENT,
111
+ cwd: normalizeFilePath(args.cwd),
112
+ reason: args.reason,
113
+ }));
114
+ if (resolution.outcome === "stale-session")
115
+ return;
116
+ if (resolution.outcome === "unwired") {
106
117
  if (!hasLoggedUnwired) {
107
118
  hasLoggedUnwired = true;
108
119
  logBusEvent({
@@ -114,6 +125,7 @@ export function publishFilesTouched(args) {
114
125
  }
115
126
  return;
116
127
  }
128
+ const busEmit = resolution.emit;
117
129
  try {
118
130
  const payload = {
119
131
  v: BUS_FILES_TOUCHED_VERSION,
@@ -129,6 +141,7 @@ export function publishFilesTouched(args) {
129
141
  }));
130
142
  }
131
143
  busEmit(BUS_FILES_TOUCHED_EVENT, payload);
144
+ hasLoggedFailure = false;
132
145
  logBusEvent({
133
146
  event: BUS_FILES_TOUCHED_EVENT,
134
147
  outcome: "emitted",
@@ -144,9 +157,11 @@ export function publishFilesTouched(args) {
144
157
  cwd: normalizeFilePath(args.cwd),
145
158
  reason: args.reason,
146
159
  error: String(err),
160
+ ctxSource: resolution.ctxSource,
147
161
  });
148
162
  if (!hasLoggedFailure) {
149
163
  hasLoggedFailure = true;
164
+ recordStaleBusFailure(BUS_FILES_TOUCHED_EVENT, err);
150
165
  args.dbg?.(`bus-publish: pilens:files:touched emit failed (further failures suppressed): ${err}`);
151
166
  }
152
167
  }
@@ -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;