@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
@@ -1,10 +1,14 @@
1
+ import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
1
2
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
2
3
  export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
3
4
  const withErrors = neighbors.filter((n) => n.diagnostics.length > 0);
4
- if (withErrors.length === 0)
5
+ const inconclusive = neighbors.filter((n) => n.inconclusive === true && n.diagnostics.length === 0);
6
+ if (withErrors.length === 0 && inconclusive.length === 0)
5
7
  return "";
6
8
  const noun = options.noun ?? "neighbor";
7
- let out = `📐 Cascade errors in ${withErrors.length} ${noun} file(s) — fix before finishing turn:`;
9
+ let out = withErrors.length > 0
10
+ ? `📐 Cascade errors in ${withErrors.length} ${noun} file(s) — fix before finishing turn:`
11
+ : "";
8
12
  for (const neighbor of withErrors) {
9
13
  const display = toRunnerDisplayPath(cwd, neighbor.filePath);
10
14
  const reason = options.includeReason ? ` reason="${neighbor.reason}"` : "";
@@ -17,5 +21,56 @@ export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
17
21
  }
18
22
  out += "\n</diagnostics>";
19
23
  }
24
+ if (inconclusive.length > 0) {
25
+ if (out)
26
+ out += "\n";
27
+ out += `⚠️ Cascade diagnostics inconclusive for ${inconclusive.length} ${noun} file(s) — no clean result was confirmed:`;
28
+ for (const neighbor of inconclusive) {
29
+ out += `\n ${toRunnerDisplayPath(cwd, neighbor.filePath)}`;
30
+ }
31
+ }
20
32
  return out;
21
33
  }
34
+ /**
35
+ * Build the turn-end `CascadeRun` for a neighbour whose diagnostics landed only
36
+ * AFTER its cascade touch skipped the in-lane wait (#1023's `resolved-found`
37
+ * quiet-window outcome; #1444 made native TS7 take that same path). Returns
38
+ * `undefined` when there is nothing agent-facing to say — no ERROR-severity
39
+ * diagnostics, or nothing the formatter renders.
40
+ *
41
+ * Lives here rather than inline in the quiet-window callback so the delivery
42
+ * path (reconcile → run → turn_end) is testable end to end; index.ts only wires
43
+ * it to `runtime.appendCascadeRun`.
44
+ */
45
+ export function buildResolvedFoundCascadeRun(cwd, neighbor) {
46
+ const { filePath } = neighbor;
47
+ const diagnostics = convertLspDiagnostics(neighbor.diagnostics.filter((d) => d.severity === 1), filePath, { tool: "lsp" });
48
+ if (diagnostics.length === 0)
49
+ return undefined;
50
+ const neighbors = [
51
+ { filePath, reason: "references", diagnostics, lspTouched: true },
52
+ ];
53
+ const formatted = formatCascadeNeighborDiagnostics(cwd, neighbors, {
54
+ noun: "cold neighbor",
55
+ });
56
+ if (!formatted)
57
+ return undefined;
58
+ return {
59
+ filePath,
60
+ result: {
61
+ filePath,
62
+ impact: {
63
+ filePath,
64
+ changedSymbols: [],
65
+ directImporters: [],
66
+ directCallers: [],
67
+ neighborFiles: [filePath],
68
+ riskFlags: [],
69
+ },
70
+ neighbors,
71
+ formatted,
72
+ },
73
+ neighborCount: 1,
74
+ diagnosticCount: diagnostics.length,
75
+ };
76
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared plumbing for best-effort, fire-and-forget child-process spawns
3
+ * (shape 4 of the recurring-defect catalog in AGENTS.md: "a timer / promise /
4
+ * worker / child that outlives its one-shot settle"). Extracted from the
5
+ * orphan reaper's `unrefReaperChild`/`spawnCollectStdout` (#1153/#1160) into a
6
+ * shared, dependency-free module so every one-shot
7
+ * `spawn(..., { stdio: ["ignore","pipe",...] })` call site in the codebase —
8
+ * the reaper's enumeration/kill spawns AND the resource sampler's Windows
9
+ * CIM/powershell spawns (#1155) — uses the exact same unref+collect shape
10
+ * instead of re-deriving it (also closes the #1155 PR's SonarCloud
11
+ * new-code-duplication finding: two near-identical spawn→collect-stdout
12
+ * blocks in `clients/resource-sampler.ts` collapsed to one shared helper).
13
+ *
14
+ * Deliberately has NO imports beyond `node:child_process` types, so both
15
+ * `clients/instance-reaper.ts` and `clients/resource-sampler.ts` (which
16
+ * `clients/safe-spawn.ts` itself depends on) can import this without risking
17
+ * a circular-import chain.
18
+ */
19
+ import { spawn as nodeSpawn } from "node:child_process";
20
+ /**
21
+ * Detach a best-effort, fire-and-forget child process from the event loop.
22
+ *
23
+ * A piped, `data`-listener-attached stdout/stderr/stdin stream keeps the libuv
24
+ * loop REFERENCED even after `child.unref()` — the child handle and every
25
+ * live stdio stream must all be unref'd, or a settled one-shot process (e.g.
26
+ * `pi --print`) cannot exit until the child `close`s. Unref only means "do
27
+ * not keep the process alive FOR this best-effort work": in an interactive/
28
+ * long-lived session the loop stays referenced for other reasons, so the
29
+ * child's `data`/`close` events still fire normally and callers still collect
30
+ * output/usage; only a genuinely-settled one-shot is allowed to exit without
31
+ * waiting for it. Never throws.
32
+ */
33
+ export function unrefChildAndPipes(child) {
34
+ try {
35
+ child.unref();
36
+ // Child stdio pipes are `net.Socket`s at runtime (which expose `unref`),
37
+ // but are typed as `Readable`/`Writable` (which do not) — cast to the
38
+ // optional-`unref` shape and guard, so an un-piped ("ignore") stream is a
39
+ // no-op rather than a crash.
40
+ for (const stream of [child.stdout, child.stderr, child.stdin]) {
41
+ stream?.unref?.();
42
+ }
43
+ }
44
+ catch {
45
+ // best-effort — unref must never throw out of a fire-and-forget spawn
46
+ }
47
+ }
48
+ /**
49
+ * Spawn a best-effort, fire-and-forget child, accumulate its full stdout, and
50
+ * resolve with the collected text (empty string on a synchronous spawn
51
+ * failure or an `error` event). Consolidates the spawn → unref →
52
+ * pipe-stdout → `close` plumbing shared by every one-shot OS-process-table
53
+ * query in the codebase — each caller supplies only its command/args/options
54
+ * and does its own output parse. The child + its stdio pipes are `unref`'d
55
+ * here (via `unrefChildAndPipes`) so a settled one-shot `pi --print` process
56
+ * can exit without waiting, and both the unref and the collect plumbing live
57
+ * in exactly ONE place rather than being re-derived at each spawn site.
58
+ * Never rejects — any failure resolves to `""`, which every caller's parse
59
+ * turns into an empty/absent result (the best-effort contract every caller
60
+ * here already has).
61
+ */
62
+ export function spawnCollectStdout(command, args, options) {
63
+ return new Promise((resolve) => {
64
+ let settled = false;
65
+ const settle = (value) => {
66
+ if (settled)
67
+ return;
68
+ settled = true;
69
+ resolve(value);
70
+ };
71
+ try {
72
+ const child = nodeSpawn(command, args, options);
73
+ unrefChildAndPipes(child);
74
+ let out = "";
75
+ child.stdout?.on("data", (chunk) => {
76
+ out += chunk.toString();
77
+ });
78
+ child.once("error", () => settle(""));
79
+ child.once("close", () => settle(out));
80
+ }
81
+ catch {
82
+ settle("");
83
+ }
84
+ });
85
+ }
@@ -13,18 +13,25 @@
13
13
  import * as fs from "node:fs";
14
14
  import * as path from "node:path";
15
15
  import { getProjectDataDir } from "./file-utils.js";
16
+ import { writeFileAtomic } from "./atomic-write.js";
17
+ import { parseSymbolKey } from "./call-graph.js";
18
+ import { detectFileRole } from "./file-role.js";
19
+ import { isExternalOrVendorFile } from "./path-utils.js";
20
+ import { isBuildArtifact } from "./source-filter.js";
16
21
  // ── Constants ─────────────────────────────────────────────────────────────────
17
- const DEFAULT_TOKEN_BUDGET = 1500;
22
+ export const DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET = 1500;
18
23
  const MAX_CALLS_PER_SYMBOL = 10;
19
24
  const MIN_IN_DEGREE = 0.5; // skip symbols with low centrality (avoids noise)
25
+ /** Persisted model schema version. Bump when the persisted shape changes. */
26
+ export const CODEBASE_MODEL_VERSION = 1;
20
27
  // ── Builder ───────────────────────────────────────────────────────────────────
21
28
  function inferKind(symbolKey) {
22
- const name = symbolKey.split(":").pop() ?? "";
23
- if (/^[A-Z]/.test(name))
29
+ const parsed = parseSymbolKey(symbolKey);
30
+ if (parsed.kind === "class")
24
31
  return "class";
25
- if (name.includes("."))
32
+ if (parsed.kind === "method" || parsed.symbolName?.includes("."))
26
33
  return "method";
27
- return "function";
34
+ return /^[A-Z]/.test(parsed.symbolName ?? "") ? "class" : "function";
28
35
  }
29
36
  function estimateTokens(entry) {
30
37
  const text = [
@@ -45,7 +52,15 @@ function estimateTokens(entry) {
45
52
  * @param cwd Project root — used to compute relative file paths.
46
53
  * @param budget Maximum total token budget (default 1500).
47
54
  */
48
- export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
55
+ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_CODEBASE_MODEL_TOKEN_BUDGET, identity) {
56
+ // Standalone callers may build an unpersisted projection without the
57
+ // review-graph coordinator. Persisted/session models pass the canonical
58
+ // review-graph identity explicitly; this fallback is only a local call-graph
59
+ // identity for builder-only use.
60
+ const modelIdentity = identity ?? {
61
+ reviewGraphVersion: graph.builtAt || "call-graph-unknown",
62
+ reviewGraphSignature: graph.builtAt || "call-graph-unknown",
63
+ };
49
64
  // Sort all callee keys by in-degree descending
50
65
  const ranked = [...graph.inDegree.entries()]
51
66
  .filter(([, score]) => score >= MIN_IN_DEGREE)
@@ -56,16 +71,16 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
56
71
  for (const [calleeKey, inDegree] of ranked) {
57
72
  if (totalTokens >= budget)
58
73
  break;
59
- const parts = calleeKey.split(":");
60
- const name = parts.pop() ?? calleeKey;
61
- const filePath = parts.join(":");
62
- // Skip if file is in test/node_modules/generated directories
63
- if (filePath.includes("node_modules") ||
64
- filePath.includes(".test.") ||
65
- filePath.includes(".spec.") ||
66
- filePath.includes("/__tests__/") ||
67
- filePath.includes("/dist/") ||
68
- filePath.includes("/generated/")) {
74
+ const parsedCallee = parseSymbolKey(calleeKey);
75
+ const name = parsedCallee.symbolName ?? calleeKey;
76
+ const filePath = parsedCallee.filePath;
77
+ // Keep this projection aligned with the shared file-role policy. The
78
+ // call graph is derived from the canonical review graph, so the model
79
+ // carries that graph's identity rather than inventing a second freshness
80
+ // policy.
81
+ const fileRole = detectFileRole(filePath);
82
+ if (fileRole === "test" || fileRole === "generated" ||
83
+ isExternalOrVendorFile(filePath, cwd) || isBuildArtifact(filePath)) {
69
84
  continue;
70
85
  }
71
86
  // Deduplicate by name when the same function appears in multiple files
@@ -73,11 +88,11 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
73
88
  continue;
74
89
  seenNames.add(name);
75
90
  const calls = [...(graph.callees.get(calleeKey) ?? new Set())]
76
- .map((k) => k.split(":").pop() ?? k)
91
+ .map((k) => parseSymbolKey(k).symbolName ?? k)
77
92
  .filter(Boolean)
78
93
  .slice(0, MAX_CALLS_PER_SYMBOL);
79
94
  const calledBy = [...(graph.callers.get(calleeKey) ?? new Set())]
80
- .map((k) => k.split(":").pop() ?? k)
95
+ .map((k) => parseSymbolKey(k).symbolName ?? k)
81
96
  .filter((n) => !n.startsWith("file:"))
82
97
  .slice(0, MAX_CALLS_PER_SYMBOL);
83
98
  const file = filePath
@@ -98,9 +113,12 @@ export function buildCodebaseModel(graph, cwd, budget = DEFAULT_TOKEN_BUDGET) {
98
113
  totalTokens += tokens;
99
114
  }
100
115
  const allFiles = new Set([...graph.callers.keys(), ...graph.callees.keys()]
101
- .map((k) => k.split(":").slice(0, -1).join(":"))
116
+ .map((k) => parseSymbolKey(k).filePath)
102
117
  .filter(Boolean));
103
118
  return {
119
+ version: CODEBASE_MODEL_VERSION,
120
+ reviewGraphVersion: modelIdentity.reviewGraphVersion,
121
+ reviewGraphSignature: modelIdentity.reviewGraphSignature,
104
122
  generatedAt: new Date().toISOString(),
105
123
  totalSymbols: graph.inDegree.size,
106
124
  totalFiles: allFiles.size,
@@ -119,16 +137,27 @@ export function saveCodebaseModel(cwd, model) {
119
137
  const cacheFile = cacheFilePath(cwd);
120
138
  try {
121
139
  fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
122
- fs.writeFileSync(cacheFile, JSON.stringify(model), "utf-8");
123
- fs.writeFileSync(metaFilePath(cwd), JSON.stringify({ savedAt: new Date().toISOString(), entryCount: model.entries.length, totalTokens: model.totalTokens }), "utf-8");
140
+ writeFileAtomic(cacheFile, JSON.stringify(model));
141
+ writeFileAtomic(metaFilePath(cwd), JSON.stringify({ savedAt: new Date().toISOString(), entryCount: model.entries.length, totalTokens: model.totalTokens }));
124
142
  }
125
143
  catch {
126
144
  // Non-fatal — next session rebuilds.
127
145
  }
128
146
  }
129
- export function loadCodebaseModel(cwd) {
147
+ /** Load only a model matching the canonical identity it was derived from. */
148
+ export function loadCodebaseModel(cwd, expectedIdentity) {
130
149
  try {
131
- return JSON.parse(fs.readFileSync(cacheFilePath(cwd), "utf-8"));
150
+ const raw = JSON.parse(fs.readFileSync(cacheFilePath(cwd), "utf-8"));
151
+ if (raw.version !== CODEBASE_MODEL_VERSION ||
152
+ typeof raw.reviewGraphVersion !== "string" || raw.reviewGraphVersion.length === 0 ||
153
+ typeof raw.reviewGraphSignature !== "string" || raw.reviewGraphSignature.length === 0 ||
154
+ typeof raw.generatedAt !== "string" ||
155
+ !Array.isArray(raw.entries))
156
+ return undefined;
157
+ if (raw.reviewGraphVersion !== expectedIdentity.reviewGraphVersion ||
158
+ raw.reviewGraphSignature !== expectedIdentity.reviewGraphSignature)
159
+ return undefined;
160
+ return raw;
132
161
  }
133
162
  catch {
134
163
  return undefined;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared test-role predicate for COLLATERAL cascade/impact surfaces (#1080).
3
+ *
4
+ * The review graph is already tests-free (`review-graph/builder.ts` applies
5
+ * `detectFileRole(file) !== "test"` at its walk/incremental chokepoints), but
6
+ * several collateral surfaces re-derive neighbors from OTHER sources that never
7
+ * saw that filter — LSP reference expansion, module-level downstream expansion,
8
+ * the reverse-dependency index, and the passive-diagnostics fallback — so an
9
+ * unignored `*.test.*` / `tests/` file can still leak into cascade neighbor
10
+ * output, the formatted impact header/counts, the turn-end call-graph advisory,
11
+ * and the persisted call-graph-impact diagnostics.
12
+ *
13
+ * This composes the SINGLE existing structural classifier (`detectFileRole`) —
14
+ * no second matcher, no private test-filename list, no duplicated patterns. The
15
+ * project ignore matcher (`getProjectIgnoreMatcher`) is applied separately and
16
+ * unchanged at each surface; this predicate only adds the test-ROLE half of the
17
+ * display policy the review graph already enforces.
18
+ *
19
+ * Honesty (#1080 acceptance criteria):
20
+ * - A KNOWN `"test"` role is filtered from the collateral surface.
21
+ * - If role classification cannot be obtained (an unexpected throw), the
22
+ * candidate is RETAINED (`false`) rather than dropped — filtering must never
23
+ * invent a heuristic or silently drop a potentially actionable file, and must
24
+ * never convert an incomplete/indeterminate computation into a clean result.
25
+ */
26
+ import { detectFileRole } from "./file-role.js";
27
+ /**
28
+ * True when `filePath` is a KNOWN test-role file that must not surface as a
29
+ * collateral cascade/impact neighbor. Path-based only (no content needed for
30
+ * the test-role branch of `detectFileRole`). Fail-open: any classifier error
31
+ * returns `false` (retain the candidate).
32
+ */
33
+ export function isTestRoleCollateral(filePath) {
34
+ try {
35
+ return detectFileRole(filePath) === "test";
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
@@ -10,6 +10,7 @@
10
10
  * maintainability index (Halstead-free), LOC/comments, code entropy, and AI-slop
11
11
  * indicators. These are silent metrics surfaced in the session summary.
12
12
  */
13
+ import { createSubsystemLogger } from "./extension-log.js";
13
14
  import * as fs from "node:fs";
14
15
  import * as path from "node:path";
15
16
  import { firstChildOfType, withTreeSitterRoot, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
@@ -371,7 +372,7 @@ export class ComplexityClient {
371
372
  log;
372
373
  constructor(verbose = false) {
373
374
  this.log = verbose
374
- ? (msg) => console.error(`[complexity] ${msg}`)
375
+ ? createSubsystemLogger("complexity")
375
376
  : () => { };
376
377
  }
377
378
  /** True if the file's grammar has a complexity node mapping. */
@@ -0,0 +1,26 @@
1
+ // Capture this before the guard's other module initialization so host boot is
2
+ // not charged to extension evaluation (#1374). Lives in its own
3
+ // side-effect-free module (#1434 S3d) — see eval-timestamp.ts's docstring for
4
+ // why: `startup-timing.ts` needs this constant but must never trigger a
5
+ // console-guard install merely by importing it.
6
+ import { PI_LENS_EVAL_STARTED_MS } from "./eval-timestamp.js";
7
+ // #1333: install the console guard as an import side-effect so it runs before
8
+ // any other module's initialization code. Installing inside the extension
9
+ // factory is too late — by the time the factory runs, every static/transitive
10
+ // import has already evaluated, so a dependency writing to console during
11
+ // module init would still hit the TUI raw (#1338 review finding). This module
12
+ // MUST remain the first import of index.ts; the enforcement test pins that.
13
+ // installConsoleGuard() itself is idempotent and no-ops under test mode and
14
+ // PI_LENS_CONSOLE_GUARD=0.
15
+ // #1434: the guard captures only while pi-lens owns execution, so the module
16
+ // window must be open for the rest of the import graph. index.ts closes it on
17
+ // its last line; an unref'd backstop closes it if that never runs.
18
+ import { installConsoleGuard, openModuleLoadConsoleWindow, } from "./extension-log.js";
19
+ installConsoleGuard();
20
+ openModuleLoadConsoleWindow();
21
+ // Not re-exported: nothing should import the timestamp from this module.
22
+ // `clients/startup-timing.ts` imports it straight from `eval-timestamp.ts`,
23
+ // which is what keeps that import side-effect-free (S3d). The `void` keeps
24
+ // the import from looking unused to lint while documenting that this module
25
+ // only needs the ordering (evaluate eval-timestamp.ts first), not the value.
26
+ void PI_LENS_EVAL_STARTED_MS;
@@ -0,0 +1,42 @@
1
+ /** Monotonic cooperative-work budgeting for event-loop-sensitive bulk work. */
2
+ export function createDeadline(budgetMs) {
3
+ const boundedBudget = Math.max(0, budgetMs);
4
+ let startedAt = performance.now();
5
+ return {
6
+ expired: () => performance.now() - startedAt >= boundedBudget,
7
+ reset: () => {
8
+ startedAt = performance.now();
9
+ },
10
+ };
11
+ }
12
+ export async function yieldIfOverBudget(deadline) {
13
+ if (!deadline.expired())
14
+ return false;
15
+ await new Promise((resolve) => setImmediate(resolve));
16
+ deadline.reset();
17
+ return true;
18
+ }
19
+ export async function forEachCooperatively(items, fn, options) {
20
+ const deadline = createDeadline(options.budgetMs);
21
+ const assertContinuing = () => {
22
+ if (options.shouldContinue?.() === false) {
23
+ throw new Error(options.abortMessage ?? "cooperative work superseded");
24
+ }
25
+ };
26
+ let index = 0;
27
+ for (const item of items) {
28
+ // Check before each unit: this per-unit check is what bounds abort
29
+ // latency (one work unit, not an iteration checkpoint). The post-yield
30
+ // check below only matters after the final unit's yield, so superseded
31
+ // work is reported aborted rather than completed.
32
+ assertContinuing();
33
+ const result = fn(item, index++);
34
+ if (result !== undefined &&
35
+ typeof result.then === "function") {
36
+ await result;
37
+ }
38
+ if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
39
+ assertContinuing();
40
+ }
41
+ }
42
+ }
@@ -11,9 +11,11 @@
11
11
  * Phase 1 ships Python via `vulture`. Future phases add Go/Rust/etc. by
12
12
  * implementing DeadCodeClient and adding to getDeadCodeClients().
13
13
  */
14
+ import { createSubsystemLogger } from "./extension-log.js";
14
15
  import * as path from "node:path";
15
16
  import { findNearestMarkerRoot } from "./path-utils.js";
16
17
  import { safeSpawnAsync } from "./safe-spawn.js";
18
+ import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
17
19
  function emptyResult(language) {
18
20
  return {
19
21
  success: false,
@@ -38,6 +40,21 @@ const VULTURE_EXCLUDES = [
38
40
  "*/__pycache__/*",
39
41
  "*/.eggs/*",
40
42
  ];
43
+ // Decorators whose target is called by a framework, never by name in the tree.
44
+ // vulture cannot see those call sites, so every such symbol is a permanent
45
+ // false positive that reappears in every scan — the fastest way to teach a
46
+ // reader to stop reading the advisory. Glob-matched by vulture itself.
47
+ const VULTURE_IGNORE_DECORATORS = [
48
+ "@pytest.fixture",
49
+ "@pytest.fixture(*",
50
+ "@fixture",
51
+ "@fixture(*",
52
+ "@app.*",
53
+ "@router.*",
54
+ "@celery.task*",
55
+ "@task",
56
+ "@shared_task*",
57
+ ];
41
58
  // vulture line: `path/to/file.py:12: unused function 'foo' (60% confidence)`
42
59
  const VULTURE_LINE = /^(.*?):(\d+): unused (\w[\w ]*?) '([^']+)' \((\d+)% confidence\)\s*$/;
43
60
  /**
@@ -85,14 +102,19 @@ export function parseVultureOutput(output, root) {
85
102
  export class PythonDeadCodeClient {
86
103
  id = "python";
87
104
  language = "Python";
88
- available = null;
105
+ /**
106
+ * Transient-aware memo (#1467): only a durable "vulture is not installed"
107
+ * verdict is remembered for the session. A probe that timed out expires and
108
+ * is re-probed, so vulture cannot be disabled for the process by one stall.
109
+ */
110
+ availabilityLatch = createAvailabilityLatch();
89
111
  resolved = null;
90
112
  ensureInFlight = null;
91
113
  inFlight = new Map();
92
114
  log;
93
115
  constructor(verbose = false) {
94
116
  this.log = verbose
95
- ? (msg) => console.error(`[dead-code:python] ${msg}`)
117
+ ? createSubsystemLogger("dead-code:python")
96
118
  : () => { };
97
119
  }
98
120
  get minConfidence() {
@@ -102,6 +124,10 @@ export class PythonDeadCodeClient {
102
124
  detect(cwd) {
103
125
  return this.resolveProjectRoot(cwd) !== null;
104
126
  }
127
+ owns(filePath) {
128
+ const ext = path.extname(filePath).toLowerCase();
129
+ return ext === ".py" || ext === ".pyi";
130
+ }
105
131
  /**
106
132
  * Nearest dir with a Python project marker, never at/above $HOME and never
107
133
  * escaping a VCS boundary — same containment rules as KnipClient so a scan
@@ -120,8 +146,9 @@ export class PythonDeadCodeClient {
120
146
  ], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
121
147
  }
122
148
  async ensureAvailable() {
123
- if (this.available !== null)
124
- return this.available;
149
+ const memo = this.availabilityLatch.read();
150
+ if (memo !== null)
151
+ return memo;
125
152
  if (this.ensureInFlight)
126
153
  return this.ensureInFlight;
127
154
  this.ensureInFlight = this.doEnsureAvailable();
@@ -145,19 +172,81 @@ export class PythonDeadCodeClient {
145
172
  { cmd: "python", prefix: ["-m", "vulture"] },
146
173
  { cmd: "python3", prefix: ["-m", "vulture"] },
147
174
  ];
175
+ // A timeout on ANY candidate means the machine, not the tool, answered —
176
+ // the run gets a bounded retry instead of a permanent skip (#1467).
177
+ let sawTransient = false;
178
+ let transientCause = "probe-timeout";
179
+ // Accumulated across ALL candidates, because the failure verdicts below
180
+ // are about the whole sweep rather than any one probe. Reporting zero
181
+ // here would erase the evidence that cracked #1467: four 5s probes and
182
+ // a stalled host look identical to an absent tool without these two
183
+ // numbers.
184
+ const sweepStartedAt = Date.now();
185
+ let sweepHostStallMs = 0;
148
186
  for (const c of candidates) {
149
- const probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
150
- timeout: 5000,
151
- });
187
+ const sampler = startHostStallSampler();
188
+ const startedAt = Date.now();
189
+ let probe;
190
+ let hostStallMs;
191
+ try {
192
+ probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
193
+ timeout: 5000,
194
+ });
195
+ }
196
+ finally {
197
+ hostStallMs = sampler.stop();
198
+ sweepHostStallMs += hostStallMs;
199
+ }
152
200
  if (!probe.error && probe.status === 0) {
153
201
  this.resolved = c;
154
- this.available = true;
202
+ this.availabilityLatch.noteAvailable();
155
203
  this.log(`vulture found: ${[c.cmd, ...c.prefix].join(" ")}`);
204
+ logAvailabilityDecision({
205
+ tool: "vulture",
206
+ verdict: "available",
207
+ outcome: "success",
208
+ cause: "ok",
209
+ elapsedMs: Date.now() - startedAt,
210
+ latched: true,
211
+ hostStallMs,
212
+ budgetMs: 5000,
213
+ });
156
214
  return true;
157
215
  }
216
+ const classified = classifyProbeFailure(probe, { hostStallMs });
217
+ if (classified.outcome === "transient") {
218
+ sawTransient = true;
219
+ transientCause = classified.cause;
220
+ }
221
+ }
222
+ if (sawTransient) {
223
+ const retryAfterMs = this.availabilityLatch.noteUnavailable("transient", transientCause);
224
+ this.log("vulture probe timed out; will retry (not treated as missing)");
225
+ logAvailabilityDecision({
226
+ tool: "vulture",
227
+ verdict: "unavailable",
228
+ outcome: "transient",
229
+ cause: transientCause,
230
+ elapsedMs: Date.now() - sweepStartedAt,
231
+ hostStallMs: sweepHostStallMs,
232
+ latched: false,
233
+ retryAfterMs,
234
+ budgetMs: 5000,
235
+ });
236
+ return false;
158
237
  }
159
- this.available = false;
238
+ this.availabilityLatch.noteUnavailable("missing", "not-found");
160
239
  this.log("vulture not installed; skipping (no auto-install)");
240
+ logAvailabilityDecision({
241
+ tool: "vulture",
242
+ verdict: "unavailable",
243
+ outcome: "missing",
244
+ cause: "not-found",
245
+ elapsedMs: Date.now() - sweepStartedAt,
246
+ hostStallMs: sweepHostStallMs,
247
+ latched: true,
248
+ budgetMs: 5000,
249
+ });
161
250
  return false;
162
251
  }
163
252
  async analyze(cwd) {
@@ -192,6 +281,7 @@ export class PythonDeadCodeClient {
192
281
  ".",
193
282
  `--min-confidence=${this.minConfidence}`,
194
283
  `--exclude=${VULTURE_EXCLUDES.join(",")}`,
284
+ `--ignore-decorators=${VULTURE_IGNORE_DECORATORS.join(",")}`,
195
285
  ];
196
286
  const result = await safeSpawnAsync(invocation.cmd, args, {
197
287
  timeout: ANALYSIS_TIMEOUT_MS,
@@ -257,30 +347,46 @@ export function deadCodeIssueCount(result) {
257
347
  result.unusedDeps.length +
258
348
  result.unlistedDeps.length);
259
349
  }
350
+ /** Every bucket flattened — delta diffing and diagnostic mapping want one list. */
351
+ export function deadCodeIssues(result) {
352
+ return [
353
+ ...result.unusedExports,
354
+ ...result.unusedFiles,
355
+ ...result.unusedDeps,
356
+ ...result.unlistedDeps,
357
+ ];
358
+ }
260
359
  /**
261
- * Format cached dead-code results into one turn_end advisory, or "" when there
262
- * is nothing to report. Merges multiple languages (polyglot repos) under one
263
- * `[Dead code]` heading so it reads consistently next to the Knip advisory.
264
- * Advisory-only: these are project-wide signals, never blockers.
360
+ * Stable identity for diffing one scan against the previous one.
361
+ *
362
+ * Deliberately excludes the line number. An edit shifts every line below it,
363
+ * and the delta is then filtered to exactly the files the edit touched — so a
364
+ * line in the key turns each shifted pre-existing finding into a "newly unused"
365
+ * report under a heading that blames the agent's own edit for orphaning it.
366
+ * Inserting four lines above one real finding produced four false ones.
265
367
  */
266
- export function formatDeadCodeAdvisory(results, maxPerLang = 10) {
267
- const withFindings = results.filter((r) => r.success && deadCodeIssueCount(r) > 0);
268
- if (withFindings.length === 0)
368
+ export function deadCodeIssueKey(issue) {
369
+ return `${issue.category}:${issue.file ?? ""}:${issue.name}`;
370
+ }
371
+ /**
372
+ * Format this turn's ATTRIBUTABLE delta — symbols that became unused in files
373
+ * the agent just edited — or "" when there is nothing to report. Mirrors the
374
+ * Knip advisory's contract deliberately: the project-wide list is not injected
375
+ * per turn (hundreds of pre-existing findings would drown the blockers and burn
376
+ * context every turn), it stays available on demand via lens_diagnostics.
377
+ */
378
+ export function formatDeadCodeDelta(issues, language, max = 5) {
379
+ if (issues.length === 0)
269
380
  return "";
270
- const lines = [];
271
- for (const r of withFindings) {
272
- const count = deadCodeIssueCount(r);
273
- lines.push(`${r.language}: ${count} unused symbol(s)`);
274
- for (const issue of r.unusedExports.slice(0, maxPerLang)) {
275
- const loc = issue.file
276
- ? ` (${issue.file}${issue.line ? `:${issue.line}` : ""})`
277
- : "";
278
- lines.push(` - unused ${issue.kind} '${issue.name}'${loc}`);
279
- }
280
- if (r.unusedExports.length > maxPerLang) {
281
- lines.push(` … and ${r.unusedExports.length - maxPerLang} more`);
282
- }
381
+ let report = `💀 Newly unused ${language} symbols in files you edited — check if callers need updating (dead-code):\n`;
382
+ for (const issue of issues.slice(0, max)) {
383
+ const loc = issue.file
384
+ ? `${issue.file}${issue.line ? `:${issue.line}` : ""}`
385
+ : "(unknown)";
386
+ report += ` ${loc} — unused ${issue.kind} ${issue.name}\n`;
387
+ }
388
+ if (issues.length > max) {
389
+ report += ` … and ${issues.length - max} more\n`;
283
390
  }
284
- return ("💀 [Dead code] project-wide unused symbols — verify before removing:\n " +
285
- lines.join("\n "));
391
+ return report;
286
392
  }