@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
@@ -7,12 +7,13 @@
7
7
  * Requires: npm install -D @ast-grep/cli
8
8
  * Rules: ./rules/ directory
9
9
  */
10
+ import { createSubsystemLogger } from "./extension-log.js";
10
11
  import * as fs from "node:fs";
11
12
  import * as os from "node:os";
12
13
  import * as path from "node:path";
13
14
  import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
14
15
  import { resolvePackagePath } from "./package-root.js";
15
- import { SgRunner } from "./sg-runner.js";
16
+ import { SgRunner, } from "./sg-runner.js";
16
17
  // --- Client ---
17
18
  function extractDebugAst(raw) {
18
19
  const lines = raw.split(/\r?\n/);
@@ -53,7 +54,7 @@ const VALIDATION_SNIPPETS = {
53
54
  html: { ext: "html", source: "<main>pi-lens</main>\n" },
54
55
  java: { ext: "java", source: "class Main { void run() {} }\n" },
55
56
  javascript: { ext: "js", source: "const piLensValidate = 1;\n" },
56
- json: { ext: "json", source: "{\"piLensValidate\":true}\n" },
57
+ json: { ext: "json", source: '{"piLensValidate":true}\n' },
57
58
  kotlin: { ext: "kt", source: "fun main() {}\n" },
58
59
  lua: { ext: "lua", source: "local pi_lens_validate = 1\n" },
59
60
  php: { ext: "php", source: "<?php $piLensValidate = 1;\n" },
@@ -66,7 +67,10 @@ const VALIDATION_SNIPPETS = {
66
67
  };
67
68
  function validationSnippetFor(language) {
68
69
  const key = language.toLowerCase().replace(/^"|"$/g, "");
69
- return VALIDATION_SNIPPETS[key] ?? { ext: key.replace(/[^a-z0-9_-]/gi, "") || "txt", source: "pi_lens_validate\n" };
70
+ return (VALIDATION_SNIPPETS[key] ?? {
71
+ ext: key.replace(/[^a-z0-9_-]/gi, "") || "txt",
72
+ source: "pi_lens_validate\n",
73
+ });
70
74
  }
71
75
  function validateInputShape(value, maxChars, label) {
72
76
  if (value.includes("\0"))
@@ -110,7 +114,7 @@ export class AstGrepClient {
110
114
  ? projectRuleDir
111
115
  : resolvePackagePath(import.meta.url, "rules"));
112
116
  this.log = verbose
113
- ? (msg) => console.error(`[ast-grep] ${msg}`)
117
+ ? createSubsystemLogger("ast-grep")
114
118
  : () => { };
115
119
  this.ruleManager = new AstGrepRuleManager(this.ruleDir, this.log);
116
120
  this.runner = new SgRunner(verbose);
@@ -129,9 +133,18 @@ export class AstGrepClient {
129
133
  const allMatches = [];
130
134
  for (const scanPath of paths) {
131
135
  if (apply) {
132
- // Stale-preview check: dry-run first
133
- const preCheck = await this.runner.tempScanAsync(scanPath, "agent-rule", ruleYaml);
134
- if (preCheck.length === 0) {
136
+ // Stale-preview check: dry-run first. Preserve CLI failures instead of
137
+ // treating an invalid rule as a stale, no-match preview.
138
+ const preCheck = await this.tempScanDetailed(scanPath, ruleYaml);
139
+ if (preCheck.failure || preCheck.error) {
140
+ return {
141
+ matches: allMatches,
142
+ totalMatches: allMatches.length,
143
+ applied: false,
144
+ error: preCheck.error ?? "ast-grep preview failed",
145
+ };
146
+ }
147
+ if (preCheck.matches.length === 0) {
135
148
  return {
136
149
  matches: [],
137
150
  totalMatches: 0,
@@ -162,12 +175,31 @@ export class AstGrepClient {
162
175
  * Routes through sg scan --config rather than sg run -p.
163
176
  * Each path is scanned independently; results are merged.
164
177
  */
165
- async searchWithRule(ruleYaml, paths) {
178
+ async tempScanDetailed(dir, ruleYaml, options = {}) {
179
+ // Keep tests and older embedders that replace the runner with the historic
180
+ // match-only seam working. The production SgRunner always has the detailed
181
+ // method, so failures cannot be mistaken for an empty result there.
182
+ const detailed = this.runner.tempScanDetailedAsync;
183
+ if (detailed) {
184
+ return detailed.call(this.runner, dir, "agent-rule", ruleYaml, 30_000, options);
185
+ }
186
+ const matches = await this.runner.tempScanAsync(dir, "agent-rule", ruleYaml, 30_000, options);
187
+ return { matches, status: 0 };
188
+ }
189
+ async searchWithRule(ruleYaml, paths, options = {}) {
166
190
  const allMatches = [];
167
191
  for (const scanPath of paths) {
168
192
  try {
169
- const results = await this.runner.tempScanAsync(scanPath, "agent-rule", ruleYaml);
170
- allMatches.push(...results);
193
+ const results = await this.tempScanDetailed(scanPath, ruleYaml, options);
194
+ if (results.failure || results.error) {
195
+ return {
196
+ matches: allMatches,
197
+ totalMatches: allMatches.length,
198
+ error: results.error ||
199
+ `ast-grep scan failed (${results.failure ?? "unknown failure"})`,
200
+ };
201
+ }
202
+ allMatches.push(...results.matches);
171
203
  }
172
204
  catch (err) {
173
205
  return {
@@ -227,11 +259,24 @@ export class AstGrepClient {
227
259
  if (options?.strictness)
228
260
  args.push("--strictness", options.strictness);
229
261
  args.push(tmpFile);
230
- const result = await this.runner.execRaw(args);
262
+ const result = await this.runner.execRaw(args, 10_000, options);
231
263
  const stderr = result.stderr.trim();
232
264
  const stdout = result.stdout.trim();
233
265
  if (result.error)
234
266
  return { valid: false, error: result.error };
267
+ if (result.status !== 0 && !(result.status === 1 && !stderr)) {
268
+ return {
269
+ valid: false,
270
+ error: stderr ||
271
+ `ast-grep validation failed with exit code ${result.status}`,
272
+ };
273
+ }
274
+ if (result.outputTruncated) {
275
+ return {
276
+ valid: false,
277
+ error: "ast-grep validation output was truncated",
278
+ };
279
+ }
235
280
  if (stderrHasError(stderr))
236
281
  return { valid: false, error: stderr };
237
282
  const warning = stderr || stdout || undefined;
@@ -249,7 +294,7 @@ export class AstGrepClient {
249
294
  }
250
295
  }
251
296
  }
252
- async validateRule(ruleYaml) {
297
+ async validateRule(ruleYaml, options = {}) {
253
298
  const shapeError = validateInputShape(ruleYaml, MAX_VALIDATE_RULE_CHARS, "rule");
254
299
  if (shapeError)
255
300
  return { valid: false, error: shapeError };
@@ -258,7 +303,14 @@ export class AstGrepClient {
258
303
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lens-sg-rule-"));
259
304
  try {
260
305
  fs.writeFileSync(path.join(tmpDir, `snippet.${snippet.ext}`), snippet.source, "utf-8");
261
- await this.runner.tempScanAsync(tmpDir, "agent-rule", ruleYaml, 10000);
306
+ const result = await this.tempScanDetailed(tmpDir, ruleYaml, options);
307
+ if (result.failure || result.error) {
308
+ return {
309
+ valid: false,
310
+ error: result.error ||
311
+ `ast-grep rule validation failed (${result.failure ?? "unknown failure"})`,
312
+ };
313
+ }
262
314
  return { valid: true };
263
315
  }
264
316
  catch (err) {
@@ -332,7 +384,7 @@ export class AstGrepClient {
332
384
  args.push("--strictness", options.strictness);
333
385
  }
334
386
  args.push(...paths);
335
- const result = await this.runner.exec(args);
387
+ const result = await this.runner.exec(args, options);
336
388
  return {
337
389
  matches: result.matches,
338
390
  totalMatches: result.totalMatches,
@@ -505,8 +557,8 @@ message: found
505
557
  }
506
558
  return exports;
507
559
  }
508
- formatMatches(matches, isDryRun = false, showModeIndicator = false) {
509
- return this.runner.formatMatches(matches, isDryRun, 50, showModeIndicator);
560
+ formatMatches(matches, isDryRun = false, showModeIndicator = false, maxItems = 50) {
561
+ return this.runner.formatMatches(matches, isDryRun, maxItems, showModeIndicator);
510
562
  }
511
563
  /**
512
564
  * Format diagnostics for LLM consumption
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Extracted to prevent circular dependencies between:
5
5
  * - ast-grep-client.ts
6
- * - ast-grep-parser.ts
7
6
  * - ast-grep-rule-manager.ts
8
7
  */
9
8
  export {};
@@ -21,7 +21,12 @@ const NODE_KIND_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
21
21
  * Returns true when at least one structural-intent field is present.
22
22
  */
23
23
  export function hasStructuralIntent(intent) {
24
- return !!(intent.insideKind || intent.hasKind || intent.follows || intent.precedes);
24
+ return !!(intent.nodeKind ||
25
+ intent.insideKind ||
26
+ intent.hasKind ||
27
+ intent.hasDescendantKind ||
28
+ intent.follows ||
29
+ intent.precedes);
25
30
  }
26
31
  /**
27
32
  * Synthesize an ast-grep YAML rule for replace operations.
@@ -33,26 +38,48 @@ export function synthesizeReplaceRule(intent) {
33
38
  return `${base}fix: ${JSON.stringify(intent.rewrite)}\n`;
34
39
  }
35
40
  /**
36
- * Synthesize an ast-grep YAML rule from a pattern and structural constraints.
41
+ * Synthesize an ast-grep YAML rule from a pattern (or node kind) and
42
+ * structural constraints.
37
43
  *
38
- * The generated rule uses `stopBy: end` on `inside` so the search climbs
39
- * all ancestors, not just the immediate parent.
44
+ * The generated rule uses `stopBy: end` on `inside` and
45
+ * `hasDescendantKind`, so those two explicit recursive forms climb all
46
+ * ancestors/descendants. `hasKind` intentionally retains ast-grep's default
47
+ * immediate-child semantics for compatibility.
40
48
  *
41
- * @throws if pattern is empty
49
+ * @throws if neither pattern nor nodeKind is supplied, or both are supplied
42
50
  */
43
51
  export function synthesizeRule(intent) {
44
- assertSafePattern(intent.pattern, "pattern");
52
+ const hasPattern = !!intent.pattern?.trim();
53
+ const hasNodeKind = !!intent.nodeKind?.trim();
54
+ if (hasPattern === hasNodeKind) {
55
+ throw new Error("provide exactly one of pattern or nodeKind");
56
+ }
57
+ const pattern = intent.pattern;
58
+ if (hasPattern && pattern)
59
+ assertSafePattern(pattern, "pattern");
60
+ if (intent.hasKind?.trim() && intent.hasDescendantKind?.trim()) {
61
+ throw new Error("hasKind and hasDescendantKind are mutually exclusive");
62
+ }
45
63
  // Canonical language name for the YAML header (ast-grep is case-sensitive here).
46
64
  const language = canonicalLanguage(intent.lang);
47
- const rule = {
48
- pattern: intent.pattern,
49
- };
65
+ const rule = hasNodeKind
66
+ ? { kind: assertSafeNodeKind(intent.nodeKind ?? "", "nodeKind") }
67
+ : { pattern };
50
68
  if (intent.insideKind) {
51
- rule.inside = { kind: assertSafeNodeKind(intent.insideKind, "insideKind"), stopBy: "end" };
69
+ rule.inside = {
70
+ kind: assertSafeNodeKind(intent.insideKind, "insideKind"),
71
+ stopBy: "end",
72
+ };
52
73
  }
53
74
  if (intent.hasKind) {
54
75
  rule.has = { kind: assertSafeNodeKind(intent.hasKind, "hasKind") };
55
76
  }
77
+ if (intent.hasDescendantKind) {
78
+ rule.has = {
79
+ kind: assertSafeNodeKind(intent.hasDescendantKind, "hasDescendantKind"),
80
+ stopBy: "end",
81
+ };
82
+ }
56
83
  if (intent.follows) {
57
84
  assertSafePattern(intent.follows, "follows");
58
85
  rule.follows = { pattern: intent.follows };
@@ -0,0 +1,63 @@
1
+ /** Owns the generic atomic-write `.tmp-<pid>-<threadId>-<seq>` namespace. */
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { threadId } from "node:worker_threads";
5
+ let stageSeq = 0;
6
+ export const STAGE_TMP_PATTERN = /\.tmp-(0|[1-9]\d*)-(0|[1-9]\d*)-(0|[1-9]\d*)$/;
7
+ const STAGE_TMP_PID_PATTERN = /\.tmp-(0|[1-9]\d*)-(?:0|[1-9]\d*)-(?:0|[1-9]\d*)$/;
8
+ export function stagePathFor(targetPath) {
9
+ return `${targetPath}.tmp-${process.pid}-${threadId}-${stageSeq++}`;
10
+ }
11
+ export function stageOwnerPidFromName(name) {
12
+ if (!STAGE_TMP_PATTERN.test(name))
13
+ return undefined;
14
+ const match = STAGE_TMP_PID_PATTERN.exec(name);
15
+ if (!match)
16
+ return undefined;
17
+ const pid = Number(match[1]);
18
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined;
19
+ }
20
+ export async function sweepOwnStagingFiles(directories, options) {
21
+ const uniqueDirectories = [...new Set(directories)].filter(Boolean);
22
+ const result = { directories: uniqueDirectories.length, scanned: 0, removed: 0, liveSkipped: 0, truncated: false };
23
+ for (const directory of uniqueDirectories) {
24
+ let handle;
25
+ try {
26
+ handle = await fs.promises.opendir(directory);
27
+ }
28
+ catch {
29
+ continue;
30
+ }
31
+ try {
32
+ let reachedEntryCap = true;
33
+ for (let i = 0; i < options.maxEntries; i++) {
34
+ const entry = await handle.read();
35
+ if (entry === null) {
36
+ reachedEntryCap = false;
37
+ break;
38
+ }
39
+ result.scanned++;
40
+ if (!entry.isFile())
41
+ continue;
42
+ const pid = stageOwnerPidFromName(entry.name);
43
+ if (pid === undefined)
44
+ continue;
45
+ if (pid === process.pid || options.isPidAlive(pid)) {
46
+ result.liveSkipped++;
47
+ continue;
48
+ }
49
+ try {
50
+ await fs.promises.rm(path.join(directory, entry.name), { force: true });
51
+ result.removed++;
52
+ }
53
+ catch { /* leave it for the next bounded sweep */ }
54
+ }
55
+ result.truncated ||= reachedEntryCap;
56
+ }
57
+ catch { /* best-effort directory scan */ }
58
+ finally {
59
+ await handle.close().catch(() => { });
60
+ }
61
+ }
62
+ return result;
63
+ }
@@ -1,16 +1,62 @@
1
1
  /**
2
2
  * Shared atomic tmp+rename file writer (closes #762).
3
3
  *
4
- * The `${target}.tmp-${process.pid}` + `renameSync` shape was independently
5
- * hand-rolled in five places (`instance-registry.ts`, `session-state-store.ts`,
4
+ * The `${target}.tmp-…` + `renameSync` shape was independently hand-rolled in
5
+ * five places (`instance-registry.ts`, `session-state-store.ts`,
6
6
  * `recent-touches.ts`, `review-graph/builder.ts`, `diagnostic-dispositions.ts`)
7
- * as each one picked up the need for a cross-process reader to never observe a
8
- * partially-written file: `rename()` replaces the destination atomically on
9
- * both POSIX and Windows (libuv uses `MOVEFILE_REPLACE_EXISTING`), so a
10
- * concurrent reader always sees either the fully-old or fully-new file, never
11
- * a torn write. Five independent copies of the same shape invite drift (e.g.
12
- * forgetting the tmp-file cleanup on the failure path) — this module is the
13
- * single implementation the rest re-use.
7
+ * as each one picked up the need for a reader to never observe a
8
+ * partially-written file. Five independent copies of the same shape invite
9
+ * drift (e.g. forgetting the tmp-file cleanup on the failure path) this
10
+ * module is the single implementation the rest re-use.
11
+ *
12
+ * ## What this guarantees (#1205)
13
+ *
14
+ * Data is written in full to a staging file that no other writer can name,
15
+ * then `rename()`d over the target. On POSIX, `rename()` replaces the
16
+ * destination atomically by construction. On Windows, libuv uses
17
+ * `MoveFileEx` with `MOVEFILE_REPLACE_EXISTING`, which is atomic in practice
18
+ * on a same-volume NTFS replace but — unlike POSIX `rename()` — is not a
19
+ * formally guaranteed atomic primitive (`ReplaceFile` /
20
+ * `SetFileInformationByHandle` are the stronger APIs). Therefore, modulo that
21
+ * caveat:
22
+ *
23
+ * - **Crash-safe replacement.** A crash mid-write leaves the target holding
24
+ * its previous complete contents plus an orphan staging file; it never
25
+ * leaves the target half-written.
26
+ * - **Tear-free publication.** Any reader — same process or not — observes
27
+ * either the fully-old or the fully-new file at the target path. Because
28
+ * the staging name is unique *per call* (pid + thread id + a monotonic
29
+ * per-thread counter), this now holds for concurrent writes from the same
30
+ * process too, on any thread. Before #1205 the staging name was only
31
+ * `.tmp-${pid}`, so two in-flight same-process writes to one target
32
+ * shared a staging inode; the first `rename` published it while the
33
+ * second writer was still writing into it, publishing a torn hybrid file.
34
+ *
35
+ * ## What this explicitly does NOT provide
36
+ *
37
+ * - **No read-modify-write isolation.** Concurrent read → mutate → write
38
+ * cycles still lose updates: each writer publishes the state it computed
39
+ * from the snapshot it read, and last-rename-wins silently discards the
40
+ * other's mutation. Callers needing this must serialize themselves (see
41
+ * `lspChildRemovalTail` in `instance-registry.ts`).
42
+ * - **No ordering.** Nothing sequences concurrent writers, within or across
43
+ * processes. The winner is whichever `rename` lands last, which is not
44
+ * necessarily the write that started or finished last.
45
+ * - **No mutual exclusion.** This is not a lock. Overlapping writes are
46
+ * permitted and expected; only *torn* output is prevented.
47
+ * - **No fsync/durability guarantee.** Neither the staging file nor the
48
+ * parent directory is fsync'd, so a power loss (as opposed to a process
49
+ * crash) may lose the rename.
50
+ * - **No self-reaping.** A process killed between the staging write and the
51
+ * rename leaves an orphan staging file. The next session_start performs a
52
+ * bounded, liveness-checked sweep through the instance-reaper lifecycle
53
+ * seam (#1228); this writer never watches or reaps its own files.
54
+ * - **Windows-specific loss under `bestEffort: true`.** `MoveFileEx` with
55
+ * `MOVEFILE_REPLACE_EXISTING` fails if a reader holds the destination
56
+ * open without `FILE_SHARE_DELETE`. On Windows this silently drops the
57
+ * write (swallowed like any other `bestEffort: true` failure); the
58
+ * identical code path succeeds on POSIX, where `rename()` does not care
59
+ * whether a reader has the destination open.
14
60
  *
15
61
  * Per-write-site error policy varies and is NOT a detail this helper should
16
62
  * paper over:
@@ -29,8 +75,62 @@
29
75
  * it swallowed. See the #757 CHANGELOG entry for the full reasoning.
30
76
  */
31
77
  import * as fs from "node:fs";
78
+ export { STAGE_TMP_PATTERN, stageOwnerPidFromName, stagePathFor, } from "./atomic-write-staging.js";
79
+ import { stagePathFor } from "./atomic-write-staging.js";
80
+ /**
81
+ * Monotonic counter that makes each staging path unique per CALL, not merely
82
+ * per process (#1205).
83
+ *
84
+ * A counter is used rather than `randomBytes`: it is allocation-free and
85
+ * synchronous on what are hot per-turn/per-touch write paths, and it is
86
+ * *exactly* as unique as needed — collisions only matter between two writes
87
+ * live at the same instant, and a monotonic counter makes those impossible by
88
+ * construction rather than merely improbable.
89
+ *
90
+ * The counter alone is per-THREAD, not per-process: a worker gets its own
91
+ * module instance and so its own `_stageSeq` starting at 0, while sharing
92
+ * `process.pid` with the main thread and every sibling worker. Two threads
93
+ * would therefore each mint `${target}.tmp-<samepid>-0` — the exact collision
94
+ * the counter exists to prevent. `threadId` (0 on the main thread, distinct
95
+ * per worker) closes that axis, so the three parts cover the three ways two
96
+ * writes can be concurrent: `process.pid` across processes, `threadId` across
97
+ * threads, `_stageSeq` within one thread (#1217).
98
+ */
99
+ /**
100
+ * Staging path for one write call: `${targetPath}.tmp-<pid>-<threadId>-<seq>`.
101
+ *
102
+ * Exported so that callers which cannot use {@link writeFileAtomic} wholesale
103
+ * — `gzip-stage-write.ts` needs its own streaming pipeline — still source the
104
+ * *name* here rather than re-deriving the scheme, and so that sweepers of
105
+ * orphaned staging files stay in lockstep with it (see
106
+ * {@link STAGE_TMP_PATTERN}).
107
+ */
108
+ /**
109
+ * Matches the trailing staging-file marker produced by {@link stagePathFor},
110
+ * for the session-start sweeper that garbage-collects staging files orphaned
111
+ * by a crashed process (#1228). The writer itself does not reap or watch files.
112
+ *
113
+ * Only the current four-component shape is matched:
114
+ * `.tmp-<pid>-<threadId>-<seq>` (#1217). Older one- and two-number suffixes
115
+ * are deliberately left alone: their shape is indistinguishable from
116
+ * legitimate user filenames, so deletion safety outweighs reclaiming the
117
+ * handful of pre-#1208 leftovers.
118
+ *
119
+ * CAUTION for any sweeper adopting this pattern: it matches staging files by
120
+ * shape, not by writer identity, so it also matches this process's own
121
+ * still-being-written staging files. A safe sweeper must separately protect
122
+ * `process.pid` and use the shared conservative liveness check for foreign
123
+ * pids. The review-graph builder's narrower stage sweep intentionally remains
124
+ * separate because it owns a different `.stage-<pid>-<generation>` namespace.
125
+ *
126
+ * Anchoring is correct for both consumer styles in this repo today
127
+ * (matching against a full path or a bare basename), but this is confined to
128
+ * pi-lens's own cache directories — a consumer sweeping a directory it does
129
+ * not fully control should not assume every match is one of this module's
130
+ * staging files.
131
+ */
32
132
  /**
33
- * Synchronous atomic write of text or binary data to `${targetPath}.tmp-${pid}`,
133
+ * Synchronous atomic write of text or binary data to a per-call staging file,
34
134
  * then `fs.renameSync` over `targetPath`. On any failure (from either step),
35
135
  * attempts a best-effort `fs.rmSync(tmp, { force: true })` cleanup, then
36
136
  * either swallows (default) or rethrows per `options.bestEffort`.
@@ -41,7 +141,7 @@ import * as fs from "node:fs";
41
141
  */
42
142
  export function writeFileAtomic(targetPath, data, options) {
43
143
  const bestEffort = options?.bestEffort ?? true;
44
- const tmpPath = `${targetPath}.tmp-${process.pid}`;
144
+ const tmpPath = stagePathFor(targetPath);
45
145
  try {
46
146
  fs.writeFileSync(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
47
147
  fs.renameSync(tmpPath, targetPath);
@@ -60,12 +160,12 @@ export function writeFileAtomic(targetPath, data, options) {
60
160
  /**
61
161
  * Async counterpart of {@link writeFileAtomic}, built on `fs.promises` instead
62
162
  * of the sync `fs` API — for call sites that must not block the event loop
63
- * (e.g. writes on a hot per-turn/per-touch path). Same tmp-naming, cleanup,
64
- * and `bestEffort` semantics.
163
+ * (e.g. writes on a hot per-turn/per-touch path). Same per-call tmp-naming,
164
+ * cleanup, and `bestEffort` semantics.
65
165
  */
66
166
  export async function writeFileAtomicAsync(targetPath, data, options) {
67
167
  const bestEffort = options?.bestEffort ?? true;
68
- const tmpPath = `${targetPath}.tmp-${process.pid}`;
168
+ const tmpPath = stagePathFor(targetPath);
69
169
  try {
70
170
  await fs.promises.writeFile(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
71
171
  await fs.promises.rename(tmpPath, targetPath);