@herbertgao/pi-extensions 2026.8.4 → 2026.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  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/index.ts +7 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  13. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  14. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  15. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  16. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  20. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  21. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  22. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  30. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  31. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  32. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  33. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  34. package/node_modules/pi-lens/README.md +13 -0
  35. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  36. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  37. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  39. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  40. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  41. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  42. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  43. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  44. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  45. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  46. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  47. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  51. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  52. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  53. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  54. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  56. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  57. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  58. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  59. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  60. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  63. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  64. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  66. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  67. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  68. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  69. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  70. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  71. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  72. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  75. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  99. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  100. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  101. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  102. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  103. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  104. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  105. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  106. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  107. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  108. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  109. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  111. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  112. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  113. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  114. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  116. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  117. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  118. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  119. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  120. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  121. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  122. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  123. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  124. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  125. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  126. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  127. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  128. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  129. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  130. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  131. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  132. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  134. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  135. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  136. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  137. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  138. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  139. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  140. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  141. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  142. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  143. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  144. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  145. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  146. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  147. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  148. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  149. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  150. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  151. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  152. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  153. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  154. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  155. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  157. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  158. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  159. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  160. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  161. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  162. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  163. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  164. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  165. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  166. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  167. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  168. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  169. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  170. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  171. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  172. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  173. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  174. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  175. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  177. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  178. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  179. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  180. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  181. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  182. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  183. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  184. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  185. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  186. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  187. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  188. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  189. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  190. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  191. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  192. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  193. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  194. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  196. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  197. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  198. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  199. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  200. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  201. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  202. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  203. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  204. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  205. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  210. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  211. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  212. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  213. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  214. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  215. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  216. package/node_modules/pi-lens/dist/index.js +49361 -36888
  217. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  218. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  219. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  220. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  221. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  222. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  223. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  224. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  225. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  226. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  227. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  228. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  229. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  230. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  231. package/node_modules/pi-lens/docs/fable.md +27 -0
  232. package/node_modules/pi-lens/docs/features.md +31 -2
  233. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  234. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  235. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  236. package/node_modules/pi-lens/docs/mcp.md +37 -4
  237. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  238. package/node_modules/pi-lens/docs/settings.md +12 -1
  239. package/node_modules/pi-lens/docs/tools.md +4 -1
  240. package/node_modules/pi-lens/docs/usage.md +18 -0
  241. package/node_modules/pi-lens/package.json +10 -10
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  252. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  253. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  254. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  255. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  256. package/node_modules/pi-mcp-adapter/README.md +34 -3
  257. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  258. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  259. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  260. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  261. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  262. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  263. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  264. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  265. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  266. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  267. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  268. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  269. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  270. package/node_modules/pi-mcp-adapter/package.json +3 -3
  271. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  272. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  273. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  274. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  275. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  276. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  277. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  278. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  279. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  280. package/package.json +11 -12
  281. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  282. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  283. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  284. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  285. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  286. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  287. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  288. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -56,11 +56,13 @@ export const LANGUAGE_POLICY = {
56
56
  lua: { lspCapable: true },
57
57
  zig: { lspCapable: true },
58
58
  haskell: { lspCapable: true },
59
+ "helm-template": { lspCapable: false },
59
60
  elixir: { lspCapable: true },
60
61
  gleam: { lspCapable: true },
61
62
  ocaml: { lspCapable: true },
62
63
  clojure: { lspCapable: true },
63
64
  terraform: { lspCapable: true },
65
+ terragrunt: { lspCapable: false },
64
66
  nix: { lspCapable: true },
65
67
  toml: { lspCapable: true, startup: { defaults: ["taplo"] } },
66
68
  };
@@ -117,7 +119,7 @@ const PRIMARY_DISPATCH_GROUPS = {
117
119
  },
118
120
  yaml: {
119
121
  mode: "all",
120
- runnerIds: ["lsp", "yamllint", "trivy-config"],
122
+ runnerIds: ["lsp", "yamllint", "trivy-config", "helm-lint"],
121
123
  filterKinds: ["yaml"],
122
124
  },
123
125
  sql: {
@@ -179,6 +181,11 @@ const PRIMARY_DISPATCH_GROUPS = {
179
181
  lua: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["lua"] },
180
182
  zig: { mode: "all", runnerIds: ["lsp", "zig-check"], filterKinds: ["zig"] },
181
183
  haskell: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["haskell"] },
184
+ "helm-template": {
185
+ mode: "all",
186
+ runnerIds: ["helm-lint"],
187
+ filterKinds: ["helm-template"],
188
+ },
182
189
  elixir: {
183
190
  mode: "all",
184
191
  runnerIds: ["lsp", "elixir-check", "credo"],
@@ -193,9 +200,14 @@ const PRIMARY_DISPATCH_GROUPS = {
193
200
  clojure: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["clojure"] },
194
201
  terraform: {
195
202
  mode: "all",
196
- runnerIds: ["lsp", "tflint"],
203
+ runnerIds: ["lsp", "tflint", "trivy-config"],
197
204
  filterKinds: ["terraform"],
198
205
  },
206
+ terragrunt: {
207
+ mode: "all",
208
+ runnerIds: ["terragrunt"],
209
+ filterKinds: ["terragrunt"],
210
+ },
199
211
  nix: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["nix"] },
200
212
  toml: {
201
213
  mode: "all",
@@ -1,12 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { CODE_KINDS, detectFileKind, DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "./file-kinds.js";
3
+ import { CODE_KINDS, detectFileKind, DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, TERRAGRUNT_FILENAMES, } from "./file-kinds.js";
4
4
  import { getProjectIgnoreMatcher } from "./file-utils.js";
5
- import { direntsHaveMarkerGlobMatch } from "./path-utils.js";
5
+ import { direntsHaveMarkerGlobMatch, normalizeMapKey } from "./path-utils.js";
6
6
  import { LANGUAGE_POLICY, } from "./language-policy.js";
7
7
  import { getSourceFiles } from "./scan-utils.js";
8
8
  import { readDirEntriesSafe, shouldRecurseIntoDir } from "./source-walker.js";
9
9
  import { findNearestDirWithAnyBasename } from "./workspace-topology.js";
10
+ import { BoundedLruCache } from "./bounded-cache.js";
10
11
  /** Every registered kind participates in project-language detection (#894). */
11
12
  export const SUPPORTED_FILE_KINDS = Object.keys(KIND_EXTENSIONS);
12
13
  const PROJECT_MARKERS_BY_KIND = {
@@ -36,6 +37,7 @@ const PROJECT_MARKERS_BY_KIND = {
36
37
  elixir: ["mix.exs"],
37
38
  gleam: ["gleam.toml"],
38
39
  terraform: [".terraform.lock.hcl"],
40
+ terragrunt: TERRAGRUNT_FILENAMES,
39
41
  nix: ["flake.nix"],
40
42
  toml: ["pyproject.toml", "Cargo.toml", "taplo.toml"],
41
43
  csharp: DOTNET_CSHARP_ROOT_MARKERS,
@@ -80,6 +82,7 @@ const ROOT_MARKERS_BY_KIND = {
80
82
  elixir: ["mix.exs"],
81
83
  gleam: ["gleam.toml"],
82
84
  terraform: [".terraform.lock.hcl"],
85
+ terragrunt: TERRAGRUNT_FILENAMES,
83
86
  nix: ["flake.nix"],
84
87
  toml: ["pyproject.toml", "Cargo.toml", "taplo.toml"],
85
88
  csharp: DOTNET_CSHARP_ROOT_MARKERS,
@@ -101,16 +104,16 @@ function hasProjectMarker(projectRoot, marker) {
101
104
  // must not pollute the no-arg cache. The synchronous getSourceFiles() call
102
105
  // inside this function does the same expensive ignoreMatcher-driven walk
103
106
  // as resolveStartupScanContext, so the same memo strategy applies.
104
- const languageProfileCache = new Map();
107
+ const languageProfileCache = new BoundedLruCache(32);
105
108
  export function detectProjectLanguageProfile(projectRoot, sourceFiles) {
106
109
  if (sourceFiles === undefined) {
107
- const cached = languageProfileCache.get(projectRoot);
110
+ const cached = languageProfileCache.get(normalizeMapKey(projectRoot));
108
111
  if (cached)
109
112
  return cached;
110
113
  }
111
114
  const result = computeProjectLanguageProfile(projectRoot, sourceFiles);
112
115
  if (sourceFiles === undefined) {
113
- languageProfileCache.set(projectRoot, result);
116
+ languageProfileCache.set(normalizeMapKey(projectRoot), result);
114
117
  }
115
118
  return result;
116
119
  }
@@ -297,6 +300,9 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
297
300
  break walk;
298
301
  }
299
302
  }
303
+ // Each iteration performs only bounded dirent/ext/ignore checks. Keep the
304
+ // count cadence: the cost does not scale with the corpus item size, and
305
+ // this walk's existing matched-file ceiling bounds the total work.
300
306
  if (++processedSinceYield % yieldEvery === 0) {
301
307
  // See countSourceFilesWithinLimitAsync for why setImmediate.
302
308
  await new Promise((resolve) => setImmediate(resolve));
@@ -306,7 +312,7 @@ export async function collectSourceFilesForWarmup(rootDir, maxFiles = MAX_WARMUP
306
312
  return [...codeOut, ...nonCodeOut];
307
313
  }
308
314
  export async function detectProjectLanguageProfileAsync(projectRoot) {
309
- const cached = languageProfileCache.get(projectRoot);
315
+ const cached = languageProfileCache.get(normalizeMapKey(projectRoot));
310
316
  if (cached)
311
317
  return cached;
312
318
  const files = await collectSourceFilesForWarmup(projectRoot);
@@ -315,6 +321,6 @@ export async function detectProjectLanguageProfileAsync(projectRoot) {
315
321
  // probe (`existsSync` for package.json / pyproject.toml / etc.) which
316
322
  // is constant-time and cheap.
317
323
  const result = detectProjectLanguageProfile(projectRoot, files);
318
- languageProfileCache.set(projectRoot, result);
324
+ languageProfileCache.set(normalizeMapKey(projectRoot), result);
319
325
  return result;
320
326
  }
@@ -9,11 +9,33 @@ const writer = createNdjsonLogger({
9
9
  filePath: LATENCY_LOG_FILE,
10
10
  maxBytes: getMaxLogSizeMB() * 1024 * 1024,
11
11
  });
12
+ /**
13
+ * Most recent non-`loop_block` phase seen by `logLatency`, for cheap block
14
+ * attribution (#1122 / #1123 item 1): the event-loop-block probe fires at
15
+ * turn_end and cannot see *what* stalled the loop, so it stamps the last phase
16
+ * that ran as a starting point for root-causing a genuine block. Tracked before
17
+ * the test-mode guard so it is deterministic and unit-testable.
18
+ */
19
+ let lastPhase;
20
+ /**
21
+ * The last non-`loop_block` phase logged, or undefined if none yet. Carries its
22
+ * own `ts` so a consumer can gauge staleness: it is intentionally NOT cleared at
23
+ * turn/window boundaries, so on a turn that logged no phase of its own it may
24
+ * point at a prior turn's phase — compare `ts` against the block time before
25
+ * trusting it as the cause (it is a breadcrumb, not proof).
26
+ */
27
+ export function getLastLoggedPhase() {
28
+ return lastPhase;
29
+ }
12
30
  export function logLatency(entry) {
31
+ const ts = new Date().toISOString();
32
+ if (entry.type === "phase" && entry.phase && entry.phase !== "loop_block") {
33
+ lastPhase = { phase: entry.phase, ts };
34
+ }
13
35
  if (isTestMode()) {
14
36
  return;
15
37
  }
16
- writer.log({ ...entry, ts: new Date().toISOString(), pid: process.pid });
38
+ writer.log({ ...entry, ts, pid: process.pid });
17
39
  }
18
40
  export function getLatencyLogPath() {
19
41
  return LATENCY_LOG_FILE;
@@ -1,3 +1,5 @@
1
+ import { logExtension } from "./extension-log.js";
2
+ import { notifyUserDegradation } from "./user-notify.js";
1
3
  import * as fs from "node:fs";
2
4
  import * as os from "node:os";
3
5
  import * as path from "node:path";
@@ -20,7 +22,16 @@ function warnInvalidGlobalConfigOnce(configPath, reason) {
20
22
  if (warnedInvalidGlobalConfigs.has(key))
21
23
  return;
22
24
  warnedInvalidGlobalConfigs.add(key);
23
- console.error(`[pi-lens] ignoring invalid global config ${configPath}: ${reason}`);
25
+ const message = `ignoring invalid global config ${configPath}: ${reason}`;
26
+ logExtension({
27
+ subsystem: "lens-config",
28
+ level: "warn",
29
+ message,
30
+ metadata: { configPath, reason },
31
+ });
32
+ // HUMAN-audience too: a config the user wrote is being ignored. Routed
33
+ // through the host's own render path (#1333), never a raw write.
34
+ notifyUserDegradation(`pi-lens: ${message}`);
24
35
  }
25
36
  /** For tests that need to force the warn-once cache to reset between cases. */
26
37
  export function resetGlobalConfigWarnCache() {
@@ -13,27 +13,28 @@
13
13
  * adds thin wrappers over the remaining internal reach-ins (latency, project
14
14
  * scan, LSP status, diagnostic stats, LSP config).
15
15
  */
16
+ import * as path from "node:path";
17
+ import { minimatch } from "./deps/minimatch.js";
16
18
  import { getDiagnosticTracker } from "./diagnostic-tracker.js";
17
19
  import { getLatencyReports, } from "./dispatch/integration.js";
18
20
  import { getResourceFootprint as getResourceFootprintSnapshot, } from "./instance-registry.js";
19
21
  import { initLSPConfig } from "./lsp/config.js";
20
22
  import { getLSPService } from "./lsp/index.js";
21
- import { getOrLoadWarmWordIndex } from "./mcp/analyze.js";
23
+ import { acquireWarmWordIndex } from "./mcp/analyze.js";
24
+ import { normalizeMapKey } from "./path-utils.js";
22
25
  import { scanProjectDiagnostics } from "./project-diagnostics/scanner.js";
26
+ import { loadProjectSnapshotWithoutWordIndex } from "./project-snapshot.js";
23
27
  import { getTreeSitterRuntimeStatus, } from "./tree-sitter-shared.js";
24
- import * as path from "node:path";
25
- import { minimatch } from "./deps/minimatch.js";
26
- import { normalizeMapKey } from "./path-utils.js";
27
- import { loadProjectSnapshot } from "./project-snapshot.js";
28
- import { centralityFromReverseDeps, deserializeWordIndex, getWordIndexBuildStatus, searchWordIndex, triggerBackgroundWordIndexBuild, } from "./word-index.js";
28
+ import { centralityFromReverseDeps, getWordIndexBuildStatus, searchWordIndex, triggerBackgroundWordIndexBuild, } from "./word-index.js";
29
29
  // --- Facades (re-exported so adapters import only this module) ---------------
30
30
  export { analyzeFile, } from "./mcp/analyze.js";
31
31
  export { createMcpHost } from "./mcp/host-shim.js";
32
- export { ipcPathForCwd, requestWarmAnalyze, } from "./mcp/ipc.js";
32
+ export { createWarmIpcLineReader, createWarmIpcRequestQueue, ipcPathForCwd, readTurnEndStatus, requestWarmAnalyze, WARM_TURN_END_SCHEMA_VERSION, } from "./mcp/ipc.js";
33
33
  export { analyzeFileFresh, canRebuildPiLens, REBUILD_UNAVAILABLE_MESSAGE, resolveRebuildScript, runRebuild, summarizeScan, } from "./mcp/review.js";
34
- export { runSessionStart, runTurnEnd, } from "./mcp/session.js";
34
+ export { acknowledgeTurnEnd, runSessionStart, runTurnEnd, runTurnEndForIpc, } from "./mcp/session.js";
35
35
  export { moduleReport, readEnclosing, readSymbol, renderCompactModuleReport, } from "./module-report.js";
36
36
  export { projectReport, renderCompactProjectReport, } from "./project-report.js";
37
+ export { createDefaultHostPorts, } from "./host-ports.js";
37
38
  // --- Query wrappers (own the remaining internal reach-ins) -------------------
38
39
  /** Recent dispatch latency reports (latency.log schema), newest first. */
39
40
  export function recentLatency(limit = 5, fileFilter) {
@@ -44,9 +45,21 @@ export function recentLatency(limit = 5, fileFilter) {
44
45
  }
45
46
  return reports.slice(-limit).reverse();
46
47
  }
47
- /** Cheap project-wide scan (tree-sitter + fact rules). */
48
- export function projectScan(cwd, maxFiles) {
49
- return scanProjectDiagnostics({ cwd, tier: "cheap", maxFiles });
48
+ /**
49
+ * Cheap project-wide scan (tree-sitter + fact rules).
50
+ *
51
+ * `includeGenerated` (#1107 phase 2 review): scan WITHOUT the generated/
52
+ * artifact NAME-heuristic filter — the actionable opt-out
53
+ * `generatedSkipNotice` below points a user at. Default `false` (existing
54
+ * filtering behavior unchanged).
55
+ */
56
+ export function projectScan(cwd, maxFiles, includeGenerated) {
57
+ return scanProjectDiagnostics({
58
+ cwd,
59
+ tier: "cheap",
60
+ maxFiles,
61
+ includeGenerated,
62
+ });
50
63
  }
51
64
  /**
52
65
  * #784: `scanTruncated` (#760) reaches this seam intact but, until now, no
@@ -66,6 +79,53 @@ export function scanTruncationNotice(snapshot) {
66
79
  return (`⚠ Scan truncated at ${snapshot.filesScanned} file(s) — results are partial; ` +
67
80
  "raise maxProjectFiles in .pi-lens.json to scan fully.");
68
81
  }
82
+ /**
83
+ * #1107 phase 2: `generatedNameOnlySkips`/`generatedDirSkips` reach this seam
84
+ * via `ProjectDiagnosticsSnapshot` but need explicit rendering, same pattern
85
+ * as `scanTruncationNotice` above — otherwise a user has no way to learn the
86
+ * generated-name heuristic silently excluded files/directories short of
87
+ * reading `latency.log`'s `source_walk_skip_summary` line directly.
88
+ *
89
+ * #1107 phase 2 review round 2 (P1, empirically proven): this deliberately
90
+ * keys off `generatedNameOnlySkips`, NOT the raw `generatedFileSkips` total.
91
+ * `generatedFileSkips` counts every file the generated/artifact heuristic
92
+ * skipped, including STRONG evidence (lockfiles, declaration files, minified/
93
+ * bundle/chunk output — expected on nearly every real repo) and
94
+ * content-CONFIRMED WEAK matches (the escape hatch checked and a genuine
95
+ * generated-code header was present). Keying the notice off that total meant
96
+ * a repo with nothing more unusual than a `package-lock.json` and an ambient
97
+ * `.d.ts` showed "2 file(s) excluded" on literally every scan forever — the
98
+ * signal drowned itself on day one. `generatedNameOnlySkips` is the narrower,
99
+ * genuinely at-risk residual: a WEAK name match trusted with NO corroborating
100
+ * evidence check at all (only reachable when a caller opts out of the header
101
+ * probe — source-filter.ts's default project-walk path always enables it).
102
+ * Post-escape-hatch, this should be rare-to-zero on the default path, which
103
+ * is exactly what makes the notice meaningful again when it DOES fire.
104
+ * `generatedDirSkips` stays in the trigger condition unchanged: directory-
105
+ * level pruning has no escape hatch at all (the walk never enumerates a
106
+ * pruned directory's contents to check them), so it is always a genuine
107
+ * "real files may be hiding in here, unverified" signal, not a
108
+ * false-positive-prone one — see `generatedDirSkips`'s doc on
109
+ * `SourceCollectionResult`.
110
+ *
111
+ * Returns `undefined` when neither counter is set (nothing to append).
112
+ */
113
+ export function generatedSkipNotice(snapshot) {
114
+ const files = snapshot.generatedNameOnlySkips ?? 0;
115
+ const dirs = snapshot.generatedDirSkips ?? 0;
116
+ if (files === 0 && dirs === 0)
117
+ return undefined;
118
+ const parts = [];
119
+ if (files > 0)
120
+ parts.push(`${files} file(s)`);
121
+ if (dirs > 0)
122
+ parts.push(`${dirs} director${dirs === 1 ? "y" : "ies"}`);
123
+ return (`ℹ ${parts.join(" and ")} excluded by generated-name heuristics with no ` +
124
+ "confirming evidence — a real hand-written file/directory whose name " +
125
+ "looks generated (e.g. `gen.ts`, `generated/`) can be excluded too; " +
126
+ "pass includeGenerated: true to pilens_project_scan / lens_diagnostics " +
127
+ "to scan without this filter if you suspect a false skip.");
128
+ }
69
129
  /** Process-wide tree-sitter health for host status surfaces. */
70
130
  export function treeSitterRuntimeStatus() {
71
131
  return getTreeSitterRuntimeStatus();
@@ -213,7 +273,7 @@ function toSymbolSearchHit(result) {
213
273
  * cwd, never blocking this call) so a retry shortly after succeeds (#348
214
274
  * decision 3).
215
275
  *
216
- * #536 rider: prefers the warm in-memory index (`getOrLoadWarmWordIndex`,
276
+ * #536 rider: prefers the warm in-memory index (`acquireWarmWordIndex`,
217
277
  * clients/mcp/analyze.ts) over a fresh disk read when one exists for this
218
278
  * cwd — a warm `pilens_analyze` call updates that live copy synchronously but
219
279
  * persists it to disk on a debounce (default 1500ms), so without this a query
@@ -239,9 +299,11 @@ function toSymbolSearchHit(result) {
239
299
  * nothing on this hot path.
240
300
  */
241
301
  export async function symbolSearch(query, cwd, limit = 20, options = {}) {
242
- const snapshot = loadProjectSnapshot(cwd);
243
- const index = getOrLoadWarmWordIndex(cwd) ?? deserializeWordIndex(snapshot?.wordIndex);
302
+ const warmLease = acquireWarmWordIndex(cwd);
303
+ const snapshot = loadProjectSnapshotWithoutWordIndex(cwd);
304
+ const index = warmLease.index;
244
305
  if (!index) {
306
+ warmLease.release();
245
307
  const priorStatus = getWordIndexBuildStatus(cwd);
246
308
  const status = priorStatus?.state === "refused"
247
309
  ? priorStatus
@@ -266,9 +328,16 @@ export async function symbolSearch(query, cwd, limit = 20, options = {}) {
266
328
  }
267
329
  // Boost well-connected files using the snapshot's reverse-dependency
268
330
  // (importedBy) counts; snapshot keys are normalized, index keys are raw.
269
- const centrality = centralityFromReverseDeps(index, snapshot?.reverseDeps, (file) => normalizeMapKey(path.resolve(file)));
270
- const fileFilter = buildSymbolSearchFileFilter(cwd, options);
271
- const results = searchWordIndex(index, query, { limit, centrality, fileFilter });
331
+ let centrality;
332
+ let results;
333
+ try {
334
+ centrality = centralityFromReverseDeps(index, snapshot?.reverseDeps, (file) => normalizeMapKey(path.resolve(file)));
335
+ const fileFilter = buildSymbolSearchFileFilter(cwd, options);
336
+ results = searchWordIndex(index, query, { limit, centrality, fileFilter });
337
+ }
338
+ finally {
339
+ warmLease.release();
340
+ }
272
341
  const hits = results.map(toSymbolSearchHit);
273
342
  const { getCachedReviewGraph } = await import("./review-graph/builder.js");
274
343
  const graph = getCachedReviewGraph(cwd);
@@ -1,3 +1,4 @@
1
+ import { logExtension } from "./extension-log.js";
1
2
  export const LENS_EVENT_VERSION = 1;
2
3
  export const LENS_EVENT_NAMES = {
3
4
  analysisComplete: "pi-lens/analysis-complete",
@@ -5,8 +6,15 @@ export const LENS_EVENT_NAMES = {
5
6
  turnFindings: "pi-lens/turn-findings",
6
7
  };
7
8
  let lensEventBus;
9
+ let lensEventBusGetter;
8
10
  export function initLensEvents(pi) {
9
11
  lensEventBus = pi.events;
12
+ lensEventBusGetter = undefined;
13
+ }
14
+ /** Keep deferred deliveries on the live extension activation. */
15
+ export function initLensEventsGetter(getter) {
16
+ lensEventBusGetter = getter;
17
+ lensEventBus = undefined;
10
18
  }
11
19
  function truncateText(value, maxChars) {
12
20
  if (value === undefined)
@@ -26,13 +34,28 @@ function normalizeDiagnostic(diagnostic) {
26
34
  function normalizeDiagnostics(diagnostics) {
27
35
  return diagnostics.map(normalizeDiagnostic);
28
36
  }
37
+ // Dropped-emit observability (#1128 review): drops are legitimate (no bus
38
+ // during startup/replacement windows) but must not be invisible. One line per
39
+ // process — enough to notice a permanently-missing bus without spamming.
40
+ let _droppedEmitLogged = false;
29
41
  function emitLensEvent(eventName, payload) {
30
- const emit = lensEventBus?.emit;
31
- if (!emit)
32
- return;
33
42
  setImmediate(() => {
34
43
  try {
35
- emit.call(lensEventBus, eventName, payload);
44
+ const bus = lensEventBusGetter?.() ?? lensEventBus;
45
+ const emit = bus?.emit;
46
+ if (!emit) {
47
+ if (!_droppedEmitLogged) {
48
+ _droppedEmitLogged = true;
49
+ logExtension({
50
+ subsystem: "lens-events",
51
+ level: "warn",
52
+ message: `lens event dropped (no live bus): ${eventName} — further drops logged once per process`,
53
+ metadata: { eventName },
54
+ });
55
+ }
56
+ return;
57
+ }
58
+ emit.call(bus, eventName, payload);
36
59
  }
37
60
  catch {
38
61
  // Inter-extension events are observational. A listener must never break
@@ -156,6 +156,14 @@ export const LENS_FLAGS = [
156
156
  default: false,
157
157
  scope: "global",
158
158
  },
159
+ {
160
+ name: "lens-compact-tool-line",
161
+ description: "Opt-in (#1327): collapse a pi-lens tool's call+result rows into ONE theme-aware line (status glyph + name + summary) instead of two. Preserves expand-to-view-full-output. Default off. Also via ui.compactToolLine=true in ~/.pi-lens/config.json.",
162
+ configKey: "ui.compactToolLine",
163
+ negated: false,
164
+ default: false,
165
+ scope: "global",
166
+ },
159
167
  ];
160
168
  const byName = new Map(LENS_FLAGS.map((spec) => [spec.name, spec]));
161
169
  export function getLensFlagSpec(name) {
@@ -21,6 +21,7 @@ import { FactStore } from "./dispatch/fact-store.js";
21
21
  import { getProjectDataDir } from "./file-utils.js";
22
22
  import { collectUntrackedIgnoredIds } from "./git-tracked-ignore.js";
23
23
  import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
24
+ import { canonicalSourceTwin } from "./source-filter.js";
24
25
  import { buildOrUpdateGraph } from "./review-graph/service.js";
25
26
  import { detectFileRole } from "./file-role.js";
26
27
  export const DEFAULT_MAX_MAP_NODES = 500;
@@ -92,11 +93,10 @@ export function aggregateGraphToFiles(graph, options) {
92
93
  // import edges). Deterministic sibling families: .js → .ts (preferred)
93
94
  // then .tsx; .mjs → .mts; .cjs → .cts. A compiled-looking file with no
94
95
  // source twin (vendored deps, pure-JS projects) is untouched.
95
- const COMPILED_TWIN_SOURCES = [
96
- [".js", [".ts", ".tsx"]],
97
- [".mjs", [".mts"]],
98
- [".cjs", [".cts"]],
99
- ];
96
+ // Keep this pure aggregation layer on the shared source-filter policy. It
97
+ // cannot call findSourceSibling because synthetic graph fixtures and cached
98
+ // nodes are not required to exist on disk.
99
+ const compiledExtensions = new Set([".js", ".jsx", ".mjs", ".cjs"]);
100
100
  // Interplay with the untracked-gitignored exclusion (`excludeIds`):
101
101
  // twin canonicalization takes PRECEDENCE over exclusion. An excluded
102
102
  // compiled file whose source twin survives is merged onto that twin
@@ -109,19 +109,11 @@ export function aggregateGraphToFiles(graph, options) {
109
109
  const excludeIds = options?.excludeIds;
110
110
  const canonicalOf = new Map();
111
111
  for (const id of allFileIds) {
112
- for (const [compiledExt, sourceExts] of COMPILED_TWIN_SOURCES) {
113
- if (!id.endsWith(compiledExt))
114
- continue;
115
- const stem = id.slice(0, id.length - compiledExt.length);
116
- for (const sourceExt of sourceExts) {
117
- const candidate = stem + sourceExt;
118
- if (allFileIds.has(candidate) && !excludeIds?.has(candidate)) {
119
- canonicalOf.set(id, candidate);
120
- break;
121
- }
122
- }
123
- break;
124
- }
112
+ if (!compiledExtensions.has(path.extname(id).toLowerCase()))
113
+ continue;
114
+ const canonical = canonicalSourceTwin(id, allFileIds, excludeIds);
115
+ if (canonical !== id)
116
+ canonicalOf.set(id, canonical);
125
117
  }
126
118
  const compiledTwinCount = canonicalOf.size;
127
119
  // Excluded ids that actually drop: in the graph, untracked-gitignored,
@@ -137,6 +129,77 @@ export function aggregateGraphToFiles(graph, options) {
137
129
  }
138
130
  }
139
131
  const ignoredFileCount = droppedIgnoredIds.size;
132
+ // Build logical symbol identities before counting symbols or edges. A compiled
133
+ // twin can carry the same declaration and edge evidence as its source; those
134
+ // are one logical observation after canonicalization. Match source declarations
135
+ // in stable insertion order so overloads with the same name/kind stay distinct.
136
+ const logicalSymbolByNode = new Map();
137
+ const sourceSymbolsByFile = new Map();
138
+ // A symbol kind is not stable across extractor surfaces: a declaration can
139
+ // be a `function` in a JS/TS fact and a `variable` in a compiled twin's
140
+ // syntax extraction. Keep a name-only index for the safe case where there is
141
+ // exactly one source declaration with that name; this prevents equivalent
142
+ // twins from inflating the preferred source while preserving overloads and
143
+ // genuinely ambiguous same-name declarations.
144
+ const sourceSymbolsByName = new Map();
145
+ for (const node of graph.nodes.values()) {
146
+ if (node.kind !== "symbol" || !node.filePath || !node.symbolName)
147
+ continue;
148
+ const raw = normalizeMapKey(node.filePath);
149
+ const canonical = canonicalOf.get(raw) ?? raw;
150
+ if (raw !== canonical)
151
+ continue;
152
+ const key = `${node.symbolName}\u0000${node.symbolKind ?? ""}`;
153
+ const byName = sourceSymbolsByFile.get(canonical) ?? new Map();
154
+ const ids = byName.get(key) ?? [];
155
+ ids.push(node.id);
156
+ byName.set(key, ids);
157
+ sourceSymbolsByFile.set(canonical, byName);
158
+ const bySymbolName = sourceSymbolsByName.get(canonical) ?? new Map();
159
+ const nameIds = bySymbolName.get(node.symbolName) ?? [];
160
+ nameIds.push(node.id);
161
+ bySymbolName.set(node.symbolName, nameIds);
162
+ sourceSymbolsByName.set(canonical, bySymbolName);
163
+ logicalSymbolByNode.set(node.id, node.id);
164
+ }
165
+ // Assign each twin's declarations by occurrence within THAT twin file, not
166
+ // through one process-wide "used" set. A source can have several compiled
167
+ // siblings (`x.ts`, `x.js`, `x.mjs`, ...); every sibling's first `run` must
168
+ // map to the source's first `run`, while overloads with the same name/kind
169
+ // remain distinct by occurrence.
170
+ const twinOccurrencesByFile = new Map();
171
+ for (const node of graph.nodes.values()) {
172
+ if (node.kind !== "symbol" || !node.filePath || !node.symbolName)
173
+ continue;
174
+ const raw = normalizeMapKey(node.filePath);
175
+ const canonical = canonicalOf.get(raw) ?? raw;
176
+ if (raw === canonical)
177
+ continue;
178
+ const key = `${node.symbolName}\u0000${node.symbolKind ?? ""}`;
179
+ const exactSourceIds = sourceSymbolsByFile.get(canonical)?.get(key) ?? [];
180
+ const nameSourceIds = sourceSymbolsByName.get(canonical)?.get(node.symbolName) ?? [];
181
+ // Prefer the kind-qualified mapping. Fall back only when the source name
182
+ // is unique; never guess among overloads or same-name declarations.
183
+ const sourceIds = exactSourceIds.length > 0
184
+ ? exactSourceIds
185
+ : nameSourceIds.length === 1
186
+ ? nameSourceIds
187
+ : [];
188
+ const occurrenceKey = exactSourceIds.length > 0 ? key : node.symbolName;
189
+ const occurrences = twinOccurrencesByFile.get(raw) ?? new Map();
190
+ const occurrence = occurrences.get(occurrenceKey) ?? 0;
191
+ occurrences.set(occurrenceKey, occurrence + 1);
192
+ twinOccurrencesByFile.set(raw, occurrences);
193
+ const logical = sourceIds[occurrence];
194
+ if (logical) {
195
+ logicalSymbolByNode.set(node.id, logical);
196
+ }
197
+ else {
198
+ // A compiled-only declaration remains real evidence.
199
+ logicalSymbolByNode.set(node.id, node.id);
200
+ }
201
+ }
202
+ const countedLogicalSymbols = new Set();
140
203
  // ── Pass 3: aggregate nodes under their canonical file identity ─────────
141
204
  for (const node of graph.nodes.values()) {
142
205
  if (node.kind === "external")
@@ -173,9 +236,13 @@ export function aggregateGraphToFiles(graph, options) {
173
236
  continue;
174
237
  const id = canonicalOf.get(raw) ?? raw;
175
238
  nodeToFile.set(node.id, id);
239
+ const logicalId = logicalSymbolByNode.get(node.id) ?? node.id;
176
240
  const existing = files.get(id);
177
241
  if (existing) {
178
- existing.symbolCount += 1;
242
+ if (!countedLogicalSymbols.has(logicalId)) {
243
+ existing.symbolCount += 1;
244
+ countedLogicalSymbols.add(logicalId);
245
+ }
179
246
  }
180
247
  else {
181
248
  files.set(id, {
@@ -184,6 +251,7 @@ export function aggregateGraphToFiles(graph, options) {
184
251
  language: node.language || "",
185
252
  symbolCount: 1,
186
253
  });
254
+ countedLogicalSymbols.add(logicalId);
187
255
  }
188
256
  continue;
189
257
  }
@@ -197,11 +265,18 @@ export function aggregateGraphToFiles(graph, options) {
197
265
  // becomes self-referential only AFTER canonicalization (e.g. x.js → x.ts)
198
266
  // collapses through the same fromFile === toFile check.
199
267
  const edgeWeights = new Map();
268
+ const seenLogicalEdges = new Set();
200
269
  for (const edge of graph.edges) {
201
270
  const fromFile = nodeToFile.get(edge.from);
202
271
  const toFile = nodeToFile.get(edge.to);
203
272
  if (!fromFile || !toFile || fromFile === toFile)
204
273
  continue;
274
+ const logicalFrom = logicalSymbolByNode.get(edge.from) ?? fromFile;
275
+ const logicalTo = logicalSymbolByNode.get(edge.to) ?? toFile;
276
+ const logicalEdgeKey = `${logicalFrom}\u0000${logicalTo}\u0000${edge.kind}`;
277
+ if (seenLogicalEdges.has(logicalEdgeKey))
278
+ continue;
279
+ seenLogicalEdges.add(logicalEdgeKey);
205
280
  // NUL separator (same idiom as review-graph/builder.ts): the one byte
206
281
  // that can never appear in a file path, written as an escape sequence so
207
282
  // this source file stays text for grep (a literal NUL makes ripgrep
@@ -0,0 +1,30 @@
1
+ import { recordDegradation } from "./degradation-ledger.js";
2
+ const STALE_CTX_MESSAGE = "This extension ctx is stale after session replacement";
3
+ /** Ledger a dead activation once, at the publisher's occurrence-window guard. */
4
+ export function recordStaleBusFailure(subject, error) {
5
+ const reason = String(error);
6
+ if (!reason.includes(STALE_CTX_MESSAGE))
7
+ return;
8
+ recordDegradation({ kind: "bus-stale", subject, reason });
9
+ }
10
+ export function createLiveBusEmitter() {
11
+ let emit;
12
+ let getter;
13
+ return {
14
+ wire(next) {
15
+ emit = next;
16
+ getter = undefined;
17
+ },
18
+ wireGetter(next) {
19
+ getter = next;
20
+ emit = undefined;
21
+ },
22
+ get() {
23
+ return getter?.() ?? emit;
24
+ },
25
+ reset() {
26
+ emit = undefined;
27
+ getter = undefined;
28
+ },
29
+ };
30
+ }
@@ -24,6 +24,7 @@ import * as fs from "node:fs";
24
24
  import * as path from "node:path";
25
25
  import { getGlobalPiLensDir } from "./file-utils.js";
26
26
  import { getRegisteredLogFiles } from "./ndjson-logger.js";
27
+ import { pathsEqual } from "./path-utils.js";
27
28
  const LOG_DIR = getGlobalPiLensDir();
28
29
  const LOGS_SUBDIR = path.join(LOG_DIR, "logs");
29
30
  /**
@@ -52,7 +53,7 @@ const LOGS_SUBDIR = path.join(LOG_DIR, "logs");
52
53
  export function getManagedLogFiles(dir = LOG_DIR) {
53
54
  const names = new Set();
54
55
  for (const absPath of getRegisteredLogFiles()) {
55
- if (path.dirname(absPath) === dir) {
56
+ if (pathsEqual(path.dirname(absPath), dir)) {
56
57
  names.add(path.basename(absPath));
57
58
  }
58
59
  }
@@ -26,7 +26,7 @@
26
26
  * snapshot's `workspaceDiagnosticsSupport.mode` (from
27
27
  * `detectWorkspaceDiagnosticsSupport`, cached at `initialize`) and combines it
28
28
  * with the `silentOnClean` marker on that server's `DiagnosticStrategy`
29
- * (`server-strategies.ts`) — the same per-server behavioral-knowledge table
29
+ * (`wait-policy/strategies.ts`) — the same per-server behavioral-knowledge table
30
30
  * the rest of the LSP layer already uses. A server with no live snapshot yet,
31
31
  * or whose mode isn't `push-only`, or that isn't marked `silentOnClean`, is
32
32
  * NOT tier-3 — the caller keeps today's full in-lane wait. Fail-safe is