@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -0,0 +1,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
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * On-demand V8 heap-snapshot writer — the `PI_LENS_DEBUG_HEAP` sibling that
3
+ * `clients/memory-sampler.ts`'s docstring names as the SEPARATE, explicitly
4
+ * opt-in mechanism for #1126's acceptance criterion ("a heap snapshot of a
5
+ * >1 GB instance identifying the dominant retainers"). The periodic
6
+ * `memory_sample` latency.log line answers "how many bytes are resident right
7
+ * now, broken down by subsystem, over time"; this answers the follow-up
8
+ * question it cannot — "WHICH objects actually retain those bytes" — by writing
9
+ * a `.heapsnapshot` an operator opens in Chrome DevTools › Memory (or clinic /
10
+ * heapdump tooling). It is the retainer-attribution half of #1126's memory
11
+ * observability, deliberately kept out of the always-on sampler because a heap
12
+ * snapshot is expensive (see COST below).
13
+ *
14
+ * `PI_LENS_DEBUG_HEAP=1` (read ONCE at module load, exactly like
15
+ * `debug-handles.ts` — a flag baked in at startup, never toggled mid-session):
16
+ * - OFF (default): every export below is a no-op that returns immediately on
17
+ * the flag check. No file is written, no `node:v8` snapshot work happens,
18
+ * no ndjson writer is constructed — zero cost on any path. The one caller,
19
+ * the on-demand `/lens-health` diagnostics command, guards on
20
+ * `isDebugHeapEnabled()` first, so an unset flag costs a single boolean
21
+ * read. This is NEVER wired to a hot hook, a timer, or a lifecycle event.
22
+ * - ON: `writeHeapSnapshotNow(label)` calls `v8.writeHeapSnapshot()` to
23
+ * `~/.pi-lens/heap-<pid>-<iso>.heapsnapshot` and appends ONE ndjson
24
+ * breadcrumb (path, pid, `rssBytes`, `durationMs`) to `heap-snapshots.log`
25
+ * via the standard size-bounded `createNdjsonLogger` family (same rotation
26
+ * bounds every other pi-lens log gets, #1101-era registry). The breadcrumb
27
+ * lets latency.log's `memory_sample` trajectory and the on-disk snapshot
28
+ * files cross-reference each other after the fact.
29
+ *
30
+ * COST: `v8.writeHeapSnapshot()` is a SYNCHRONOUS, multi-hundred-MB, multi-
31
+ * second operation that pauses the whole process — which is exactly why it is
32
+ * (a) strictly opt-in behind `PI_LENS_DEBUG_HEAP`, and (b) only ever invoked on
33
+ * explicit operator demand via `/lens-health`, never on an automatic path.
34
+ * Auto-capturing a snapshot when a `memory_sample` crosses an RSS threshold is
35
+ * a DELIBERATELY-DEFERRED follow-up (see #1126) precisely because a threshold
36
+ * trigger would reintroduce that multi-second pause onto an automatic path.
37
+ *
38
+ * Bounded axis (AGENTS.md recurring-defect shape 9 — "a resource bounded on one
39
+ * axis while it grows on another"): the `.heapsnapshot` FILES are the growing
40
+ * axis — each is large, and repeated `/lens-health` invocations accumulate them
41
+ * — and unlike the breadcrumb log they are NOT ndjson-rotated. So every
42
+ * `writeHeapSnapshotNow()` prunes the snapshot directory down to the newest
43
+ * `SNAPSHOT_RETENTION` files after writing. Keeping the NEWEST N (not pinning
44
+ * the earliest, the opposite of `debug-handles.ts`'s creation-site tracker) is
45
+ * the correct policy HERE: an operator triggers each snapshot deliberately to
46
+ * capture the CURRENT grown state of a long-lived instance, so the latest
47
+ * snapshots ARE the evidence — there is no "leak hides among the earliest
48
+ * handles" ordering to protect, as there is for the handle tracer.
49
+ */
50
+ import * as fs from "node:fs";
51
+ import * as path from "node:path";
52
+ import * as v8 from "node:v8";
53
+ import { getGlobalPiLensDir } from "./file-utils.js";
54
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
55
+ import { createNdjsonLogger } from "./ndjson-logger.js";
56
+ /** Read once at module load — see module docstring. Not re-read per call. */
57
+ const DEBUG_HEAP_ENABLED = process.env.PI_LENS_DEBUG_HEAP === "1";
58
+ /** Newest-N `.heapsnapshot` files kept in the pi-lens dir; older ones are
59
+ * pruned after every write. See the module docstring's shape-9 note for why
60
+ * newest-kept (not earliest-pinned) is the right retention policy here. */
61
+ export const SNAPSHOT_RETENTION = 3;
62
+ const SNAPSHOT_PREFIX = "heap-";
63
+ const SNAPSHOT_SUFFIX = ".heapsnapshot";
64
+ const HEAP_SNAPSHOT_LOG_FILE = path.join(getGlobalPiLensDir(), "heap-snapshots.log");
65
+ // Only ever constructed when the flag is on — the entire cost of this module
66
+ // when unset is the one boolean check at the top of each export below.
67
+ const writer = DEBUG_HEAP_ENABLED
68
+ ? createNdjsonLogger({
69
+ filePath: HEAP_SNAPSHOT_LOG_FILE,
70
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
71
+ })
72
+ : null;
73
+ /** True iff `PI_LENS_DEBUG_HEAP=1` was set when this module first loaded. */
74
+ export function isDebugHeapEnabled() {
75
+ return DEBUG_HEAP_ENABLED;
76
+ }
77
+ export function getHeapSnapshotLogPath() {
78
+ return HEAP_SNAPSHOT_LOG_FILE;
79
+ }
80
+ /** Resolve once all enqueued breadcrumb writes are on disk (tests). */
81
+ export function flushHeapSnapshotLog() {
82
+ return writer ? writer.flush() : Promise.resolve();
83
+ }
84
+ /**
85
+ * PURE: build a filesystem-safe snapshot filename. The ISO timestamp's `:`/`.`
86
+ * are illegal in Windows paths, so they are folded to `-` (this repo develops
87
+ * on Windows; see AGENTS.md's OS-agnostic rule). Exported for tests.
88
+ */
89
+ export function snapshotFileName(pid, isoTs) {
90
+ return `${SNAPSHOT_PREFIX}${pid}-${isoTs.replace(/[:.]/g, "-")}${SNAPSHOT_SUFFIX}`;
91
+ }
92
+ /**
93
+ * Prune the snapshot directory to the newest `keep` `.heapsnapshot` files,
94
+ * deleting older ones (bounds the growing on-disk axis — see the module
95
+ * docstring). Best-effort: unreadable dir / unstattable / undeletable files are
96
+ * skipped, never thrown. Returns the paths actually removed (for tests).
97
+ */
98
+ export function pruneOldSnapshots(dir, keep = SNAPSHOT_RETENTION) {
99
+ let names;
100
+ try {
101
+ names = fs.readdirSync(dir);
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ const snapshots = names
107
+ .filter((name) => name.startsWith(SNAPSHOT_PREFIX) && name.endsWith(SNAPSHOT_SUFFIX))
108
+ .map((name) => {
109
+ const full = path.join(dir, name);
110
+ let mtimeMs = 0;
111
+ try {
112
+ mtimeMs = fs.statSync(full).mtimeMs;
113
+ }
114
+ catch {
115
+ // unstattable (racing delete): sorts oldest, pruned first
116
+ }
117
+ return { full, mtimeMs };
118
+ })
119
+ .sort((a, b) => b.mtimeMs - a.mtimeMs); // newest first
120
+ const removed = [];
121
+ for (const stale of snapshots.slice(Math.max(0, keep))) {
122
+ try {
123
+ fs.rmSync(stale.full, { force: true });
124
+ removed.push(stale.full);
125
+ }
126
+ catch {
127
+ // best-effort — a failed prune must never break the caller
128
+ }
129
+ }
130
+ return removed;
131
+ }
132
+ /**
133
+ * Write one heap snapshot to `~/.pi-lens/`, append a breadcrumb line, and prune
134
+ * to the newest `SNAPSHOT_RETENTION` files. A pure no-op returning `null` — no
135
+ * file, no allocation past the flag check — when `PI_LENS_DEBUG_HEAP` was unset
136
+ * at startup. `label` records the trigger (currently only `"lens_health"`).
137
+ */
138
+ export function writeHeapSnapshotNow(label) {
139
+ if (!DEBUG_HEAP_ENABLED)
140
+ return null;
141
+ const dir = getGlobalPiLensDir();
142
+ try {
143
+ fs.mkdirSync(dir, { recursive: true });
144
+ }
145
+ catch {
146
+ // dir already exists / unwritable — writeHeapSnapshot will surface it
147
+ }
148
+ const ts = new Date().toISOString();
149
+ const target = path.join(dir, snapshotFileName(process.pid, ts));
150
+ const rssBytes = process.memoryUsage().rss;
151
+ const start = Date.now();
152
+ let written;
153
+ try {
154
+ // Returns the filename it actually wrote to.
155
+ written = v8.writeHeapSnapshot(target);
156
+ }
157
+ catch {
158
+ // A snapshot can fail under memory pressure — the very condition it is
159
+ // most wanted in. Best-effort: report failure by returning null rather
160
+ // than throwing into the diagnostics command.
161
+ return null;
162
+ }
163
+ const durationMs = Date.now() - start;
164
+ pruneOldSnapshots(dir);
165
+ writer?.log({ ts, label, path: written, pid: process.pid, rssBytes, durationMs });
166
+ return { path: written, rssBytes, durationMs };
167
+ }
@@ -0,0 +1,153 @@
1
+ /** Bounded, process-local telemetry for behavior degraded during one session. */
2
+ import { logExtension } from "./extension-log.js";
3
+ const ENTRIES_PER_KIND = 20;
4
+ const MAX_DISTINCT_KINDS = 32;
5
+ const OVERFLOW_KIND = "other";
6
+ const groups = new Map();
7
+ const onceKeys = new Set();
8
+ const tallies = new Map();
9
+ export function recordDegradation(record) {
10
+ try {
11
+ const kind = boundedKind(record.kind);
12
+ const subject = truncateForLedger(record.subject);
13
+ const reason = truncateForLedger(record.reason);
14
+ let group = groups.get(kind);
15
+ if (!group) {
16
+ group = { count: 0, entries: [] };
17
+ groups.set(kind, group);
18
+ }
19
+ group.count += 1;
20
+ // Bounded at RECORD time (#1366 review): reasons carry arbitrary error
21
+ // text; a 10KB message must never become a 10KB health line or a 10KB
22
+ // retained string.
23
+ group.entries.push({ subject, reason });
24
+ if (group.entries.length > ENTRIES_PER_KIND)
25
+ group.entries.shift();
26
+ }
27
+ catch (error) {
28
+ debugLedgerFailure("record", error);
29
+ // Telemetry must never break the observed path.
30
+ }
31
+ }
32
+ /** Record at most once per kind/subject during the current session. */
33
+ export function recordDegradationOnce(record) {
34
+ try {
35
+ const kind = boundedKind(record.kind);
36
+ const subject = truncateForLedger(record.subject);
37
+ const key = `${kind}\0${subject}`;
38
+ if (onceKeys.has(key))
39
+ return;
40
+ onceKeys.add(key);
41
+ recordDegradation({ kind, subject, reason: record.reason });
42
+ }
43
+ catch (error) {
44
+ debugLedgerFailure("record-once", error);
45
+ // Telemetry must never break the observed path.
46
+ }
47
+ }
48
+ /**
49
+ * Count a repeated degradation while retaining one latest-reason entry per
50
+ * kind/subject. The group count remains the exact event total.
51
+ */
52
+ export function incrementDegradationCount(record) {
53
+ try {
54
+ const kind = boundedKind(record.kind);
55
+ const subject = truncateForLedger(record.subject);
56
+ const reason = truncateForLedger(record.reason);
57
+ const key = `${kind}\0${subject}`;
58
+ const count = (tallies.get(key) ?? 0) + 1;
59
+ tallies.set(key, count);
60
+ let group = groups.get(kind);
61
+ if (!group) {
62
+ group = { count: 0, entries: [] };
63
+ groups.set(kind, group);
64
+ }
65
+ group.count += 1;
66
+ const entry = { subject, reason: truncateForLedger(`${reason} (count: ${count})`) };
67
+ const existing = group.entries.findIndex((candidate) => candidate.subject === subject);
68
+ if (existing >= 0)
69
+ group.entries.splice(existing, 1);
70
+ group.entries.push(entry);
71
+ if (group.entries.length > ENTRIES_PER_KIND)
72
+ group.entries.shift();
73
+ }
74
+ catch (error) {
75
+ debugLedgerFailure("increment", error);
76
+ // Telemetry must never break the observed path.
77
+ }
78
+ }
79
+ /** Detached snapshot, grouped in first-seen kind order. */
80
+ const LEDGER_FIELD_MAX = 200;
81
+ function normalizeForLedger(value) {
82
+ return String(value ?? "unknown");
83
+ }
84
+ function boundedKind(value) {
85
+ const kind = truncateForLedger(value);
86
+ if (groups.has(kind) || kind === OVERFLOW_KIND)
87
+ return kind;
88
+ // Keep one slot available for all kinds beyond the cardinality bound.
89
+ return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
90
+ }
91
+ function truncateForLedger(value) {
92
+ const text = normalizeForLedger(value);
93
+ return text.length > LEDGER_FIELD_MAX
94
+ ? `${text.slice(0, LEDGER_FIELD_MAX)}…`
95
+ : text;
96
+ }
97
+ export function getDegradationSummary() {
98
+ return [...groups.entries()].map(([kind, group]) => ({
99
+ kind,
100
+ count: group.count,
101
+ droppedCount: group.count - group.entries.length,
102
+ latestReasons: group.entries.map((entry) => ({ ...entry })),
103
+ }));
104
+ }
105
+ function isRenderableSummary(value) {
106
+ if (!Array.isArray(value))
107
+ return false;
108
+ return value.every((group) => {
109
+ if (group === null || typeof group !== "object")
110
+ return false;
111
+ const candidate = group;
112
+ return (typeof candidate.kind === "string" &&
113
+ typeof candidate.count === "number" &&
114
+ Array.isArray(candidate.latestReasons) &&
115
+ candidate.latestReasons.every((entry) => entry !== null &&
116
+ typeof entry === "object" &&
117
+ typeof entry.subject === "string" &&
118
+ typeof entry.reason === "string"));
119
+ });
120
+ }
121
+ export function renderDegradationLines(summary = getDegradationSummary()) {
122
+ if (!isRenderableSummary(summary))
123
+ return [];
124
+ if (summary.length === 0)
125
+ return [];
126
+ return [
127
+ "Degradations:",
128
+ ...summary.map((group) => {
129
+ const latest = group.latestReasons.at(-1);
130
+ return ` ⚠ ${group.kind}: ${group.count}${latest ? ` — ${latest.subject}: ${latest.reason}` : ""}`;
131
+ }),
132
+ ];
133
+ }
134
+ function debugLedgerFailure(operation, error) {
135
+ try {
136
+ logExtension({
137
+ subsystem: "degradation-ledger",
138
+ level: "debug",
139
+ message: `${operation} failed: ${error instanceof Error ? error.message : String(error)}`,
140
+ });
141
+ }
142
+ catch {
143
+ // Debug logging must not compromise the non-fatal telemetry contract.
144
+ }
145
+ }
146
+ /** Session-boundary/test reset. */
147
+ export function resetDegradationLedger() {
148
+ groups.clear();
149
+ onceKeys.clear();
150
+ tallies.clear();
151
+ }
152
+ export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
153
+ export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;