@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Automatic smells self-surfacing (#1123 item 3).
3
+ *
4
+ * `scripts/analyze-pi-lens-logs.mjs` (`npm run logs:smells`) already detects
5
+ * a wide catalogue of operational smells, but it is MANUAL — an operator has
6
+ * to think to run it. The #1123 investigation found 20 stale-ctx
7
+ * `emit_failed` rows (see `session-lifecycle.ts`'s stale-ctx guard) and 37
8
+ * opengrep respawns sitting in logs for days before an audit went looking.
9
+ * This module closes that gap with a small, ALWAYS-ON rollup that surfaces a
10
+ * subset of the analyzer's smells without anyone running the script.
11
+ *
12
+ * COST BOUND (the constraint that shapes the design): `~/.pi-lens/*.log`
13
+ * files are size-rotated at `PI_LENS_MAX_LOG_SIZE_MB` (default 10MB,
14
+ * `clients/log-cleanup.ts`). Re-scanning a whole rotated log on every
15
+ * `session_start` — the analyzer's own approach — is exactly the megabyte
16
+ * full-file read `session_start` (a hot, input-latency-sensitive path, see
17
+ * `runtime-session.ts`'s cold-start comment) cannot afford. So this module
18
+ * does a BOUNDED TAIL READ: at most `SMELLS_TAIL_BYTES_PER_FILE` bytes from
19
+ * the END of each source log, via a single `pread`-style `fs.readSync` at a
20
+ * computed offset — never a full-file read, by construction (see
21
+ * `tailReadText`). Two source files, ~64KB each: ~128KB total I/O, once per
22
+ * session_start plus one bounded re-check every `SMELLS_TURN_CHECK_INTERVAL`
23
+ * turns — negligible next to the multi-second full startup walk it rides
24
+ * alongside.
25
+ *
26
+ * The tail scan is bounded, but the tail can contain prior-session rows. The
27
+ * session-start caller supplies the session boundary, and rows are admitted
28
+ * only when their own UTC timestamp is at or after that boundary. This keeps
29
+ * the same bounded read without reporting historical failures as current, so
30
+ * this module does not additionally instrument the write call sites
31
+ * (`bus-publish.ts`, `clients/lsp/index.ts`'s respawn path) with parallel
32
+ * live counters. That keeps the change contained to one new module + three
33
+ * small call sites (session_start, `/lens-health`, `turn_end`) instead of
34
+ * touching every producer of the two source logs.
35
+ *
36
+ * Smells covered (deliberately a SUBSET — the two the issue named as having
37
+ * gone unnoticed; the full catalogue stays in the manual analyzer):
38
+ * - stale-ctx `emit_failed`: `bus-events.log` rows where
39
+ * `outcome === "emit_failed"` and `error` contains the SDK's
40
+ * `"stale after session replacement"` fragment (the known-benign class
41
+ * `session-lifecycle.ts` documents — still worth a glance if it recurs
42
+ * often, since a HIGH count could mask a real regression).
43
+ * - opengrep respawns: `latency.log` rows where
44
+ * `phase === "lsp_server_respawn"` and `metadata.serverId === "opengrep"`
45
+ * (`clients/lsp/index.ts`'s existing respawn log point — unmodified).
46
+ *
47
+ * Both counters are gated by trivial threshold constants (`SMELLS_THRESHOLDS`)
48
+ * so a single stray event never surfaces noise — only a repeating pattern
49
+ * does, matching the analyzer's own low/medium/high severity gating.
50
+ */
51
+ import * as fs from "node:fs";
52
+ import * as path from "node:path";
53
+ import { getGlobalPiLensDir } from "./file-utils.js";
54
+ /** Bounded tail-read budget PER source log file — never a full-file scan. */
55
+ export const SMELLS_TAIL_BYTES_PER_FILE = 64 * 1024;
56
+ /** Re-check cadence at `turn_end`, mirroring `memory-sampler.ts`'s pattern —
57
+ * cheap enough (bounded ~128KB I/O) not to need finer throttling, but a
58
+ * fixed interval keeps it from running on every single turn. */
59
+ export const SMELLS_TURN_CHECK_INTERVAL = 20;
60
+ /** Trivial threshold constants (#1123 item 3's explicit design ask). Counts
61
+ * strictly below these never surface anywhere. */
62
+ export const SMELLS_THRESHOLDS = {
63
+ staleCtxEmitFailed: 5,
64
+ opengrepRespawn: 5,
65
+ };
66
+ const SMELL_LABELS = {
67
+ staleCtxEmitFailed: "stale-ctx emit_failed (bus-events.log)",
68
+ opengrepRespawn: "opengrep respawn (latency.log)",
69
+ };
70
+ const SMELL_KEYS = ["staleCtxEmitFailed", "opengrepRespawn"];
71
+ /**
72
+ * Read at most `maxBytes` from the END of `filePath`. Never reads more than
73
+ * that regardless of the file's actual size — the cost bound is enforced by
74
+ * construction (a single sized `fs.readSync` at a computed offset), not by a
75
+ * convention callers have to remember. Missing file / unreadable → "".
76
+ *
77
+ * The leading (possibly partial) line is dropped whenever the read started
78
+ * mid-file, since we can't tell if it's a truncated JSON row.
79
+ */
80
+ export function tailReadText(filePath, maxBytes) {
81
+ let fd;
82
+ try {
83
+ fd = fs.openSync(filePath, "r");
84
+ }
85
+ catch {
86
+ return "";
87
+ }
88
+ try {
89
+ const size = fs.fstatSync(fd).size;
90
+ const readBytes = Math.min(size, maxBytes);
91
+ if (readBytes <= 0)
92
+ return "";
93
+ const start = size - readBytes;
94
+ const buf = Buffer.alloc(readBytes);
95
+ fs.readSync(fd, buf, 0, readBytes, start);
96
+ const text = buf.toString("utf8");
97
+ if (start <= 0)
98
+ return text;
99
+ const firstNewline = text.indexOf("\n");
100
+ return firstNewline >= 0 ? text.slice(firstNewline + 1) : "";
101
+ }
102
+ catch {
103
+ return "";
104
+ }
105
+ finally {
106
+ try {
107
+ fs.closeSync(fd);
108
+ }
109
+ catch {
110
+ /* already closed / nothing to do */
111
+ }
112
+ }
113
+ }
114
+ function countMatchingLines(text, predicate, sessionStartMs) {
115
+ let count = 0;
116
+ for (const line of text.split("\n")) {
117
+ if (!line.trim())
118
+ continue;
119
+ let entry;
120
+ try {
121
+ entry = JSON.parse(line);
122
+ }
123
+ catch {
124
+ continue; // partial/corrupt line at the tail boundary — skip, don't throw
125
+ }
126
+ if (entry &&
127
+ typeof entry === "object" &&
128
+ (sessionStartMs === undefined ||
129
+ isAtOrAfterSessionStart(entry, sessionStartMs)) &&
130
+ predicate(entry)) {
131
+ count++;
132
+ }
133
+ }
134
+ return count;
135
+ }
136
+ function isAtOrAfterSessionStart(entry, sessionStartMs) {
137
+ const timestamp = typeof entry.ts === "string" ? Date.parse(entry.ts) : NaN;
138
+ return Number.isFinite(timestamp) && timestamp >= sessionStartMs;
139
+ }
140
+ function isStaleCtxEmitFailed(entry) {
141
+ return (entry.outcome === "emit_failed" &&
142
+ typeof entry.error === "string" &&
143
+ entry.error.includes("stale after session replacement"));
144
+ }
145
+ function isOpengrepRespawn(entry) {
146
+ if (entry.phase !== "lsp_server_respawn")
147
+ return false;
148
+ const metadata = entry.metadata;
149
+ return metadata?.serverId === "opengrep";
150
+ }
151
+ /**
152
+ * Bounded tail-scan of the two source logs. Total I/O is capped at
153
+ * `2 * SMELLS_TAIL_BYTES_PER_FILE` regardless of how large the (size-rotated,
154
+ * up to ~10MB) source files are. `root` is injectable for tests.
155
+ */
156
+ export function countRecentSmells(root = getGlobalPiLensDir(), sessionStartMs) {
157
+ const busTail = tailReadText(path.join(root, "bus-events.log"), SMELLS_TAIL_BYTES_PER_FILE);
158
+ const latencyTail = tailReadText(path.join(root, "latency.log"), SMELLS_TAIL_BYTES_PER_FILE);
159
+ return {
160
+ staleCtxEmitFailed: countMatchingLines(busTail, isStaleCtxEmitFailed, sessionStartMs),
161
+ opengrepRespawn: countMatchingLines(latencyTail, isOpengrepRespawn, sessionStartMs),
162
+ };
163
+ }
164
+ /**
165
+ * One `sessionstart.log` line, gated to fire only when at least one smell is
166
+ * AT/ABOVE its threshold — `null` (nothing logged) otherwise, so an ordinary
167
+ * session never gets a noise line. Pairs with `logSessionStart` the same way
168
+ * every other `session_start ...` line does (see `runtime-session.ts`).
169
+ */
170
+ export function formatSmellsSessionStartLine(counts) {
171
+ const bits = [];
172
+ for (const key of SMELL_KEYS) {
173
+ if (counts[key] >= SMELLS_THRESHOLDS[key]) {
174
+ bits.push(`${SMELL_LABELS[key]} x${counts[key]}`);
175
+ }
176
+ }
177
+ if (bits.length === 0)
178
+ return null;
179
+ const tailKb = Math.round(SMELLS_TAIL_BYTES_PER_FILE / 1024);
180
+ return `session_start smells (last ${tailKb}KB tail): ${bits.join(", ")} — run \`npm run logs:smells\` for detail`;
181
+ }
182
+ /**
183
+ * Compact, ALWAYS-shown `/lens-health` line (unconditional, like
184
+ * `memory-sampler.ts`'s `formatMemoryHealthLine` — the health command is
185
+ * explicitly requested, so it always renders current counts even when
186
+ * everything is below threshold).
187
+ */
188
+ export function formatSmellsHealthLine(counts) {
189
+ return (`Smells (recent tail-scan): stale-ctx emit_failed=${counts.staleCtxEmitFailed}` +
190
+ ` · opengrep respawn=${counts.opengrepRespawn}`);
191
+ }
192
+ /** `true` on turn 20, 40, 60, ... — never turn 0. Pure so the cadence is
193
+ * unit-testable without driving a real turn loop (mirrors
194
+ * `memory-sampler.ts`'s `shouldEmitMemorySample`). */
195
+ export function shouldCheckSmellsThisTurn(turnIndex) {
196
+ return turnIndex > 0 && turnIndex % SMELLS_TURN_CHECK_INTERVAL === 0;
197
+ }
198
+ // Module-scope session state: which smells have already produced a turn-end
199
+ // note THIS session. Bounded to once per session per smell by construction —
200
+ // see `checkSmellsAndNoteOnce`.
201
+ const notifiedThisSession = new Set();
202
+ /**
203
+ * Given a fresh `counts` read, return zero or more turn-end note strings for
204
+ * smells crossing threshold for the FIRST time this session. A smell already
205
+ * notified this session never fires again, even if its count keeps growing —
206
+ * "once per session per smell" per #1123 item 3's design ask.
207
+ */
208
+ export function checkSmellsAndNoteOnce(counts) {
209
+ const notes = [];
210
+ for (const key of SMELL_KEYS) {
211
+ if (notifiedThisSession.has(key))
212
+ continue;
213
+ if (counts[key] >= SMELLS_THRESHOLDS[key]) {
214
+ notifiedThisSession.add(key);
215
+ notes.push(`pi-lens smell: ${SMELL_LABELS[key]} x${counts[key]} (recent tail-scan) — run \`npm run logs:smells\` for detail`);
216
+ }
217
+ }
218
+ return notes;
219
+ }
220
+ /** Reset the "already notified this session" state — call at `session_start`
221
+ * (a fresh session should be able to re-report) and from tests. */
222
+ export function resetSmellsSessionState() {
223
+ notifiedThisSession.clear();
224
+ }
@@ -19,8 +19,9 @@
19
19
  import * as fs from "node:fs";
20
20
  import * as path from "node:path";
21
21
  import { getProjectIgnoreMatcher } from "./file-utils.js";
22
- import { isDeclarationFile, isGeneratedOrArtifact, } from "./generated-artifacts.js";
22
+ import { classifyGeneratedOrArtifactDetailed, isDeclarationFile, } from "./generated-artifacts.js";
23
23
  import { isCodeKindFile, KIND_EXTENSIONS } from "./file-kinds.js";
24
+ import { logLatency } from "./latency-logger.js";
24
25
  import { normalizeEphemeralMapKey } from "./path-utils.js";
25
26
  import { isSlowFs, SLOW_FS_REDUCED_MAX_FILES } from "./slow-fs.js";
26
27
  import { shouldRecurseIntoDir, walkTreeRecursiveSync, walkTreeStackAsync, } from "./source-walker.js";
@@ -49,12 +50,50 @@ function probeExists(filePath, cache) {
49
50
  * Order matters: first entry has highest precedence.
50
51
  */
51
52
  export const SOURCE_PRECEDENCE = {
52
- ".ts": [".js", ".mjs", ".cjs"],
53
+ ".ts": [".js", ".jsx", ".mjs", ".cjs"],
53
54
  ".tsx": [".jsx", ".js", ".mjs", ".cjs"],
55
+ ".mts": [".mjs", ".js", ".jsx", ".cjs"],
56
+ ".cts": [".cjs", ".js", ".jsx", ".mjs"],
54
57
  ".vue": [".js", ".mjs"],
55
58
  ".svelte": [".js", ".mjs"],
56
59
  ".coffee": [".js"],
57
60
  };
61
+ /**
62
+ * Return source-twin candidates in the policy order for a compiled path.
63
+ * This is the pure counterpart to {@link findSourceSibling}: callers that
64
+ * already have a complete file identity set (the review graph and lens map)
65
+ * must not probe the filesystem or reimplement the extension matrix.
66
+ */
67
+ export function sourceTwinCandidates(filePath) {
68
+ const ext = path.extname(filePath).toLowerCase();
69
+ const stem = filePath.slice(0, filePath.length - ext.length);
70
+ const sourceExts = ext === ".mjs"
71
+ ? [".mts", ".ts", ".tsx", ".cts"]
72
+ : ext === ".cjs"
73
+ ? [".cts", ".ts", ".tsx", ".mts"]
74
+ : ext === ".jsx"
75
+ // Deliberately retain the broad fallback: a .jsx next to a .ts
76
+ // is treated as a build artifact even without a .tsx sibling.
77
+ ? [".tsx", ".ts", ".mts", ".cts"]
78
+ : ext === ".js"
79
+ ? [".ts", ".tsx", ".mts", ".cts"]
80
+ : [];
81
+ return sourceExts.map((sourceExt) => `${stem}${sourceExt}`);
82
+ }
83
+ /**
84
+ * Resolve a compiled file to its canonical source identity within a known
85
+ * file set. The caller supplies normalized identities; this helper deliberately
86
+ * does not touch the filesystem, so complete source-set walks remain bounded
87
+ * and point-in-time consistent.
88
+ */
89
+ export function canonicalSourceTwin(filePath, availableFiles, excludedFiles) {
90
+ for (const candidate of sourceTwinCandidates(filePath)) {
91
+ if (availableFiles.has(candidate) && !excludedFiles?.has(candidate)) {
92
+ return candidate;
93
+ }
94
+ }
95
+ return filePath;
96
+ }
58
97
  /**
59
98
  * Every extension belonging to a supported file kind. KIND_EXTENSIONS is the
60
99
  * single language-extension authority; deriving here makes a newly registered
@@ -142,16 +181,78 @@ function createKeptFilesAccumulator(maxFiles, prioritizeCodeKinds) {
142
181
  list: () => [...codeFiles, ...otherFiles].slice(0, maxFiles),
143
182
  };
144
183
  }
145
- function shouldSkipGeneratedOrArtifact(filePath, options) {
184
+ function createSourceWalkSkipCounters() {
185
+ return {
186
+ generatedOrArtifactSkips: 0,
187
+ buildArtifactSkips: 0,
188
+ generatedDirSkips: 0,
189
+ generatedNameOverrides: 0,
190
+ generatedNameOnlySkips: 0,
191
+ };
192
+ }
193
+ /**
194
+ * Log a one-line rollup for a walk's skip counters through the existing
195
+ * latency/scan logging channel (`logLatency`, ndjson `latency.log`) — mirrors
196
+ * how `getGraphSourceFiles` logs `review_graph_source_walk_entry_budget`.
197
+ * Silent (no log line) when both counters are zero, so a healthy walk with no
198
+ * name/artifact-probe skips produces no new log noise.
199
+ *
200
+ * Logged once per walk call, at the walk layer itself
201
+ * (`collectSourceFilesWithBudget`/`collectSourceFilesWithBudgetAsync`) rather
202
+ * than at each of its several consumers (review-graph builder, project-
203
+ * diagnostics scanner, word-index, …): every one of those consumers funnels
204
+ * through these two functions (directly or via the plain `collectSourceFiles`/
205
+ * `collectSourceFilesAsync` wrappers), so logging here is a single choke point
206
+ * that covers all of them — the smaller diff versus exporting the counters and
207
+ * wiring a log call into each consumer separately.
208
+ */
209
+ function logSourceWalkSkipsIfAny(rootDir, counters) {
210
+ if (counters.generatedOrArtifactSkips === 0 &&
211
+ counters.buildArtifactSkips === 0 &&
212
+ counters.generatedDirSkips === 0 &&
213
+ counters.generatedNameOverrides === 0) {
214
+ return;
215
+ }
216
+ logLatency({
217
+ type: "phase",
218
+ phase: "source_walk_skip_summary",
219
+ filePath: rootDir,
220
+ durationMs: 0,
221
+ metadata: {
222
+ generatedOrArtifactSkips: counters.generatedOrArtifactSkips,
223
+ buildArtifactSkips: counters.buildArtifactSkips,
224
+ generatedDirSkips: counters.generatedDirSkips,
225
+ generatedNameOverrides: counters.generatedNameOverrides,
226
+ generatedNameOnlySkips: counters.generatedNameOnlySkips,
227
+ },
228
+ });
229
+ }
230
+ /**
231
+ * Full verdict+evidence form of {@link shouldSkipGeneratedOrArtifact} (#1107
232
+ * phase 2) — exposes `classifyGeneratedOrArtifactDetailed`'s `"override"`
233
+ * outcome (a WEAK name-only match the content-probe escape hatch rescued)
234
+ * AND, for a `"generated"` verdict, WHICH evidence tier decided it, so
235
+ * `classifyEntry` can count a genuine skip, a rescue, and the residual
236
+ * "unconfirmed name-only skip" bucket all distinctly. See
237
+ * `GeneratedArtifactEvidence`'s doc for why the tier split matters: a
238
+ * tool-facing notice keyed on the RAW skip count fires permanently on any
239
+ * repo with a lockfile, drowning the signal it exists to surface.
240
+ */
241
+ function classifySkipGeneratedOrArtifact(filePath, options) {
146
242
  const includeDeclarations = options?.includeDeclarationFiles === true;
147
243
  if (options?.includeGenerated === true) {
148
- return !includeDeclarations && isDeclarationFile(filePath);
244
+ return !includeDeclarations && isDeclarationFile(filePath)
245
+ ? { verdict: "generated", evidence: "strong" }
246
+ : { verdict: "clean" };
149
247
  }
150
- return isGeneratedOrArtifact(filePath, {
248
+ return classifyGeneratedOrArtifactDetailed(filePath, {
151
249
  readContentHeader: options?.inspectGeneratedHeaders !== false,
152
250
  includeDeclarations: !includeDeclarations,
153
251
  });
154
252
  }
253
+ function shouldSkipGeneratedOrArtifact(filePath, options) {
254
+ return classifySkipGeneratedOrArtifact(filePath, options).verdict === "generated";
255
+ }
155
256
  /**
156
257
  * Extract the basename (filename without extension) from a path.
157
258
  */
@@ -170,17 +271,20 @@ function getDir(filePath) {
170
271
  * Returns the shadowing source file path if found, null otherwise.
171
272
  */
172
273
  export function findSourceSibling(filePath, probeCache) {
274
+ for (const candidate of sourceTwinCandidates(filePath)) {
275
+ if (probeExists(candidate, probeCache))
276
+ return candidate;
277
+ }
278
+ // Keep the legacy non-JS source policies (Vue/Svelte/CoffeeScript) on the
279
+ // same seam without making them part of the JS/TS twin matrix above.
173
280
  const ext = path.extname(filePath).toLowerCase();
174
281
  const dir = getDir(filePath);
175
282
  const base = getBasename(filePath);
176
- // Find which precedence group this extension belongs to
177
283
  for (const [sourceExt, shadowedExts] of Object.entries(SOURCE_PRECEDENCE)) {
178
- if (shadowedExts.includes(ext)) {
179
- // This file could be shadowed by a source file with sourceExt
284
+ if (!sourceExtsForCompiledExt(sourceExt).length && shadowedExts.includes(ext)) {
180
285
  const siblingPath = path.join(dir, base + sourceExt);
181
- if (probeExists(siblingPath, probeCache)) {
286
+ if (probeExists(siblingPath, probeCache))
182
287
  return siblingPath;
183
- }
184
288
  }
185
289
  }
186
290
  return null;
@@ -191,6 +295,11 @@ export function findSourceSibling(filePath, probeCache) {
191
295
  * @param probeCache - Optional per-walk memo (see {@link ArtifactProbeCache}).
192
296
  * Omit for the original, uncached behavior.
193
297
  */
298
+ function sourceExtsForCompiledExt(sourceExt) {
299
+ return sourceExt === ".ts" || sourceExt === ".tsx" || sourceExt === ".mts" || sourceExt === ".cts"
300
+ ? [sourceExt]
301
+ : [];
302
+ }
194
303
  export function isBuildArtifact(filePath, probeCache) {
195
304
  return findSourceSibling(filePath, probeCache) !== null;
196
305
  }
@@ -261,7 +370,7 @@ function resolveCollectionConfig(rootDir, options, config) {
261
370
  * produce identical results — the only difference between the two is that the
262
371
  * async variant yields to the event loop every N entries.
263
372
  */
264
- function classifyEntry(entry, fullPath, cfg, probeCache) {
373
+ function classifyEntry(entry, fullPath, cfg, probeCache, skipCounters) {
265
374
  const { ignoreMatcher, extraExcludePatterns, extensions, options } = cfg;
266
375
  if (entry.isDirectory()) {
267
376
  const canRecurse = shouldRecurseIntoDir(entry, fullPath, {
@@ -269,7 +378,14 @@ function classifyEntry(entry, fullPath, cfg, probeCache) {
269
378
  extraExcludeDirs: extraExcludePatterns,
270
379
  skipGeneratedArtifactDirs: options?.includeGenerated !== true,
271
380
  followSymlinks: options?.followSymlinks === true,
272
- });
381
+ },
382
+ // #1107 phase 2: counts one event per PRUNED DIRECTORY, not per file
383
+ // inside it — see `generatedDirSkips`'s doc on `SourceCollectionResult`.
384
+ skipCounters
385
+ ? () => {
386
+ skipCounters.generatedDirSkips += 1;
387
+ }
388
+ : undefined);
273
389
  if (!canRecurse)
274
390
  return {};
275
391
  return { recurseInto: fullPath };
@@ -281,10 +397,35 @@ function classifyEntry(entry, fullPath, cfg, probeCache) {
281
397
  if (!extensions.has(ext))
282
398
  return {};
283
399
  // Skip if this is a build artifact or generated/codegen output.
284
- if (isBuildArtifact(fullPath, probeCache))
400
+ // #1107: counted separately (both are observability-only — neither
401
+ // changes what gets skipped) so an invisible coverage hole (a real
402
+ // `src/gen.ts` silently dropped by the name heuristic) is at least
403
+ // visible via `SourceCollectionResult` and the walk's rollup log line.
404
+ if (isBuildArtifact(fullPath, probeCache)) {
405
+ if (skipCounters)
406
+ skipCounters.buildArtifactSkips += 1;
285
407
  return {};
286
- if (shouldSkipGeneratedOrArtifact(fullPath, options))
408
+ }
409
+ // #1107 phase 2: the content-probe escape hatch can rescue a WEAK
410
+ // name-only match ("override") instead of skipping it — see
411
+ // `classifyGeneratedOrArtifactDetailed`'s doc for the evidence
412
+ // order/tradeoff. `evidence` additionally distinguishes a STRONG/
413
+ // content-CONFIRMED skip (expected, not a coverage-hole signal) from
414
+ // the residual "name-only" skip (unconfirmed — the one bucket a
415
+ // tool-facing notice should actually key off).
416
+ const classification = classifySkipGeneratedOrArtifact(fullPath, options);
417
+ if (classification.verdict === "generated") {
418
+ if (skipCounters) {
419
+ skipCounters.generatedOrArtifactSkips += 1;
420
+ if (classification.evidence === "name-only") {
421
+ skipCounters.generatedNameOnlySkips += 1;
422
+ }
423
+ }
287
424
  return {};
425
+ }
426
+ if (classification.verdict === "override" && skipCounters) {
427
+ skipCounters.generatedNameOverrides += 1;
428
+ }
288
429
  return { keepFile: fullPath };
289
430
  }
290
431
  return {};
@@ -320,6 +461,8 @@ export function collectSourceFilesWithBudget(dir, options) {
320
461
  // on return — never persisted across calls.
321
462
  const probeCache = createArtifactProbeCache();
322
463
  const budget = createEntryBudget(cfg.maxScanEntries);
464
+ // #1107: per-walk skip counters, discarded on return like the probe cache.
465
+ const skipCounters = createSourceWalkSkipCounters();
323
466
  // #761: immediate-descent recursion driver (result-array order preserved),
324
467
  // with both caps kept as this walker's own per-entry policy: the hard
325
468
  // `maxFiles` results cap (#250) is checked BEFORE charging the entry budget
@@ -330,14 +473,23 @@ export function collectSourceFilesWithBudget(dir, options) {
330
473
  return "stop"; // hard cap (#250)
331
474
  if (!chargeEntryBudget(budget))
332
475
  return "stop"; // entry budget (#760)
333
- const { recurseInto, keepFile } = classifyEntry(entry, fullPath, cfg, probeCache);
476
+ const { recurseInto, keepFile } = classifyEntry(entry, fullPath, cfg, probeCache, skipCounters);
334
477
  if (recurseInto)
335
478
  return "recurse";
336
479
  if (keepFile)
337
480
  kept.push(keepFile);
338
481
  return "skip";
339
482
  });
340
- return { files: kept.list(), entryBudgetExceeded: budget.exceeded };
483
+ logSourceWalkSkipsIfAny(rootDir, skipCounters);
484
+ return {
485
+ files: kept.list(),
486
+ entryBudgetExceeded: budget.exceeded,
487
+ generatedOrArtifactSkips: skipCounters.generatedOrArtifactSkips,
488
+ buildArtifactSkips: skipCounters.buildArtifactSkips,
489
+ generatedDirSkips: skipCounters.generatedDirSkips,
490
+ generatedNameOverrides: skipCounters.generatedNameOverrides,
491
+ generatedNameOnlySkips: skipCounters.generatedNameOnlySkips,
492
+ };
341
493
  }
342
494
  export function collectSourceFiles(dir, options) {
343
495
  return collectSourceFilesWithBudget(dir, options).files;
@@ -345,7 +497,7 @@ export function collectSourceFiles(dir, options) {
345
497
  /**
346
498
  * Async, chunked-yield twin of {@link collectSourceFiles}. Returns the exact
347
499
  * same file list (it shares `classifyEntry`), but yields to the event loop
348
- * every `yieldEvery` directory entries so a large tree never holds the loop in
500
+ * on a monotonic time budget so a large tree never holds the loop in
349
501
  * one synchronous burst.
350
502
  *
351
503
  * Why this exists: on a ~2k-file project the synchronous `collectSourceFiles`
@@ -372,15 +524,18 @@ export async function collectSourceFilesWithBudgetAsync(dir, options) {
372
524
  // caching across the whole call remains invalidation-free.
373
525
  const probeCache = createArtifactProbeCache();
374
526
  const budget = createEntryBudget(cfg.maxScanEntries);
527
+ // #1107: per-walk skip counters, discarded on return like the probe cache.
528
+ const skipCounters = createSourceWalkSkipCounters();
375
529
  // #761: shared depth-first stack driver (its reverse-push mirrors the sync
376
530
  // collector's left-to-right recursion). The async collector charges the
377
531
  // entry budget (#760) FIRST, then checks the `maxFiles` cap immediately
378
532
  // after keeping a file — subtly different from the sync collector's ordering
379
533
  // but preserved verbatim, so both stay byte-identical to their pre-#761 form.
380
534
  await walkTreeStackAsync(rootDir, (entry, fullPath) => {
535
+ options?.onEntryVisited?.();
381
536
  if (!chargeEntryBudget(budget))
382
537
  return "stop"; // entry budget (#760)
383
- const { recurseInto, keepFile } = classifyEntry(entry, fullPath, cfg, probeCache);
538
+ const { recurseInto, keepFile } = classifyEntry(entry, fullPath, cfg, probeCache, skipCounters);
384
539
  if (recurseInto)
385
540
  return "recurse";
386
541
  if (keepFile) {
@@ -394,13 +549,22 @@ export async function collectSourceFilesWithBudgetAsync(dir, options) {
394
549
  // even on a cold scan where every kept file pays the 4 KB generated-
395
550
  // header read (measured on a 2k-file fixture). Larger values regress
396
551
  // past the ~50ms event-loop budget; see PERF-AUDIT.md.
397
- yieldEvery: Math.max(1, options?.yieldEvery ?? 50),
552
+ budgetMs: Math.max(1, options?.budgetMs ?? 8),
398
553
  // #703: prime the tracked-files set once before the walk so a tracked
399
554
  // file matching a `.gitignore`/global pattern still surfaces. Fail-open
400
555
  // on no-git/spawn failure.
401
556
  beforeWalk: () => cfg.ignoreMatcher.ensureTrackedIndex(),
402
557
  });
403
- return { files: kept.list(), entryBudgetExceeded: budget.exceeded };
558
+ logSourceWalkSkipsIfAny(rootDir, skipCounters);
559
+ return {
560
+ files: kept.list(),
561
+ entryBudgetExceeded: budget.exceeded,
562
+ generatedOrArtifactSkips: skipCounters.generatedOrArtifactSkips,
563
+ buildArtifactSkips: skipCounters.buildArtifactSkips,
564
+ generatedDirSkips: skipCounters.generatedDirSkips,
565
+ generatedNameOverrides: skipCounters.generatedNameOverrides,
566
+ generatedNameOnlySkips: skipCounters.generatedNameOnlySkips,
567
+ };
404
568
  }
405
569
  /**
406
570
  * Get statistics about source file filtering for debugging/monitoring.