@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
@@ -14,11 +14,12 @@
14
14
  */
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
+ import { BoundedLruCache } from "../bounded-cache.js";
17
18
  import { normalizeFilePath, walkUpDirs } from "../path-utils.js";
18
19
  // Per-process cache of resolved gitdir location keyed by cwd — the gitdir
19
20
  // location itself is stable for the life of the process; only HEAD's
20
21
  // CONTENT changes (on commit/checkout), so that's re-read fresh every call.
21
- const _gitDirCache = new Map();
22
+ const _gitDirCache = new BoundedLruCache(32);
22
23
  export function _resetGitIdentityCacheForTests() {
23
24
  _gitDirCache.clear();
24
25
  }
@@ -90,7 +90,7 @@ function resolveDart(cwd, filePath, source) {
90
90
  // JS/TS extension from the specifier before re-appending candidate extensions
91
91
  // lets that universal pattern resolve to the real source file. Exported so
92
92
  // builder.ts's warm `localImportToFile` shares the exact same regex (#694).
93
- export const JS_TS_EXT_RE = /\.(mjs|cjs|jsx?|tsx?)$/i;
93
+ export const JS_TS_EXT_RE = /\.(mjs|cjs|mts|cts|jsx?|tsx?)$/i;
94
94
  /**
95
95
  * Ordered candidate list for resolving a relative JS/TS import specifier
96
96
  * (already resolved to an absolute base path) to an on-disk file, with
@@ -122,13 +122,18 @@ function jsTsExtensionCandidates(base, strippedBase, ext) {
122
122
  candidates.push(`${strippedBase}.mts`);
123
123
  if (ext === ".cjs")
124
124
  candidates.push(`${strippedBase}.cts`);
125
- candidates.push(`${strippedBase}.ts`, `${strippedBase}.tsx`);
125
+ if (ext === ".mts")
126
+ candidates.push(`${strippedBase}.mts`);
127
+ if (ext === ".cts")
128
+ candidates.push(`${strippedBase}.cts`);
129
+ candidates.push(`${strippedBase}.ts`, `${strippedBase}.tsx`, `${strippedBase}.mts`, `${strippedBase}.cts`);
126
130
  // The literal specifier path (covers both "no extension in the specifier"
127
131
  // — base === strippedBase — and the compiled/as-written extension itself).
128
- candidates.push(base);
129
- candidates.push(`${strippedBase}.js`, `${strippedBase}.jsx`);
130
- candidates.push(path.join(base, "index.ts"), path.join(base, "index.tsx"), path.join(base, "index.js"), path.join(base, "index.jsx"));
131
- return candidates;
132
+ candidates.push(base, `${strippedBase}.js`, `${strippedBase}.jsx`, `${strippedBase}.mjs`, `${strippedBase}.cjs`);
133
+ for (const index of ["index.ts", "index.tsx", "index.mts", "index.cts", "index.js", "index.jsx", "index.mjs", "index.cjs"]) {
134
+ candidates.push(path.join(base, index));
135
+ }
136
+ return [...new Set(candidates)];
132
137
  }
133
138
  export function jsTsCandidatePaths(filePath, source) {
134
139
  const base = path.resolve(path.dirname(filePath), source);
@@ -198,10 +203,16 @@ function workspaceEntryCandidates(pkgRoot) {
198
203
  // missing/unreadable package.json — fall through to index candidates
199
204
  }
200
205
  const candidates = [];
201
- if (main)
202
- candidates.push(path.resolve(pkgRoot, main));
203
- candidates.push(path.join(pkgRoot, "index.ts"), path.join(pkgRoot, "index.tsx"), path.join(pkgRoot, "index.js"), path.join(pkgRoot, "src", "index.ts"), path.join(pkgRoot, "src", "index.js"));
204
- return candidates;
206
+ if (main) {
207
+ const mainPath = path.resolve(pkgRoot, main);
208
+ const ext = path.extname(mainPath).toLowerCase();
209
+ const stripped = mainPath.replace(JS_TS_EXT_RE, "");
210
+ candidates.push(...jsTsExtensionCandidates(mainPath, stripped, ext));
211
+ }
212
+ for (const base of [pkgRoot, path.join(pkgRoot, "src")]) {
213
+ candidates.push(path.join(base, "index.ts"), path.join(base, "index.tsx"), path.join(base, "index.mts"), path.join(base, "index.cts"), path.join(base, "index.js"), path.join(base, "index.jsx"), path.join(base, "index.mjs"), path.join(base, "index.cjs"));
214
+ }
215
+ return [...new Set(candidates)];
205
216
  }
206
217
  /** Candidate paths for a workspace-package SUBPATH import (`@scope/pkg/foo`)
207
218
  * — same source-twin-preferring extension probe as a relative import, rooted
@@ -1,3 +1,4 @@
1
+ import { detectFileRole } from "../file-role.js";
1
2
  import { normalizeMapKey } from "../path-utils.js";
2
3
  import { findModuleForPath, getDownstreamModules, getModuleSourceFiles, } from "./workspace-modules.js";
3
4
  function dedupe(items) {
@@ -76,6 +77,29 @@ export function computeImpactCascade(graph, changedFile, moduleGraph) {
76
77
  const normalizedFile = normalizeMapKey(changedFile);
77
78
  const fileNodeId = graph.fileNodes.get(normalizedFile);
78
79
  if (!fileNodeId) {
80
+ // #1445: a missing node has two causes that look identical here but mean
81
+ // opposite things — "the graph SHOULD know this file but doesn't" (a real
82
+ // gap) versus "this file's role is excluded from the graph by design"
83
+ // (test files, #260 — every graph-admission chokepoint in builder.ts
84
+ // guards on this exact condition, `detectFileRole(file) === "test"`;
85
+ // reused verbatim here rather than a second role list). A test edit not
86
+ // cascading is expected behavior, not a graph failure, so it must not
87
+ // produce the "review graph was unavailable" advisory that misled agents
88
+ // into distrusting a healthy graph 19% of the time in dogfooding.
89
+ if (detectFileRole(normalizedFile) === "test") {
90
+ return {
91
+ filePath: normalizedFile,
92
+ changedSymbols: [],
93
+ directImporters: [],
94
+ directCallers: [],
95
+ neighborFiles: [],
96
+ riskFlags: [],
97
+ indeterminate: {
98
+ reason: "excluded_by_role",
99
+ detail: "test-role file — excluded from the review graph by design (#260)",
100
+ },
101
+ };
102
+ }
79
103
  // #1023: the changed file has no node in the graph — we CANNOT enumerate
80
104
  // its dependents, so this empty result is "couldn't compute", NOT "nothing
81
105
  // depends on it". Mark it indeterminate so callers surface an honest
@@ -55,3 +55,49 @@
55
55
  export function buildSymbolId(filePath, name, kind, startLine) {
56
56
  return `${filePath}:${name}:${kind}:${startLine}`;
57
57
  }
58
+ /**
59
+ * Structural shape of a kind token in a canonical symbol id: every kind this
60
+ * repo mints — the fixed `buildSymbolId` vocabulary (`function`, `method`,
61
+ * `class`, ...) AND the open-ended LSP-fallback vocabulary from
62
+ * `lspSymbolKindName` (`enum`, `struct`, `type-parameter`, `enum-member`, the
63
+ * `lsp-symbol-<n>` catch-all for unknown LSP `SymbolKind` numbers, ...) —
64
+ * is a lowercase word optionally hyphen-segmented, never containing a colon.
65
+ * Matching this SHAPE (rather than whitelisting specific kind strings) keeps
66
+ * `parseSymbolKey` in agreement with whatever kind vocabulary either minter
67
+ * uses, present or future, without needing a matching update here.
68
+ */
69
+ const KIND_TOKEN_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
70
+ /**
71
+ * Parse both canonical symbol IDs and the legacy `file:name` compatibility
72
+ * shape. Canonical IDs are recognized from their typed line suffix, so drive
73
+ * letters and colons in POSIX filenames are data rather than separators. A
74
+ * known file hint makes the legacy form lossless too; without it, legacy IDs
75
+ * remain inherently ambiguous and use their historical final separator.
76
+ */
77
+ export function parseSymbolKey(key, knownFilePath) {
78
+ if (key.startsWith("file:")) {
79
+ return { filePath: key.slice("file:".length) };
80
+ }
81
+ const canonical = /^(.*):([^:]*):([^:]+):(\d+)$/.exec(key);
82
+ if (canonical && KIND_TOKEN_RE.test(canonical[3])) {
83
+ return {
84
+ filePath: canonical[1],
85
+ symbolName: canonical[2] || undefined,
86
+ kind: canonical[3],
87
+ line: Number(canonical[4]),
88
+ };
89
+ }
90
+ if (knownFilePath && key.startsWith(`${knownFilePath}:`)) {
91
+ return {
92
+ filePath: knownFilePath,
93
+ symbolName: key.slice(knownFilePath.length + 1) || undefined,
94
+ };
95
+ }
96
+ const separator = key.lastIndexOf(":");
97
+ return separator < 0
98
+ ? { filePath: key }
99
+ : {
100
+ filePath: key.slice(0, separator),
101
+ symbolName: key.slice(separator + 1) || undefined,
102
+ };
103
+ }
@@ -1,9 +1,10 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
+ import { BoundedLruCache } from "../bounded-cache.js";
3
4
  import * as path from "node:path";
4
5
  import { findGoverningTsconfigDir, getDirectoryMarkers, } from "../workspace-topology.js";
5
- const cache = new Map();
6
- const referencesCache = new Map();
6
+ const cache = new BoundedLruCache(64);
7
+ const referencesCache = new BoundedLruCache(64);
7
8
  /** Strip JSONC comments and trailing commas without touching string contents. */
8
9
  function parseJsonc(content) {
9
10
  let output = "";
@@ -44,6 +45,50 @@ function parseJsonc(content) {
44
45
  }
45
46
  return JSON.parse(output.replace(/,\s*([}\]])/g, "$1"));
46
47
  }
48
+ function configSignature(configPath) {
49
+ try {
50
+ const stat = fs.statSync(configPath);
51
+ return `${stat.mtimeMs}:${stat.size}`;
52
+ }
53
+ catch {
54
+ return "missing";
55
+ }
56
+ }
57
+ function configDependencyPaths(configPath) {
58
+ const paths = new Set();
59
+ const visit = (currentPath) => {
60
+ const normalized = path.resolve(currentPath);
61
+ if (paths.has(normalized))
62
+ return;
63
+ paths.add(normalized);
64
+ let json;
65
+ try {
66
+ json = parseJsonc(fs.readFileSync(normalized, "utf8"));
67
+ }
68
+ catch {
69
+ return;
70
+ }
71
+ if (typeof json.extends === "string") {
72
+ const parent = resolveExtends(normalized, json.extends);
73
+ if (parent)
74
+ visit(parent);
75
+ }
76
+ for (const reference of json.references ?? []) {
77
+ if (typeof reference?.path !== "string")
78
+ continue;
79
+ const referenced = resolveReferenceConfig(normalized, reference.path);
80
+ if (referenced)
81
+ visit(referenced);
82
+ }
83
+ };
84
+ visit(configPath);
85
+ return [...paths].sort((a, b) => a.localeCompare(b));
86
+ }
87
+ function dependencySignature(configPath) {
88
+ return configDependencyPaths(configPath)
89
+ .map((dependency) => `${dependency}:${configSignature(dependency)}`)
90
+ .join("|");
91
+ }
47
92
  function resolveExtends(configPath, value) {
48
93
  if (!value.startsWith("."))
49
94
  return undefined;
@@ -180,14 +225,17 @@ function collectReferencedProjects(configPath, result, visited) {
180
225
  }
181
226
  /** Find and parse the nearest governing tsconfig, cached per importer directory. */
182
227
  export function parseTsconfigPaths(cwd, homeDir = os.homedir()) {
183
- const key = path.resolve(cwd);
228
+ const normalizedCwd = path.resolve(cwd);
229
+ const configDir = findGoverningTsconfigDir(normalizedCwd, homeDir);
230
+ const configPath = configDir ? path.join(configDir, "tsconfig.json") : "";
231
+ const signature = configPath ? dependencySignature(configPath) : "missing";
232
+ const key = `${normalizedCwd}|${configPath}|${signature}`;
184
233
  const cached = cache.get(key);
185
234
  if (cached)
186
235
  return cached;
187
236
  // Home-guarding is enforced inside findGoverningTsconfigDir's walk itself
188
237
  // (via workspace-topology's shared isAtOrAboveHomeDir ceiling), so a hit
189
238
  // here is never at/above homeDir.
190
- const configDir = findGoverningTsconfigDir(key, homeDir);
191
239
  if (!configDir) {
192
240
  cache.set(key, []);
193
241
  return [];
@@ -224,11 +272,14 @@ export function aliasedImportTargets(specifier, importerDir) {
224
272
  }
225
273
  /** Resolve an exact package-name import through the governing config's project references. */
226
274
  export function referencedProjectImportTarget(specifier, importerDir) {
227
- const key = path.resolve(importerDir);
275
+ const normalizedImporterDir = path.resolve(importerDir);
276
+ const governingDir = findGoverningTsconfigDir(normalizedImporterDir);
277
+ const governingPath = governingDir ? path.join(governingDir, "tsconfig.json") : "";
278
+ const key = `${normalizedImporterDir}|${governingPath}|${governingPath ? dependencySignature(governingPath) : "missing"}`;
228
279
  let projects = referencesCache.get(key);
229
280
  if (!projects) {
230
281
  projects = new Map();
231
- const configDir = findGoverningTsconfigDir(key);
282
+ const configDir = governingDir;
232
283
  if (configDir) {
233
284
  collectReferencedProjects(path.join(configDir, "tsconfig.json"), projects, new Set());
234
285
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as fs from "node:fs";
8
8
  import * as path from "node:path";
9
- import { getProjectIgnoreMatcher, isExcludedDirName } from "../file-utils.js";
9
+ import { getProjectIgnoreMatcher, isExcludedDirName, } from "../file-utils.js";
10
10
  import { normalizeMapKey } from "../path-utils.js";
11
11
  import { getWorkspaceManifestMarkers } from "../workspace-topology.js";
12
12
  // Cap on candidate sub-directories probed per workspace glob level (#262).
@@ -387,6 +387,7 @@ export function buildModuleGraph(cwd) {
387
387
  }
388
388
  export function clearModuleGraphCache() {
389
389
  _moduleGraphCache = null;
390
+ _moduleSourceFilesMemo.clear();
390
391
  }
391
392
  /**
392
393
  * Find the module that owns a given file path.
@@ -419,13 +420,40 @@ export function getDownstreamModules(graph, moduleName) {
419
420
  }
420
421
  return [...downstream];
421
422
  }
423
+ const _moduleSourceFilesMemo = new Map();
424
+ const MTIME_GRANULARITY_GUARD_MS = 2_000;
425
+ function isMemoEntryFresh(entry) {
426
+ const validatedAt = Date.now();
427
+ for (const { dir, mtimeMs, stampedAt } of entry.dirs) {
428
+ if (validatedAt - stampedAt < MTIME_GRANULARITY_GUARD_MS)
429
+ return false;
430
+ try {
431
+ if (fs.statSync(dir).mtimeMs !== mtimeMs)
432
+ return false;
433
+ }
434
+ catch {
435
+ return false;
436
+ }
437
+ }
438
+ return true;
439
+ }
422
440
  /**
423
- * Get representative source files in a module.
441
+ * Get representative source files in a module. Memoized per module root (see
442
+ * above) — the returned array is a copy; mutating it does not affect the memo.
424
443
  */
425
444
  export function getModuleSourceFiles(moduleRoot, maxFiles = 20) {
426
- const files = [];
427
445
  const root = normalizeMapKey(moduleRoot);
428
446
  const ignoreMatcher = getProjectIgnoreMatcher(root);
447
+ const cached = _moduleSourceFilesMemo.get(root);
448
+ if (cached &&
449
+ cached.maxFiles === maxFiles &&
450
+ cached.matcher === ignoreMatcher &&
451
+ isMemoEntryFresh(cached)) {
452
+ return [...cached.files];
453
+ }
454
+ const files = [];
455
+ const dirs = [];
456
+ let allDirsStamped = true;
429
457
  const visit = (dir, depth) => {
430
458
  if (files.length >= maxFiles || depth > 4)
431
459
  return;
@@ -436,6 +464,18 @@ export function getModuleSourceFiles(moduleRoot, maxFiles = 20) {
436
464
  catch {
437
465
  return;
438
466
  }
467
+ // Stamp AFTER the readdir: a change landing between readdir and stat
468
+ // records the newer mtime, so validation re-walks (fail-safe direction).
469
+ try {
470
+ dirs.push({
471
+ dir,
472
+ mtimeMs: fs.statSync(dir).mtimeMs,
473
+ stampedAt: Date.now(),
474
+ });
475
+ }
476
+ catch {
477
+ allDirsStamped = false;
478
+ }
439
479
  entries.sort((a, b) => a.name.localeCompare(b.name));
440
480
  for (const entry of entries) {
441
481
  if (files.length >= maxFiles)
@@ -458,5 +498,13 @@ export function getModuleSourceFiles(moduleRoot, maxFiles = 20) {
458
498
  }
459
499
  };
460
500
  visit(root, 0);
501
+ if (allDirsStamped) {
502
+ _moduleSourceFilesMemo.set(root, {
503
+ files: [...files],
504
+ maxFiles,
505
+ dirs,
506
+ matcher: ignoreMatcher,
507
+ });
508
+ }
461
509
  return files;
462
510
  }
@@ -5,11 +5,40 @@ import { createNdjsonLogger } from "./ndjson-logger.js";
5
5
  const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensDir();
6
6
  const REVIEW_GRAPH_LOG_FILE = path.join(REVIEW_GRAPH_LOG_DIR, "review-graph.log");
7
7
  const writer = createNdjsonLogger({ filePath: REVIEW_GRAPH_LOG_FILE });
8
+ /**
9
+ * Build the one canonical graph metadata shape used by build and persist logs.
10
+ * Counts come from the graph instance; source-file count may use the exact
11
+ * signature-map count when the build has one, otherwise the graph file index.
12
+ */
13
+ export function makeReviewGraphBuildMetadata(graph, options = {}) {
14
+ return {
15
+ ...(options.buildId === undefined ? {} : { buildId: options.buildId }),
16
+ ...(graph.buildGeneration === undefined
17
+ ? {}
18
+ : { graphGeneration: graph.buildGeneration }),
19
+ builtAt: graph.builtAt,
20
+ ...(options.projectSeq === undefined
21
+ ? {}
22
+ : { projectSeq: options.projectSeq }),
23
+ ...(options.seqHint === undefined ? {} : { seqHint: options.seqHint }),
24
+ ...(options.mode === undefined ? {} : { mode: options.mode }),
25
+ sourceFiles: options.sourceFileCount ?? graph.fileNodes.size,
26
+ ...(options.sourceFileCountTruncated ||
27
+ graph.persistCoverage?.sourceFilesTruncated
28
+ ? { sourceFilesTruncated: true }
29
+ : {}),
30
+ nodes: graph.nodes.size,
31
+ edges: graph.edges.length,
32
+ ...(graph.persistCoverage
33
+ ? { persistCoverage: graph.persistCoverage }
34
+ : {}),
35
+ };
36
+ }
8
37
  export function logReviewGraph(entry) {
9
38
  if (isTestMode()) {
10
39
  return;
11
40
  }
12
- writer.log({ ts: new Date().toISOString(), ...entry });
41
+ writer.log({ ts: new Date().toISOString(), ...entry, pid: process.pid });
13
42
  }
14
43
  export function getReviewGraphLogPath() {
15
44
  return REVIEW_GRAPH_LOG_FILE;
@@ -7,62 +7,36 @@
7
7
  * Requires: pip install ruff
8
8
  * Docs: https://docs.astral.sh/ruff/
9
9
  */
10
+ import { createSubsystemLogger } from "./extension-log.js";
10
11
  import * as fs from "node:fs";
11
12
  import * as path from "node:path";
13
+ import { createAvailabilityChecker, resolveAvailableOrInstall } from "./dispatch/runners/utils/runner-helpers.js";
12
14
  import { isFileKind } from "./file-kinds.js";
13
15
  import { safeSpawnAsync } from "./safe-spawn.js";
16
+ import { ruffConfigArgs } from "./tool-policy.js";
14
17
  // --- Client ---
18
+ const ruffAvailability = createAvailabilityChecker("ruff", ".exe");
15
19
  export class RuffClient {
16
- ruffAvailable = null;
17
- ensureInFlight = null;
20
+ ruffCommand = "ruff";
18
21
  log;
19
22
  constructor(verbose = false) {
20
23
  this.log = verbose
21
- ? (msg) => console.error(`[ruff] ${msg}`)
24
+ ? createSubsystemLogger("ruff")
22
25
  : () => { };
23
26
  }
24
27
  /**
25
28
  * Check if ruff CLI is available, auto-install if not.
26
29
  *
27
- * Re-entrancy safe: concurrent first-time callers share a single
28
- * `ensureInFlight` promise so probing/auto-install isn't duplicated.
29
- * Mirrors the dedupe pattern in `SgRunner` / `KnipClient` /
30
- * `DependencyChecker`.
30
+ * Re-entrancy safe: the shared availability seam deduplicates the complete
31
+ * probe/auto-install transaction per cwd and tool, so concurrent callers do
32
+ * not duplicate installation attempts.
31
33
  */
32
34
  async ensureAvailable() {
33
- if (this.ruffAvailable !== null)
34
- return this.ruffAvailable;
35
- if (this.ensureInFlight)
36
- return this.ensureInFlight;
37
- this.ensureInFlight = this.doEnsureAvailable();
38
- try {
39
- return await this.ensureInFlight;
40
- }
41
- finally {
42
- this.ensureInFlight = null;
43
- }
44
- }
45
- async doEnsureAvailable() {
46
- // Check if available in PATH
47
- const result = await safeSpawnAsync("ruff", ["--version"], {
48
- timeout: 5000,
49
- });
50
- this.ruffAvailable = !result.error && result.status === 0;
51
- if (this.ruffAvailable) {
52
- this.log(`Ruff found: ${result.stdout.trim()}`);
53
- return true;
54
- }
55
- // Auto-install via pi-lens installer
56
- this.log("Ruff not found, attempting auto-install...");
57
- const { ensureTool } = await import("./installer/index.js");
58
- const installedPath = await ensureTool("ruff");
59
- if (installedPath) {
60
- this.log(`Ruff auto-installed: ${installedPath}`);
61
- this.ruffAvailable = true;
62
- return true;
63
- }
64
- this.log("Ruff auto-install failed");
65
- return false;
35
+ const resolved = await resolveAvailableOrInstall(ruffAvailability, "ruff", process.cwd());
36
+ if (!resolved)
37
+ return false;
38
+ this.ruffCommand = resolved;
39
+ return true;
66
40
  }
67
41
  /**
68
42
  * Check if a file is a Python file
@@ -72,8 +46,10 @@ export class RuffClient {
72
46
  }
73
47
  /**
74
48
  * Async auto-fix variant for pipeline use (non-blocking spawn).
49
+ * `cwd` is the dispatch language root (used for config discovery); when
50
+ * omitted it defaults to the file's directory.
75
51
  */
76
- async fixFileAsync(filePath) {
52
+ async fixFileAsync(filePath, cwd) {
77
53
  if (!(await this.ensureAvailable())) {
78
54
  return {
79
55
  success: false,
@@ -93,19 +69,26 @@ export class RuffClient {
93
69
  }
94
70
  try {
95
71
  const before = await fs.promises.readFile(absolutePath, "utf-8");
96
- const pre = await safeSpawnAsync("ruff", [
72
+ // Shared config-args seam (#1247): the lint runner consumes the same
73
+ // builder, so `check --fix` can never drift to ruff's default rule
74
+ // set when the project lacks its own config and the package-owned
75
+ // core.toml fallback applies.
76
+ const configArgs = ruffConfigArgs(cwd ?? path.dirname(absolutePath));
77
+ const spawnOpts = { timeout: 10000, cwd: cwd ?? path.dirname(absolutePath) };
78
+ const pre = await safeSpawnAsync(this.ruffCommand, [
97
79
  "check",
98
80
  "--output-format",
99
81
  "json",
100
82
  "--target-version",
101
83
  "py310",
84
+ ...configArgs,
102
85
  absolutePath,
103
- ], { timeout: 10000 });
86
+ ], spawnOpts);
104
87
  const beforeDiags = pre.stdout?.trim()
105
88
  ? this.parseOutput(pre.stdout, absolutePath)
106
89
  : [];
107
90
  const fixableCount = beforeDiags.filter((d) => d.fixable).length;
108
- const fix = await safeSpawnAsync("ruff", ["check", "--fix", absolutePath], { timeout: 15000 });
91
+ const fix = await safeSpawnAsync(this.ruffCommand, ["check", "--fix", ...configArgs, absolutePath], { timeout: 15000, cwd: cwd ?? path.dirname(absolutePath) });
109
92
  if (fix.error) {
110
93
  return {
111
94
  success: false,
@@ -0,0 +1,55 @@
1
+ /**
2
+ * #1479/#1480: one place decides whether a test run was actually timed, and
3
+ * one place renders the answer.
4
+ *
5
+ * `TestResult.duration` is optional, and the two states are different facts.
6
+ * A present number is a MEASUREMENT — including `0`, because pytest really
7
+ * does print `in 0.00s` and a suite whose `startTime` equals its `endTime`
8
+ * really did run in under a millisecond. An absent value means no
9
+ * runner-reported figure was found at all.
10
+ *
11
+ * That contract was written down once, in the doc comment on
12
+ * `TestResult.duration`, and then re-derived at every site that reads or
13
+ * produces a duration. A reader of any one of those sites cannot see the
14
+ * contract, and a producer that reaches for `0` when it means "did not
15
+ * measure" reintroduces exactly the defect #1479 removed. These three
16
+ * functions are the contract in executable form: `isMeasuredDuration`
17
+ * classifies, `toMeasuredDurationMs` normalises a freshly parsed figure into
18
+ * it, and `formatRunDurationMs` renders it.
19
+ *
20
+ * NOTE FOR ANYONE EDITING `isMeasuredDuration`: the predicate is `>= 0`, not
21
+ * `> 0`. A measured zero is real. `> 0` looks like a harmless tightening and
22
+ * is the single change that would silently restore the bug.
23
+ *
24
+ * Kept in its own module rather than exported from `test-runner-client.ts`:
25
+ * `runtime-turn.ts` needs it on the hot turn-end path, and the client is
26
+ * deliberately loaded lazily (`bootstrap.ts` dynamic-imports it), so a value
27
+ * import from there would drag the whole runner into startup.
28
+ */
29
+ /**
30
+ * True only for a value that represents a real measurement.
31
+ *
32
+ * `0` is measured. `undefined`, `null`, `NaN`, the infinities, and a negative
33
+ * figure are not: the last three can only come from a garbled parse, and a
34
+ * garbled parse must degrade to "we did not measure this", never to a wrong
35
+ * number.
36
+ */
37
+ export function isMeasuredDuration(duration) {
38
+ return (typeof duration === "number" && Number.isFinite(duration) && duration >= 0);
39
+ }
40
+ /**
41
+ * Normalise a parsed elapsed time to the `TestResult.duration` contract.
42
+ *
43
+ * Returns whole milliseconds for a real reading — `0` included — and
44
+ * `undefined` for anything that is not one, so a producer never has to spell
45
+ * out the absent case itself.
46
+ */
47
+ export function toMeasuredDurationMs(duration) {
48
+ return isMeasuredDuration(duration) ? Math.round(duration) : undefined;
49
+ }
50
+ /** Render a duration for a log line: `123ms`, or `unmeasured` when absent. */
51
+ export function formatRunDurationMs(duration) {
52
+ return isMeasuredDuration(duration)
53
+ ? `${Math.round(duration)}ms`
54
+ : "unmeasured";
55
+ }