@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * - VIEW commands (cat/head/tail/sed -n) → reads, recorded with the exact line
6
6
  * range shown (like the Read tool's delivered range).
7
- * - WRITE commands (redirects, tee, sed -i, cp/mv dest, touch) → the agent
7
+ * - WRITE commands (redirects, tee, sed -i, cp/mv dest, touch, and explicit
8
+ * in-place formatter/fixer targets) → the agent
8
9
  * authored/owns the resulting file, exactly like the Write tool — these are
9
10
  * registered via noteCreatedFile + recordWritten so a follow-up edit is not
10
11
  * blocked.
@@ -44,10 +45,10 @@ export function tokenizeShellCommand(command) {
44
45
  word = "";
45
46
  atTokenStart = true;
46
47
  };
47
- const flushSegment = () => {
48
+ const flushSegment = (terminator) => {
48
49
  flushWord();
49
50
  if (tokens.length > 0 || unsupported)
50
- segments.push({ tokens, unsupported });
51
+ segments.push({ tokens, unsupported, terminator });
51
52
  tokens = [];
52
53
  unsupported = false;
53
54
  atTokenStart = true;
@@ -90,7 +91,7 @@ export function tokenizeShellCommand(command) {
90
91
  // Windows routinely contain native paths (C:\\src\\file.ts). Preserve
91
92
  // backslashes before ordinary path characters while still honoring
92
93
  // shell escapes and line continuations.
93
- if (next === "\n" || /[\s\\'\";$|&<>]/.test(next ?? "")) {
94
+ if (next === "\n" || /[\s\\'";$|&<>]/.test(next ?? "")) {
94
95
  escaped = true;
95
96
  }
96
97
  else {
@@ -121,13 +122,18 @@ export function tokenizeShellCommand(command) {
121
122
  }
122
123
  if (ch === ";" || ch === "\n" || ch === "|" || ch === "&") {
123
124
  flushWord();
125
+ let terminator;
124
126
  if (ch === "|" && next === "|")
125
127
  i++;
126
128
  else if (ch === "&" && next === "&")
127
129
  i++;
128
- else if (ch === "|" || ch === "&")
130
+ else if (ch === "|") {
129
131
  unsupported = true;
130
- flushSegment();
132
+ terminator = "pipe";
133
+ }
134
+ else if (ch === "&")
135
+ unsupported = true;
136
+ flushSegment(terminator);
131
137
  continue;
132
138
  }
133
139
  if (ch === "<" || ch === ">") {
@@ -162,6 +168,37 @@ function parseCountFlag(token) {
162
168
  function commandSegments(command) {
163
169
  return tokenizeShellCommand(command).map((segment) => segment.tokens);
164
170
  }
171
+ /** Unwrap the common agent-authored `npx <tool>` launcher form. */
172
+ function unwrapCommand(tokens) {
173
+ let verb = path.basename(tokens[0] ?? "");
174
+ let args = tokens.slice(1);
175
+ if (verb === "npx") {
176
+ while (args[0] === "-y" || args[0] === "--yes")
177
+ args = args.slice(1);
178
+ verb = path.basename(args[0] ?? "");
179
+ args = args.slice(1);
180
+ }
181
+ return { verb, args };
182
+ }
183
+ /**
184
+ * Return source-file operands while excluding values consumed by known options.
185
+ * This stays deliberately narrower than a general CLI parser: only formatter
186
+ * options whose values can themselves look like source files belong here.
187
+ */
188
+ function formatterFileArgs(args, start = 0, valueOptions = new Set()) {
189
+ const files = [];
190
+ for (let i = start; i < args.length; i += 1) {
191
+ const arg = args[i] ?? "";
192
+ if (valueOptions.has(arg)) {
193
+ i += 1;
194
+ continue;
195
+ }
196
+ if (arg === "--" || arg.startsWith("-"))
197
+ continue;
198
+ files.push(arg);
199
+ }
200
+ return files;
201
+ }
165
202
  /** Find redirect targets without treating quoted `>` characters as syntax. */
166
203
  function extractRedirectTargets(command) {
167
204
  const targets = [];
@@ -344,6 +381,80 @@ const GREP_OPTIONS_WITH_VALUE = new Set([
344
381
  "--before-context",
345
382
  "--context",
346
383
  ]);
384
+ function parseContextValue(inline, next) {
385
+ const raw = inline !== "" ? inline : next;
386
+ if (raw === undefined)
387
+ return undefined;
388
+ const n = Number.parseInt(raw, 10);
389
+ if (!Number.isFinite(n) || n < 0)
390
+ return undefined;
391
+ // Bound the credit: a huge -C would credit a whole file from one hit.
392
+ return Math.min(n, 100);
393
+ }
394
+ /**
395
+ * Read the context lines a `grep` invocation actually PRINTS around each hit
396
+ * (#1904 item 2). Only these lines were delivered to the model, so only these
397
+ * may be credited as read. Handles `-A N`, `-A3`, clustered short flags such as
398
+ * `-rnA3`, `--after-context=N`, the `-B`/`--before-context` mirror, `-C`
399
+ * (both sides), and the bare `-NUM` form.
400
+ */
401
+ export function parseGrepContextLines(args) {
402
+ let before = 0;
403
+ let after = 0;
404
+ for (let i = 0; i < args.length; i++) {
405
+ const token = stripQuotes(args[i]);
406
+ if (token === "--")
407
+ break;
408
+ const nextToken = args[i + 1] === undefined ? undefined : stripQuotes(args[i + 1]);
409
+ const long = /^--(after|before)-context(?:=(\d+))?$/.exec(token);
410
+ if (long) {
411
+ const value = parseContextValue(long[2] ?? "", nextToken);
412
+ if (value === undefined)
413
+ continue;
414
+ if (long[1] === "after")
415
+ after = Math.max(after, value);
416
+ else
417
+ before = Math.max(before, value);
418
+ continue;
419
+ }
420
+ const longBoth = /^--context(?:=(\d+))?$/.exec(token);
421
+ if (longBoth) {
422
+ const value = parseContextValue(longBoth[1] ?? "", nextToken);
423
+ if (value === undefined)
424
+ continue;
425
+ before = Math.max(before, value);
426
+ after = Math.max(after, value);
427
+ continue;
428
+ }
429
+ // Bare numeric context form: `grep -3 pattern file`.
430
+ const bare = /^-(\d+)$/.exec(token);
431
+ if (bare) {
432
+ const value = parseContextValue(bare[1], undefined);
433
+ if (value === undefined)
434
+ continue;
435
+ before = Math.max(before, value);
436
+ after = Math.max(after, value);
437
+ continue;
438
+ }
439
+ // Short flags, possibly clustered: the context flag must be LAST in the
440
+ // cluster because it consumes the remaining characters as its value.
441
+ const short = /^-[A-Za-z]*([ABC])(\d*)$/.exec(token);
442
+ if (!short)
443
+ continue;
444
+ const value = parseContextValue(short[2], nextToken);
445
+ if (value === undefined)
446
+ continue;
447
+ if (short[1] === "A")
448
+ after = Math.max(after, value);
449
+ else if (short[1] === "B")
450
+ before = Math.max(before, value);
451
+ else {
452
+ before = Math.max(before, value);
453
+ after = Math.max(after, value);
454
+ }
455
+ }
456
+ return { before, after };
457
+ }
347
458
  function extractGrepSearchFiles(args, cwd) {
348
459
  const files = [];
349
460
  let patternSeen = false;
@@ -408,10 +519,99 @@ function parseGrepLineWithoutFile(line, file) {
408
519
  return undefined;
409
520
  return { file, startLine: lineNumber, endLine: lineNumber };
410
521
  }
522
+ /**
523
+ * Verbs that DROP printed lines from a piped stream (#1908, review F4): a
524
+ * hit that survives such a tail cannot be trusted to still carry the
525
+ * -A/-B/-C context grep's flags declared, because that context may have
526
+ * been among the dropped lines. `head`/`tail` drop by position (even bare,
527
+ * since both default to a 10-line window); `sed` with a `q` command drops
528
+ * everything after the addressed line; `uniq` drops adjacent duplicate
529
+ * lines, which can include a context line that happens to repeat.
530
+ *
531
+ * The criterion is DROPPING, not "changes grep's output shape" — a review
532
+ * finding (#1913 F4) caught this file previously conflating the two:
533
+ * - `head`/`tail`/`sed q`/`uniq` downstream of a matching grep → some
534
+ * printed lines are genuinely gone, so credit falls back to
535
+ * match-line-only (the same conservative default already used for
536
+ * unparseable commands) rather than trusting the declared flags.
537
+ * - `sort` and other REORDER-ONLY filters keep every line grep printed,
538
+ * just in a different order — a surviving `file:line:` match still
539
+ * carries the full context grep actually printed, so no special case is
540
+ * needed (proven by the "still credits full context through a
541
+ * non-truncating pipe tail" test below).
542
+ * - `wc` and similar filters that REPLACE grep's output entirely (with a
543
+ * count, say) leave no `file:line:`-shaped line at all, so
544
+ * `parseGrepOutputSearchReads` already finds zero matches in the real
545
+ * captured stdout. Also no special case needed, but for the OPPOSITE
546
+ * reason from `sort`: nothing survives to credit, rather than
547
+ * everything surviving intact.
548
+ * - A pass-through filter (`cat`, `grep -v`, ...) between grep and a
549
+ * dropping tail still counts: the walk below follows the whole pipe
550
+ * chain, not just the immediate next segment.
551
+ * - Out of scope, deliberately: `sed -n 'Np'` range-address filtering
552
+ * without `q`, and `sort -u`, also drop lines but aren't in #1908's
553
+ * stated scope — revisit if they recur in the field. Also out of
554
+ * scope: a truncating tail hidden inside a subshell/brace-group
555
+ * (`( ... )`, `{ ...; }`) or reimplemented in `awk`/`perl` — the shared
556
+ * tokenizer this file uses doesn't parse inside those, matching its
557
+ * documented "small conservative shell lexer" scope.
558
+ */
559
+ const LINE_DROPPING_TAIL_VERBS = new Set(["head", "tail", "uniq"]);
560
+ function isSedQuitCommand(args) {
561
+ for (const arg of args) {
562
+ const token = stripQuotes(arg);
563
+ if (token.startsWith("-"))
564
+ continue;
565
+ // A `q` command anchored at the start of the script or after a `;`/
566
+ // newline separator, optionally preceded by a numeric/`$` address
567
+ // (`q`, `1q`, `$q`, `1,3p;q`). Deliberately not a full sed parser.
568
+ if (/(^|[;\n])\s*(\d+|\$)?\s*q\b/.test(token))
569
+ return true;
570
+ }
571
+ return false;
572
+ }
573
+ function isLineDroppingTailCommand(tokens) {
574
+ const verb = path.basename(stripQuotes(tokens[0] ?? ""));
575
+ if (LINE_DROPPING_TAIL_VERBS.has(verb))
576
+ return true;
577
+ if (verb === "sed")
578
+ return isSedQuitCommand(tokens.slice(1));
579
+ return false;
580
+ }
581
+ /**
582
+ * True when the grep segment starting at `index` feeds — directly or through
583
+ * a chain of pipes — into a line-dropping tail (#1908). Only an unquoted `|`
584
+ * counts: a `;`/`&&`/`&`-separated command downstream never receives grep's
585
+ * stdout, so it must not downgrade credit (#1913 F2 — this was previously
586
+ * unverified by any test that could tell a real pipe from mere adjacency).
587
+ */
588
+ function grepPipesIntoTruncatingTail(segments, index) {
589
+ if (segments[index]?.terminator !== "pipe")
590
+ return false;
591
+ for (let j = index + 1; j < segments.length; j++) {
592
+ if (isLineDroppingTailCommand(segments[j].tokens))
593
+ return true;
594
+ if (segments[j].terminator !== "pipe")
595
+ return false;
596
+ }
597
+ return false;
598
+ }
411
599
  function collectGrepCommandFiles(command, cwd) {
412
600
  const files = new Set();
413
601
  let hasLineNumberGrep = false;
414
- for (const tokens of commandSegments(command)) {
602
+ // A command can chain several greps whose output all reaches the model, but
603
+ // the parsed hits carry no segment identity. Credit the SMALLEST context any
604
+ // contributing grep printed, so no line is credited that a hit from the
605
+ // narrowest grep never delivered.
606
+ let context;
607
+ // #1908: if ANY contributing grep pipes into a truncating tail, fall back
608
+ // to match-line-only credit for the whole command — the parsed hits carry
609
+ // no segment identity, so a per-segment override isn't representable in
610
+ // the aggregate `context` this function returns.
611
+ let truncated = false;
612
+ const segments = tokenizeShellCommand(command);
613
+ for (let i = 0; i < segments.length; i++) {
614
+ const tokens = segments[i].tokens;
415
615
  const verb = path.basename(stripQuotes(tokens[0] ?? ""));
416
616
  if (verb !== "grep" && verb !== "egrep" && verb !== "fgrep")
417
617
  continue;
@@ -419,10 +619,23 @@ function collectGrepCommandFiles(command, cwd) {
419
619
  if (!grepHasLineNumbers(args))
420
620
  continue;
421
621
  hasLineNumberGrep = true;
622
+ const segmentContext = parseGrepContextLines(args);
623
+ context = context
624
+ ? {
625
+ before: Math.min(context.before, segmentContext.before),
626
+ after: Math.min(context.after, segmentContext.after),
627
+ }
628
+ : segmentContext;
422
629
  for (const file of extractGrepSearchFiles(args, cwd))
423
630
  files.add(file);
631
+ if (grepPipesIntoTruncatingTail(segments, i))
632
+ truncated = true;
424
633
  }
425
- return { hasLineNumberGrep, files };
634
+ return {
635
+ hasLineNumberGrep,
636
+ files,
637
+ context: truncated ? { before: 0, after: 0 } : (context ?? { before: 0, after: 0 }),
638
+ };
426
639
  }
427
640
  function dedupePushSearchRead(out, seen, loc) {
428
641
  if (!loc)
@@ -452,17 +665,25 @@ function parseGrepOutputSearchReads(output, cwd, singleFile) {
452
665
  * so the latter is only accepted when the command names exactly one source file.
453
666
  */
454
667
  export function extractGrepSearchReadsFromOutput(command, cwd, output) {
455
- const { hasLineNumberGrep, files } = collectGrepCommandFiles(command, cwd);
668
+ const { hasLineNumberGrep, files, context } = collectGrepCommandFiles(command, cwd);
456
669
  if (!hasLineNumberGrep)
457
670
  return [];
458
671
  const singleFile = files.size === 1 ? [...files][0] : undefined;
459
- return parseGrepOutputSearchReads(output, cwd, singleFile);
672
+ const locations = parseGrepOutputSearchReads(output, cwd, singleFile);
673
+ // Only the printed context lines were delivered to the model, so only they
674
+ // are credited. A bare grep credits its match line alone (#1904 item 2).
675
+ for (const loc of locations) {
676
+ loc.contextBefore = context.before;
677
+ loc.contextAfter = context.after;
678
+ }
679
+ return locations;
460
680
  }
461
681
  /**
462
682
  * Extract files a bash command WROTE/created, so the read-guard can treat them
463
683
  * as authored by the agent (mirrors the Write tool). Handles:
464
684
  * redirects: `> FILE`, `>> FILE`, `N> FILE`, `&> FILE` (with or without space)
465
- * tee [-a] FILE..., sed -i ... FILE, cp/mv/install ... DEST, touch FILE...
685
+ * tee [-a] FILE..., sed -i ... FILE, cp/mv/install ... DEST, touch FILE...,
686
+ * and common in-place formatter/fixer invocations with explicit file targets.
466
687
  *
467
688
  * Returns absolute paths. The file need not exist yet (it may be created) —
468
689
  * existence is confirmed later by recordWritten at tool_result time.
@@ -490,8 +711,7 @@ export function extractWrittenPathsFromCommand(command, cwd) {
490
711
  for (const tokens of commandSegments(command)) {
491
712
  if (tokens.length === 0)
492
713
  continue;
493
- const verb = path.basename(tokens[0] ?? "");
494
- const args = tokens.slice(1);
714
+ const { verb, args } = unwrapCommand(tokens);
495
715
  if (verb === "tee" || verb === "touch") {
496
716
  for (const a of args)
497
717
  if (!a.startsWith("-"))
@@ -502,10 +722,89 @@ export function extractWrittenPathsFromCommand(command, cwd) {
502
722
  add(a);
503
723
  }
504
724
  else if (verb === "cp" || verb === "mv" || verb === "install") {
725
+ // Known miss (#1668 review F3): the GNU `-t DEST`/`--target-directory`
726
+ // form puts the destination BEFORE the sources — this always treats
727
+ // the LAST non-flag argument as the destination, so `-t DEST SRC` and
728
+ // multi-source `-t DEST SRC1 SRC2...` are misread. Rare in agent-
729
+ // authored bash (source-before-dest is the common form); documented
730
+ // rather than fixed, since correctly parsing `-t` means recognizing it
731
+ // takes a value while telling it apart from every other single-letter
732
+ // flag this same branch already ignores.
505
733
  const files = args.filter((a) => !a.startsWith("-"));
506
734
  if (files.length >= 1)
507
735
  add(files[files.length - 1]); // destination
508
736
  }
737
+ else if (verb === "biome") {
738
+ const sub = args[0];
739
+ if ((sub === "format" || sub === "check") && args.includes("--write")) {
740
+ for (const file of formatterFileArgs(args, 1, new Set(["--config-path"])))
741
+ add(file);
742
+ }
743
+ }
744
+ else if (verb === "prettier" && args.includes("--write")) {
745
+ for (const file of formatterFileArgs(args, 0, new Set(["--config", "--ignore-path", "--parser", "--plugin"])))
746
+ add(file);
747
+ }
748
+ else if (verb === "eslint" && args.includes("--fix")) {
749
+ for (const file of formatterFileArgs(args, 0, new Set(["--config", "--ignore-path", "--parser", "--plugin"])))
750
+ add(file);
751
+ }
752
+ else if (verb === "ruff") {
753
+ const sub = args[0];
754
+ if (sub === "format" || args.includes("--fix")) {
755
+ const start = sub === "format" || sub === "check" ? 1 : 0;
756
+ for (const file of formatterFileArgs(args, start, new Set(["--config"])))
757
+ add(file);
758
+ }
759
+ }
760
+ else if (verb === "gofmt" && args.includes("-w")) {
761
+ for (const file of formatterFileArgs(args))
762
+ add(file);
763
+ }
764
+ else if (verb === "cargo" && args[0] === "fmt") {
765
+ // Bare cargo fmt is project-scoped and cannot be enumerated without a
766
+ // filesystem walk. Only explicit rustfmt operands after `--` are safe.
767
+ const separator = args.indexOf("--");
768
+ if (separator >= 0) {
769
+ for (const file of formatterFileArgs(args, separator + 1, new Set(["--edition", "--config-path"])))
770
+ add(file);
771
+ }
772
+ }
773
+ else if (verb === "rustfmt") {
774
+ for (const file of formatterFileArgs(args, 0, new Set(["--edition", "--config-path"])))
775
+ add(file);
776
+ }
777
+ else if (verb === "black") {
778
+ for (const file of formatterFileArgs(args, 0, new Set([
779
+ "--config",
780
+ "--exclude",
781
+ "--include",
782
+ "--line-length",
783
+ "--target-version",
784
+ ])))
785
+ add(file);
786
+ }
787
+ else if (verb === "clang-format" && args.includes("-i")) {
788
+ for (const file of formatterFileArgs(args, 0, new Set(["--style", "--fallback-style", "--assume-filename"])))
789
+ add(file);
790
+ }
791
+ else if (verb === "dotnet" && args[0] === "format") {
792
+ // dotnet format is normally solution-scoped. `--include` is the one
793
+ // invocation form that provides attributable source paths.
794
+ for (let i = 1; i < args.length; i += 1) {
795
+ if (args[i] === "--include" && args[i + 1]) {
796
+ for (const file of (args[i + 1] ?? "").split(","))
797
+ add(file);
798
+ i += 1;
799
+ }
800
+ else if (args[i]?.startsWith("--include=")) {
801
+ for (const file of (args[i] ?? "")
802
+ .slice("--include=".length)
803
+ .split(","))
804
+ add(file);
805
+ }
806
+ }
807
+ }
509
808
  else if (verb === "git") {
510
809
  // git ops that REWRITE working-tree files with explicit paths:
511
810
  // git checkout [<ref>] -- <files> git restore [opts] <files>
@@ -524,6 +823,75 @@ export function extractWrittenPathsFromCommand(command, cwd) {
524
823
  for (const a of fileArgs)
525
824
  add(a);
526
825
  }
826
+ else if (sub === "mv") {
827
+ // git mv SRC... DEST — the destination is a write, exactly like
828
+ // plain `mv` above (#1668 review F2). The source side is a delete,
829
+ // handled by extractDeletedPathsFromCommand.
830
+ const files = args.slice(1).filter((a) => !a.startsWith("-"));
831
+ if (files.length >= 1)
832
+ add(files[files.length - 1]);
833
+ }
834
+ }
835
+ }
836
+ return Array.from(out);
837
+ }
838
+ /**
839
+ * Extract files a bash command likely DELETED, for the type-3 (Deleted)
840
+ * watched-files gap (#1668): `rm FILE...`, `git rm FILE...`, and the SOURCE
841
+ * side of `mv SRC DEST` / `git mv SRC DEST` (the destination is a write,
842
+ * covered by `extractWrittenPathsFromCommand` — including for `git mv`,
843
+ * #1668 review F2).
844
+ *
845
+ * Deliberately narrow: only commands naming an explicit file target are
846
+ * handled. A bare directory op (`rm -rf dir/`, `git clean`) is skipped —
847
+ * resolving what vanished inside a directory would mean listing it before
848
+ * and after, the "stat the world" cost this module exists to avoid. The
849
+ * caller confirms each candidate by checking it no longer exists on disk;
850
+ * this function only proposes what the command named.
851
+ */
852
+ export function extractDeletedPathsFromCommand(command, cwd) {
853
+ const out = new Set();
854
+ const add = (token) => {
855
+ const abs = resolveCandidate(token, cwd);
856
+ if (abs)
857
+ out.add(abs);
858
+ };
859
+ // mv SRC... DEST / git mv SRC... DEST — every argument except the last is
860
+ // a source that vanishes from its original path once the move lands.
861
+ // Known miss (#1668 review F3): the same `-t DEST`/`--target-directory`
862
+ // form documented in `extractWrittenPathsFromCommand` misreads here too —
863
+ // this assumes source-before-destination order.
864
+ const addMoveSources = (args) => {
865
+ const files = args.filter((a) => !a.startsWith("-"));
866
+ if (files.length >= 2) {
867
+ for (const src of files.slice(0, -1))
868
+ add(src);
869
+ }
870
+ };
871
+ for (const tokens of commandSegments(command)) {
872
+ if (tokens.length === 0)
873
+ continue;
874
+ const verb = path.basename(tokens[0] ?? "");
875
+ const args = tokens.slice(1);
876
+ if (verb === "rm") {
877
+ for (const a of args)
878
+ if (!a.startsWith("-"))
879
+ add(a);
880
+ }
881
+ else if (verb === "git" && args[0] === "rm") {
882
+ const rmArgs = args.slice(1);
883
+ const dashDash = rmArgs.indexOf("--");
884
+ const fileArgs = dashDash >= 0
885
+ ? rmArgs.slice(dashDash + 1)
886
+ : rmArgs.filter((a) => !a.startsWith("-"));
887
+ for (const a of fileArgs)
888
+ add(a);
889
+ }
890
+ else if (verb === "git" && args[0] === "mv") {
891
+ addMoveSources(args.slice(1));
892
+ }
893
+ else if (verb === "mv") {
894
+ addMoveSources(args);
527
895
  }
528
896
  }
529
897
  return Array.from(out);
@@ -14,9 +14,10 @@ import { isFileKind } from "./file-kinds.js";
14
14
  import { getGlobalPiLensDir } from "./file-utils.js";
15
15
  import { findGlobalBinary } from "./package-manager.js";
16
16
  import { safeSpawnAsync } from "./safe-spawn.js";
17
+ import { createSingleFlight } from "./single-flight.js";
17
18
  import { biomeConfigArgs } from "./tool-policy.js";
18
19
  import { resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
19
- import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
20
+ import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
20
21
  // --- Client ---
21
22
  const PROBE_TIMEOUT_MS = 10_000;
22
23
  export class BiomeClient {
@@ -39,6 +40,8 @@ export class BiomeClient {
39
40
  */
40
41
  lastProbeElapsedMs = 0;
41
42
  lastProbeHostStallMs = 0;
43
+ /** Raw facts from the last probe, carried into the decision record (#1500). */
44
+ lastProbeEvidence;
42
45
  // Per-cwd cache of the resolved biome binary. Keying by cwd matters in
43
46
  // monorepos where different sub-packages each ship their own biome
44
47
  // installation; sharing one slot across the whole client would cause
@@ -48,7 +51,11 @@ export class BiomeClient {
48
51
  // (lives under ~/.pi-lens/tools), so it's stored separately from the
49
52
  // per-cwd cache and used as a final fallback before npx.
50
53
  autoInstalledBinaryPath = null;
51
- ensureInFlight = null;
54
+ /**
55
+ * At-most-one probe/auto-install in flight, via the shared primitive
56
+ * (#1753). One instance owns one question, so the key is a constant.
57
+ */
58
+ ensureFlight = createSingleFlight();
52
59
  log;
53
60
  constructor(verbose = false) {
54
61
  this.log = verbose
@@ -70,13 +77,14 @@ export class BiomeClient {
70
77
  const cached = this.localBinaryByCwd.get(resolveCwd);
71
78
  if (cached)
72
79
  return { cmd: cached, args: [] };
73
- if (this.autoInstalledBinaryPath) {
74
- return { cmd: this.autoInstalledBinaryPath, args: [] };
75
- }
76
- // Walk up from cwd looking for node_modules/.bin/biome.
77
- // Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
78
- // so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when
79
- // the tool is already installed but not in the project's node_modules.
80
+ // Walk up from cwd looking for node_modules/.bin/biome BEFORE trusting
81
+ // `autoInstalledBinaryPath` (#1731). That field is set once, the first
82
+ // time `ensureAvailable()` auto-installs for ANY cwd this session, and
83
+ // every later call for every OTHER cwd short-circuited on it — so a
84
+ // project that ships its own biome never won once the session's first
85
+ // managed install had already happened. Project-local-first (discipline
86
+ // B, #1721) means the project's pinned version and config resolution
87
+ // always outrank a managed copy, autoinstalled or not.
80
88
  // On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
81
89
  const isWin = process.platform === "win32";
82
90
  const piLensBin = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
@@ -84,20 +92,29 @@ export class BiomeClient {
84
92
  ? [
85
93
  path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
86
94
  path.join(resolveCwd, "node_modules", ".bin", "biome"),
87
- path.join(piLensBin, "biome.cmd"),
88
- path.join(piLensBin, "biome"),
89
95
  ]
90
- : [
91
- path.join(resolveCwd, "node_modules", ".bin", "biome"),
92
- path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
93
- path.join(piLensBin, "biome"),
94
- ];
96
+ : [path.join(resolveCwd, "node_modules", ".bin", "biome")];
95
97
  for (const p of candidates) {
96
98
  if (fs.existsSync(p)) {
97
99
  this.localBinaryByCwd.set(resolveCwd, p);
98
100
  return { cmd: p, args: [] };
99
101
  }
100
102
  }
103
+ if (this.autoInstalledBinaryPath) {
104
+ return { cmd: this.autoInstalledBinaryPath, args: [] };
105
+ }
106
+ // Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
107
+ // so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when the
108
+ // tool is already installed but not in the project's node_modules.
109
+ const managedCandidates = isWin
110
+ ? [path.join(piLensBin, "biome.cmd"), path.join(piLensBin, "biome")]
111
+ : [path.join(piLensBin, "biome")];
112
+ for (const p of managedCandidates) {
113
+ if (fs.existsSync(p)) {
114
+ this.localBinaryByCwd.set(resolveCwd, p);
115
+ return { cmd: p, args: [] };
116
+ }
117
+ }
101
118
  // Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx —
102
119
  // catches `pnpm add -g @biomejs/biome` installs that PATH misses (#375).
103
120
  const global = await findGlobalBinary("biome");
@@ -116,10 +133,11 @@ export class BiomeClient {
116
133
  * Ensure Biome is available, auto-installing if necessary.
117
134
  * Prefer this over isAvailable() for auto-install behavior.
118
135
  *
119
- * Re-entrancy safe: concurrent first-time callers share a single
120
- * `ensureInFlight` promise so probing/auto-install isn't duplicated.
121
- * Mirrors the dedupe pattern in `SgRunner` / `KnipClient` /
122
- * `DependencyChecker`.
136
+ * Re-entrancy safe: concurrent first-time callers share one flight, so
137
+ * probing/auto-install isn't duplicated. The share and the clear-in-finally
138
+ * belong to `singleFlight` (#1753); this method owns only the latch
139
+ * short-circuit above it. #1690 exists because the hand-rolled clear here
140
+ * was never exercised by a test.
123
141
  *
124
142
  * The memo returns `null` when the last verdict was transient and its
125
143
  * cooldown expired, which re-enters the probe: "biome is not installed" is a
@@ -129,15 +147,7 @@ export class BiomeClient {
129
147
  const memo = this.availabilityLatch.read();
130
148
  if (memo !== null)
131
149
  return memo;
132
- if (this.ensureInFlight)
133
- return this.ensureInFlight;
134
- this.ensureInFlight = this.doEnsureAvailable();
135
- try {
136
- return await this.ensureInFlight;
137
- }
138
- finally {
139
- this.ensureInFlight = null;
140
- }
150
+ return this.ensureFlight.run("biome", () => this.doEnsureAvailable());
141
151
  }
142
152
  /**
143
153
  * Probe `biome --version` and classify the failure through the shared
@@ -177,7 +187,7 @@ export class BiomeClient {
177
187
  // `unclassifiedFailureOutcome: "missing"` preserves the pre-#1476
178
188
  // meaning of a plain non-zero exit (npx reporting no biome package):
179
189
  // still "missing", still installable. Only the timeout/abort arm changes.
180
- const { outcome, cause } = classifyProbeFailure(result, {
190
+ const { outcome, cause, evidence } = classifyProbeFailure(result, {
181
191
  hostStallMs,
182
192
  unclassifiedFailureOutcome: "missing",
183
193
  });
@@ -187,6 +197,7 @@ export class BiomeClient {
187
197
  this.lastProbeCause = cause;
188
198
  this.lastProbeElapsedMs = elapsedMs;
189
199
  this.lastProbeHostStallMs = hostStallMs;
200
+ this.lastProbeEvidence = evidence;
190
201
  // The record is emitted by `doEnsureAvailable`, once the latch has said how
191
202
  // long this verdict holds.
192
203
  return { outcome: failureOutcome };
@@ -224,6 +235,19 @@ export class BiomeClient {
224
235
  // session exactly as before.
225
236
  const cause = this.lastProbeCause ?? "not-found";
226
237
  const retryAfterMs = this.availabilityLatch.noteUnavailable(resolved.outcome, cause);
238
+ // `missing` IS the install-failure arm (#1500 review). `resolveManagedToolClient`
239
+ // only reaches the installer when the probe said `missing`, so every `missing`
240
+ // verdict here has already been through it — declined, suppressed, or tried
241
+ // and failed, which is what `describeInstallAttempt` separates. The earlier
242
+ // marker sat on the `non-installable` arm instead, and that arm cannot happen
243
+ // for biome at all: its `acceptInstalled` always accepts. So the one row that
244
+ // needed the fact was the one shipping without it.
245
+ let installEvidence;
246
+ if (resolved.outcome === "missing") {
247
+ const { getInstallAttempt } = await import("./installer/index.js");
248
+ installEvidence = describeInstallAttempt(getInstallAttempt("biome"));
249
+ }
250
+ const evidence = { ...this.lastProbeEvidence, ...installEvidence };
227
251
  logAvailabilityDecision({
228
252
  tool: "biome",
229
253
  verdict: "unavailable",
@@ -234,6 +258,13 @@ export class BiomeClient {
234
258
  hostStallMs: this.lastProbeHostStallMs,
235
259
  ...(retryAfterMs > 0 && { retryAfterMs }),
236
260
  budgetMs: PROBE_TIMEOUT_MS,
261
+ // Per arm: a probe-derived verdict says `probe`, while a verdict asserted
262
+ // by the install seam — or a cause that fell back because no probe ran —
263
+ // says `caller`.
264
+ classifiedBy: this.lastProbeCause === null || resolved.outcome === "non-installable"
265
+ ? "caller"
266
+ : "probe",
267
+ ...(Object.keys(evidence).length > 0 && { evidence }),
237
268
  });
238
269
  if (resolved.outcome === "transient") {
239
270
  this.log("biome availability probe timed out; will retry (not installing)");