@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared plumbing for best-effort, fire-and-forget child-process spawns
3
+ * (shape 4 of the recurring-defect catalog in AGENTS.md: "a timer / promise /
4
+ * worker / child that outlives its one-shot settle"). Extracted from the
5
+ * orphan reaper's `unrefReaperChild`/`spawnCollectStdout` (#1153/#1160) into a
6
+ * shared, dependency-free module so every one-shot
7
+ * `spawn(..., { stdio: ["ignore","pipe",...] })` call site in the codebase —
8
+ * the reaper's enumeration/kill spawns AND the resource sampler's Windows
9
+ * CIM/powershell spawns (#1155) — uses the exact same unref+collect shape
10
+ * instead of re-deriving it (also closes the #1155 PR's SonarCloud
11
+ * new-code-duplication finding: two near-identical spawn→collect-stdout
12
+ * blocks in `clients/resource-sampler.ts` collapsed to one shared helper).
13
+ *
14
+ * Deliberately has NO imports beyond `node:child_process` types, so both
15
+ * `clients/instance-reaper.ts` and `clients/resource-sampler.ts` (which
16
+ * `clients/safe-spawn.ts` itself depends on) can import this without risking
17
+ * a circular-import chain.
18
+ */
19
+ import { spawn as nodeSpawn } from "node:child_process";
20
+ /**
21
+ * Detach a best-effort, fire-and-forget child process from the event loop.
22
+ *
23
+ * A piped, `data`-listener-attached stdout/stderr/stdin stream keeps the libuv
24
+ * loop REFERENCED even after `child.unref()` — the child handle and every
25
+ * live stdio stream must all be unref'd, or a settled one-shot process (e.g.
26
+ * `pi --print`) cannot exit until the child `close`s. Unref only means "do
27
+ * not keep the process alive FOR this best-effort work": in an interactive/
28
+ * long-lived session the loop stays referenced for other reasons, so the
29
+ * child's `data`/`close` events still fire normally and callers still collect
30
+ * output/usage; only a genuinely-settled one-shot is allowed to exit without
31
+ * waiting for it. Never throws.
32
+ */
33
+ export function unrefChildAndPipes(child) {
34
+ try {
35
+ child.unref();
36
+ // Child stdio pipes are `net.Socket`s at runtime (which expose `unref`),
37
+ // but are typed as `Readable`/`Writable` (which do not) — cast to the
38
+ // optional-`unref` shape and guard, so an un-piped ("ignore") stream is a
39
+ // no-op rather than a crash.
40
+ for (const stream of [child.stdout, child.stderr, child.stdin]) {
41
+ stream?.unref?.();
42
+ }
43
+ }
44
+ catch {
45
+ // best-effort — unref must never throw out of a fire-and-forget spawn
46
+ }
47
+ }
48
+ /**
49
+ * Spawn a best-effort, fire-and-forget child, accumulate its full stdout, and
50
+ * resolve with the collected text (empty string on a synchronous spawn
51
+ * failure or an `error` event). Consolidates the spawn → unref →
52
+ * pipe-stdout → `close` plumbing shared by every one-shot OS-process-table
53
+ * query in the codebase — each caller supplies only its command/args/options
54
+ * and does its own output parse. The child + its stdio pipes are `unref`'d
55
+ * here (via `unrefChildAndPipes`) so a settled one-shot `pi --print` process
56
+ * can exit without waiting, and both the unref and the collect plumbing live
57
+ * in exactly ONE place rather than being re-derived at each spawn site.
58
+ * Never rejects — any failure resolves to `""`, which every caller's parse
59
+ * turns into an empty/absent result (the best-effort contract every caller
60
+ * here already has).
61
+ */
62
+ export function spawnCollectStdout(command, args, options) {
63
+ return new Promise((resolve) => {
64
+ let settled = false;
65
+ const settle = (value) => {
66
+ if (settled)
67
+ return;
68
+ settled = true;
69
+ resolve(value);
70
+ };
71
+ try {
72
+ const child = nodeSpawn(command, args, options);
73
+ unrefChildAndPipes(child);
74
+ let out = "";
75
+ child.stdout?.on("data", (chunk) => {
76
+ out += chunk.toString();
77
+ });
78
+ child.once("error", () => settle(""));
79
+ child.once("close", () => settle(out));
80
+ }
81
+ catch {
82
+ settle("");
83
+ }
84
+ });
85
+ }
@@ -13,18 +13,25 @@
13
13
  import * as fs from "node:fs";
14
14
  import * as path from "node:path";
15
15
  import { getProjectDataDir } from "./file-utils.js";
16
+ import { writeFileAtomic } from "./atomic-write.js";
17
+ import { parseSymbolKey } from "./call-graph.js";
18
+ import { detectFileRole } from "./file-role.js";
19
+ import { isExternalOrVendorFile } from "./path-utils.js";
20
+ import { isBuildArtifact } from "./source-filter.js";
16
21
  // ── Constants ─────────────────────────────────────────────────────────────────
17
- const DEFAULT_TOKEN_BUDGET = 1500;
22
+ export const DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET = 1500;
18
23
  const MAX_CALLS_PER_SYMBOL = 10;
19
24
  const MIN_IN_DEGREE = 0.5; // skip symbols with low centrality (avoids noise)
25
+ /** Persisted model schema version. Bump when the persisted shape changes. */
26
+ export const CODEBASE_MODEL_VERSION = 1;
20
27
  // ── Builder ───────────────────────────────────────────────────────────────────
21
28
  function inferKind(symbolKey) {
22
- const name = symbolKey.split(":").pop() ?? "";
23
- if (/^[A-Z]/.test(name))
29
+ const parsed = parseSymbolKey(symbolKey);
30
+ if (parsed.kind === "class")
24
31
  return "class";
25
- if (name.includes("."))
32
+ if (parsed.kind === "method" || parsed.symbolName?.includes("."))
26
33
  return "method";
27
- return "function";
34
+ return /^[A-Z]/.test(parsed.symbolName ?? "") ? "class" : "function";
28
35
  }
29
36
  function estimateTokens(entry) {
30
37
  const text = [
@@ -45,7 +52,15 @@ function estimateTokens(entry) {
45
52
  * @param cwd Project root — used to compute relative file paths.
46
53
  * @param budget Maximum total token budget (default 1500).
47
54
  */
48
- export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
55
+ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, identity) {
56
+ // Standalone callers may build an unpersisted projection without the
57
+ // review-graph coordinator. Persisted/session models pass the canonical
58
+ // review-graph identity explicitly; this fallback is only a local call-graph
59
+ // identity for builder-only use.
60
+ const modelIdentity = identity ?? {
61
+ reviewGraphVersion: graph.builtAt || "call-graph-unknown",
62
+ reviewGraphSignature: graph.builtAt || "call-graph-unknown",
63
+ };
49
64
  // Sort all callee keys by in-degree descending
50
65
  const ranked = [...graph.inDegree.entries()]
51
66
  .filter(([, score]) => score >= MIN_IN_DEGREE)
@@ -56,16 +71,16 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
56
71
  for (const [calleeKey, inDegree] of ranked) {
57
72
  if (totalTokens >= budget)
58
73
  break;
59
- const parts = calleeKey.split(":");
60
- const name = parts.pop() ?? calleeKey;
61
- const filePath = parts.join(":");
62
- // Skip if file is in test/node_modules/generated directories
63
- if (filePath.includes("node_modules") ||
64
- filePath.includes(".test.") ||
65
- filePath.includes(".spec.") ||
66
- filePath.includes("/__tests__/") ||
67
- filePath.includes("/dist/") ||
68
- filePath.includes("/generated/")) {
74
+ const parsedCallee = parseSymbolKey(calleeKey);
75
+ const name = parsedCallee.symbolName ?? calleeKey;
76
+ const filePath = parsedCallee.filePath;
77
+ // Keep this projection aligned with the shared file-role policy. The
78
+ // call graph is derived from the canonical review graph, so the model
79
+ // carries that graph's identity rather than inventing a second freshness
80
+ // policy.
81
+ const fileRole = detectFileRole(filePath);
82
+ if (fileRole === "test" || fileRole === "generated" ||
83
+ isExternalOrVendorFile(filePath, cwd) || isBuildArtifact(filePath)) {
69
84
  continue;
70
85
  }
71
86
  // Deduplicate by name when the same function appears in multiple files
@@ -73,11 +88,11 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
73
88
  continue;
74
89
  seenNames.add(name);
75
90
  const calls = [...(graph.callees.get(calleeKey) ?? new Set())]
76
- .map((k) => k.split(":").pop() ?? k)
91
+ .map((k) => parseSymbolKey(k).symbolName ?? k)
77
92
  .filter(Boolean)
78
93
  .slice(0, MAX_CALLS_PER_SYMBOL);
79
94
  const calledBy = [...(graph.callers.get(calleeKey) ?? new Set())]
80
- .map((k) => k.split(":").pop() ?? k)
95
+ .map((k) => parseSymbolKey(k).symbolName ?? k)
81
96
  .filter((n) => !n.startsWith("file:"))
82
97
  .slice(0, MAX_CALLS_PER_SYMBOL);
83
98
  const file = filePath
@@ -98,9 +113,12 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
98
113
  totalTokens += tokens;
99
114
  }
100
115
  const allFiles = new Set([...graph.callers.keys(), ...graph.callees.keys()]
101
- .map((k) => k.split(":").slice(0, -1).join(":"))
116
+ .map((k) => parseSymbolKey(k).filePath)
102
117
  .filter(Boolean));
103
118
  return {
119
+ version: CODEBASE_MODEL_VERSION,
120
+ reviewGraphVersion: modelIdentity.reviewGraphVersion,
121
+ reviewGraphSignature: modelIdentity.reviewGraphSignature,
104
122
  generatedAt: new Date().toISOString(),
105
123
  totalSymbols: graph.inDegree.size,
106
124
  totalFiles: allFiles.size,
@@ -119,16 +137,27 @@ export function saveCodebaseModel(cwd, model) {
119
137
  const cacheFile = cacheFilePath(cwd);
120
138
  try {
121
139
  fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
122
- fs.writeFileSync(cacheFile, JSON.stringify(model), "utf-8");
123
- fs.writeFileSync(metaFilePath(cwd), JSON.stringify({ savedAt: new Date().toISOString(), entryCount: model.entries.length, totalTokens: model.totalTokens }), "utf-8");
140
+ writeFileAtomic(cacheFile, JSON.stringify(model));
141
+ writeFileAtomic(metaFilePath(cwd), JSON.stringify({ savedAt: new Date().toISOString(), entryCount: model.entries.length, totalTokens: model.totalTokens }));
124
142
  }
125
143
  catch {
126
144
  // Non-fatal — next session rebuilds.
127
145
  }
128
146
  }
129
- export function loadCodebaseModel(cwd) {
147
+ /** Load only a model matching the canonical identity it was derived from. */
148
+ export function loadCodebaseModel(cwd, expectedIdentity) {
130
149
  try {
131
- return JSON.parse(fs.readFileSync(cacheFilePath(cwd), "utf-8"));
150
+ const raw = JSON.parse(fs.readFileSync(cacheFilePath(cwd), "utf-8"));
151
+ if (raw.version !== CODEBASE_MODEL_VERSION ||
152
+ typeof raw.reviewGraphVersion !== "string" || raw.reviewGraphVersion.length === 0 ||
153
+ typeof raw.reviewGraphSignature !== "string" || raw.reviewGraphSignature.length === 0 ||
154
+ typeof raw.generatedAt !== "string" ||
155
+ !Array.isArray(raw.entries))
156
+ return undefined;
157
+ if (raw.reviewGraphVersion !== expectedIdentity.reviewGraphVersion ||
158
+ raw.reviewGraphSignature !== expectedIdentity.reviewGraphSignature)
159
+ return undefined;
160
+ return raw;
132
161
  }
133
162
  catch {
134
163
  return undefined;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared test-role predicate for COLLATERAL cascade/impact surfaces (#1080).
3
+ *
4
+ * The review graph is already tests-free (`review-graph/builder.ts` applies
5
+ * `detectFileRole(file) !== "test"` at its walk/incremental chokepoints), but
6
+ * several collateral surfaces re-derive neighbors from OTHER sources that never
7
+ * saw that filter — LSP reference expansion, module-level downstream expansion,
8
+ * the reverse-dependency index, and the passive-diagnostics fallback — so an
9
+ * unignored `*.test.*` / `tests/` file can still leak into cascade neighbor
10
+ * output, the formatted impact header/counts, the turn-end call-graph advisory,
11
+ * and the persisted call-graph-impact diagnostics.
12
+ *
13
+ * This composes the SINGLE existing structural classifier (`detectFileRole`) —
14
+ * no second matcher, no private test-filename list, no duplicated patterns. The
15
+ * project ignore matcher (`getProjectIgnoreMatcher`) is applied separately and
16
+ * unchanged at each surface; this predicate only adds the test-ROLE half of the
17
+ * display policy the review graph already enforces.
18
+ *
19
+ * Honesty (#1080 acceptance criteria):
20
+ * - A KNOWN `"test"` role is filtered from the collateral surface.
21
+ * - If role classification cannot be obtained (an unexpected throw), the
22
+ * candidate is RETAINED (`false`) rather than dropped — filtering must never
23
+ * invent a heuristic or silently drop a potentially actionable file, and must
24
+ * never convert an incomplete/indeterminate computation into a clean result.
25
+ */
26
+ import { detectFileRole } from "./file-role.js";
27
+ /**
28
+ * True when `filePath` is a KNOWN test-role file that must not surface as a
29
+ * collateral cascade/impact neighbor. Path-based only (no content needed for
30
+ * the test-role branch of `detectFileRole`). Fail-open: any classifier error
31
+ * returns `false` (retain the candidate).
32
+ */
33
+ export function isTestRoleCollateral(filePath) {
34
+ try {
35
+ return detectFileRole(filePath) === "test";
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
@@ -10,6 +10,7 @@
10
10
  * maintainability index (Halstead-free), LOC/comments, code entropy, and AI-slop
11
11
  * indicators. These are silent metrics surfaced in the session summary.
12
12
  */
13
+ import { createSubsystemLogger } from "./extension-log.js";
13
14
  import * as fs from "node:fs";
14
15
  import * as path from "node:path";
15
16
  import { firstChildOfType, withTreeSitterRoot, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
@@ -371,7 +372,7 @@ export class ComplexityClient {
371
372
  log;
372
373
  constructor(verbose = false) {
373
374
  this.log = verbose
374
- ? (msg) => console.error(`[complexity] ${msg}`)
375
+ ? createSubsystemLogger("complexity")
375
376
  : () => { };
376
377
  }
377
378
  /** True if the file's grammar has a complexity node mapping. */
@@ -0,0 +1,14 @@
1
+ import { performance } from "node:perf_hooks";
2
+ // Capture this before the guard's other module initialization so host boot is
3
+ // not charged to extension evaluation (#1374).
4
+ export const PI_LENS_EVAL_STARTED_MS = performance.now();
5
+ // #1333: install the console guard as an import side-effect so it runs before
6
+ // any other module's initialization code. Installing inside the extension
7
+ // factory is too late — by the time the factory runs, every static/transitive
8
+ // import has already evaluated, so a dependency writing to console during
9
+ // module init would still hit the TUI raw (#1338 review finding). This module
10
+ // MUST remain the first import of index.ts; the enforcement test pins that.
11
+ // installConsoleGuard() itself is idempotent and no-ops under test mode and
12
+ // PI_LENS_CONSOLE_GUARD=0.
13
+ import { installConsoleGuard } from "./extension-log.js";
14
+ installConsoleGuard();
@@ -0,0 +1,42 @@
1
+ /** Monotonic cooperative-work budgeting for event-loop-sensitive bulk work. */
2
+ export function createDeadline(budgetMs) {
3
+ const boundedBudget = Math.max(0, budgetMs);
4
+ let startedAt = performance.now();
5
+ return {
6
+ expired: () => performance.now() - startedAt >= boundedBudget,
7
+ reset: () => {
8
+ startedAt = performance.now();
9
+ },
10
+ };
11
+ }
12
+ export async function yieldIfOverBudget(deadline) {
13
+ if (!deadline.expired())
14
+ return false;
15
+ await new Promise((resolve) => setImmediate(resolve));
16
+ deadline.reset();
17
+ return true;
18
+ }
19
+ export async function forEachCooperatively(items, fn, options) {
20
+ const deadline = createDeadline(options.budgetMs);
21
+ const assertContinuing = () => {
22
+ if (options.shouldContinue?.() === false) {
23
+ throw new Error(options.abortMessage ?? "cooperative work superseded");
24
+ }
25
+ };
26
+ let index = 0;
27
+ for (const item of items) {
28
+ // Check before each unit: this per-unit check is what bounds abort
29
+ // latency (one work unit, not an iteration checkpoint). The post-yield
30
+ // check below only matters after the final unit's yield, so superseded
31
+ // work is reported aborted rather than completed.
32
+ assertContinuing();
33
+ const result = fn(item, index++);
34
+ if (result !== undefined &&
35
+ typeof result.then === "function") {
36
+ await result;
37
+ }
38
+ if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
39
+ assertContinuing();
40
+ }
41
+ }
42
+ }
@@ -11,6 +11,7 @@
11
11
  * Phase 1 ships Python via `vulture`. Future phases add Go/Rust/etc. by
12
12
  * implementing DeadCodeClient and adding to getDeadCodeClients().
13
13
  */
14
+ import { createSubsystemLogger } from "./extension-log.js";
14
15
  import * as path from "node:path";
15
16
  import { findNearestMarkerRoot } from "./path-utils.js";
16
17
  import { safeSpawnAsync } from "./safe-spawn.js";
@@ -92,7 +93,7 @@ export class PythonDeadCodeClient {
92
93
  log;
93
94
  constructor(verbose = false) {
94
95
  this.log = verbose
95
- ? (msg) => console.error(`[dead-code:python] ${msg}`)
96
+ ? createSubsystemLogger("dead-code:python")
96
97
  : () => { };
97
98
  }
98
99
  get minConfidence() {
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Handle-origin tracer, institutionalized from the hand-rolled `async_hooks`
3
+ * instrumentation the #1097 investigation used to find a leaked, ref'd
4
+ * `setTimeout` keeping a `--print --no-session` process alive after
5
+ * `agent_settled` (root-caused/fixed in #1110). The #1097 reporter explicitly
6
+ * asked for a built-in active-resource dump (#1123 item 4) so the next
7
+ * "process won't exit" report is a one-line diagnostic instead of a fresh
8
+ * multi-hour expedition.
9
+ *
10
+ * `PI_LENS_DEBUG_HANDLES=1` (read ONCE at module load — i.e. must be set
11
+ * before pi-lens starts, not toggled mid-session):
12
+ * - OFF (default): every export below is a no-op that returns immediately
13
+ * on the flag check — no writer constructed, no async_hooks hook
14
+ * installed, zero allocations on any hot path. `dumpActiveHandles` is
15
+ * called from live lifecycle hooks (index.ts `agent_settled` /
16
+ * `session_shutdown`), so this module must cost nothing when unset.
17
+ * - ON: (a) `dumpActiveHandles(label)` calls `process.getActiveResourcesInfo()`,
18
+ * tallies counts by resource type, and writes one ndjson line to
19
+ * `~/.pi-lens/debug-handles.log` via the standard `createNdjsonLogger`
20
+ * family (size-bounded, log-cleanup registered — the same rotation
21
+ * bounds every other pi-lens log gets, #1101-era registry via
22
+ * clients/ndjson-logger.ts's self-registration + clients/log-cleanup.ts).
23
+ * (b) an `async_hooks` creation-site tracker, installed at module load
24
+ * ONLY when the flag is on, records a trimmed `Error().stack` per
25
+ * tracked resource id for timer/socket/handle async-hook types and
26
+ * removes the entry on `destroy`. This is a real per-resource-creation
27
+ * cost — `async_hooks` hooks run on every init/destroy in the process —
28
+ * so it is strictly opt-in and documented here as a debug-only expense,
29
+ * never installed unless the flag was on before this module first loads.
30
+ *
31
+ * Bounded tracker (the one-axis-rule, AGENTS.md "resource bounded along one
32
+ * axis but unbounded along another"): the creation-site map is capped at
33
+ * `TRACKER_MAX_ENTRIES` before adding a new entry past the cap — never
34
+ * unbounded growth even in a pathological timer-storm session. Eviction
35
+ * protects the first `TRACKER_PROTECTED_COUNT` insertion-order entries and
36
+ * evicts the oldest entry OUTSIDE that protected zone instead: a #1097-style
37
+ * leaked handle is typically among the EARLIEST created in a session (it's
38
+ * the one nothing ever cleaned up), so a naive drop-oldest policy would let a
39
+ * later burst past the cap evict exactly the evidence this tracer exists to
40
+ * capture, silently. `evictedCount` (a running total, always present on the
41
+ * dump entry once the tracker is installed) makes any eviction — protected
42
+ * or not — an explicit, visible fact instead of a silent gap in attribution.
43
+ */
44
+ import * as asyncHooks from "node:async_hooks";
45
+ import * as path from "node:path";
46
+ import { getGlobalPiLensDir } from "./file-utils.js";
47
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
48
+ import { createNdjsonLogger } from "./ndjson-logger.js";
49
+ /** Read once at module load — see module docstring. Not re-read per call. */
50
+ const DEBUG_HANDLES_ENABLED = process.env.PI_LENS_DEBUG_HANDLES === "1";
51
+ const DEBUG_HANDLES_LOG_FILE = path.join(getGlobalPiLensDir(), "debug-handles.log");
52
+ /** Cap on tracked in-flight creation sites (bounded past this). */
53
+ export const TRACKER_MAX_ENTRIES = 500;
54
+ /** The first N insertion-order entries are never evicted — see the module
55
+ * docstring's "leaked handles skew early" rationale. Must stay well below
56
+ * `TRACKER_MAX_ENTRIES` so there is always an evictable, unprotected tail. */
57
+ export const TRACKER_PROTECTED_COUNT = 100;
58
+ /** async_hooks `type` strings worth attributing: timers, sockets, process/
59
+ * signal handles — the shapes #1097's hand-rolled tracer actually needed
60
+ * (a `Timeout`). Excludes high-frequency non-handle async resources
61
+ * (`PROMISE`, `FSREQCALLBACK`, ...) that would dominate the map with no
62
+ * "why is the process still alive" signal. */
63
+ const TRACKED_TYPE_RE = /^(Timeout|Immediate|TCPWRAP|TCPSOCKETWRAP|TCPCONNECTWRAP|PIPEWRAP|PIPECONNECTWRAP|UDPWRAP|UDPSENDWRAP|TTYWRAP|ProcessWrap|Signal)/;
64
+ function trimStack(raw) {
65
+ if (!raw)
66
+ return "";
67
+ // Drop the "Error" header line and this module's own init() frame; keep a
68
+ // bounded number of caller frames — enough to identify the call site
69
+ // without unbounded per-entry memory.
70
+ const lines = raw.split("\n").slice(1);
71
+ const callerLines = lines.filter((line) => !line.includes("debug-handles.js") && !line.includes("debug-handles.ts"));
72
+ return callerLines.slice(0, 8).join("\n").slice(0, 2000);
73
+ }
74
+ /** Running total of tracker evictions this process (never reset) — surfaced
75
+ * on every dump entry once the tracker is installed, per the P2 fix: an
76
+ * eviction must always be a visible fact, never a silent attribution gap. */
77
+ let trackedEvictedCount = 0;
78
+ /** Test-only accessor for the running eviction total. */
79
+ export function _evictedCountForTest() {
80
+ return trackedEvictedCount;
81
+ }
82
+ /** Record one tracked resource's creation site. Once the bounded cap is
83
+ * reached, evicts the oldest entry OUTSIDE the first `TRACKER_PROTECTED_COUNT`
84
+ * insertion-order entries (Map iteration order == insertion order) — see the
85
+ * module docstring for why the earliest entries are protected instead of
86
+ * evicted. Shared by the real async_hooks `init` callback and the test-only
87
+ * simulator so the eviction policy has exactly one implementation. */
88
+ function recordTrackedInit(map, asyncId, type) {
89
+ if (!TRACKED_TYPE_RE.test(type))
90
+ return;
91
+ if (map.size >= TRACKER_MAX_ENTRIES) {
92
+ let index = 0;
93
+ let evictKey;
94
+ for (const key of map.keys()) {
95
+ if (index >= TRACKER_PROTECTED_COUNT) {
96
+ evictKey = key;
97
+ break;
98
+ }
99
+ index++;
100
+ }
101
+ if (evictKey !== undefined) {
102
+ map.delete(evictKey);
103
+ trackedEvictedCount++;
104
+ }
105
+ }
106
+ map.set(asyncId, { type, stack: trimStack(new Error().stack) });
107
+ }
108
+ function installHandleTracker() {
109
+ const map = new Map();
110
+ const hook = asyncHooks.createHook({
111
+ init(asyncId, type) {
112
+ recordTrackedInit(map, asyncId, type);
113
+ },
114
+ destroy(asyncId) {
115
+ map.delete(asyncId);
116
+ },
117
+ });
118
+ hook.enable();
119
+ return map;
120
+ }
121
+ // Only ever constructed/installed when the flag is on — the entire cost of
122
+ // this module when unset is this one boolean check per export below.
123
+ const trackedResources = DEBUG_HANDLES_ENABLED ? installHandleTracker() : null;
124
+ const writer = DEBUG_HANDLES_ENABLED
125
+ ? createNdjsonLogger({
126
+ filePath: DEBUG_HANDLES_LOG_FILE,
127
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
128
+ })
129
+ : null;
130
+ /** True iff `PI_LENS_DEBUG_HANDLES=1` was set when this module first loaded. */
131
+ export function isDebugHandlesEnabled() {
132
+ return DEBUG_HANDLES_ENABLED;
133
+ }
134
+ export function getDebugHandlesLogPath() {
135
+ return DEBUG_HANDLES_LOG_FILE;
136
+ }
137
+ /** Resolve once all enqueued debug-handles writes are on disk (tests). */
138
+ export function flushDebugHandlesLog() {
139
+ return writer ? writer.flush() : Promise.resolve();
140
+ }
141
+ function safeGetActiveResourcesInfo() {
142
+ try {
143
+ const fn = process.getActiveResourcesInfo;
144
+ return typeof fn === "function" ? fn.call(process) : [];
145
+ }
146
+ catch {
147
+ return [];
148
+ }
149
+ }
150
+ /** One representative stack per tracked TYPE (not per resource id) — keeps
151
+ * each dump line bounded regardless of how many live entries the capped
152
+ * map currently holds. */
153
+ function summarizeCreationSites(map) {
154
+ const byType = new Map();
155
+ for (const { type, stack } of map.values()) {
156
+ const existing = byType.get(type);
157
+ if (existing) {
158
+ existing.count++;
159
+ }
160
+ else {
161
+ byType.set(type, { type, count: 1, sampleStack: stack });
162
+ }
163
+ }
164
+ return [...byType.values()];
165
+ }
166
+ /**
167
+ * Dump `process.getActiveResourcesInfo()` counts-by-type (and, when the
168
+ * tracker was installed at startup, per-type creation-site attribution) to
169
+ * `debug-handles.log`. A pure no-op — no allocation past the flag check —
170
+ * when `PI_LENS_DEBUG_HANDLES` was unset at startup.
171
+ */
172
+ export function dumpActiveHandles(label) {
173
+ if (!DEBUG_HANDLES_ENABLED || !writer)
174
+ return;
175
+ const resources = safeGetActiveResourcesInfo();
176
+ const counts = {};
177
+ for (const type of resources) {
178
+ counts[type] = (counts[type] ?? 0) + 1;
179
+ }
180
+ const entry = {
181
+ ts: new Date().toISOString(),
182
+ label,
183
+ total: resources.length,
184
+ counts,
185
+ };
186
+ if (trackedResources) {
187
+ entry.creationSites = summarizeCreationSites(trackedResources);
188
+ entry.evictedCount = trackedEvictedCount;
189
+ }
190
+ writer.log(entry);
191
+ }
192
+ /** Test-only view of the tracker's current size (bounded-cap assertions). */
193
+ export function _trackedResourceCountForTest() {
194
+ return trackedResources?.size ?? 0;
195
+ }
196
+ /** Test-only: is `asyncId` still tracked? (protected-vs-evicted assertions). */
197
+ export function _isTrackedForTest(asyncId) {
198
+ return trackedResources?.has(asyncId) ?? false;
199
+ }
200
+ /** Test-only: synthesize an `init` call against the installed tracker without
201
+ * depending on real async resource creation timing. No-op when disabled. */
202
+ export function _simulateTrackedInitForTest(asyncId, type) {
203
+ if (!trackedResources)
204
+ return;
205
+ recordTrackedInit(trackedResources, asyncId, type);
206
+ }