@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,22 @@
1
+ /**
2
+ * True when a tool both failed and produced nothing to parse — it never got as
3
+ * far as linting the file. Callers map this to `skipped`, never `succeeded`.
4
+ *
5
+ * Guarding on `result.error` alone covers only half of it: safe-spawn sets
6
+ * `error` for spawn/timeout/signal failures and resolves a NORMAL exit with no
7
+ * `error` at any status (see `SpawnResult.failure` — "nonzero exit statuses are
8
+ * not spawn failures"). An unknown subcommand, a rejected flag, or a config
9
+ * that fails to load exits non-zero with the message on stderr and an empty
10
+ * stdout, so an `error`-only guard falls through, parses "" into zero
11
+ * diagnostics, and reports a clean file that nothing ever checked.
12
+ *
13
+ * Tools that exit non-zero *because* they found something are unaffected: their
14
+ * findings are on stdout, so the output half of the test is false.
15
+ *
16
+ * `output` defaults to stdout. Runners that parse stderr too pass the exact
17
+ * string they are about to hand the parser, so "nothing to parse" means both
18
+ * streams rather than just one.
19
+ */
20
+ export function spawnFailedWithNoOutput(result, output = result.stdout) {
21
+ return (Boolean(result.error) || result.status !== 0) && !output?.trim();
22
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Shared toolchain-availability lifecycle (#1476 Sonar follow-up).
3
+ *
4
+ * `candidate-probe.ts` factored out the PATH sweep the Go and Rust clients ran,
5
+ * but the LIFECYCLE around it stayed written twice: the transient-aware latch,
6
+ * the in-flight dedupe that keeps concurrent first-time callers to one sweep,
7
+ * the path memo, and the two `availability_decision` records. Two copies of one
8
+ * rule is how #1467's fix missed seven sites, so the rule lives here once and
9
+ * each client contributes only its configuration.
10
+ *
11
+ * Callers keep their own public method names — `isGoAvailableAsync`,
12
+ * `findCargoPathAsync` — because other modules call them; only the body moves.
13
+ */
14
+ import { createAvailabilityLatch, logAvailabilityDecision, } from "./availability-policy.js";
15
+ import { probeAvailabilityCandidates } from "./candidate-probe.js";
16
+ /**
17
+ * Own one toolchain's availability: sweep the platform candidate list, memoize
18
+ * the path that answered, and park the verdict behind the shared latch so a
19
+ * timed-out probe expires instead of latching "the toolchain is not installed"
20
+ * for the life of the process.
21
+ */
22
+ export function createToolchainAvailability(config) {
23
+ const availabilityLatch = createAvailabilityLatch();
24
+ let toolPath = null;
25
+ /** Classification of the candidate sweep, for the retry decision. */
26
+ let sweepSawTransient = false;
27
+ let sweepTransientCause = "probe-timeout";
28
+ let sweepHostStallMs = 0;
29
+ let ensureInFlight = null;
30
+ async function findPath() {
31
+ if (toolPath)
32
+ return toolPath;
33
+ const paths = process.platform === "win32" ? config.windowsPaths : config.unixPaths;
34
+ const sweep = await probeAvailabilityCandidates(paths, config.probeArgs, config.budgetMs);
35
+ sweepSawTransient = sweep.sawTransient;
36
+ sweepTransientCause = sweep.transientCause;
37
+ sweepHostStallMs = sweep.hostStallMs;
38
+ if (sweep.foundPath)
39
+ toolPath = sweep.foundPath;
40
+ return sweep.foundPath;
41
+ }
42
+ async function resolveAvailability() {
43
+ const startedAt = Date.now();
44
+ const found = (await findPath()) !== null;
45
+ if (found) {
46
+ availabilityLatch.noteAvailable();
47
+ config.log(`${config.label} found: ${toolPath}`);
48
+ logAvailabilityDecision({
49
+ tool: config.tool,
50
+ verdict: "available",
51
+ outcome: "success",
52
+ cause: "ok",
53
+ elapsedMs: Date.now() - startedAt,
54
+ latched: true,
55
+ hostStallMs: sweepHostStallMs,
56
+ budgetMs: config.budgetMs,
57
+ });
58
+ return true;
59
+ }
60
+ // A timed-out version probe is evidence about this moment, not about
61
+ // whether the toolchain is installed; it expires instead of latching.
62
+ const outcome = sweepSawTransient ? "transient" : "missing";
63
+ const cause = sweepSawTransient ? sweepTransientCause : "not-found";
64
+ const retryAfterMs = availabilityLatch.noteUnavailable(outcome, cause);
65
+ logAvailabilityDecision({
66
+ tool: config.tool,
67
+ verdict: "unavailable",
68
+ outcome,
69
+ cause,
70
+ elapsedMs: Date.now() - startedAt,
71
+ latched: outcome !== "transient",
72
+ hostStallMs: sweepHostStallMs,
73
+ ...(retryAfterMs > 0 && { retryAfterMs }),
74
+ budgetMs: config.budgetMs,
75
+ });
76
+ return false;
77
+ }
78
+ async function isAvailable() {
79
+ // `read()` returns null when the last verdict was transient and its
80
+ // cooldown expired, which re-enters the candidate sweep (#1476).
81
+ const memo = availabilityLatch.read();
82
+ if (memo !== null)
83
+ return memo;
84
+ // Now that a verdict can expire, concurrent callers arriving just after a
85
+ // cooldown must share ONE sweep rather than each spawning their own.
86
+ if (ensureInFlight)
87
+ return ensureInFlight;
88
+ ensureInFlight = resolveAvailability();
89
+ try {
90
+ return await ensureInFlight;
91
+ }
92
+ finally {
93
+ ensureInFlight = null;
94
+ }
95
+ }
96
+ return { findPath, isAvailable };
97
+ }
@@ -15,6 +15,7 @@ import { createHash } from "node:crypto";
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
17
  import yaml from "../../deps/js-yaml.js";
18
+ import { BoundedLruCache } from "../../bounded-cache.js";
18
19
  // --- Constants ---
19
20
  /** Overly broad patterns that match everything (cause false positive explosions) */
20
21
  export const OVERLY_BROAD_PATTERNS = [
@@ -28,10 +29,10 @@ export const OVERLY_BROAD_PATTERNS = [
28
29
  /** Maximum complexity score for rules in blockingOnly mode */
29
30
  export const MAX_BLOCKING_RULE_COMPLEXITY = 8;
30
31
  // --- Caches ---
31
- const rulesCache = new Map();
32
- const blockingRulesCache = new Map();
33
- const contentRulesCache = new Map();
34
- const contentBlockingRulesCache = new Map();
32
+ const rulesCache = new BoundedLruCache(64);
33
+ const blockingRulesCache = new BoundedLruCache(64);
34
+ const contentRulesCache = new BoundedLruCache(64);
35
+ const contentBlockingRulesCache = new BoundedLruCache(64);
35
36
  // --- Public API ---
36
37
  export function clearRulesCache() {
37
38
  rulesCache.clear();
@@ -24,9 +24,10 @@
24
24
  import { logBusEvent } from "./bus-events-logger.js";
25
25
  import { isBusPublishEnabled } from "./bus-publish.js";
26
26
  import { normalizeFilePath } from "./path-utils.js";
27
+ import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
27
28
  export const BUS_DISPOSITION_EVENT = "pilens:diagnostic:disposition";
28
29
  export const BUS_DISPOSITION_VERSION = 1;
29
- let busEmit;
30
+ const liveEmitter = createLiveBusEmitter();
30
31
  let hasLoggedFailure = false;
31
32
  let hasLoggedUnwired = false;
32
33
  let hasLoggedDisabled = false;
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
38
39
  * the MCP server path run in (no pi host, no `pi.events`).
39
40
  */
40
41
  export function wireDispositionBusEmitter(emitFn) {
41
- busEmit = emitFn;
42
+ liveEmitter.wire(emitFn);
43
+ }
44
+ export function wireDispositionBusEmitterGetter(getter) {
45
+ liveEmitter.wireGetter(getter);
42
46
  }
43
47
  /** Test-only: reset module state between test files. */
44
48
  export function _resetDispositionPublishForTests() {
45
- busEmit = undefined;
49
+ liveEmitter.reset();
46
50
  hasLoggedFailure = false;
47
51
  hasLoggedUnwired = false;
48
52
  hasLoggedDisabled = false;
@@ -63,7 +67,13 @@ export function publishDisposition(args) {
63
67
  }
64
68
  return;
65
69
  }
66
- if (!busEmit) {
70
+ const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
71
+ event: BUS_DISPOSITION_EVENT,
72
+ cwd: normalizeFilePath(args.cwd),
73
+ }));
74
+ if (resolution.outcome === "stale-session")
75
+ return;
76
+ if (resolution.outcome === "unwired") {
67
77
  if (!hasLoggedUnwired) {
68
78
  hasLoggedUnwired = true;
69
79
  logBusEvent({
@@ -74,6 +84,7 @@ export function publishDisposition(args) {
74
84
  }
75
85
  return;
76
86
  }
87
+ const busEmit = resolution.emit;
77
88
  try {
78
89
  const payload = {
79
90
  v: BUS_DISPOSITION_VERSION,
@@ -88,6 +99,7 @@ export function publishDisposition(args) {
88
99
  reason: args.reason,
89
100
  };
90
101
  busEmit(BUS_DISPOSITION_EVENT, payload);
102
+ hasLoggedFailure = false;
91
103
  logBusEvent({
92
104
  event: BUS_DISPOSITION_EVENT,
93
105
  outcome: "emitted",
@@ -100,9 +112,11 @@ export function publishDisposition(args) {
100
112
  outcome: "emit_failed",
101
113
  cwd: normalizeFilePath(args.cwd),
102
114
  error: String(err),
115
+ ctxSource: resolution.ctxSource,
103
116
  });
104
117
  if (!hasLoggedFailure) {
105
118
  hasLoggedFailure = true;
119
+ recordStaleBusFailure(BUS_DISPOSITION_EVENT, err);
106
120
  args.dbg?.(`disposition-publish: pilens:diagnostic:disposition emit failed (further failures suppressed): ${err}`);
107
121
  }
108
122
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Synchronous locked read-modify-write commit seam for behavior-gating JSON
3
+ * stores shared by multiple pi-lens processes.
4
+ *
5
+ * The authoritative read happens only after the bounded PID lock is held; the
6
+ * caller merges only its delta, publication is a throwing atomic replacement,
7
+ * and success telemetry runs only after publication succeeds.
8
+ *
9
+ * Short synchronous commits use a file lock. Awaited commits use the shared
10
+ * quarantine directory lock, preserving bounded contention and stale-owner
11
+ * recovery without allowing a late release to delete a replacement owner.
12
+ */
13
+ import * as fs from "node:fs";
14
+ import fsp from "node:fs/promises";
15
+ import { writeFileAtomic, writeFileAtomicAsync } from "./atomic-write.js";
16
+ import { acquireBoundedPidFileLock, acquireQuarantinePidFileLock, } from "./bounded-pid-file-lock.js";
17
+ function readLocked(path) {
18
+ try {
19
+ return fs.readFileSync(path, "utf8");
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ export function commitDurableStore(options) {
26
+ const release = options.onContention === "skip-log"
27
+ ? acquireBoundedPidFileLock(`${options.path}.lock`, {
28
+ waitMs: options.waitMs,
29
+ retryMs: options.retryMs,
30
+ timeoutMessage: options.timeoutMessage,
31
+ onContention: "skip-log",
32
+ logContention: options.logContention,
33
+ })
34
+ : acquireBoundedPidFileLock(`${options.path}.lock`, {
35
+ waitMs: options.waitMs,
36
+ retryMs: options.retryMs,
37
+ timeoutMessage: options.timeoutMessage,
38
+ onContention: "throw",
39
+ });
40
+ if (!release)
41
+ return undefined;
42
+ let committed;
43
+ try {
44
+ const current = options.deserialize(readLocked(options.path));
45
+ committed = options.merge(current);
46
+ writeFileAtomic(options.path, options.serialize(committed), {
47
+ bestEffort: false,
48
+ });
49
+ options.afterWriteLocked?.(committed);
50
+ }
51
+ finally {
52
+ release();
53
+ }
54
+ return committed;
55
+ }
56
+ async function readLockedAsync(path) {
57
+ try {
58
+ return await fsp.readFile(path, "utf8");
59
+ }
60
+ catch (error) {
61
+ if (error.code === "ENOENT")
62
+ return undefined;
63
+ throw error;
64
+ }
65
+ }
66
+ export async function commitDurableStoreAsync(options) {
67
+ const release = options.onContention === "skip-log"
68
+ ? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
69
+ waitMs: options.waitMs,
70
+ retryMs: options.retryMs,
71
+ staleMs: options.staleMs,
72
+ timeoutMessage: options.timeoutMessage,
73
+ onContention: "skip-log",
74
+ logContention: options.logContention,
75
+ })
76
+ : await acquireQuarantinePidFileLock(`${options.path}.lock`, {
77
+ waitMs: options.waitMs,
78
+ retryMs: options.retryMs,
79
+ staleMs: options.staleMs,
80
+ timeoutMessage: options.timeoutMessage,
81
+ onContention: "throw",
82
+ });
83
+ if (!release)
84
+ return undefined;
85
+ try {
86
+ const current = options.deserialize(await readLockedAsync(options.path));
87
+ const committed = options.merge(current);
88
+ await writeFileAtomicAsync(options.path, options.serialize(committed), {
89
+ bestEffort: false,
90
+ });
91
+ await options.afterWriteLocked?.(committed);
92
+ return committed;
93
+ }
94
+ finally {
95
+ await release();
96
+ }
97
+ }
@@ -0,0 +1,17 @@
1
+ import { performance } from "node:perf_hooks";
2
+ /**
3
+ * The moment pi-lens's module graph started evaluating, captured as this
4
+ * module's first statement (#1374 host-boot vs extension-eval split).
5
+ *
6
+ * This module is otherwise side-effect-free by design (#1434 S3d review): it
7
+ * does nothing but capture a timestamp. `clients/console-guard-install.ts`
8
+ * imports it as its FIRST import so the capture happens before the guard's
9
+ * own install work, and `clients/startup-timing.ts` imports it too, for the
10
+ * constant alone. Before this split, `startup-timing.ts` imported the
11
+ * constant FROM `console-guard-install.ts` — which meant importing
12
+ * startup-timing anywhere (e.g. a test measuring load time) silently
13
+ * installed the console guard as a side effect. Splitting the constant out
14
+ * means importing this module, or `startup-timing.ts`, never installs
15
+ * anything.
16
+ */
17
+ export const PI_LENS_EVAL_STARTED_MS = performance.now();
@@ -10,11 +10,50 @@
10
10
  * of how late the loop services its own timer, i.e. how long it was blocked —
11
11
  * with **no per-event JS overhead**. `max` ≈ the worst synchronous block since
12
12
  * the last reset.
13
+ *
14
+ * ## System-stall contamination (#1122 / #1123)
15
+ *
16
+ * `monitorEventLoopDelay` measures timer lag with the monotonic clock
17
+ * (`uv_hrtime`, backed by `QueryPerformanceCounter` on Windows). When the whole
18
+ * process is frozen or descheduled — machine sleep / Modern Standby, or paging
19
+ * thrash under commit-charge exhaustion — its next timer fires late by the
20
+ * *entire* wall-clock gap, and the histogram records that gap as a "block".
21
+ * Two distinct system stalls were confirmed against the Windows System event
22
+ * log: (1) a 290,179 ms block that lined up exactly with a 14:33:05Z→14:37:55Z
23
+ * Modern Standby window (Kernel-Power 506/507), reported byte-identical by two
24
+ * independent pids because the HDR histogram quantizes ~290 s into one bucket;
25
+ * (2) a later silent host exit with zero sleep events but twelve
26
+ * Resource-Exhaustion-Detector (id 2004) events at 97% commit charge — the
27
+ * process was paging, not sleeping. Both are machine artifacts, not pi-lens
28
+ * work; latency.log also held multi-*hour* "blocks" that can only be overnight
29
+ * sleep.
30
+ *
31
+ * Comparing a wall clock to a monotonic clock does NOT catch these: on Windows
32
+ * both advance across Modern Standby (the histogram's monotonic delta already
33
+ * equalled the wall gap). The reliable discriminator is **CPU consumption**: a
34
+ * genuine synchronous block of D ms burns ≈ D ms of main-thread CPU, so the
35
+ * window that contains it must have consumed at least ~D ms of CPU. A frozen or
36
+ * thrashing process consumes ~0 CPU across the gap, so when the worst block
37
+ * exceeds all the CPU the window could account for, it was a stall, not work.
38
+ * We window per turn (so the CPU accounting is bounded and each block is
39
+ * attributable to its turn) and tag system-stall-suspected samples instead of
40
+ * letting them poison the "worst real block" high-water.
13
41
  */
42
+ import { logExtension } from "./extension-log.js";
14
43
  import { monitorEventLoopDelay } from "node:perf_hooks";
15
44
  const NS_PER_MS = 1e6;
45
+ const US_PER_MS = 1e3;
16
46
  let histogram;
17
47
  let monitorUnavailable = false;
48
+ // Per-window (per-turn) baselines for CPU-vs-wall accounting. Captured when the
49
+ // monitor starts and re-captured on every reset, so each window's CPU budget is
50
+ // measured against exactly the histogram window it will be compared to.
51
+ let windowStartWallMs = 0;
52
+ let windowStartCpuMs = 0;
53
+ const cpuTotalMs = () => {
54
+ const c = process.cpuUsage();
55
+ return (c.user + c.system) / US_PER_MS;
56
+ };
18
57
  /**
19
58
  * Start the monitor (idempotent). Call once, as early as possible, so startup
20
59
  * blocks are captured. Cheap — the sampling is native; nothing runs per event.
@@ -32,26 +71,76 @@ export function startEventLoopMonitor(resolutionMs = 20) {
32
71
  const h = monitorEventLoopDelay({ resolution: resolutionMs });
33
72
  h.enable();
34
73
  histogram = h;
74
+ windowStartWallMs = Date.now();
75
+ windowStartCpuMs = cpuTotalMs();
35
76
  }
36
77
  catch (err) {
37
78
  monitorUnavailable = true;
38
- console.error(`[pi-lens] event-loop occupancy telemetry disabled (runtime lacks monitorEventLoopDelay): ${err?.message ?? String(err)}`);
79
+ logExtension({
80
+ subsystem: "event-loop-monitor",
81
+ message: `event-loop occupancy telemetry disabled (runtime lacks monitorEventLoopDelay): ${err?.message ?? String(err)}`,
82
+ });
39
83
  }
40
84
  }
41
85
  const safeMs = (ns) => Number.isFinite(ns) ? Math.round((ns / NS_PER_MS) * 10) / 10 : 0;
86
+ /**
87
+ * Classify a worst-block sample as genuine CPU work or a suspend/freeze
88
+ * artifact. Pure so the discrimination is testable without the (vitest-flaky)
89
+ * native histogram or a real machine sleep.
90
+ *
91
+ * A block is system-stall-suspected only when it is both (a) larger than any
92
+ * plausible synchronous pi-lens stall (`floorMs`, default 20 s — the real tier
93
+ * observed in latency.log tops out ~15 s) and (b) unaccounted for by the
94
+ * window's CPU budget (`windowCpuMs + slopMs < maxMs`). The floor keeps a real
95
+ * multi-second I/O-bound block (low CPU, but genuine) from being mislabeled,
96
+ * while a frozen or paging process — ~0 CPU across a minutes-to-hours gap —
97
+ * always trips. Sub-floor blocks are never auto-tagged, but the logged
98
+ * `windowCpuMs`/`windowWallMs` still expose the CPU-vs-wall ratio so a reviewer
99
+ * can spot a shorter paging stall by hand.
100
+ *
101
+ * KNOWN AMBIGUITY (honest, not fully resolvable from CPU alone): a genuine
102
+ * pi-lens block that is BLOCKED IN A SYSCALL for >20 s — e.g. a `readdirSync` /
103
+ * `statSync` stalled on a OneDrive/cloud-backed path fetching a dehydrated file,
104
+ * or an antivirus-throttled read — also consumes ~0 CPU while wall time
105
+ * advances, so it is CPU-indistinguishable from a suspend and WILL be tagged
106
+ * `suspectSystemStall` and excluded from the health high-waters. That is the
107
+ * conservative choice (a >20 s synchronous FS stall is itself a P0-worthy bug we
108
+ * do NOT want silently counted as normal), and it is not silenced: such a sample
109
+ * still re-logs every turn with its `lastPhase` attribution, which is the
110
+ * forensic breadcrumb for exactly this class. Sharper corroboration (a magnitude
111
+ * ceiling, a `maxMs`-vs-`windowWallMs` ratio) is tracked as a follow-up note on
112
+ * #1123, not decided here.
113
+ */
114
+ export function isSuspendSuspectedBlock(maxMs, windowCpuMs, floorMs = 20000, slopMs = 1000) {
115
+ if (maxMs < floorMs)
116
+ return false;
117
+ return windowCpuMs + slopMs < maxMs;
118
+ }
42
119
  /** Current occupancy stats, or undefined if the monitor was never started. */
43
120
  export function getEventLoopStats() {
44
121
  if (!histogram)
45
122
  return undefined;
123
+ const maxMs = safeMs(histogram.max);
124
+ const windowWallMs = Math.max(0, Date.now() - windowStartWallMs);
125
+ const windowCpuMs = Math.max(0, cpuTotalMs() - windowStartCpuMs);
46
126
  return {
47
- maxMs: safeMs(histogram.max),
127
+ maxMs,
48
128
  p99Ms: safeMs(histogram.percentile(99)),
49
129
  meanMs: safeMs(histogram.mean),
130
+ windowWallMs: Math.round(windowWallMs),
131
+ windowCpuMs: Math.round(windowCpuMs),
132
+ suspectSystemStall: isSuspendSuspectedBlock(maxMs, windowCpuMs),
50
133
  };
51
134
  }
52
- /** Reset the histogram — e.g. at session/turn boundaries for per-window stats. */
135
+ /**
136
+ * Reset the histogram and re-baseline the CPU/wall window — called at turn
137
+ * boundaries so each window's worst block is attributable to that turn and its
138
+ * CPU budget is measured over the same span (#192 intent, wired in #1122).
139
+ */
53
140
  export function resetEventLoopMonitor() {
54
141
  histogram?.reset();
142
+ windowStartWallMs = Date.now();
143
+ windowStartCpuMs = cpuTotalMs();
55
144
  }
56
145
  /**
57
146
  * Decide whether a freeze is worth persisting to `latency.log`. Pure so the
@@ -68,4 +157,6 @@ export function _stopEventLoopMonitorForTest() {
68
157
  histogram?.disable();
69
158
  histogram = undefined;
70
159
  monitorUnavailable = false;
160
+ windowStartWallMs = 0;
161
+ windowStartCpuMs = 0;
71
162
  }