@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
@@ -13,16 +13,19 @@
13
13
  */
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
- import { CacheManager } from "../cache-manager.js";
16
+ import { CacheManager, MCP_TURN_STATE_OWNER_ID } from "../cache-manager.js";
17
17
  import { CASCADE_GRAPH_KINDS, dispatchLintWithResult, getLatencyReports, } from "../dispatch/integration.js";
18
18
  import { FactStore } from "../dispatch/fact-store.js";
19
19
  import { detectFileKind } from "../file-kinds.js";
20
20
  import { getDiagnosticTracker } from "../diagnostic-tracker.js";
21
21
  import { getLSPService } from "../lsp/index.js";
22
+ import { PathKeyedMap } from "../path-keyed-map.js";
23
+ import { normalizeMapKey } from "../path-utils.js";
22
24
  import { loadProjectSnapshot } from "../project-snapshot.js";
23
25
  import { buildOrUpdateGraph } from "../review-graph/service.js";
24
26
  import { recordDiagnostics } from "../widget-state.js";
25
27
  import { deserializeWordIndex, removeWordIndexDocument, scheduleWordIndexPersist, updateWordIndexDocument, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
28
+ import { logWordIndex } from "../word-index-logger.js";
26
29
  import { createMcpHost } from "./host-shim.js";
27
30
  // #536: module-scoped FactStore for the warm-analyze graph seam, mirroring the
28
31
  // per-edit cascade path's own module-level `sessionFacts` singleton
@@ -43,7 +46,59 @@ const warmGraphFacts = new FactStore();
43
46
  // nothing usable" (index missing or pre-phase-2/no-forward-map), distinct from
44
47
  // "never checked" (key absent) — avoids re-attempting a snapshot load with no
45
48
  // forward index on every single analyze call.
46
- const warmWordIndexes = new Map();
49
+ const DEFAULT_WORD_INDEX_IDLE_EVICT_MS = 20 * 60_000;
50
+ const DEFAULT_WORD_INDEX_MAX_WARM_ROOTS = 8;
51
+ const warmWordIndexes = new PathKeyedMap(normalizeMapKey);
52
+ function positiveEnv(name, fallback) {
53
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
54
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
55
+ }
56
+ function idleEvictMs() {
57
+ return positiveEnv("PI_LENS_WORD_INDEX_IDLE_EVICT_MS", DEFAULT_WORD_INDEX_IDLE_EVICT_MS);
58
+ }
59
+ function maxWarmRoots() {
60
+ return positiveEnv("PI_LENS_WORD_INDEX_MAX_WARM_ROOTS", DEFAULT_WORD_INDEX_MAX_WARM_ROOTS);
61
+ }
62
+ function clearWarmWordIndexTimer(entry) {
63
+ if (entry.timer)
64
+ clearTimeout(entry.timer);
65
+ entry.timer = undefined;
66
+ }
67
+ function evictWarmWordIndex(key, entry, reason) {
68
+ if (entry.leases > 0 || warmWordIndexes.get(key) !== entry)
69
+ return false;
70
+ clearWarmWordIndexTimer(entry);
71
+ warmWordIndexes.delete(key);
72
+ logWordIndex({
73
+ phase: "warm_cache_evicted",
74
+ cwd: key,
75
+ trigger: "mcp",
76
+ reason,
77
+ });
78
+ return true;
79
+ }
80
+ function scheduleWarmWordIndexIdleEviction(key, entry) {
81
+ clearWarmWordIndexTimer(entry);
82
+ const generation = ++entry.generation;
83
+ const timer = setTimeout(() => {
84
+ entry.timer = undefined;
85
+ if (warmWordIndexes.get(key) !== entry || entry.generation !== generation)
86
+ return;
87
+ if (!evictWarmWordIndex(key, entry, "idle"))
88
+ scheduleWarmWordIndexIdleEviction(key, entry);
89
+ }, idleEvictMs());
90
+ timer.unref?.();
91
+ entry.timer = timer;
92
+ }
93
+ function enforceWarmWordIndexLruCap() {
94
+ while (warmWordIndexes.size > maxWarmRoots()) {
95
+ const victim = [...warmWordIndexes.entries()]
96
+ .filter(([, entry]) => entry.leases === 0)
97
+ .sort(([, a], [, b]) => a.lastUsedAt - b.lastUsedAt)[0];
98
+ if (!victim || !evictWarmWordIndex(victim[0], victim[1], "lru"))
99
+ return;
100
+ }
101
+ }
47
102
  /**
48
103
  * Look up (loading from the persisted snapshot on first use per cwd) the warm
49
104
  * in-memory word index this analyze facade keeps mutated in place. Exported so
@@ -52,26 +107,57 @@ const warmWordIndexes = new Map();
52
107
  * following a warm `pilens_analyze` call in the SAME process would read a
53
108
  * stale on-disk snapshot until the debounced persist (default 1500ms) flushes.
54
109
  */
55
- export function getOrLoadWarmWordIndex(cwd) {
110
+ export function acquireWarmWordIndex(cwd) {
56
111
  const key = path.resolve(cwd);
57
- if (warmWordIndexes.has(key))
58
- return warmWordIndexes.get(key);
59
- const snapshot = loadProjectSnapshot(key);
60
- const index = deserializeWordIndex(snapshot?.wordIndex) ?? undefined;
61
- // Same phase-2 rule as updateWordIndexForCascade: no forward index ⇒ no
62
- // incremental primitive available, so don't cache it as "usable" — this
63
- // call site's whole point is the incremental single-doc update.
64
- const usable = index?.forward ? index : undefined;
65
- warmWordIndexes.set(key, usable);
66
- return usable;
112
+ let entry = warmWordIndexes.get(key);
113
+ if (!entry) {
114
+ const snapshot = loadProjectSnapshot(key);
115
+ const index = deserializeWordIndex(snapshot?.wordIndex) ?? undefined;
116
+ entry = {
117
+ index: index?.forward ? index : undefined,
118
+ timer: undefined,
119
+ leases: 0,
120
+ lastUsedAt: Date.now(),
121
+ generation: 0,
122
+ };
123
+ warmWordIndexes.set(key, entry);
124
+ }
125
+ entry.leases++;
126
+ entry.lastUsedAt = Date.now();
127
+ scheduleWarmWordIndexIdleEviction(key, entry);
128
+ enforceWarmWordIndexLruCap();
129
+ let released = false;
130
+ return {
131
+ index: entry.index,
132
+ release() {
133
+ if (released)
134
+ return;
135
+ released = true;
136
+ entry.leases = Math.max(0, entry.leases - 1);
137
+ entry.lastUsedAt = Date.now();
138
+ if (warmWordIndexes.get(key) === entry) {
139
+ scheduleWarmWordIndexIdleEviction(key, entry);
140
+ enforceWarmWordIndexLruCap();
141
+ }
142
+ },
143
+ };
67
144
  }
68
145
  /**
69
146
  * Test-only reset — the module-level warm cache otherwise survives across
70
147
  * unrelated test cases in the same vitest worker.
71
148
  */
72
149
  export function _resetWarmWordIndexCacheForTests() {
150
+ for (const entry of warmWordIndexes.values())
151
+ clearWarmWordIndexTimer(entry);
73
152
  warmWordIndexes.clear();
74
153
  }
154
+ export function _getWarmWordIndexCacheStateForTests() {
155
+ return {
156
+ size: warmWordIndexes.size,
157
+ keys: [...warmWordIndexes.keys()],
158
+ timers: [...warmWordIndexes.values()].flatMap((entry) => entry.timer ? [entry.timer] : []),
159
+ };
160
+ }
75
161
  // Generous warm-up budgets: a cold language server needs to spawn AND publish
76
162
  // diagnostics. The per-edit dispatch runner caps these tightly (spawn budget +
77
163
  // 2500ms) for latency; a review tool prioritises completeness, so we pre-warm
@@ -153,6 +239,10 @@ export async function analyzeFile(filePath, cwd, options = {}) {
153
239
  }
154
240
  const reportsBefore = getLatencyReports().length;
155
241
  const start = Date.now();
242
+ // No telemetryModel/telemetryProvider here (#1448): this MCP facade has no
243
+ // RuntimeCoordinator to hold a host-reported identity (see the module doc
244
+ // above), so any worklog entries this dispatch produces get a blank
245
+ // model/provider — that's correct, not a gap.
156
246
  const result = await dispatchLintWithResult(absPath, cwd, host, undefined, undefined, {
157
247
  blockingOnly: options.blockingOnly ?? false,
158
248
  });
@@ -167,11 +257,11 @@ export async function analyzeFile(filePath, cwd, options = {}) {
167
257
  }
168
258
  if (options.registerTurnState) {
169
259
  // Full-file range, importsChanged=true (conservative → dep/knip re-check
170
- // broadly). No sessionId — leaving it unset avoids turn_end's stale-session
171
- // eviction. Best-effort.
260
+ // broadly). Clear any stale pi session ID because the MCP Stop route owns
261
+ // this workspace-scoped worklist independently. Best-effort.
172
262
  try {
173
263
  const lineCount = fs.readFileSync(absPath, "utf8").split("\n").length;
174
- new CacheManager().addModifiedRange(absPath, { start: 1, end: lineCount }, true, cwd);
264
+ new CacheManager().addModifiedRange(absPath, { start: 1, end: lineCount }, true, cwd, options.ownerId ?? MCP_TURN_STATE_OWNER_ID, "mcp");
175
265
  }
176
266
  catch {
177
267
  // unreadable — skip turn-state registration
@@ -215,24 +305,30 @@ export async function analyzeFile(filePath, cwd, options = {}) {
215
305
  // INTERNALLY (PathKeyedMap), so this edit-form key and the build path's
216
306
  // walk-derived key converge on the same entry regardless of casing/
217
307
  // separator — the old duplicate-orphan hazard is gone at the map layer.
218
- const warmIndex = getOrLoadWarmWordIndex(cwd);
219
- if (warmIndex) {
220
- try {
221
- const content = fs.readFileSync(absPath, "utf8");
222
- const byteLength = Buffer.byteLength(content, "utf-8");
223
- if (byteLength > WORD_INDEX_MAX_BYTES) {
224
- removeWordIndexDocument(warmIndex, absPath);
308
+ const warmLease = acquireWarmWordIndex(cwd);
309
+ const warmIndex = warmLease.index;
310
+ try {
311
+ if (warmIndex) {
312
+ try {
313
+ const content = fs.readFileSync(absPath, "utf8");
314
+ const byteLength = Buffer.byteLength(content, "utf-8");
315
+ if (byteLength > WORD_INDEX_MAX_BYTES) {
316
+ removeWordIndexDocument(warmIndex, absPath);
317
+ }
318
+ else {
319
+ updateWordIndexDocument(warmIndex, { path: absPath, content });
320
+ }
321
+ scheduleWordIndexPersist(cwd, warmIndex);
225
322
  }
226
- else {
227
- updateWordIndexDocument(warmIndex, { path: absPath, content });
323
+ catch {
324
+ // unreadable/deleted, or an update failure — best-effort, same as the
325
+ // graph update above.
228
326
  }
229
- scheduleWordIndexPersist(cwd, warmIndex);
230
- }
231
- catch {
232
- // unreadable/deleted, or an update failure — best-effort, same as the
233
- // graph update above.
234
327
  }
235
328
  }
329
+ finally {
330
+ warmLease.release();
331
+ }
236
332
  }
237
333
  // dispatchForFile appended a latency report during the call above. Match the
238
334
  // newly-added report for this exact path; fall back to the most recent new
@@ -243,8 +339,7 @@ export async function analyzeFile(filePath, cwd, options = {}) {
243
339
  const lspRunner = latencyReport?.runners.find((runner) => runner.runnerId === "lsp");
244
340
  const lsp = lspRunner
245
341
  ? {
246
- ran: lspRunner.status !== "skipped" &&
247
- lspRunner.status !== "when_skipped",
342
+ ran: lspRunner.status !== "skipped" && lspRunner.status !== "when_skipped",
248
343
  status: lspRunner.status,
249
344
  diagnosticCount: lspRunner.diagnosticCount,
250
345
  durationMs: lspRunner.durationMs,
@@ -6,16 +6,19 @@
6
6
  * connects to it to get LSP-complete diagnostics from the warm process instead
7
7
  * of running its own cold analysis.
8
8
  *
9
- * This module is the CLIENT + the shared path derivation only — deliberately
10
- * light (node:net + type-only result), so the bin can try the warm path WITHOUT
11
- * loading the dispatch graph. The server side lives in mcp/server.ts (which
12
- * already holds the analysis engine). If the warm path is unavailable, the
13
- * client resolves `undefined` and the caller falls back to cold local analysis.
9
+ * This module is the CLIENT + the shared path derivation + the one-shot line
10
+ * reader the server wires into its socket (deliberately light: node:net +
11
+ * type-only result, so the bin can try the warm path WITHOUT loading the
12
+ * dispatch graph). The analysis engine lives in mcp/server.ts. If the warm
13
+ * path is unavailable, the client resolves `undefined` and the caller falls
14
+ * back to cold local analysis.
14
15
  */
15
16
  import * as crypto from "node:crypto";
17
+ import * as fs from "node:fs";
16
18
  import * as net from "node:net";
17
19
  import * as os from "node:os";
18
20
  import * as path from "node:path";
21
+ import { writeFileAtomic } from "../atomic-write.js";
19
22
  export const WARM_DIAGNOSTICS_SCHEMA_VERSION = 1;
20
23
  export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
21
24
  /**
@@ -25,19 +28,23 @@ export const WARM_CODE_ACTION_LOOKUP_LIMIT = 6;
25
28
  * the same project they meet. Mismatch → the client just falls back to cold.
26
29
  */
27
30
  export function ipcPathForCwd(cwd) {
28
- const root = path.resolve(cwd).toLowerCase();
29
- // sha256 (not for security — just a stable short id for the IPC socket/pipe
30
- // name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
31
- const hash = crypto
32
- .createHash("sha256")
33
- .update(root)
34
- .digest("hex")
35
- .slice(0, 16);
31
+ const hash = workspaceHash(cwd);
36
32
  if (process.platform === "win32") {
37
33
  return `\\\\.\\pipe\\pi-lens-mcp-${hash}`;
38
34
  }
39
35
  return path.join(os.tmpdir(), `pi-lens-mcp-${hash}.sock`);
40
36
  }
37
+ /**
38
+ * The single stable per-workspace id both endpoint derivations key on. Every
39
+ * per-workspace side-channel name (socket/pipe, turn-end status file) must come
40
+ * from HERE, so the hook process and the server process cannot drift apart.
41
+ */
42
+ function workspaceHash(cwd) {
43
+ const root = path.resolve(cwd).toLowerCase();
44
+ // sha256 (not for security — just a stable short id for the IPC socket/pipe
45
+ // name keyed by cwd; sha256 over sha1 keeps SonarCloud's weak-hash check quiet)
46
+ return crypto.createHash("sha256").update(root).digest("hex").slice(0, 16);
47
+ }
41
48
  /** PID-scoped endpoint used by pi sessions. The legacy MCP analyze endpoint
42
49
  * remains workspace-scoped for compatibility with the PostToolUse hook. */
43
50
  export function diagnosticsIpcPathForCwd(cwd, pid) {
@@ -50,15 +57,16 @@ export function contentHash(content) {
50
57
  return crypto.createHash("sha256").update(content).digest("hex");
51
58
  }
52
59
  /**
53
- * Shared one-shot request/response transport for the warm-attach IPC routes
54
- * (#822): connect to the incumbent's PID-scoped endpoint, write one JSON
55
- * line, read one JSON line back, classify. The per-route `validate` callback
56
- * returns a failure reason for an on-time but unusable reply (schema skew,
57
- * staleness) or `undefined` to accept it; transport failures map uniformly to
58
- * timeout/ipc-error. One transport, N routes — the diagnostics and
59
- * code-action clients cannot drift apart.
60
+ * Shared one-shot request/response transport for the tagged IPC routes (#822):
61
+ * connect to `endpoint`, write one JSON line, read one JSON line back,
62
+ * classify. The endpoint is PID-scoped for the warm-attach routes and
63
+ * workspace-scoped for the hook bin's turn-end route. The per-route `validate`
64
+ * callback returns a failure reason for an on-time but unusable reply (schema
65
+ * skew, staleness) or `undefined` to accept it; transport failures map
66
+ * uniformly to timeout/ipc-error. One transport, N routes — the clients cannot
67
+ * drift apart.
60
68
  */
61
- function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate) {
69
+ function requestOverWarmIpc(endpoint, timeoutMs, buildRequest, validate) {
62
70
  return new Promise((resolve) => {
63
71
  let settled = false;
64
72
  const finish = (value) => {
@@ -70,7 +78,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
70
78
  resolve(value);
71
79
  };
72
80
  const deadlineAt = Date.now() + timeoutMs;
73
- const socket = net.createConnection(diagnosticsIpcPathForCwd(cwd, incumbentPid));
81
+ const socket = net.createConnection(endpoint);
74
82
  socket.setEncoding("utf8");
75
83
  let buffer = "";
76
84
  const timer = setTimeout(() => finish({ available: false, reason: "timeout" }), timeoutMs);
@@ -108,7 +116,7 @@ function requestOverWarmIpc(cwd, incumbentPid, timeoutMs, buildRequest, validate
108
116
  }
109
117
  export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeoutMs) {
110
118
  const expectedHash = contentHash(content);
111
- return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
119
+ return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
112
120
  route: "diagnostics",
113
121
  version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
114
122
  file,
@@ -131,7 +139,7 @@ export function requestWarmDiagnostics(cwd, incumbentPid, file, content, timeout
131
139
  });
132
140
  }
133
141
  export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentHash, ranges, timeoutMs) {
134
- return requestOverWarmIpc(cwd, incumbentPid, timeoutMs, (deadlineAt) => ({
142
+ return requestOverWarmIpc(diagnosticsIpcPathForCwd(cwd, incumbentPid), timeoutMs, (deadlineAt) => ({
135
143
  route: "code-actions",
136
144
  version: WARM_DIAGNOSTICS_SCHEMA_VERSION,
137
145
  file,
@@ -154,6 +162,99 @@ export function requestWarmCodeActions(cwd, incumbentPid, file, expectedContentH
154
162
  return undefined;
155
163
  });
156
164
  }
165
+ // v2 adds an explicit receipt acknowledgement. A v1 server must reject rather
166
+ // than consume a pass without the new delivery contract.
167
+ export const WARM_TURN_END_SCHEMA_VERSION = 2;
168
+ /**
169
+ * Ask the warm server to run pi-lens's real turn-end pass. Execution and
170
+ * delivery are separate: the first one-shot connection returns a capability,
171
+ * then a second one-shot connection acknowledges receipt. If the client
172
+ * deadline or either connection loses, the server leaves the durable finding
173
+ * cache untouched for a later Stop. 55 s expires inside Claude Code's 60 s hook
174
+ * timeout.
175
+ */
176
+ export async function requestWarmTurnEnd(cwd, timeoutMs = 55_000) {
177
+ const startedAt = Date.now();
178
+ const first = await requestOverWarmIpc(ipcPathForCwd(cwd), timeoutMs, () => ({
179
+ route: "turn-end",
180
+ version: WARM_TURN_END_SCHEMA_VERSION,
181
+ cwd,
182
+ }), (result) => result.route === "turn-end" &&
183
+ result.version === WARM_TURN_END_SCHEMA_VERSION
184
+ ? undefined
185
+ : "schema-mismatch");
186
+ if (!first.available || !first.response.deliveryId)
187
+ return first;
188
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
189
+ if (remainingMs <= 0)
190
+ return { available: false, reason: "timeout" };
191
+ const ack = await requestOverWarmIpc(ipcPathForCwd(cwd), remainingMs, () => ({
192
+ route: "turn-end-ack",
193
+ version: WARM_TURN_END_SCHEMA_VERSION,
194
+ cwd,
195
+ deliveryId: first.response.deliveryId,
196
+ }), (result) => result.route === "turn-end-ack" && result.acknowledged === true
197
+ ? undefined
198
+ : "ipc-error");
199
+ return ack.available
200
+ ? { available: true, response: first.response }
201
+ : { available: false, reason: ack.reason };
202
+ }
203
+ /** Per-workspace status file, keyed by the same hash as the IPC endpoint. */
204
+ export function turnEndStatusPathForCwd(cwd) {
205
+ return path.join(os.tmpdir(), `pi-lens-turn-end-${workspaceHash(cwd)}.json`);
206
+ }
207
+ export function readTurnEndStatus(cwd) {
208
+ try {
209
+ const raw = fs.readFileSync(turnEndStatusPathForCwd(cwd), "utf8");
210
+ const parsed = JSON.parse(raw);
211
+ if (!parsed || typeof parsed !== "object")
212
+ return undefined;
213
+ return {
214
+ ran: typeof parsed.ran === "number" ? parsed.ran : 0,
215
+ skipped: typeof parsed.skipped === "number" ? parsed.skipped : 0,
216
+ lastSkipReason: parsed.lastSkipReason,
217
+ lastRunAt: parsed.lastRunAt,
218
+ lastSkipAt: parsed.lastSkipAt,
219
+ };
220
+ }
221
+ catch {
222
+ // never written, unreadable, or corrupt — "no turn-end activity recorded"
223
+ return undefined;
224
+ }
225
+ }
226
+ /**
227
+ * Append one turn-end outcome from the hook process. Never throws.
228
+ *
229
+ * Read-modify-write against a shared per-workspace tmpdir file: two Stop
230
+ * hooks for the same workspace can race this concurrently (separate
231
+ * processes, no lock). `writeFileAtomic` only closes the *publication* half
232
+ * of that — it guarantees this write's own staging+rename can't torn-read
233
+ * (parse-fail and reset counters) or hit a Windows sharing violation against
234
+ * a concurrent writer's rename. It does NOT fix the read-modify-write race
235
+ * itself: two overlapping calls can still both read the same `previous` and
236
+ * publish from it, silently dropping one increment. That's accepted here —
237
+ * this is bounded best-effort telemetry (self-heals, errors already
238
+ * swallowed below), and a lock on this path isn't worth it.
239
+ */
240
+ export function recordTurnEndOutcome(cwd, outcome) {
241
+ try {
242
+ const now = new Date().toISOString();
243
+ const previous = readTurnEndStatus(cwd) ?? { ran: 0, skipped: 0 };
244
+ const next = outcome.ran
245
+ ? { ...previous, ran: previous.ran + 1, lastRunAt: now }
246
+ : {
247
+ ...previous,
248
+ skipped: previous.skipped + 1,
249
+ lastSkipReason: outcome.reason,
250
+ lastSkipAt: now,
251
+ };
252
+ writeFileAtomic(turnEndStatusPathForCwd(cwd), `${JSON.stringify(next)}\n`);
253
+ }
254
+ catch {
255
+ // telemetry only — a read-only tmpdir must not break the Stop hook
256
+ }
257
+ }
157
258
  /**
158
259
  * Ask the warm server to analyze a file. Resolves the server's result, or
159
260
  * `undefined` on ANY failure (no server, refused, stale socket, timeout, bad
@@ -200,3 +301,35 @@ export function requestWarmAnalyze(cwd, file, timeoutMs = 30_000) {
200
301
  socket.on("close", () => finish(undefined));
201
302
  });
202
303
  }
304
+ /**
305
+ * One-shot line reader for the warm IPC socket (#1219). The clients write
306
+ * exactly one newline-terminated request per connection and read one reply, so
307
+ * the server must dispatch at most one line and ignore anything after it — a
308
+ * `data` handler that keeps re-reading the same buffered line re-dispatches
309
+ * the request on stray bytes. Returns the handler to attach to the socket's
310
+ * `data` event.
311
+ */
312
+ export function createWarmIpcLineReader(onLine) {
313
+ let buffer = "";
314
+ let dispatched = false;
315
+ return (chunk) => {
316
+ if (dispatched)
317
+ return;
318
+ buffer += chunk;
319
+ const newline = buffer.indexOf("\n");
320
+ if (newline === -1)
321
+ return;
322
+ dispatched = true;
323
+ onLine(buffer.slice(0, newline));
324
+ };
325
+ }
326
+ export function createWarmIpcRequestQueue() {
327
+ let chain = Promise.resolve();
328
+ return {
329
+ enqueue(work) {
330
+ const next = chain.then(work);
331
+ chain = next.catch(() => undefined);
332
+ return next;
333
+ },
334
+ };
335
+ }