@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
@@ -56,7 +56,7 @@
56
56
  import { createHash } from "node:crypto";
57
57
  import * as fs from "node:fs";
58
58
  import * as path from "node:path";
59
- import { writeFileAtomic } from "./atomic-write.js";
59
+ import { commitDurableStore } from "./durable-store.js";
60
60
  import { logDispositionEvent } from "./disposition-logger.js";
61
61
  import { publishDisposition } from "./disposition-publish.js";
62
62
  import { getProjectDataDir } from "./file-utils.js";
@@ -140,11 +140,27 @@ function statePath(cwd) {
140
140
  return path.join(getProjectDataDir(cwd), "cache", "diagnostic-dispositions.json");
141
141
  }
142
142
  let stateCache = null;
143
+ const DISPOSITION_LOCK_WAIT_MS = 2_000;
144
+ const DISPOSITION_LOCK_RETRY_MS = 10;
145
+ let beforeDispositionCommitForTests = null;
146
+ let beforeDispositionCacheRefreshForTests = null;
147
+ let dispositionStatSync = fs.statSync;
148
+ /** Test seam after the caller's cached read and before commit lock acquisition. */
149
+ export function _setBeforeDispositionCommitForTests(hook) {
150
+ beforeDispositionCommitForTests = hook;
151
+ }
152
+ /** Test seam immediately before the committed state refreshes the cache. */
153
+ export function _setBeforeDispositionCacheRefreshForTests(hook) {
154
+ beforeDispositionCacheRefreshForTests = hook;
155
+ }
156
+ export function _setDispositionStatForTests(statSync) {
157
+ dispositionStatSync = statSync ?? fs.statSync;
158
+ }
143
159
  function readState(cwd) {
144
160
  const p = statePath(cwd);
145
161
  let stat;
146
162
  try {
147
- stat = fs.statSync(p);
163
+ stat = dispositionStatSync(p);
148
164
  }
149
165
  catch {
150
166
  if (stateCache && stateCache.path === p && stateCache.missing) {
@@ -188,6 +204,17 @@ function readState(cwd) {
188
204
  };
189
205
  return state;
190
206
  }
207
+ function deserializeState(contents) {
208
+ try {
209
+ const parsed = JSON.parse(contents ?? "");
210
+ return parsed && typeof parsed === "object"
211
+ ? parsed
212
+ : {};
213
+ }
214
+ catch {
215
+ return {};
216
+ }
217
+ }
191
218
  // Atomic tmp+rename via clients/atomic-write.ts (#762; shared with
192
219
  // instance-registry.ts / recent-touches.ts / review-graph/builder.ts): a
193
220
  // cross-process reader must never observe a partially-written file —
@@ -198,10 +225,7 @@ function readState(cwd) {
198
225
  // failure still propagates (matches the pre-atomic writeFileSync's behavior,
199
226
  // which never swallowed errors either) — a disposition mark silently vanishing
200
227
  // is a correctness bug for this store, not just a lost observability sample.
201
- function writeState(cwd, state) {
202
- const p = statePath(cwd);
203
- fs.mkdirSync(path.dirname(p), { recursive: true });
204
- writeFileAtomic(p, JSON.stringify(state, null, 2), { bestEffort: false });
228
+ function refreshStateCache(p, state) {
205
229
  // Refresh the cache from the write we just did instead of invalidating it —
206
230
  // avoids an immediate re-stat+re-parse of the file we already have in hand,
207
231
  // and guards against coarse filesystem mtime granularity making a
@@ -215,6 +239,33 @@ function writeState(cwd, state) {
215
239
  state,
216
240
  };
217
241
  }
242
+ function commitDisposition(cwd, anchor, entry) {
243
+ const p = statePath(cwd);
244
+ fs.mkdirSync(path.dirname(p), { recursive: true });
245
+ const hook = beforeDispositionCommitForTests;
246
+ beforeDispositionCommitForTests = null;
247
+ hook?.();
248
+ commitDurableStore({
249
+ path: p,
250
+ deserialize: deserializeState,
251
+ merge: (state) => {
252
+ state.dispositions ??= {};
253
+ state.dispositions[anchor] = entry;
254
+ return state;
255
+ },
256
+ serialize: (state) => JSON.stringify(state, null, 2),
257
+ waitMs: DISPOSITION_LOCK_WAIT_MS,
258
+ retryMs: DISPOSITION_LOCK_RETRY_MS,
259
+ timeoutMessage: "timed out acquiring diagnostic disposition store lock",
260
+ onContention: "throw",
261
+ afterWriteLocked: (state) => {
262
+ const cacheHook = beforeDispositionCacheRefreshForTests;
263
+ beforeDispositionCacheRefreshForTests = null;
264
+ cacheHook?.();
265
+ refreshStateCache(p, state);
266
+ },
267
+ });
268
+ }
218
269
  /** Test-only escape hatch — the state cache is module-level, so tests that
219
270
  * write the store file out-of-band (or across separate cwds sharing a stat
220
271
  * coincidence) need to reset it between cases. */
@@ -274,19 +325,17 @@ export function markDisposition(cwd, target, disposition, reason) {
274
325
  // entry can already exist at the same weak anchor (a prior flagged/suppress
275
326
  // mark) — the log should record what this mark shadowed either way.
276
327
  const existing = readState(cwd).dispositions?.[anchor];
277
- emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
278
328
  if (disposition === "defer") {
279
329
  deferredThisSession.add(anchor);
330
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
280
331
  return anchor;
281
332
  }
282
- const state = readState(cwd);
283
- state.dispositions ??= {};
284
333
  const now = new Date().toISOString();
285
334
  const capturesFixContext = disposition === "flagged";
286
335
  const lineText = capturesFixContext
287
336
  ? (target.content?.split(/\r?\n/)[target.line !== undefined ? target.line - 1 : -1] ?? existing?.lineText)?.trim()
288
337
  : existing?.lineText;
289
- state.dispositions[anchor] = {
338
+ const entry = {
290
339
  disposition,
291
340
  reason: reason ?? existing?.reason,
292
341
  createdAt: existing?.createdAt ?? now,
@@ -294,7 +343,8 @@ export function markDisposition(cwd, target, disposition, reason) {
294
343
  line: capturesFixContext ? (target.line ?? existing?.line) : existing?.line,
295
344
  lineText,
296
345
  };
297
- writeState(cwd, state);
346
+ commitDisposition(cwd, anchor, entry);
347
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
298
348
  return anchor;
299
349
  }
300
350
  export function getDisposition(cwd, anchor) {
@@ -61,12 +61,13 @@
61
61
  */
62
62
  import { logBusEvent } from "./bus-events-logger.js";
63
63
  import { normalizeFilePath } from "./path-utils.js";
64
+ import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
64
65
  import { isBusPublishEnabled } from "./bus-publish.js";
65
66
  export const BUS_DIAGNOSTICS_EVENT = "pilens:diagnostics";
66
67
  export const BUS_DIAGNOSTICS_VERSION = 1;
67
68
  /** Max diagnostics carried per file per event — aligned with the widget's own per-file storage cap (`MAX_STORED_DIAGNOSTICS_PER_FILE`, clients/widget-state.ts). */
68
69
  export const MAX_DIAGNOSTICS_PER_FILE_EVENT = 12;
69
- let busEmit;
70
+ const liveEmitter = createLiveBusEmitter();
70
71
  let hasLoggedFailure = false;
71
72
  let hasLoggedUnwired = false;
72
73
  let hasLoggedDisabled = false;
@@ -79,11 +80,14 @@ const reportedDirtyPaths = new Set();
79
80
  * producers share the identical `pi.events.emit` binding.
80
81
  */
81
82
  export function wireDiagnosticsBusEmitter(emitFn) {
82
- busEmit = emitFn;
83
+ liveEmitter.wire(emitFn);
84
+ }
85
+ export function wireDiagnosticsBusEmitterGetter(getter) {
86
+ liveEmitter.wireGetter(getter);
83
87
  }
84
88
  /** Test-only: reset module state between test files. */
85
89
  export function _resetDiagnosticsPublishForTests() {
86
- busEmit = undefined;
90
+ liveEmitter.reset();
87
91
  hasLoggedFailure = false;
88
92
  hasLoggedUnwired = false;
89
93
  hasLoggedDisabled = false;
@@ -145,6 +149,7 @@ export function publishDiagnostics(args) {
145
149
  }
146
150
  return;
147
151
  }
152
+ const busEmit = liveEmitter.get();
148
153
  if (!busEmit) {
149
154
  if (!hasLoggedUnwired) {
150
155
  hasLoggedUnwired = true;
@@ -184,6 +189,7 @@ export function publishDiagnostics(args) {
184
189
  files: fileEntries,
185
190
  };
186
191
  busEmit(BUS_DIAGNOSTICS_EVENT, payload);
192
+ hasLoggedFailure = false;
187
193
  logBusEvent({
188
194
  event: BUS_DIAGNOSTICS_EVENT,
189
195
  outcome: "emitted",
@@ -201,6 +207,7 @@ export function publishDiagnostics(args) {
201
207
  });
202
208
  if (!hasLoggedFailure) {
203
209
  hasLoggedFailure = true;
210
+ recordStaleBusFailure(BUS_DIAGNOSTICS_EVENT, err);
204
211
  args.dbg?.(`diagnostics-publish: pilens:diagnostics emit failed (further failures suppressed): ${err}`);
205
212
  }
206
213
  }
@@ -13,6 +13,7 @@
13
13
  * - OutputSemantic: How to display (blocking, warning, silent, etc.)
14
14
  * - BaselineStore: Track pre-existing issues for delta mode
15
15
  */
16
+ import { logExtension } from "../extension-log.js";
16
17
  import * as fs from "node:fs";
17
18
  import * as path from "node:path";
18
19
  import { recordRunner } from "../widget-state.js";
@@ -32,6 +33,7 @@ import { applyDispositions } from "../diagnostic-dispositions.js";
32
33
  import { applyInlineSuppressions } from "./inline-suppressions.js";
33
34
  import { getToolPlan } from "./plan.js";
34
35
  import { resolveRunnerPath } from "./runner-context.js";
36
+ import { applyRulePolicy, rulePolicyMapFromConfig } from "./rule-policy.js";
35
37
  import { getToolProfile } from "./tool-profile.js";
36
38
  import { formatDiagnostics } from "./utils/format-utils.js";
37
39
  // --- Runner Registry ---
@@ -122,9 +124,13 @@ function readFilePrefix(filePath, maxBytes = 4096) {
122
124
  }
123
125
  }
124
126
  }
125
- export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, modifiedRanges) {
127
+ export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, modifiedRanges,
128
+ /** Authoritative workspace root; `cwd` may be a nested language root. */
129
+ projectRoot,
130
+ /** Ordered per-file pipeline token, when this is a post-write dispatch. */
131
+ writeIndex) {
126
132
  const absoluteFilePath = resolveRunnerPath(cwd, filePath);
127
- const normalizedProjectRoot = normalizeMapKey(path.resolve(cwd));
133
+ const normalizedProjectRoot = normalizeMapKey(path.resolve(projectRoot ?? cwd));
128
134
  const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
129
135
  const normalizedFilePath = normalizeMapKey(absoluteFilePath);
130
136
  const kind = detectFileKind(normalizedFilePath);
@@ -143,11 +149,18 @@ export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, mo
143
149
  projectConfig,
144
150
  blockingOnly,
145
151
  modifiedRanges,
152
+ writeIndex,
146
153
  async hasTool(command) {
147
154
  return checkToolAvailability(command, facts);
148
155
  },
149
156
  log(message) {
150
- console.error(`[dispatch] ${message}`);
157
+ // #1333: pi owns the terminal — a runner advisory must never be a raw
158
+ // write. Every DispatchContext.log line lands in extension.log instead.
159
+ logExtension({
160
+ subsystem: "dispatch",
161
+ message,
162
+ metadata: { filePath: normalizedFilePath, kind },
163
+ });
151
164
  },
152
165
  };
153
166
  }
@@ -493,7 +506,7 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
493
506
  }
494
507
  : undefined,
495
508
  });
496
- recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration);
509
+ recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration, ctx.writeIndex);
497
510
  diagnostics.push(...result.diagnostics);
498
511
  const resultSemantic = result.semantic ?? semantic;
499
512
  if ((resultSemantic === "blocking" && result.diagnostics.length > 0) ||
@@ -563,26 +576,56 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
563
576
  // Apply delta mode ONCE across the full diagnostic set.
564
577
  // This avoids partial-baseline corruption when processing multiple groups.
565
578
  const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
566
- const dockerOverlapSuppressed = suppressTrivyConfigDockerOverlap(dedupedDiagnostics);
567
- const overlapSuppressed = suppressLintOverlapsWithLsp(dockerOverlapSuppressed);
568
579
  const fileContent = ctx.facts.getFileFact(ctx.filePath, "file.content") ?? "";
569
- const inlineSuppressed = applyInlineSuppressions(overlapSuppressed, fileContent);
570
- // #690: agent/user disposition layer — drop false-positive/suppress marks
571
- // and anything deferred this session. flagged marks are left in place;
572
- // lens_diagnostics tags them at render time via the same anchor.
580
+ // Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`).
581
+ //
582
+ // Resolved from `ctx.projectRoot` (falling back to `ctx.cwd`), NOT
583
+ // `ctx.projectConfig` — `ctx.projectConfig` is loaded from the nested
584
+ // LANGUAGE root (`resolveLanguageRootForFile`), so in a monorepo where a
585
+ // package directory has its own `.pi-lens.json`, `discoverPiLensProjectConfig`'s
586
+ // upward walk stops there and never sees a repo-root policy.
587
+ // `lens_diagnostics` loads its policy map from `runtime.projectRoot`; using
588
+ // the same root here keeps the two surfaces in agreement. `ctx.projectConfig`
589
+ // itself is untouched — thresholds and mutation flags keep their existing
590
+ // language-root resolution.
591
+ const rulePolicy = rulePolicyMapFromConfig(loadPiLensProjectConfig(ctx.projectRoot ?? ctx.cwd).rules);
592
+ // The output-only filter pipeline: LSP/docker overlap suppression + inline
593
+ // `pi-lens-ignore` + agent/user dispositions + project rule policy. Applied
594
+ // AFTER dedupe so the pi renderer, widget, and delta all see one filtered
595
+ // set. #690: dispositions drop false-positive/suppress marks and anything
596
+ // deferred this session (flagged marks stay; lens_diagnostics tags them at
597
+ // render time). #1030: the disposition anchor + store MUST key off the
598
+ // PROJECT ROOT, not ctx.cwd — the mark tool writes dispositions under
599
+ // runtime.projectRoot, so reading from ctx.cwd (the nested language root in
600
+ // a monorepo) opened a different diagnostic-dispositions.json and silently
601
+ // no-op'd every mark under a nested marker.
573
602
  //
574
- // #1030: anchor + store MUST key off the PROJECT ROOT, not ctx.cwd. In a
575
- // monorepo ctx.cwd is the nested language root (resolveLanguageRootForFile,
576
- // used for tool/config resolution), but the mark tool writes dispositions
577
- // under runtime.projectRoot. Keying the read on ctx.cwd computed a different
578
- // anchor AND opened a different diagnostic-dispositions.json (getProjectDataDir
579
- // is keyed on cwd), so every false-positive/flagged/defer on a file under a
580
- // nested marker silently no-op'd. Read from the project root to match the write.
581
- const dispositionFiltered = applyDispositions(inlineSuppressed, ctx.projectRoot ?? ctx.cwd, ctx.filePath, fileContent);
582
- let visibleDiagnostics = dispositionFiltered;
603
+ // #1087: "silencing is not fixing" applies to the WHOLE class, not just the
604
+ // policy member. This single pipeline is applied identically to the live set
605
+ // AND (below) to the delta baseline, so a finding persistently dropped by
606
+ // ANY layer — overlap, inline suppression, disposition, or policy — is
607
+ // absent from both sides of the delta and never oscillates into `fixed`
608
+ // (which `trackAgentFixed` would otherwise inflate forever). The stored
609
+ // baseline remains the unfiltered `dedupedDiagnostics`, so editing a
610
+ // project's policy/suppressions never resets or corrupts the user-authored
611
+ // delta baseline — the filtering is re-derived on read for both sides.
612
+ const applyOutputFilters = (diags) => {
613
+ const dockerOverlap = suppressTrivyConfigDockerOverlap(diags);
614
+ const overlap = suppressLintOverlapsWithLsp(dockerOverlap);
615
+ const inline = applyInlineSuppressions(overlap, fileContent);
616
+ const disposition = applyDispositions(inline, ctx.projectRoot ?? ctx.cwd, ctx.filePath, fileContent);
617
+ return applyRulePolicy(disposition, rulePolicy);
618
+ };
619
+ let visibleDiagnostics = applyOutputFilters(dedupedDiagnostics);
583
620
  let resolvedCount = 0;
584
621
  if (ctx.deltaMode && previousBaseline) {
585
- const filtered = filterDelta(visibleDiagnostics, previousBaseline, (d) => d.id);
622
+ // Silencing a rule is not fixing it. The stored baseline is deliberately
623
+ // unfiltered (below), so compare against a fully-filtered view of it
624
+ // through the SAME pipeline — otherwise every persistently-suppressed
625
+ // finding sits in `fixed` on every dispatch and inflates the agent's
626
+ // resolved tally (trackAgentFixed) forever. Only `fixed` changes:
627
+ // filtered-out ids are absent from `after` either way.
628
+ const filtered = filterDelta(visibleDiagnostics, applyOutputFilters(previousBaseline), (d) => d.id);
586
629
  visibleDiagnostics = promoteDeltaUnusedToBlockers(filtered.new);
587
630
  resolvedCount = filtered.fixed.length;
588
631
  }
@@ -1,3 +1,4 @@
1
+ import { isJstsFactFile } from "../../file-kinds.js";
1
2
  import { findOwnerName } from "../../symbol-containment.js";
2
3
  import { extractFactsFromTree, firstChildOfType, walk, } from "./tree-sitter-facts.js";
3
4
  const BOUNDARY_PREFIXES = [
@@ -56,12 +57,26 @@ function getParameters(node) {
56
57
  if (!fp)
57
58
  return [];
58
59
  return (fp.children ?? []).filter((c) => c &&
59
- (c.type === "required_parameter" ||
60
+ (c.type === "identifier" ||
61
+ c.type === "required_parameter" ||
60
62
  c.type === "optional_parameter" ||
61
- c.type === "rest_pattern"));
63
+ c.type === "rest_pattern" ||
64
+ c.type === "assignment_pattern" ||
65
+ // Plain JavaScript's grammar places a top-level destructured
66
+ // parameter directly as an object_pattern/array_pattern child
67
+ // of formal_parameters — no required_parameter wrapper (the TS
68
+ // grammar always wraps every parameter, destructured or not,
69
+ // which is why the wrapper types above already covered TS).
70
+ // Without this, `function f({a, b})` counted 0 params in JS
71
+ // while the TS-annotated equivalent counted correctly (#1089 P3-4).
72
+ c.type === "object_pattern" ||
73
+ c.type === "array_pattern"));
62
74
  }
63
75
  function parameterName(param) {
64
- return firstNamedChild(param)?.text ?? "";
76
+ // JavaScript's grammar keeps a leaf parameter as the identifier itself;
77
+ // there is no named child to unwrap. TypeScript parameter wrapper nodes still
78
+ // use their first named child (type annotation/default/rest target).
79
+ return param.type === "identifier" ? param.text : firstNamedChild(param)?.text ?? "";
65
80
  }
66
81
  function getFunctionName(node) {
67
82
  if (node.type === "function_declaration") {
@@ -254,10 +269,10 @@ function hasReturnAwaitCall(node) {
254
269
  }
255
270
  export const functionFactProvider = {
256
271
  id: "fact.file.functions",
257
- provides: ["file.functionSummaries"],
272
+ provides: ["file.functionSummaries", "file.functionFactsCoverage"],
258
273
  requires: ["file.content"],
259
274
  appliesTo(ctx) {
260
- return /\.tsx?$/.test(ctx.filePath);
275
+ return isJstsFactFile(ctx.filePath);
261
276
  },
262
277
  async run(ctx, store) {
263
278
  await extractFactsFromTree(ctx, store, { "file.functionSummaries": [] }, (root) => {
@@ -309,7 +324,8 @@ export const functionFactProvider = {
309
324
  addSummary(node);
310
325
  if (node.type === "class_declaration" ||
311
326
  node.type === "interface_declaration") {
312
- const nameNode = firstChildOfType(node, "type_identifier");
327
+ const nameNode = firstChildOfType(node, "type_identifier") ??
328
+ firstChildOfType(node, "identifier");
313
329
  if (nameNode) {
314
330
  containers.push({
315
331
  name: nameNode.text,
@@ -325,6 +341,6 @@ export const functionFactProvider = {
325
341
  summary.owner = owner;
326
342
  }
327
343
  return { "file.functionSummaries": summaries };
328
- });
344
+ }, "file.functionFactsCoverage");
329
345
  },
330
346
  };
@@ -1,18 +1,6 @@
1
1
  import { logLatency } from "../../latency-logger.js";
2
+ import { isJstsFactFile } from "../../file-kinds.js";
2
3
  import { childrenOfType, firstChildOfType, withFactTree, walk, } from "./tree-sitter-facts.js";
3
- // JS/TS extensions this provider handles (→ tree-sitter typescript/tsx/javascript
4
- // grammars via resolveTreeSitterLanguage). Ported off the `typescript` compiler API
5
- // onto tree-sitter (#402) — the parse is served from the shared, cached client.
6
- const JSTS_EXTS = new Set([
7
- ".ts",
8
- ".tsx",
9
- ".mts",
10
- ".cts",
11
- ".js",
12
- ".jsx",
13
- ".mjs",
14
- ".cjs",
15
- ]);
16
4
  function stripQuotes(raw) {
17
5
  return raw.replace(/^["'`]+|["'`]+$/g, "");
18
6
  }
@@ -78,19 +66,19 @@ function parseReExport(node) {
78
66
  }
79
67
  export const importFactProvider = {
80
68
  id: "fact.file.imports",
81
- provides: ["file.imports", "file.reexports"],
69
+ provides: ["file.imports", "file.reexports", "file.importFactsCoverage"],
82
70
  requires: ["file.content"],
83
71
  appliesTo(ctx) {
84
- const ext = ctx.filePath.slice(ctx.filePath.lastIndexOf(".")).toLowerCase();
85
- return JSTS_EXTS.has(ext);
72
+ return isJstsFactFile(ctx.filePath);
86
73
  },
87
74
  async run(ctx, store) {
88
75
  const content = store.getFileFact(ctx.filePath, "file.content");
89
- const setEmpty = () => {
76
+ const setEmpty = (complete = false) => {
90
77
  store.setFileFact(ctx.filePath, "file.imports", []);
91
78
  store.setFileFact(ctx.filePath, "file.reexports", []);
79
+ store.setFileFact(ctx.filePath, "file.importFactsCoverage", complete ? "complete" : "unavailable");
92
80
  };
93
- if (!content) {
81
+ if (content == null) {
94
82
  setEmpty();
95
83
  return;
96
84
  }
@@ -205,5 +193,7 @@ export const importFactProvider = {
205
193
  });
206
194
  if (!parsed.parsed)
207
195
  setEmpty();
196
+ else
197
+ store.setFileFact(ctx.filePath, "file.importFactsCoverage", "complete");
208
198
  },
209
199
  };
@@ -17,15 +17,18 @@ export const withFactTree = withTreeSitterRoot;
17
17
  * (there is no typescript-compiler fallback by design, #402). Centralising this
18
18
  * keeps each provider to just its extraction logic (and de-duplicates the prologue).
19
19
  */
20
- export async function extractFactsFromTree(ctx, store, defaults, extract) {
21
- const writeAll = (facts) => {
20
+ export async function extractFactsFromTree(ctx, store, defaults, extract, coverageFact) {
21
+ const writeAll = (facts, complete) => {
22
22
  for (const key of Object.keys(defaults)) {
23
23
  store.setFileFact(ctx.filePath, key, facts[key] ?? defaults[key]);
24
24
  }
25
+ if (coverageFact) {
26
+ store.setFileFact(ctx.filePath, coverageFact, complete ? "complete" : "unavailable");
27
+ }
25
28
  };
26
29
  const content = store.getFileFact(ctx.filePath, "file.content");
27
- if (!content)
28
- return writeAll(defaults);
30
+ if (content == null)
31
+ return writeAll(defaults, false);
29
32
  const parsed = await withFactTree(ctx.filePath, content, (root) => extract(root, content));
30
- writeAll(parsed.parsed ? parsed.value : defaults);
33
+ writeAll(parsed.parsed ? parsed.value : defaults, parsed.parsed);
31
34
  }
@@ -0,0 +1,35 @@
1
+ const DEFAULT_INDENTATION = { style: "space", width: 2 };
2
+ /**
3
+ * Infer the prevailing indentation convention from lines that are indented.
4
+ * The detector deliberately has no language knowledge: formatter callers use
5
+ * it only as a conservative fallback when the repository has no config.
6
+ */
7
+ export function detectIndentation(content) {
8
+ const lines = content.split(/\r?\n/);
9
+ const tabs = lines.filter((line) => /^\t+\S/.test(line)).length;
10
+ const spaceCounts = lines
11
+ .map((line) => line.match(/^ +(?=\S)/)?.[0].length ?? 0)
12
+ .filter((count) => count > 0);
13
+ if (tabs === 0 && spaceCounts.length === 0)
14
+ return DEFAULT_INDENTATION;
15
+ if (tabs > spaceCounts.length)
16
+ return { style: "tab", width: 1 };
17
+ if (spaceCounts.length > tabs) {
18
+ const counts = new Map();
19
+ for (const count of spaceCounts) {
20
+ for (const other of spaceCounts) {
21
+ if (other > count) {
22
+ const delta = other - count;
23
+ counts.set(delta, (counts.get(delta) ?? 0) + 1);
24
+ }
25
+ }
26
+ }
27
+ const inferredWidth = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0]?.[0] ?? Math.min(...spaceCounts);
28
+ return { style: "space", width: inferredWidth <= 8 ? inferredWidth : 2 };
29
+ }
30
+ return DEFAULT_INDENTATION;
31
+ }
32
+ /** Whether the content supplied evidence from which a style can be inferred. */
33
+ export function hasDetectableIndentation(content) {
34
+ return /^(?:\t+| {1,})\S/m.test(content);
35
+ }
@@ -9,17 +9,17 @@
9
9
  * (Python/Ruby/…), comma-separated for multiple rules, on the same line as the
10
10
  * diagnostic or the line immediately above it.
11
11
  */
12
+ import { normalizeRuleId } from "./rule-id-normalize.js";
12
13
  const SUPPRESS_RE = /(?:\/\/|#)\s*pi-lens-ignore:\s*(.+)/;
13
14
  /**
14
15
  * Normalize a rule id to the form a user writes in a `pi-lens-ignore` comment.
15
16
  * The napi scan and the ast-grep LSP tag the same rule as `ast-grep:<id>` /
16
- * `<id>-js` in some surfaces (see `normalizeRuleForDedup` in lens-diagnostics);
17
+ * `<id>-js` in some surfaces (see the dedup key in lens-diagnostics);
17
18
  * a user's bare `<id>` must still suppress those, so we match the normalized form
18
- * as well as the raw one.
19
+ * as well as the raw one. Shared via `rule-id-normalize.ts` so the inline
20
+ * suppression parser and the project rule policy matcher apply the same
21
+ * normalization.
19
22
  */
20
- function normalizeSuppressRule(ruleId) {
21
- return ruleId.replace(/^ast-grep:/, "").replace(/-js$/, "");
22
- }
23
23
  /**
24
24
  * Drop diagnostics suppressed by an inline `pi-lens-ignore: <rule[,rule2]>`
25
25
  * comment in `content` (the file the diagnostics belong to). A diagnostic is
@@ -44,8 +44,17 @@ export function applyInlineSuppressions(diagnostics, content) {
44
44
  const suppressedLine = i + 1; // same line (1-based)
45
45
  const nextLine = i + 2; // next line (1-based)
46
46
  for (const ruleId of rules) {
47
- suppressed.add(`${suppressedLine}:${ruleId}`);
48
- suppressed.add(`${nextLine}:${ruleId}`);
47
+ // #1087: normalize the COMMENT token too, not just the diagnostic id.
48
+ // The diagnostic side below matches raw OR normalized, so storing only
49
+ // the raw comment token made `// pi-lens-ignore: no-eval-js` fail to
50
+ // suppress a finding surfaced under the normalized `no-eval`, while the
51
+ // identical `disable: ["no-eval-js"]` config key worked (rule-policy
52
+ // normalizes both sides). Add the normalized form so the two suppression
53
+ // surfaces stay symmetric.
54
+ for (const key of new Set([ruleId, normalizeRuleId(ruleId)])) {
55
+ suppressed.add(`${suppressedLine}:${key}`);
56
+ suppressed.add(`${nextLine}:${key}`);
57
+ }
49
58
  }
50
59
  }
51
60
  if (suppressed.size === 0)
@@ -55,7 +64,7 @@ export function applyInlineSuppressions(diagnostics, content) {
55
64
  const line = d.line ?? 1;
56
65
  if (suppressed.has(`${line}:${rawId}`))
57
66
  return false;
58
- const normId = normalizeSuppressRule(rawId);
67
+ const normId = normalizeRuleId(rawId);
59
68
  return normId === rawId || !suppressed.has(`${line}:${normId}`);
60
69
  });
61
70
  }