@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
@@ -1,32 +1,699 @@
1
+ import * as nodeFs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isPathIgnoredByProject } from "./file-utils.js";
4
+ import { tokenizeShellCommand } from "./bash-file-access.js";
5
+ import { logLatency } from "./latency-logger.js";
6
+ import { advisoryFileHash, advisoryPathKey, MAX_ADVISORY_AFFECTED_FILES, snapshotAdvisoryProvenance, } from "./advisory-provenance.js";
7
+ function resolveGuardPath(filePath, cwd) {
8
+ return path.resolve(cwd, filePath);
9
+ }
10
+ function guardPathKey(filePath, cwd) {
11
+ return advisoryPathKey(filePath, cwd);
12
+ }
13
+ function fileFingerprint(filePath) {
14
+ return advisoryFileHash(filePath);
15
+ }
16
+ function currentFileFingerprint(filePath) {
17
+ try {
18
+ nodeFs.statSync(filePath);
19
+ return fileFingerprint(filePath);
20
+ }
21
+ catch (err) {
22
+ return err.code === "ENOENT"
23
+ ? "missing"
24
+ : `unreadable:${err.code ?? "unknown"}`;
25
+ }
26
+ }
27
+ function capAffectedFiles(files, cwd) {
28
+ const unique = [...new Set(files.map((file) => resolveGuardPath(file, cwd)))];
29
+ return {
30
+ files: unique.slice(0, MAX_ADVISORY_AFFECTED_FILES),
31
+ truncated: unique.length > MAX_ADVISORY_AFFECTED_FILES,
32
+ };
33
+ }
34
+ /** Recovery is safe only when blocker content and its file provenance agree. */
35
+ function hasCompleteBlockingProvenance(blockerContent, blockingFiles, cwd) {
36
+ if (typeof blockerContent !== "string" || blockerContent.length === 0)
37
+ return false;
38
+ if (!Array.isArray(blockingFiles) || blockingFiles.length === 0)
39
+ return false;
40
+ if (blockingFiles.some((file) => typeof file !== "string" || file.trim().length === 0)) {
41
+ return false;
42
+ }
43
+ const provenanceKeys = blockingFiles.map((file) => guardPathKey(file, cwd));
44
+ if (new Set(provenanceKeys).size !== provenanceKeys.length)
45
+ return false;
46
+ const blockerKeys = blockerContent.split("\n").map((line) => {
47
+ const separator = line.indexOf(": ");
48
+ if (separator <= 0)
49
+ return undefined;
50
+ const file = line.slice(0, separator).trim();
51
+ return file.length > 0 ? guardPathKey(file, cwd) : undefined;
52
+ });
53
+ if (blockerKeys.some((key) => key === undefined))
54
+ return false;
55
+ const uniqueBlockerKeys = new Set(blockerKeys);
56
+ return (uniqueBlockerKeys.size === blockerKeys.length &&
57
+ uniqueBlockerKeys.size === provenanceKeys.length &&
58
+ [...uniqueBlockerKeys].every((key) => provenanceKeys.includes(key)));
59
+ }
1
60
  function getShellCommand(input) {
2
61
  if (!input || typeof input !== "object")
3
62
  return "";
4
63
  const raw = input;
5
- if (typeof raw.command === "string")
64
+ if (typeof raw.command === "string" && raw.command.trim())
6
65
  return raw.command;
7
- if (typeof raw.cmd === "string")
66
+ if (typeof raw.cmd === "string" && raw.cmd.trim())
8
67
  return raw.cmd;
68
+ if (typeof raw.command === "string")
69
+ return raw.command;
9
70
  return "";
10
71
  }
72
+ function executableName(value) {
73
+ const normalized = value.replace(/\\/g, "/");
74
+ let name = (normalized.slice(normalized.lastIndexOf("/") + 1) ?? "").toLowerCase();
75
+ // Shell launchers are commonly supplied as resolved Windows paths or as
76
+ // PATHEXT-qualified names. Guard classification must happen after the same
77
+ // basename/extension normalization for every wrapper family.
78
+ return name.replace(/\.(?:exe|com|bat|cmd)$/i, "");
79
+ }
80
+ function isShellWrapper(value) {
81
+ return new Set([
82
+ "sh",
83
+ "bash",
84
+ "dash",
85
+ "zsh",
86
+ "ash",
87
+ "cmd",
88
+ "pwsh",
89
+ "powershell",
90
+ ]).has(executableName(value));
91
+ }
92
+ function isGitExecutable(value) {
93
+ return new Set(["git", "git.exe", "git.cmd", "git.bat"]).has(executableName(value));
94
+ }
95
+ const COMMAND_STRING_WRAPPERS = new Set(["busybox", "toybox", "nix-shell"]);
96
+ function isCommandStringWrapper(value) {
97
+ return COMMAND_STRING_WRAPPERS.has(executableName(value));
98
+ }
99
+ /**
100
+ * Canonicalize shell parameter separators before command classification. The
101
+ * quote-aware pass deliberately leaves literal arguments alone; the lexer
102
+ * then supplies the command-position boundaries used by the guard.
103
+ */
104
+ function canonicalizeGuardCommand(command) {
105
+ let result = "";
106
+ let quote;
107
+ let escaped = false;
108
+ for (let i = 0; i < command.length; i++) {
109
+ const ch = command[i];
110
+ if (quote === "single") {
111
+ result += ch;
112
+ if (ch === "'")
113
+ quote = undefined;
114
+ continue;
115
+ }
116
+ if (quote === "double") {
117
+ result += ch;
118
+ if (escaped)
119
+ escaped = false;
120
+ else if (ch === "\\")
121
+ escaped = true;
122
+ else if (ch === '"')
123
+ quote = undefined;
124
+ continue;
125
+ }
126
+ if (ch === "'" || ch === '"') {
127
+ quote = ch === "'" ? "single" : "double";
128
+ result += ch;
129
+ continue;
130
+ }
131
+ const parameter = command.slice(i).match(/^\$\{IFS[^}]*\}/)?.[0];
132
+ const positional = command.slice(i).match(/^\$IFS(?:\$[0-9]+)?/)?.[0];
133
+ if (parameter || positional) {
134
+ result += " ";
135
+ i += (parameter ?? positional ?? "").length - 1;
136
+ continue;
137
+ }
138
+ result += ch;
139
+ }
140
+ let collapsed = "";
141
+ let pendingSpace = false;
142
+ quote = undefined;
143
+ for (const ch of result) {
144
+ if (!quote && /\s/.test(ch)) {
145
+ pendingSpace = collapsed.length > 0;
146
+ continue;
147
+ }
148
+ if (pendingSpace)
149
+ collapsed += " ";
150
+ pendingSpace = false;
151
+ collapsed += ch;
152
+ if (!quote && (ch === "'" || ch === '"')) {
153
+ quote = ch === "'" ? "single" : "double";
154
+ }
155
+ else if ((quote === "single" && ch === "'") || (quote === "double" && ch === '"')) {
156
+ quote = undefined;
157
+ }
158
+ }
159
+ return collapsed.trim();
160
+ }
161
+ /** Normalize only a command-position token; never apply this to path args. */
162
+ function normalizeGuardVerbToken(value) {
163
+ return value
164
+ .replace(/\\(?=[A-Za-z0-9_])/g, "")
165
+ .replace(/`(?=.)/g, "")
166
+ .replace(/\^(?=.)/g, "");
167
+ }
168
+ function expandGuardVerbToken(value) {
169
+ return normalizeGuardVerbToken(value).trim().split(/\s+/).filter(Boolean);
170
+ }
171
+ function delimitedSubstitutionBody(command, start, opener, closer) {
172
+ let depth = 1;
173
+ let quote;
174
+ let escaped = false;
175
+ for (let i = start + opener.length; i < command.length; i++) {
176
+ const ch = command[i];
177
+ if (quote === "single") {
178
+ if (ch === "'")
179
+ quote = undefined;
180
+ continue;
181
+ }
182
+ if (quote === "double" && ch === '"') {
183
+ quote = undefined;
184
+ continue;
185
+ }
186
+ if (escaped) {
187
+ escaped = false;
188
+ continue;
189
+ }
190
+ if (ch === "\\") {
191
+ escaped = true;
192
+ continue;
193
+ }
194
+ if (ch === "'" || ch === '"') {
195
+ quote = ch === "'" ? "single" : "double";
196
+ continue;
197
+ }
198
+ if (command.startsWith(opener, i)) {
199
+ depth += 1;
200
+ i += opener.length - 1;
201
+ continue;
202
+ }
203
+ if (command.startsWith(closer, i) && --depth === 0) {
204
+ return { body: command.slice(start + opener.length, i), end: i };
205
+ }
206
+ }
207
+ return undefined;
208
+ }
209
+ function containsGuardedSubstitution(command, depth) {
210
+ if (depth > 3)
211
+ return false;
212
+ let quote;
213
+ let escaped = false;
214
+ for (let i = 0; i < command.length; i++) {
215
+ const ch = command[i];
216
+ if (quote === "single") {
217
+ if (ch === "'")
218
+ quote = undefined;
219
+ continue;
220
+ }
221
+ if (quote === "double" && ch === '"') {
222
+ quote = undefined;
223
+ continue;
224
+ }
225
+ if (escaped) {
226
+ escaped = false;
227
+ continue;
228
+ }
229
+ if (ch === "\\") {
230
+ escaped = true;
231
+ continue;
232
+ }
233
+ if (ch === '"' || ch === "'") {
234
+ quote = ch === "'" ? "single" : "double";
235
+ continue;
236
+ }
237
+ const substitution = command.startsWith("$(", i)
238
+ ? delimitedSubstitutionBody(command, i, "$(", ")")
239
+ : command.startsWith("<(", i)
240
+ ? delimitedSubstitutionBody(command, i, "<(", ")")
241
+ : command.startsWith(">(", i)
242
+ ? delimitedSubstitutionBody(command, i, ">(", ")")
243
+ : ch === "`"
244
+ ? { body: command.slice(i + 1, command.indexOf("`", i + 1)), end: command.indexOf("`", i + 1) }
245
+ : undefined;
246
+ if (substitution && substitution.end >= 0) {
247
+ const nested = canonicalizeGuardCommand(substitution.body);
248
+ if (containsGuardedSubstitution(nested, depth + 1) ||
249
+ tokenizeShellCommand(nested).some((segment) => containsCommitOrPush(segment.tokens, depth + 1))) {
250
+ return true;
251
+ }
252
+ i = substitution.end;
253
+ }
254
+ }
255
+ return false;
256
+ }
257
+ function containsCommitOrPush(tokens, depth) {
258
+ if (depth > 3 || tokens.length === 0)
259
+ return false;
260
+ let commandTokens = tokens;
261
+ while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(commandTokens[0] ?? "")) {
262
+ commandTokens = commandTokens.slice(1);
263
+ }
264
+ if (commandTokens.length === 0)
265
+ return false;
266
+ const expandedHead = expandGuardVerbToken(commandTokens[0] ?? "");
267
+ if (expandedHead.length > 1) {
268
+ commandTokens = [...expandedHead, ...commandTokens.slice(1)];
269
+ }
270
+ if (isCommandStringWrapper(commandTokens[0] ?? "")) {
271
+ const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
272
+ const runIndex = lower.findIndex((token) => token === "--run");
273
+ if (runIndex >= 0 && runIndex + 1 < commandTokens.length) {
274
+ const nestedCommand = commandTokens.slice(runIndex + 2).join(" ");
275
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
276
+ }
277
+ }
278
+ // These commands consume their following words as text/patterns; a bare
279
+ // git token in their arguments is not an indirect executable invocation.
280
+ // `$(git push)` is execution and must block; `"git push"` as literal text
281
+ // is allowed. Substitutions are screened before this text-consumer escape.
282
+ if (["echo", "printf", "grep"].includes(executableName(commandTokens[0] ?? ""))) {
283
+ return false;
284
+ }
285
+ const gitIndex = commandTokens.findIndex((token) => isGitExecutable(token));
286
+ if (gitIndex >= 0) {
287
+ // Any non-leading git invocation is indirect. Do not maintain a wrapper
288
+ // or flag allowlist: unknown launchers are the security boundary here.
289
+ if (gitIndex > 0)
290
+ return true;
291
+ const gitTokens = commandTokens.slice(gitIndex);
292
+ let i = 1;
293
+ const takesValue = new Set([
294
+ "-C",
295
+ "-c",
296
+ "--config-env",
297
+ "--git-dir",
298
+ "--work-tree",
299
+ "--exec-path",
300
+ "--namespace",
301
+ ]);
302
+ while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
303
+ const option = gitTokens[i];
304
+ if (["--help", "-h", "--version", "-v", "-V"].includes(option))
305
+ return false;
306
+ if (option === "--")
307
+ return gitTokens[i + 1] === "commit" || gitTokens[i + 1] === "push";
308
+ if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
309
+ i += 1;
310
+ continue;
311
+ }
312
+ if (["--config-env", "--git-dir", "--work-tree", "--exec-path", "--namespace"].some((prefix) => option.startsWith(`${prefix}=`))) {
313
+ i += 1;
314
+ continue;
315
+ }
316
+ i += takesValue.has(option) ? 2 : 1;
317
+ }
318
+ const verbs = expandGuardVerbToken(gitTokens[i] ?? "");
319
+ return verbs.length === 1 && (verbs[0] === "commit" || verbs[0] === "push");
320
+ }
321
+ const leadingExecutable = commandTokens[0] ?? "";
322
+ const knownCommandStringWrapper = isShellWrapper(leadingExecutable) || isCommandStringWrapper(leadingExecutable);
323
+ if (!knownCommandStringWrapper) {
324
+ // Unknown launchers are a fail-closed boundary only when they explicitly
325
+ // accept a command string. Re-tokenizing the value keeps literal mentions
326
+ // such as `myprog -c "echo git push"` out of the guarded-command path.
327
+ const unknownSwitchIndex = commandTokens.slice(1).findIndex((token) => (token.startsWith("-") || token.startsWith("/")) && token.length > 1);
328
+ if (unknownSwitchIndex < 0)
329
+ return false;
330
+ const commandIndex = unknownSwitchIndex + 2;
331
+ if (commandIndex >= commandTokens.length)
332
+ return false;
333
+ const nestedCommand = commandTokens.slice(commandIndex).join(" ");
334
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
335
+ }
336
+ const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
337
+ const switchIndex = lower.findIndex((token) => token === "-c" ||
338
+ token === "--run" ||
339
+ token === "-lc" ||
340
+ (/^-[^-]*c$/.test(token) && !token.startsWith("--")) ||
341
+ token === "/c" ||
342
+ token === "-command" ||
343
+ token === "-command:" ||
344
+ token === "-encodedcommand");
345
+ if (switchIndex < 0 || switchIndex + 1 >= commandTokens.length)
346
+ return false;
347
+ // Encoded PowerShell is intentionally unsupported: decoding it here would
348
+ // be a second shell/parser and could turn an ambiguous command into a false
349
+ // allow. Plain -Command/-c is safely handed back to the shared lexer.
350
+ if (lower[switchIndex] === "-encodedcommand")
351
+ return false;
352
+ // switchIndex is relative to commandTokens.slice(1), so +2 addresses the
353
+ // command token after the switch for both cmd and PowerShell. This also
354
+ // handles cmd options preceding /C (for example `/S /C`).
355
+ let commandIndex = switchIndex + 2;
356
+ if (commandTokens[commandIndex] === "--")
357
+ commandIndex += 1;
358
+ const nestedCommand = commandTokens.slice(commandIndex).join(" ");
359
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
360
+ }
361
+ /** Analyze actual executable invocations, not substrings in shell text. */
11
362
  export function isGitCommitOrPushAttempt(toolName, input) {
12
363
  if (toolName !== "bash")
13
364
  return false;
14
- const cmd = getShellCommand(input).toLowerCase();
15
- if (!cmd)
365
+ const command = getShellCommand(input);
366
+ if (!command)
16
367
  return false;
17
- return /(^|\s|&&|;|\|)git\s+(commit|push)(\s|$)/.test(cmd);
368
+ const canonical = canonicalizeGuardCommand(command);
369
+ if (containsGuardedSubstitution(canonical, 0))
370
+ return true;
371
+ return tokenizeShellCommand(canonical).some((segment) => containsCommitOrPush(segment.tokens, 0));
372
+ }
373
+ function isTurnEndFindingsCache(value) {
374
+ if (!value || typeof value !== "object")
375
+ return false;
376
+ const record = value;
377
+ return (typeof record.content === "string" &&
378
+ typeof record.hasBlockers === "boolean" &&
379
+ Array.isArray(record.affectedFiles) &&
380
+ record.affectedFiles.every((file) => typeof file === "string") &&
381
+ typeof record.sessionId === "string" &&
382
+ typeof record.projectSeqStart === "number" &&
383
+ typeof record.projectSeqEnd === "number" &&
384
+ Number.isFinite(record.projectSeqStart) &&
385
+ Number.isFinite(record.projectSeqEnd) &&
386
+ !!record.fileSeqByPath &&
387
+ typeof record.fileSeqByPath === "object" &&
388
+ Object.values(record.fileSeqByPath).every((seq) => typeof seq === "number" && Number.isFinite(seq)) &&
389
+ !!record.fileContentHashes &&
390
+ typeof record.fileContentHashes === "object" &&
391
+ Object.values(record.fileContentHashes).every((hash) => typeof hash === "string") &&
392
+ (record.affectedFilesTruncated === undefined || typeof record.affectedFilesTruncated === "boolean") &&
393
+ (record.blockingFiles === undefined || Array.isArray(record.blockingFiles)));
394
+ }
395
+ function cacheRecord(cacheManager, cwd) {
396
+ const entry = cacheManager.readCache("turn-end-findings", cwd);
397
+ return entry && isTurnEndFindingsCache(entry.data) ? entry.data : undefined;
398
+ }
399
+ function markCacheUnknown(runtime, reason) {
400
+ runtime.markGitGuardCacheUnknown(reason);
401
+ }
402
+ /** Persist a complete, bounded, content-bound guard record. */
403
+ export function writeGitGuardRecord(cacheManager, runtime, cwd, record) {
404
+ const capped = capAffectedFiles(Array.isArray(record.affectedFiles) ? record.affectedFiles : [], cwd);
405
+ const fileSeqByPath = { ...(record.fileSeqByPath ?? {}) };
406
+ for (const file of capped.files) {
407
+ const key = guardPathKey(file, cwd);
408
+ if (fileSeqByPath[key] === undefined) {
409
+ fileSeqByPath[key] = runtime.getFileSeq(resolveGuardPath(file, cwd));
410
+ }
411
+ }
412
+ const currentProvenance = snapshotAdvisoryProvenance({
413
+ cwd,
414
+ runtime,
415
+ generation: 0,
416
+ files: capped.files.map((file) => ({ path: file, role: "affected" })),
417
+ truncated: capped.truncated,
418
+ });
419
+ const fileContentHashes = Object.fromEntries(currentProvenance.files.map((file) => [
420
+ guardPathKey(file.path, cwd),
421
+ file.sha256,
422
+ ]));
423
+ const data = {
424
+ ...record,
425
+ affectedFiles: capped.files,
426
+ affectedFilesTruncated: capped.truncated,
427
+ fileSeqByPath,
428
+ blockingFiles: Array.isArray(record.blockingFiles)
429
+ ? capAffectedFiles(record.blockingFiles, cwd).files
430
+ : undefined,
431
+ fileContentHashes,
432
+ provenance: record.provenance ?? currentProvenance,
433
+ };
434
+ try {
435
+ cacheManager.writeCache("turn-end-findings", data, cwd);
436
+ runtime.clearGitGuardCacheUnknown();
437
+ return true;
438
+ }
439
+ catch {
440
+ markCacheUnknown(runtime, "cache_write_failed");
441
+ return false;
442
+ }
443
+ }
444
+ function logDecision(cwd, decision, reasonCategory, metadata = {}) {
445
+ logLatency({
446
+ type: "phase",
447
+ toolName: "git-guard",
448
+ filePath: cwd,
449
+ phase: "decision",
450
+ durationMs: 0,
451
+ result: decision,
452
+ metadata: { decision, reasonCategory, ...metadata },
453
+ });
454
+ }
455
+ function unknown(cwd, reasonCategory, metadata = {}) {
456
+ logDecision(cwd, "unknown", reasonCategory, metadata);
457
+ return {
458
+ block: true,
459
+ unknown: true,
460
+ reason: `🔴 COMMIT BLOCKED (--lens-guard): blocker state is unknown (${reasonCategory}). Re-run pi-lens checks or start a fresh session, then retry.`,
461
+ };
462
+ }
463
+ /**
464
+ * Reconcile the one persisted record after a per-file dispatch. This is called
465
+ * on tool_result, never tool_call, and therefore does not add disk I/O to the
466
+ * edit preflight path.
467
+ */
468
+ export function syncGitGuardRecord(runtime, cacheManager, cwd, editedFilePath) {
469
+ const entries = runtime.getInlineBlockersSnapshot?.() ?? [];
470
+ const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
471
+ const existing = cacheRecord(cacheManager, cwd);
472
+ if (existing &&
473
+ existing.sessionId !== runtime.telemetrySessionId &&
474
+ entries.length === 0) {
475
+ markCacheUnknown(runtime, "session_mismatch");
476
+ return;
477
+ }
478
+ if (!existing && inspection !== "missing" && entries.length === 0) {
479
+ markCacheUnknown(runtime, `cache_${inspection}`);
480
+ return;
481
+ }
482
+ const fileSeqByPath = {};
483
+ for (const [filePath, seq] of runtime.getFileSeqEntries?.() ?? []) {
484
+ fileSeqByPath[guardPathKey(filePath, cwd)] = seq;
485
+ }
486
+ const inlineFiles = entries.map((entry) => resolveGuardPath(entry.filePath, cwd));
487
+ const existingBlockingFiles = existing?.blockingFiles ?? [];
488
+ const provenanceComplete = existing?.blockerContent
489
+ ? hasCompleteBlockingProvenance(existing.blockerContent, existingBlockingFiles, cwd)
490
+ : true;
491
+ if (existing?.blockerContent && !provenanceComplete) {
492
+ markCacheUnknown(runtime, "blocking_provenance_untrusted");
493
+ return;
494
+ }
495
+ const editedKey = editedFilePath ? guardPathKey(editedFilePath, cwd) : undefined;
496
+ const testFiles = existing?.testFailureFiles ?? [];
497
+ const remainingBlockingFiles = editedKey && existingBlockingFiles.length > 0
498
+ ? existingBlockingFiles.filter((file) => guardPathKey(file, cwd) !== editedKey)
499
+ : existingBlockingFiles;
500
+ let affectedFiles = [...(existing?.affectedFiles ?? []), ...inlineFiles];
501
+ if (!entries.length && editedKey && existing) {
502
+ const isStillTestFailure = testFiles.some((file) => guardPathKey(file, cwd) === editedKey);
503
+ const isUnknownBlockingPath = existingBlockingFiles.length === 0 && !!existing.blockerContent;
504
+ if (!isStillTestFailure && !isUnknownBlockingPath) {
505
+ affectedFiles = affectedFiles.filter((file) => guardPathKey(file, cwd) !== editedKey);
506
+ }
507
+ }
508
+ // A clean per-file dispatch is authoritative for that file. When the
509
+ // persisted record has explicit blocking-file provenance and the last such
510
+ // file just reconciled clean, retaining blockerContent would resurrect a
511
+ // stale blocker on every later git-guard lookup. Records without that
512
+ // provenance remain fail-closed: they cannot be safely cleared here.
513
+ const clearedLastKnownBlocker = !entries.length &&
514
+ !!editedKey &&
515
+ provenanceComplete &&
516
+ existingBlockingFiles.length > 0 &&
517
+ remainingBlockingFiles.length === 0;
518
+ const blockerContent = entries.length > 0
519
+ ? entries.map((entry) => `${entry.filePath}: ${entry.summary}`).join("\n")
520
+ : clearedLastKnownBlocker
521
+ ? undefined
522
+ : existing?.blockerContent;
523
+ const hasTestFailures = existing?.testFailures === true;
524
+ const hasBlockers = !!blockerContent || hasTestFailures;
525
+ const content = [blockerContent, hasTestFailures ? existing?.testFailureContent : undefined]
526
+ .filter((value) => !!value)
527
+ .join("\n\n");
528
+ if (!hasBlockers && !content) {
529
+ cacheManager.clearCache("turn-end-findings", cwd);
530
+ return;
531
+ }
532
+ writeGitGuardRecord(cacheManager, runtime, cwd, {
533
+ content: content || existing?.content || "",
534
+ blockerContent,
535
+ blockingFiles: entries.length > 0 ? inlineFiles : remainingBlockingFiles,
536
+ hasBlockers,
537
+ affectedFiles,
538
+ sessionId: runtime.telemetrySessionId,
539
+ projectSeqStart: runtime.turnStartProjectSeq,
540
+ projectSeqEnd: runtime.projectSeq,
541
+ fileSeqByPath,
542
+ fileContentHashes: {},
543
+ consumed: false,
544
+ testFailures: existing?.testFailures,
545
+ testFailureContent: existing?.testFailureContent,
546
+ testFailureFiles: existing?.testFailureFiles,
547
+ });
548
+ }
549
+ /** Add blocking test failures to the same turn-end record. */
550
+ export function mergeGitGuardTestFailure(cacheManager, cwd, runtime, content, files) {
551
+ const existing = cacheRecord(cacheManager, cwd);
552
+ const fileSeqByPath = {};
553
+ for (const [filePath, seq] of runtime.getFileSeqEntries?.() ?? []) {
554
+ fileSeqByPath[guardPathKey(filePath, cwd)] = seq;
555
+ }
556
+ const failedFiles = files.map((file) => resolveGuardPath(file, cwd));
557
+ const blockerContent = existing?.blockerContent;
558
+ const testFailureFiles = [
559
+ ...(existing?.testFailureFiles ?? []),
560
+ ...failedFiles,
561
+ ].filter((file, index, all) => all.findIndex((candidate) => guardPathKey(candidate, cwd) === guardPathKey(file, cwd)) === index);
562
+ writeGitGuardRecord(cacheManager, runtime, cwd, {
563
+ content: [blockerContent, content].filter(Boolean).join("\n\n"),
564
+ blockerContent,
565
+ blockingFiles: existing?.blockingFiles ?? [],
566
+ testFailureContent: content,
567
+ testFailureFiles: testFailureFiles,
568
+ hasBlockers: true,
569
+ affectedFiles: [...(existing?.affectedFiles ?? []), ...failedFiles],
570
+ sessionId: runtime.telemetrySessionId,
571
+ projectSeqStart: runtime.turnStartProjectSeq,
572
+ projectSeqEnd: runtime.projectSeq,
573
+ fileSeqByPath,
574
+ fileContentHashes: {},
575
+ consumed: false,
576
+ testFailures: true,
577
+ });
578
+ }
579
+ /** A passing test run resolves only its own previous test failures. */
580
+ export function clearGitGuardTestFailure(cacheManager, cwd, runtime, passedFiles = []) {
581
+ const existing = cacheRecord(cacheManager, cwd);
582
+ if (!existing?.testFailures)
583
+ return;
584
+ const passedKeys = new Set((passedFiles.length > 0 ? passedFiles : existing.testFailureFiles ?? []).map((file) => guardPathKey(file, cwd)));
585
+ const remainingFiles = (existing.testFailureFiles ?? []).filter((file) => !passedKeys.has(guardPathKey(file, cwd)));
586
+ const blockerContent = existing.blockerContent ?? "";
587
+ if (!blockerContent && remainingFiles.length === 0) {
588
+ cacheManager.clearCache("turn-end-findings", cwd);
589
+ return;
590
+ }
591
+ const blockingKeys = new Set((existing.blockingFiles ?? []).map((file) => guardPathKey(file, cwd)));
592
+ const affectedFiles = existing.affectedFiles.filter((file) => blockingKeys.has(guardPathKey(file, cwd)) ||
593
+ remainingFiles.some((testFile) => guardPathKey(testFile, cwd) === guardPathKey(file, cwd)));
594
+ writeGitGuardRecord(cacheManager, runtime, cwd, {
595
+ ...existing,
596
+ content: blockerContent,
597
+ blockerContent,
598
+ affectedFiles,
599
+ testFailures: remainingFiles.length > 0,
600
+ testFailureContent: remainingFiles.length > 0 ? existing.testFailureContent : undefined,
601
+ testFailureFiles: remainingFiles.length > 0 ? remainingFiles : undefined,
602
+ hasBlockers: !!blockerContent || remainingFiles.length > 0,
603
+ sessionId: runtime.telemetrySessionId,
604
+ projectSeqStart: runtime.turnStartProjectSeq,
605
+ projectSeqEnd: runtime.projectSeq,
606
+ fileSeqByPath: Object.fromEntries(runtime.getFileSeqEntries().map(([filePath, seq]) => [guardPathKey(filePath, cwd), seq])),
607
+ fileContentHashes: {},
608
+ });
18
609
  }
19
610
  export function evaluateGitGuard(runtime, cacheManager, cwd) {
611
+ if (runtime.gitGuardHasBlockers) {
612
+ logDecision(cwd, "blocked", "runtime_blockers", {
613
+ projectSeq: runtime.projectSeq,
614
+ });
615
+ const detail = runtime.gitGuardSummary ? `\n${runtime.gitGuardSummary}` : "";
616
+ return {
617
+ block: true,
618
+ reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.${detail}\nRun lens_diagnostics mode=all for full details, then commit again.`,
619
+ };
620
+ }
621
+ if (runtime.gitGuardCacheUnknownReason) {
622
+ return unknown(cwd, runtime.gitGuardCacheUnknownReason);
623
+ }
624
+ const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
625
+ if (inspection === "missing") {
626
+ logDecision(cwd, "allowed", "no_record");
627
+ return { block: false };
628
+ }
629
+ if (inspection !== "fresh")
630
+ return unknown(cwd, `cache_${inspection}`);
20
631
  const pending = cacheManager.readCache("turn-end-findings", cwd);
21
- const turnEndHasBlockers = !!pending?.data?.content;
22
- if (!runtime.gitGuardHasBlockers && !turnEndHasBlockers) {
632
+ if (!pending || !isTurnEndFindingsCache(pending.data)) {
633
+ return unknown(cwd, "cache_malformed");
634
+ }
635
+ const record = pending.data;
636
+ if (!record.hasBlockers) {
637
+ logDecision(cwd, "allowed", "advisory_only");
638
+ return { block: false };
639
+ }
640
+ if (record.affectedFilesTruncated) {
641
+ return unknown(cwd, "affected_files_truncated");
642
+ }
643
+ if (record.sessionId !== runtime.telemetrySessionId) {
644
+ return unknown(cwd, "session_mismatch");
645
+ }
646
+ if (record.projectSeqEnd !== runtime.projectSeq) {
647
+ return unknown(cwd, "project_sequence_mismatch", {
648
+ recordedProjectSeq: record.projectSeqEnd,
649
+ currentProjectSeq: runtime.projectSeq,
650
+ });
651
+ }
652
+ const liveFiles = [];
653
+ for (const file of record.affectedFiles) {
654
+ const resolved = resolveGuardPath(file, cwd);
655
+ const key = guardPathKey(resolved, cwd);
656
+ const recordedSeq = record.fileSeqByPath[key];
657
+ if (recordedSeq === undefined ||
658
+ recordedSeq !== (runtime.getFileSeq?.(resolved) ?? 0)) {
659
+ return unknown(cwd, "file_sequence_mismatch", { file: resolved });
660
+ }
661
+ try {
662
+ if (!isPathIgnoredByProject(resolved, runtime.projectRoot || cwd, false)) {
663
+ const currentHash = currentFileFingerprint(resolved);
664
+ if (currentHash !== "missing") {
665
+ liveFiles.push(resolved);
666
+ const expectedHash = record.fileContentHashes[key];
667
+ if (!expectedHash ||
668
+ expectedHash.startsWith("unreadable:") ||
669
+ currentHash.startsWith("unreadable:") ||
670
+ expectedHash !== currentHash) {
671
+ return unknown(cwd, "file_content_changed", { file: resolved });
672
+ }
673
+ }
674
+ }
675
+ }
676
+ catch {
677
+ return unknown(cwd, "file_unreadable", { file: resolved });
678
+ }
679
+ }
680
+ if (record.hasBlockers && record.affectedFiles.length === 0) {
681
+ return unknown(cwd, "blocker_without_affected_file");
682
+ }
683
+ // A deleted/ignored affected file has been resolved; no stale blocker is
684
+ // allowed to survive solely because its old path remains in the record.
685
+ if (record.hasBlockers && liveFiles.length === 0 && record.affectedFiles.length > 0) {
686
+ logDecision(cwd, "allowed", "affected_files_resolved", {
687
+ projectSeq: record.projectSeqEnd,
688
+ });
23
689
  return { block: false };
24
690
  }
25
- const details = runtime.gitGuardSummary
26
- ? `\n${runtime.gitGuardSummary}`
27
- : "";
691
+ logDecision(cwd, "blocked", "cache_blockers", {
692
+ projectSeq: record.projectSeqEnd,
693
+ affectedFileCount: record.affectedFiles.length,
694
+ });
28
695
  return {
29
696
  block: true,
30
- reason: `🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.${details}\nRun lens_diagnostics mode=all for full details, then commit again.`,
697
+ reason: "🔴 COMMIT BLOCKED (--lens-guard): unresolved blockers must be fixed before commit/push.\nRun lens_diagnostics mode=all for full details, then commit again.",
31
698
  };
32
699
  }