@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -28,6 +28,11 @@ pi-lens gives AI coding agents fast, language-aware feedback while they write/ed
28
28
  - `/lens-map` — interactive HTML dependency map of the project
29
29
  - Read-guard and edit-autopatch support to reduce bad edits
30
30
  - Background security/dependency scans for opted-in projects
31
+ - Runtime health telemetry (`/lens-health`) including a bounded degradation
32
+ ledger for silently-degraded behavior (LSP breakers, formatter
33
+ skips/failures, idle evictions, timeouts)
34
+ - MCP server (experimental) so Claude Code or any MCP client can drive the
35
+ same diagnostics/read-substitute tools pi-lens exposes to pi
31
36
 
32
37
  ## Install
33
38
 
@@ -70,6 +75,8 @@ pi install git:github.com/apmantza/pi-lens
70
75
  and formatters
71
76
  - [Dependencies](docs/dependencies.md) — auto-install policy and external tools
72
77
  - [Custom rules](docs/custom-rules.md) — project ast-grep and tree-sitter rules
78
+ - [MCP server](docs/mcp.md) — experimental MCP server for Claude Code and
79
+ other MCP clients
73
80
 
74
81
  ## Contributing
75
82
 
@@ -206,6 +213,12 @@ Thanks goes to these wonderful people:
206
213
  </tr>
207
214
  <tr>
208
215
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pppobear"><img src="https://avatars.githubusercontent.com/u/21952175?v=4" width="100px;" alt=""/><br /><sub><b>pppobear</b></sub></a><br /><a href="#bug-pppobear" title="Bug reports">🐛</a></td>
216
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/3choBoomer"><img src="https://avatars.githubusercontent.com/u/521828?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Cooke</b></sub></a><br /><a href="#code-3choBoomer" title="Code">💻</a></td>
217
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/white-hat"><img src="https://avatars.githubusercontent.com/u/922988?v=4" width="100px;" alt=""/><br /><sub><b>Eli Stark</b></sub></a><br /><a href="#code-white-hat" title="Code">💻</a></td>
218
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/marvtub"><img src="https://avatars.githubusercontent.com/u/33159023?v=4" width="100px;" alt=""/><br /><sub><b>Marvin Aziz</b></sub></a><br /><a href="#code-marvtub" title="Code">💻</a></td>
219
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark Faga</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
220
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/aeturnal"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
221
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/floatGray"><img src="https://avatars.githubusercontent.com/u/44295302?v=4" width="100px;" alt=""/><br /><sub><b>floatGray</b></sub></a><br /><a href="#code-floatGray" title="Code">💻</a></td>
209
222
  </tr>
210
223
  </tbody>
211
224
  </table>
@@ -4,9 +4,16 @@ import * as path from "node:path";
4
4
  import { applyWorkspaceEdit } from "./lsp/edits.js";
5
5
  import { getLSPService } from "./lsp/index.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
+ import { recordLspMutationBatch, } from "./lsp-mutation.js";
7
8
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
8
9
  import { logActionableWarningsEvent } from "./actionable-warnings-logger.js";
9
10
  import { getProjectDataDir } from "./file-utils.js";
11
+ import { commitDurableStore } from "./durable-store.js";
12
+ let beforeWarningStateLockForTests = null;
13
+ /** Test seam for a sibling process commit immediately before lock acquisition. */
14
+ export function _setBeforeWarningStateLockForTests(hook) {
15
+ beforeWarningStateLockForTests = hook;
16
+ }
10
17
  function normalizeMessage(message) {
11
18
  return message.replace(/\s+/g, " ").trim().toLowerCase();
12
19
  }
@@ -53,10 +60,9 @@ function serializeAction(action) {
53
60
  skipReason: safety.reason,
54
61
  };
55
62
  }
56
- function readSuppressionState(cwd) {
57
- const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
63
+ function deserializeSuppressionState(contents) {
58
64
  try {
59
- const parsed = JSON.parse(fs.readFileSync(statePath, "utf-8"));
65
+ const parsed = JSON.parse(contents ?? "");
60
66
  return parsed && typeof parsed === "object"
61
67
  ? parsed
62
68
  : {};
@@ -65,23 +71,49 @@ function readSuppressionState(cwd) {
65
71
  return {};
66
72
  }
67
73
  }
68
- function updateWarningState(cwd, warnings) {
74
+ function readSuppressionState(cwd) {
69
75
  const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
70
- const now = new Date().toISOString();
71
- const state = readSuppressionState(cwd);
72
- state.warnings ??= {};
73
- for (const warning of warnings) {
74
- const existing = state.warnings[warning.id] ?? {};
75
- state.warnings[warning.id] = {
76
- ...existing,
77
- status: existing.status ?? "active",
78
- firstSeenAt: existing.firstSeenAt ?? now,
79
- lastSeenAt: now,
80
- seenCount: (existing.seenCount ?? 0) + 1,
81
- };
76
+ try {
77
+ return deserializeSuppressionState(fs.readFileSync(statePath, "utf8"));
78
+ }
79
+ catch {
80
+ return {};
82
81
  }
82
+ }
83
+ function updateWarningState(cwd, warnings) {
84
+ const statePath = path.join(getProjectDataDir(cwd), "cache", "actionable-warning-state.json");
83
85
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
84
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
86
+ const hook = beforeWarningStateLockForTests;
87
+ beforeWarningStateLockForTests = null;
88
+ hook?.();
89
+ commitDurableStore({
90
+ path: statePath,
91
+ deserialize: deserializeSuppressionState,
92
+ merge: (state) => {
93
+ const now = new Date().toISOString();
94
+ state.warnings ??= {};
95
+ for (const warning of warnings) {
96
+ const existing = state.warnings[warning.id] ?? {};
97
+ state.warnings[warning.id] = {
98
+ ...existing,
99
+ status: existing.status ?? "active",
100
+ firstSeenAt: existing.firstSeenAt ?? now,
101
+ lastSeenAt: now,
102
+ seenCount: (existing.seenCount ?? 0) + 1,
103
+ };
104
+ }
105
+ return state;
106
+ },
107
+ serialize: (state) => JSON.stringify(state, null, 2),
108
+ waitMs: 2_000,
109
+ retryMs: 10,
110
+ timeoutMessage: "timed out acquiring actionable warning store lock",
111
+ onContention: "skip-log",
112
+ logContention: () => logActionableWarningsEvent({
113
+ event: "warning_state_write_dropped",
114
+ metadata: { reason: "lock_contention" },
115
+ }),
116
+ });
85
117
  }
86
118
  function suppressionFor(cwd, id) {
87
119
  const entry = readSuppressionState(cwd).warnings?.[id];
@@ -452,6 +484,8 @@ export async function applyConservativeActionableWarningFixes(args) {
452
484
  skipped: [],
453
485
  };
454
486
  const changedFiles = new Set();
487
+ const appliedResults = [];
488
+ let failedCount = 0;
455
489
  const lspService = getLSPService();
456
490
  const maxFixes = Math.max(0, args.maxFixes ?? 5);
457
491
  for (const file of args.report.files) {
@@ -504,12 +538,23 @@ export async function applyConservativeActionableWarningFixes(args) {
504
538
  continue;
505
539
  }
506
540
  const edit = selected.edit;
507
- const applied = await applyWorkspaceEdit(edit, args.cwd);
541
+ const applied = await applyWorkspaceEdit(edit, args.cwd, args.mutationContext
542
+ ? { mutationContext: { ...args.mutationContext, emitSummary: false } }
543
+ : undefined);
544
+ appliedResults.push(applied);
508
545
  for (const changedFile of applied.files)
509
546
  changedFiles.add(changedFile);
510
547
  summary.applied++;
511
548
  }
512
549
  catch (err) {
550
+ failedCount++;
551
+ const partial = err
552
+ .appliedWorkspaceEdit;
553
+ if (partial) {
554
+ appliedResults.push(partial);
555
+ for (const changedFile of partial.files)
556
+ changedFiles.add(changedFile);
557
+ }
513
558
  const message = err instanceof Error ? err.message : String(err);
514
559
  args.dbg?.(`actionable_warnings_autofix failed for ${warning.id}: ${message}`);
515
560
  summary.skipped.push({ id: warning.id, reason: "apply_failed" });
@@ -517,6 +562,16 @@ export async function applyConservativeActionableWarningFixes(args) {
517
562
  }
518
563
  }
519
564
  summary.changedFiles = [...changedFiles];
565
+ if (args.mutationContext && (summary.considered > 0 || appliedResults.length > 0)) {
566
+ recordLspMutationBatch(args.mutationContext, {
567
+ results: appliedResults,
568
+ considered: summary.considered,
569
+ completed: summary.applied,
570
+ failedCount,
571
+ status: failedCount > 0 ? "failed" : appliedResults.length > 0 ? "success" : "skipped",
572
+ bookkeep: false,
573
+ });
574
+ }
520
575
  return summary;
521
576
  }
522
577
  export function formatActionableWarningsAdvisory(report) {
@@ -0,0 +1,314 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { logLatency } from "./latency-logger.js";
5
+ import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
6
+ import { resolveRunnerPath } from "./dispatch/runner-context.js";
7
+ export const MAX_ADVISORY_AFFECTED_FILES = 256;
8
+ export function advisoryPathKey(filePath, cwd) {
9
+ return normalizeMapKey(path.resolve(cwd, filePath));
10
+ }
11
+ /** Shared with git guard: there is one SHA-256 implementation for advisories. */
12
+ export function advisoryFileHash(filePath) {
13
+ try {
14
+ return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
15
+ }
16
+ catch (error) {
17
+ const code = error.code ?? "unknown";
18
+ return code === "ENOENT" ? "missing" : `unreadable:${code}`;
19
+ }
20
+ }
21
+ function snapshotOne(filePath, cwd, role) {
22
+ const resolved = path.resolve(cwd, filePath);
23
+ try {
24
+ const stat = fs.statSync(resolved);
25
+ return {
26
+ path: resolved,
27
+ role,
28
+ mtimeMs: stat.mtimeMs,
29
+ size: stat.size,
30
+ sha256: advisoryFileHash(resolved),
31
+ };
32
+ }
33
+ catch (error) {
34
+ const code = error.code ?? "unknown";
35
+ return {
36
+ path: resolved,
37
+ role,
38
+ mtimeMs: -1,
39
+ size: -1,
40
+ sha256: code === "ENOENT" ? "missing" : `unreadable:${code}`,
41
+ };
42
+ }
43
+ }
44
+ export function snapshotAdvisoryProvenance(args) {
45
+ const seen = new Set();
46
+ const files = [];
47
+ for (const file of args.files) {
48
+ const key = advisoryPathKey(file.path, args.cwd);
49
+ if (seen.has(key))
50
+ continue;
51
+ seen.add(key);
52
+ files.push(snapshotOne(file.path, args.cwd, file.role));
53
+ }
54
+ return {
55
+ revision: {
56
+ sessionId: args.runtime.telemetrySessionId,
57
+ projectSeq: args.runtime.projectSeq,
58
+ turnIndex: args.runtime.turnIndex,
59
+ generation: args.generation,
60
+ capturedAt: args.capturedAt ?? Date.now(),
61
+ },
62
+ files,
63
+ ...(args.truncated ? { truncated: true } : {}),
64
+ };
65
+ }
66
+ function isFiniteNumber(value) {
67
+ return typeof value === "number" && Number.isFinite(value);
68
+ }
69
+ function isCapturedHash(value) {
70
+ return typeof value === "string" &&
71
+ (/^[a-f0-9]{64}$/.test(value) || value === "missing" || value.startsWith("unreadable:"));
72
+ }
73
+ function isWellFormed(value) {
74
+ if (!value || typeof value !== "object")
75
+ return false;
76
+ const record = value;
77
+ const revision = record.revision;
78
+ return !!revision && typeof revision.sessionId === "string" &&
79
+ isFiniteNumber(revision.projectSeq) && isFiniteNumber(revision.turnIndex) &&
80
+ isFiniteNumber(revision.generation) && isFiniteNumber(revision.capturedAt) &&
81
+ Array.isArray(record.files) && record.files.length > 0 && record.files.every((file) => !!file && typeof file.path === "string" &&
82
+ (file.role === "source" || file.role === "test" || file.role === "affected") &&
83
+ isFiniteNumber(file.mtimeMs) && isFiniteNumber(file.size) &&
84
+ isCapturedHash(file.sha256));
85
+ }
86
+ export function validateAdvisoryProvenance(record, cwd, runtime) {
87
+ if (!isWellFormed(record.provenance)) {
88
+ return { status: "unknown", reasons: ["malformed-or-legacy-provenance"], allFilesDeleted: false, changedPathCount: 0 };
89
+ }
90
+ const provenance = record.provenance;
91
+ const reasons = [];
92
+ let unknown = provenance.truncated === true;
93
+ if (unknown)
94
+ reasons.push("truncated-provenance");
95
+ if (runtime) {
96
+ if (provenance.revision.sessionId !== runtime.telemetrySessionId)
97
+ reasons.push("session-mismatch");
98
+ }
99
+ let deletedFiles = 0;
100
+ const changedPaths = new Set();
101
+ for (const captured of provenance.files) {
102
+ const resolved = path.resolve(cwd, captured.path);
103
+ const reasonsBefore = reasons.length;
104
+ let stat;
105
+ try {
106
+ stat = fs.statSync(resolved);
107
+ }
108
+ catch (error) {
109
+ const code = error.code ?? "unknown";
110
+ if (code === "ENOENT") {
111
+ if (captured.sha256 !== "missing") {
112
+ deletedFiles += 1;
113
+ reasons.push(`missing:${advisoryPathKey(resolved, cwd)}`);
114
+ changedPaths.add(advisoryPathKey(resolved, cwd));
115
+ }
116
+ }
117
+ else {
118
+ unknown = true;
119
+ reasons.push(`unreadable:${advisoryPathKey(resolved, cwd)}:${code}`);
120
+ changedPaths.add(advisoryPathKey(resolved, cwd));
121
+ }
122
+ continue;
123
+ }
124
+ if (captured.sha256.startsWith("unreadable:")) {
125
+ unknown = true;
126
+ reasons.push(`capture-unreadable:${advisoryPathKey(resolved, cwd)}`);
127
+ changedPaths.add(advisoryPathKey(resolved, cwd));
128
+ continue;
129
+ }
130
+ if (captured.sha256 === "missing") {
131
+ reasons.push(`created:${advisoryPathKey(resolved, cwd)}`);
132
+ changedPaths.add(advisoryPathKey(resolved, cwd));
133
+ continue;
134
+ }
135
+ if (stat.mtimeMs !== captured.mtimeMs || stat.size !== captured.size) {
136
+ reasons.push(`metadata-changed:${advisoryPathKey(resolved, cwd)}`);
137
+ }
138
+ const currentHash = advisoryFileHash(resolved);
139
+ if (currentHash.startsWith("unreadable:")) {
140
+ unknown = true;
141
+ reasons.push(`${currentHash}:${advisoryPathKey(resolved, cwd)}`);
142
+ }
143
+ else if (currentHash !== captured.sha256) {
144
+ reasons.push(`content-changed:${advisoryPathKey(resolved, cwd)}`);
145
+ }
146
+ if (reasons.length > reasonsBefore)
147
+ changedPaths.add(advisoryPathKey(resolved, cwd));
148
+ }
149
+ const allFilesDeleted = deletedFiles === provenance.files.length;
150
+ if (unknown)
151
+ return { status: "unknown", reasons, allFilesDeleted, changedPathCount: changedPaths.size };
152
+ return reasons.length > 0
153
+ ? { status: "superseded", reasons, allFilesDeleted, changedPathCount: changedPaths.size }
154
+ : { status: "current", reasons: [], allFilesDeleted, changedPathCount: 0 };
155
+ }
156
+ // ── Finding-cited path existence (#1461 slice 1) ──────────────────────────────
157
+ //
158
+ // `validateAdvisoryProvenance` above answers "did the files the agent EDITED
159
+ // change since capture?". A cached scanner finding asks a different question:
160
+ // "does the file this finding NAMES still exist?". #1460's live case is the gap
161
+ // between them — a gitleaks blocker for a directory deleted eleven minutes
162
+ // earlier shipped as `current` seven consecutive times, because the edited
163
+ // files were all intact and the cited path was never in the envelope.
164
+ //
165
+ // CONTRACT (the five remaining #1461 slices reuse this verbatim):
166
+ // - Input is any finding shape; the caller supplies `citedPath`, so nothing
167
+ // here knows about gitleaks, trivy, govulncheck, vulture, or delta mode.
168
+ // - A finding whose cited path is absent (ENOENT/ENOTDIR) is DROPPED, not
169
+ // demoted. There is no remediation for a file that is gone — the agent
170
+ // cannot rotate a credential in it or delete a line from it. Content drift
171
+ // on a SURVIVING file stays `validateAdvisoryProvenance`'s job, and that
172
+ // one demotes.
173
+ // - Fail open, never closed: a finding with no cited path, an unreadable
174
+ // path (EACCES/EPERM/EBUSY), or a path past the stat budget is DELIVERED.
175
+ // Unreadable is not absent; a missed drop is noise, a wrong drop is a lost
176
+ // secret.
177
+ // - Bounded cost: findings are deduped by their RAW cited string first —
178
+ // zero filesystem work — before anything pays for `resolveRunnerPath`'s
179
+ // ancestor walk or `normalizeMapKey`'s realpath. A first cut that deduped
180
+ // by the resolved/canonical key instead still ran that expensive step
181
+ // once per FINDING, not once per unique path, because the dedup lookup
182
+ // came after the cost it was meant to avoid: #1461's live 126-finding
183
+ // record measured 72.7ms in that shape, all of it before the first
184
+ // `statSync`. Deduping the raw string first drops the same record to a
185
+ // ~1.6ms median (this repo, Windows, 10-sample bench) — the remaining
186
+ // cost is one canonicalization + one stat per distinct cited string,
187
+ // capped at `MAX_FINDING_PATH_STATS`.
188
+ // - Path identity uses the guard normalizer (`resolveRunnerPath` →
189
+ // `normalizeMapKey`) for the canonical-key dedup and the shape-aware
190
+ // `toProjectRelativePath` for display, so Windows spellings of one path
191
+ // collapse to one stat and one log entry (defect shapes 1 and 2). This
192
+ // helper has no zero-I/O contract of its own to protect — it stats.
193
+ // Slice 3 (delta mode) must find and name its OWN seam before reusing
194
+ // this shape; `formatDeltaMode` (tools/lens-diagnostics.ts) reads only
195
+ // actionable-warnings, code-quality-warnings, and the delta report, none
196
+ // of which any #1461 slice writes yet, so nothing here currently touches
197
+ // it.
198
+ /** Stat budget: one per unique cited path, sharing the envelope's own cap. */
199
+ export const MAX_FINDING_PATH_STATS = MAX_ADVISORY_AFFECTED_FILES;
200
+ /** How many dead paths a single drop record names before it stops. */
201
+ const MAX_LOGGED_DEAD_PATHS = 3;
202
+ export function findingPathExistence(resolvedPath) {
203
+ try {
204
+ fs.statSync(resolvedPath);
205
+ return "live";
206
+ }
207
+ catch (error) {
208
+ const code = error.code ?? "unknown";
209
+ // ENOTDIR: an ancestor component is no longer a directory — the cited
210
+ // path cannot exist either, same as ENOENT.
211
+ return code === "ENOENT" || code === "ENOTDIR" ? "missing" : "unknown";
212
+ }
213
+ }
214
+ /**
215
+ * Partition findings into `{live, dropped}` by whether the path each one names
216
+ * still exists. Pure apart from the `fs.statSync` probe, which is injectable so
217
+ * the partition rules are unit-testable without a filesystem.
218
+ */
219
+ export function partitionFindingsByCitedPath(args) {
220
+ const limit = args.maxUniquePaths ?? MAX_FINDING_PATH_STATS;
221
+ const probe = args.existence ?? findingPathExistence;
222
+ // Two dedup layers, cheapest first. `rawVerdicts` collapses findings that
223
+ // cite the IDENTICAL string with zero filesystem work — the dominant case
224
+ // (#1460's live record: 126 findings over a handful of distinct `file`
225
+ // strings). Only a raw string not seen before pays for
226
+ // `resolveRunnerPath`/`advisoryPathKey`, which folds FS-confirmed spelling
227
+ // variants (case, separators, ancestor walk-up) into `verdicts`, the
228
+ // canonical-key map that `statCount` reports. Keying the expensive work by
229
+ // the RESOLVED path instead of the raw one (the pre-#1461-HIGH-2 shape)
230
+ // still called it once per finding, since the dedup lookup came after the
231
+ // cost it was meant to dedupe.
232
+ const rawVerdicts = new Map();
233
+ const verdicts = new Map();
234
+ const live = [];
235
+ const dropped = [];
236
+ const deadPaths = [];
237
+ let truncated = false;
238
+ for (const finding of args.findings) {
239
+ const cited = args.citedPath(finding);
240
+ if (!cited) {
241
+ live.push(finding);
242
+ continue;
243
+ }
244
+ let verdict = rawVerdicts.get(cited);
245
+ if (verdict === undefined) {
246
+ // Ancestor-tolerant, same as `toRunnerDisplayPath`'s resolution in
247
+ // runtime-turn.ts — a bare `path.resolve` here would decide the drop
248
+ // against a different root than the one used to render the survivor,
249
+ // dropping findings the display path would have shown correctly
250
+ // (#1461 HIGH-1). `resolveRunnerPath` already runs its result through
251
+ // `normalizeMapKey`, so the resolved path IS the canonical key — a
252
+ // second `advisoryPathKey` pass would re-pay the same realpath cost.
253
+ const resolved = resolveRunnerPath(args.cwd, cited);
254
+ const key = resolved;
255
+ verdict = verdicts.get(key);
256
+ if (verdict === undefined) {
257
+ if (verdicts.size >= limit) {
258
+ // Budget spent on paths we have not seen before — deliver rather
259
+ // than guess. Already-probed paths keep their cached verdict.
260
+ truncated = true;
261
+ verdict = "live";
262
+ }
263
+ else {
264
+ verdict = probe(resolved);
265
+ verdicts.set(key, verdict);
266
+ if (verdict === "missing")
267
+ deadPaths.push(resolved);
268
+ }
269
+ }
270
+ rawVerdicts.set(cited, verdict);
271
+ }
272
+ if (verdict === "missing")
273
+ dropped.push(finding);
274
+ else
275
+ live.push(finding);
276
+ }
277
+ return { live, dropped, deadPaths, statCount: verdicts.size, truncated };
278
+ }
279
+ /**
280
+ * Delivery-seam wrapper: partition, then emit one bounded `finding_dead_path_drop`
281
+ * record when anything was dropped, and return only what is safe to deliver.
282
+ *
283
+ * The record is the #1432 Gap 1 principle applied here — an eviction that logs
284
+ * nothing is only confirmable by the absence of complaints. One record per
285
+ * store per delivery, with a capped path sample; never one per finding.
286
+ */
287
+ export function dropFindingsForMissingPaths(args) {
288
+ const partition = partitionFindingsByCitedPath(args);
289
+ if (partition.dropped.length === 0)
290
+ return partition.live;
291
+ logLatency({
292
+ type: "phase",
293
+ phase: "finding_dead_path_drop",
294
+ filePath: args.cwd,
295
+ durationMs: 0,
296
+ metadata: {
297
+ store: args.store,
298
+ droppedDeadPaths: partition.dropped.length,
299
+ deadPathCount: partition.deadPaths.length,
300
+ deliveredCount: partition.live.length,
301
+ statCount: partition.statCount,
302
+ samplePaths: partition.deadPaths
303
+ .slice(0, MAX_LOGGED_DEAD_PATHS)
304
+ .map((deadPath) => toProjectRelativePath(deadPath, args.cwd)),
305
+ ...(partition.truncated ? { truncated: true } : {}),
306
+ },
307
+ });
308
+ return partition.live;
309
+ }
310
+ export function provenanceStamp(provenance) {
311
+ if (!isWellFormed(provenance))
312
+ return "session unknown / turn unknown / generation unknown";
313
+ return `session ${provenance.revision.sessionId} / turn ${provenance.revision.turnIndex} / generation ${provenance.revision.generation}`;
314
+ }
@@ -2,7 +2,8 @@
2
2
  * AgentBehaviorClient for pi-lens
3
3
  *
4
4
  * Tracks tool call sequences and flags anti-patterns in real-time:
5
- * - Blind writes: editing or writing without reading first
5
+ * - Blind writes: editing or writing without reading first (a same-session
6
+ * write/edit of the same path counts as file knowledge)
6
7
  * - Thrashing: repeated identical tool calls with no progress
7
8
  *
8
9
  * No external dependencies — purely tracks tool call history.
@@ -23,6 +24,7 @@ export class AgentBehaviorClient {
23
24
  lastToolTimestamp = 0;
24
25
  // Per-file tracking
25
26
  fileEditCount = new Map();
27
+ static FILE_EDIT_IDLE_MS = 30 * 60_000;
26
28
  /**
27
29
  * Record a tool call and return any warnings triggered.
28
30
  * Called from tool_result handler.
@@ -63,7 +65,14 @@ export class AgentBehaviorClient {
63
65
  // Check for blind writes
64
66
  if (WRITE_OPS.has(toolName)) {
65
67
  const recentWindow = this.toolHistory.slice(-BLIND_WRITE_WINDOW);
66
- const hasRecentRead = recentWindow.some((r) => READ_OPS.has(r.toolName));
68
+ // A read in the window proves file knowledge; so does a same-session
69
+ // write/edit of THIS path — the agent authored the content, so the
70
+ // write-then-edit loop on a self-authored file is not a blind write.
71
+ const hasRecentRead = recentWindow.some((r) => READ_OPS.has(r.toolName) ||
72
+ (WRITE_OPS.has(r.toolName) &&
73
+ r.filePath !== undefined &&
74
+ normalizedPath !== null &&
75
+ normalizeMapKey(r.filePath) === normalizedPath));
67
76
  if (!hasRecentRead && recentWindow.length > 0) {
68
77
  // Count how many writes in the window without reads
69
78
  const writesWithoutRead = recentWindow.filter((r) => WRITE_OPS.has(r.toolName)).length;
@@ -82,7 +91,15 @@ export class AgentBehaviorClient {
82
91
  // Track edits per file
83
92
  if (filePath) {
84
93
  const key = normalizeMapKey(filePath);
85
- this.fileEditCount.set(key, (this.fileEditCount.get(key) ?? 0) + 1);
94
+ this.fileEditCount.set(key, {
95
+ count: (this.fileEditCount.get(key)?.count ?? 0) + 1,
96
+ lastUsedAt: now,
97
+ });
98
+ for (const [pathKey, entry] of this.fileEditCount) {
99
+ if (now - entry.lastUsedAt > AgentBehaviorClient.FILE_EDIT_IDLE_MS) {
100
+ this.fileEditCount.delete(pathKey);
101
+ }
102
+ }
86
103
  }
87
104
  }
88
105
  // Add to history (keep last 50 entries)
@@ -104,7 +121,7 @@ export class AgentBehaviorClient {
104
121
  * Get edit count for a file in this session.
105
122
  */
106
123
  getEditCount(filePath) {
107
- return this.fileEditCount.get(filePath) ?? 0;
124
+ return this.fileEditCount.get(normalizeMapKey(filePath))?.count ?? 0;
108
125
  }
109
126
  /**
110
127
  * Reset state (e.g., on session start).
@@ -169,12 +169,19 @@ export function wireAgentNudgeSubscriber(args) {
169
169
  * - all local → "after your last turn" (the original #485 wording,
170
170
  * unchanged — verified by the pre-existing #485
171
171
  * tests);
172
- * - all cross-process → "by another pi-lens instance (e.g. a subagent's)";
173
- * - mixed → "after your last turn (N of them by another
174
- * pi-lens instance)" — the base framing stays local
172
+ * - all cross-process → "by an automatic run outside your turn";
173
+ * - mixed → "after your last turn (N of them by an automatic
174
+ * run outside it)" — the base framing stays local
175
175
  * and the cross-process portion is counted out
176
- * precisely, so no local file is ever misattributed
177
- * to another instance.
176
+ * precisely, so no local file is ever misattributed.
177
+ *
178
+ * The cross-process wording names the ACTION, not the process identity. It
179
+ * used to read "by another pi-lens instance (e.g. a subagent's)", which sent
180
+ * agents investigating what other instance was running — the exact behavior
181
+ * this nudge exists to prevent. Which process formatted the file is an
182
+ * implementation detail the reading agent cannot act on; that it was an
183
+ * automatic run outside their turn is the whole actionable content. The
184
+ * origin split stays exact in the telemetry for human debugging.
178
185
  *
179
186
  * Clears the accumulator ONLY here, on actual injection — never on
180
187
  * agent_end/agent_settled/turn_start. Files formatted at the last turn_end of
@@ -222,10 +229,10 @@ export function consumeAgentNudge(dbg) {
222
229
  // file to another instance — a mixed batch keeps the local base framing
223
230
  // and calls out the cross-process portion by exact count.
224
231
  const attribution = localCount === 0
225
- ? "by another pi-lens instance (e.g. a subagent's)"
232
+ ? "by an automatic run outside your turn"
226
233
  : crossProcessCount === 0
227
234
  ? "after your last turn"
228
- : `after your last turn (${crossProcessCount} of them by another pi-lens instance)`;
235
+ : `after your last turn (${crossProcessCount} of them by an automatic run outside it)`;
229
236
  const message = `pi-lens: ${filesTotal} file(s) were ${verbLabel} ${attribution}: ${nameList} — working-tree changes to these are expected; re-read before editing.`;
230
237
  logLatency({
231
238
  type: "phase",