@herbertgao/pi-extensions 2026.8.6 → 2026.8.7

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 (343) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +2 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  49. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  50. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  51. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  52. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  53. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  54. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  55. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  56. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  57. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  58. package/node_modules/pi-lens/CHANGELOG.md +909 -0
  59. package/node_modules/pi-lens/README.md +11 -3
  60. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  61. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  62. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  63. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  64. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  65. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  66. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  67. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  68. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  69. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  70. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  71. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  72. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  73. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  74. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  75. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  76. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  77. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  78. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  79. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  80. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  81. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  84. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  86. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  87. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  122. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  123. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  124. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  125. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  126. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  127. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  128. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  129. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  130. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  131. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  134. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  135. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  136. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  137. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  138. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  139. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  140. package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
  141. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  142. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  144. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  145. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  146. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  147. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  148. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
  149. package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
  150. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  151. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  152. package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
  153. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  154. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  155. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  156. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  157. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  158. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  159. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  160. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  161. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  163. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  164. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  165. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  166. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  167. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  168. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  169. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  170. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  171. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  172. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  173. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  174. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  175. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  176. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  177. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  178. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  179. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  180. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  181. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  182. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  183. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  185. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  186. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  187. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  188. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  189. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  190. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
  191. package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
  192. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  193. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  194. package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
  195. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  196. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  197. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  198. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  199. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  200. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  201. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  202. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  203. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  204. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  205. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  210. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  211. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  212. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  213. package/node_modules/pi-lens/dist/index.js +67738 -55739
  214. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
  215. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  216. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  217. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  218. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  219. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  220. package/node_modules/pi-lens/docs/features.md +19 -5
  221. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  222. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  223. package/node_modules/pi-lens/docs/mcp.md +2 -2
  224. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  225. package/node_modules/pi-lens/docs/settings.md +19 -1
  226. package/node_modules/pi-lens/docs/usage.md +1 -0
  227. package/node_modules/pi-lens/package.json +10 -6
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  237. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  238. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  239. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  240. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  241. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  252. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  253. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  254. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  255. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  256. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  257. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  258. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  288. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  289. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  290. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  291. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  292. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  293. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  294. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  295. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  296. package/node_modules/pi-mcp-adapter/README.md +47 -5
  297. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  298. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  299. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  300. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  301. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  302. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  303. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  304. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  305. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  306. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  307. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  308. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  309. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  310. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  311. package/node_modules/pi-mcp-adapter/package.json +3 -1
  312. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  313. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  314. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  315. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  316. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  317. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  318. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  319. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  320. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  321. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  322. package/node_modules/pi-web-access/CHANGELOG.md +23 -0
  323. package/node_modules/pi-web-access/README.md +27 -9
  324. package/node_modules/pi-web-access/anysearch.ts +4 -2
  325. package/node_modules/pi-web-access/brave.ts +16 -4
  326. package/node_modules/pi-web-access/curator-page.ts +6 -3
  327. package/node_modules/pi-web-access/curator-server.ts +4 -1
  328. package/node_modules/pi-web-access/exa.ts +18 -7
  329. package/node_modules/pi-web-access/extract.ts +28 -3
  330. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  331. package/node_modules/pi-web-access/index.ts +41 -57
  332. package/node_modules/pi-web-access/package.json +1 -1
  333. package/node_modules/pi-web-access/page-query.ts +4 -2
  334. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  335. package/node_modules/pi-web-access/parallel.ts +2 -2
  336. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  337. package/node_modules/pi-web-access/serper.ts +211 -0
  338. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  339. package/node_modules/pi-web-access/summary-review.ts +44 -22
  340. package/node_modules/pi-web-access/tavily.ts +16 -4
  341. package/node_modules/pi-web-access/utils.ts +87 -0
  342. package/node_modules/pi-web-access/valyu.ts +199 -0
  343. package/package.json +13 -13
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Capture-generation-before-await, check-after — as one primitive (#1754).
3
+ *
4
+ * The shape: a write that follows an `await` must prove the world it captured
5
+ * still exists. A session reset, a cache refresh, or a newer request for the
6
+ * same key can land while the write's producer is in flight, and the late
7
+ * write then publishes an answer the caller no longer wants — or, in the
8
+ * eviction direction, deletes an entry a SUCCESSOR already installed.
9
+ *
10
+ * This module was extracted after the pattern was hand-rolled four times and
11
+ * its absence was a review finding twice:
12
+ *
13
+ * - `clients/dispatch/runners/utils/runner-helpers.ts` held the guarded form
14
+ * at one site and the unguarded form at another IN THE SAME FILE; #1674's
15
+ * F5 round copied the guard by hand to three more write sites.
16
+ * - `clients/lsp/client.ts`'s per-(path, identifier) pull sequences (#1682),
17
+ * whose first guard was vacuous until a review round bound it.
18
+ * - `clients/lsp/workspace-diagnostics-cache.ts`'s per-cwd epochs (#1669),
19
+ * where the epoch guarded `persist()` but not `lookup()`.
20
+ * - `clients/review-graph/builder.ts`'s workspace-cache epoch, the shape the
21
+ * one above was modelled on.
22
+ *
23
+ * Two things every one of those needed and at least one got wrong:
24
+ *
25
+ * 1. The check must run at WRITE time, not only at capture time.
26
+ * 2. The stale branch must be OBSERVABLE. A silently dropped write looks
27
+ * exactly like a write that never happened, which is why the vacuous
28
+ * guards survived review. `guardedWrite` emits one bounded degradation
29
+ * record per (source, subject) with the count of repeats, so a dogfood
30
+ * session can tell "the guard is working" from "the guard never fires".
31
+ *
32
+ * ## Scope and lifetime
33
+ *
34
+ * A `GenerationSource` is a plain counter owned by the RESETTING SEAM — the
35
+ * function that already clears the state the generation protects. This module
36
+ * holds no session state of its own: sources live in the modules that create
37
+ * them, and their reset policy is that module's declaration in
38
+ * `tests/support/session-state-registry.ts`. The only module-level state here
39
+ * is the name registry below, which exists so the #1754 sweep can ask "which
40
+ * stores declare a generation" instead of guessing from identifier names.
41
+ *
42
+ * ## Cost
43
+ *
44
+ * `isCurrent()` on an unkeyed source is a field read and a compare. On a
45
+ * keyed map it is a compare while nothing has been invalidated, and a
46
+ * normalize plus one `Map.get` once something has. The hand-rolled form it
47
+ * replaces normalized on EVERY call, and this repo's normalizer runs
48
+ * `realpathSync.native` on Windows, so an uninterrupted sweep pays that
49
+ * syscall once rather than once per file. See
50
+ * `GenerationMapOptions.normalizeKey` for the correctness reason the slow
51
+ * path exists at all.
52
+ */
53
+ import { incrementDegradationCount } from "./degradation-ledger.js";
54
+ /**
55
+ * Names of every generation-carrying store created through this module.
56
+ *
57
+ * Registration is by CONSTRUCTION: you cannot make a `GenerationSource`
58
+ * without appearing here. `tests/clients/generation-guard-sweep.test.ts`
59
+ * reads this to enforce the ratchet — a file that hand-rolls the pattern
60
+ * instead of declaring it here must carry an explicit exemption reason.
61
+ *
62
+ * Process-lifetime and bounded: names are compile-time literals from module
63
+ * initialization, so this cannot grow with workload.
64
+ */
65
+ const declaredSources = new Set();
66
+ const MAX_DECLARED_SOURCES = 128;
67
+ function declare(name) {
68
+ if (!name.trim()) {
69
+ throw new Error("generation source needs a non-empty name");
70
+ }
71
+ if (declaredSources.size < MAX_DECLARED_SOURCES)
72
+ declaredSources.add(name);
73
+ return name;
74
+ }
75
+ /** Every generation-carrying store name declared this process. */
76
+ export function listDeclaredGenerationSources() {
77
+ return [...declaredSources].sort((a, b) => a.localeCompare(b));
78
+ }
79
+ function recordStaleWrite(sourceName, subject, captured, observed) {
80
+ incrementDegradationCount({
81
+ kind: "generation-guard-stale-write",
82
+ subject: `${sourceName}:${subject}`,
83
+ reason: `write dropped: captured generation ${captured}, current ${observed}`,
84
+ });
85
+ }
86
+ function makeHandle(sourceName, generation, read) {
87
+ return {
88
+ generation,
89
+ isCurrent() {
90
+ return read() === generation;
91
+ },
92
+ guardedWrite(subject, write) {
93
+ const observed = read();
94
+ if (observed !== generation) {
95
+ recordStaleWrite(sourceName, subject, generation, observed);
96
+ return undefined;
97
+ }
98
+ return write();
99
+ },
100
+ };
101
+ }
102
+ /**
103
+ * Create a counter for a store invalidated as a whole.
104
+ *
105
+ * `name` appears in the stale-write ledger subject and in the sweep's
106
+ * declaration list, so it must name the STORE, not the module.
107
+ */
108
+ export function createGenerationSource(name) {
109
+ declare(name);
110
+ let generation = 0;
111
+ const read = () => generation;
112
+ return {
113
+ name,
114
+ current: read,
115
+ bump() {
116
+ generation += 1;
117
+ return generation;
118
+ },
119
+ capture() {
120
+ return makeHandle(name, generation, read);
121
+ },
122
+ };
123
+ }
124
+ const DEFAULT_MAX_KEYS = 512;
125
+ /**
126
+ * Create a per-key family of counters.
127
+ *
128
+ * Use this when invalidation is scoped: a cache refresh for ONE cwd must not
129
+ * drop an in-flight write for another, and a newer pull request for one
130
+ * (path, source) pair must not invalidate a different pair's.
131
+ */
132
+ export function createGenerationMap(name, options = {}) {
133
+ declare(name);
134
+ const normalize = options.normalizeKey ?? ((key) => key);
135
+ const maxKeys = Math.max(1, options.maxKeys ?? DEFAULT_MAX_KEYS);
136
+ // Insertion-ordered: the oldest key is evicted first. Re-stamping a key
137
+ // refreshes its position so a hot cwd is not evicted by a burst of
138
+ // one-shot ones.
139
+ const stamps = new Map();
140
+ // ONE ticket counter for the whole map. See GenerationMap's doc comment:
141
+ // per-key counters starting at 0 make eviction and forget() fail OPEN.
142
+ // 0 is reserved for "this key holds no stamp" and is never issued.
143
+ let nextTicket = 0;
144
+ // Counts every mutation that can change what a key's stamp reads: a bump,
145
+ // a forget, an eviction. A handle records this at capture and re-derives
146
+ // its key only when it has moved. See normalizeKey's doc comment.
147
+ let invalidations = 0;
148
+ const read = (key) => stamps.get(key) ?? 0;
149
+ function issue(key) {
150
+ nextTicket += 1;
151
+ stamps.delete(key);
152
+ stamps.set(key, nextTicket);
153
+ while (stamps.size > maxKeys) {
154
+ const oldest = stamps.keys().next().value;
155
+ if (oldest === undefined)
156
+ break;
157
+ stamps.delete(oldest);
158
+ invalidations += 1;
159
+ }
160
+ return nextTicket;
161
+ }
162
+ return {
163
+ name,
164
+ current(key) {
165
+ return read(normalize(key));
166
+ },
167
+ bump(key) {
168
+ invalidations += 1;
169
+ return issue(normalize(key));
170
+ },
171
+ capture(key) {
172
+ const normalized = normalize(key);
173
+ // A first-use capture ISSUES a stamp rather than reading 0. A handle
174
+ // holding 0 would be indistinguishable from one whose key was later
175
+ // forgotten or evicted, which is the fail-open hole this closes.
176
+ const stamp = read(normalized) || issue(normalized);
177
+ const capturedInvalidations = invalidations;
178
+ return makeHandle(`${name}[${normalized}]`, stamp, () => {
179
+ // Fast path: nothing has been invalidated since capture, so no key
180
+ // can have changed stamps and the normalizer need not run again.
181
+ if (invalidations === capturedInvalidations)
182
+ return stamp;
183
+ // Something moved. Re-derive the key from the ORIGINAL string: a
184
+ // normalizer that consults the filesystem can resolve the same
185
+ // input to a different key once the path exists, and a stamp read
186
+ // under the stale spelling would falsely read current.
187
+ return read(normalize(key));
188
+ });
189
+ },
190
+ forget(key) {
191
+ invalidations += 1;
192
+ stamps.delete(normalize(key));
193
+ },
194
+ size() {
195
+ return stamps.size;
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * Capture the generation, run `fn`, and hand it the handle.
201
+ *
202
+ * Sugar for the common `const handle = source.capture(); ... await ...;
203
+ * handle.guardedWrite(...)` sequence. Use `capture()` directly when the
204
+ * handle must outlive the function that made it — an eviction guard in a
205
+ * `.finally()` is the usual case, and it is what both #1754 migrations do.
206
+ *
207
+ * This ships ahead of its first production caller. Both migrated sites need
208
+ * the handle to outlive its producer, so neither uses this form; it exists
209
+ * because the issue specifies it and because the sites still to migrate
210
+ * (`runtime-coordinator.ts`, `runtime-turn.ts`) write inside the async
211
+ * function that captured. Said plainly rather than left for a reader to
212
+ * discover by grep.
213
+ */
214
+ export async function withGeneration(source, fn) {
215
+ return fn(source.capture());
216
+ }
@@ -465,6 +465,37 @@ function unknown(cwd, reasonCategory, metadata = {}) {
465
465
  * on tool_result, never tool_call, and therefore does not add disk I/O to the
466
466
  * edit preflight path.
467
467
  */
468
+ /**
469
+ * Retire one file's inline blocker on a confirmed-clean verdict, then bring the
470
+ * commit gate back in line with the map (#1561 F2).
471
+ *
472
+ * Retiring the map entry alone is not enough and claiming otherwise was wrong:
473
+ * the gate reads a LATCH (`gitGuardHasBlockers`) and a PERSISTED record, and
474
+ * `evaluateGitGuard` short-circuits on the latch before it ever looks at the
475
+ * record. A retire that touched neither left the commit blocked and quoted the
476
+ * retired blocker back as the reason.
477
+ *
478
+ * Both are recomputed exactly the way a clean dispatch recomputes them:
479
+ * `updateGitGuardStatus(false, "")` re-derives the latch from the blocker map
480
+ * — it cannot clear a latch another file's live blocker still justifies,
481
+ * because that file's entry is still in the map — and `syncGitGuardRecord`
482
+ * rewrites the persisted record from the same source of truth.
483
+ *
484
+ * Colocated with the gate rather than inlined at the wiring site so the claim
485
+ * is testable without booting the extension.
486
+ *
487
+ * @returns true when an entry was retired (the caller logs it — #1432 Gap 1).
488
+ */
489
+ export function retireInlineBlockerAndResyncGuard(args) {
490
+ const retired = args.runtime.retireInlineBlockerOnConfirmedClean(args.filePath, args.writeIndex, args.coveredSources);
491
+ if (!retired)
492
+ return false;
493
+ args.runtime.updateGitGuardStatus(false, "");
494
+ if (args.lensGuardEnabled) {
495
+ syncGitGuardRecord(args.runtime, args.cacheManager, args.cwd, args.filePath);
496
+ }
497
+ return true;
498
+ }
468
499
  export function syncGitGuardRecord(runtime, cacheManager, cwd, editedFilePath) {
469
500
  const entries = runtime.getInlineBlockersSnapshot?.() ?? [];
470
501
  const inspection = cacheManager.inspectCache("turn-end-findings", cwd);
@@ -28,19 +28,133 @@
28
28
  * (installer entry registered in clients/installer/index.ts) and runs
29
29
  * `gitleaks detect --no-git --report-format json` against the analysis root.
30
30
  *
31
- * Refs: #130
31
+ * Scope policy (#1562): gitleaks is handed a `--config` generated per scan
32
+ * (`writeScopedGitleaksConfig`) that EXTENDS the project's own `.gitleaks.toml`
33
+ * (or gitleaks's built-in defaults, if none) with two additions — an
34
+ * `[allowlist] paths` entry per NARROW secrets-lane scratch tree
35
+ * (`scratch-tree-policy.ts`'s `SECRETS_LANE_SCRATCH_DIR_NAMES` — pi-ecosystem/
36
+ * agent data dirs, `node_modules`, `.git`, generic build caches; deliberately
37
+ * NOT the broader walker-parity `EXCLUDED_DIRS` list knip/jscpd use, which
38
+ * also drops real leak surfaces like `dist/`, `vendor/`, `.vscode/` — see that
39
+ * module's doc for the review-round probe that caught the too-broad first
40
+ * cut) and an `[allowlist] regexes` entry per known placeholder-secret shape
41
+ * (`YOUR_API_KEY`, `<your-key>`, `xxxxxxxx`, …). An allowlist entry is a
42
+ * POST-DETECTION filter, not a walk exclusion — gitleaks still visits every
43
+ * file; the allowlist only decides which raw hits get suppressed from the
44
+ * report. Deliberately NOT `.gitignore`-based — see `scratch-tree-policy.ts`'s
45
+ * module doc for why blanket gitignore-respect is wrong for a secrets scanner
46
+ * (an untracked `.env` with a real credential is exactly the case gitleaks
47
+ * exists to catch, and `.env` is commonly gitignored). `classifyAndFilterFindings`
48
+ * backstops the config with a TS-side re-classification: a finding under the
49
+ * secrets-lane scratch tier is DEMOTED (not deleted) to `severity: "info"`,
50
+ * `semantic: "none"`, `pathStatus: "scratch"` — visible for an audit read,
51
+ * but it can never surface as a blocking "leaked secret" alarm. Every other
52
+ * finding gets `pathStatus` from git's tracked/ignored view.
53
+ *
54
+ * Refs: #130, #1562
32
55
  */
33
56
  import * as fs from "node:fs";
34
57
  import * as os from "node:os";
35
58
  import * as path from "node:path";
36
59
  import { mkdtempSync } from "node:fs";
60
+ import { collectTrackedFiles, collectUntrackedIgnoredIds, } from "./git-tracked-ignore.js";
61
+ import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
37
62
  import { safeSpawnAsync } from "./safe-spawn.js";
38
63
  import { SecurityScanClient } from "./security-scan-client.js";
64
+ import { getSecretsLaneAllowlistPaths, isUnderSecretsLaneScratchTree, } from "./scratch-tree-policy.js";
39
65
  const EMPTY_RESULT = {
40
66
  success: false,
41
67
  findings: [],
42
68
  };
43
69
  const SCAN_TIMEOUT_MS = 120_000;
70
+ // gitleaks discovers its own config at `<source>/.gitleaks.toml` (or `.yaml`/
71
+ // `.yml`) when no `--config` is passed. Kept as a named constant so the
72
+ // generated scoped config (below) can EXTEND whichever of these the project
73
+ // already has, instead of silently replacing the project's own rules.
74
+ const LOCAL_GITLEAKS_CONFIG_NAMES = [
75
+ ".gitleaks.toml",
76
+ ".gitleaks.yaml",
77
+ ".gitleaks.yml",
78
+ ];
79
+ function findLocalGitleaksConfig(cwd) {
80
+ for (const name of LOCAL_GITLEAKS_CONFIG_NAMES) {
81
+ const candidate = path.join(cwd, name);
82
+ try {
83
+ if (fs.existsSync(candidate))
84
+ return candidate;
85
+ }
86
+ catch {
87
+ // non-fatal
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ // The #1562 incident: a curl doc example's `-H "Authorization: Bearer
93
+ // YOUR_API_KEY"` (cached under gitignored scratch, see the secrets-lane
94
+ // exclusion below) read as a leaked secret. These match gitleaks's own
95
+ // `Secret`/`Match` value case-insensitively, catching the canonical
96
+ // placeholder shapes: `YOUR_API_KEY`-style tokens, `<your-key>`-style angle
97
+ // brackets, run-of-`x` filler, and the common changeme/example/dummy family.
98
+ // Scoped to the whole matched value (`^...$`) so a REAL secret that merely
99
+ // contains the substring "key" is never caught by accident.
100
+ //
101
+ // #1562 review-round F3: regex 2's alternation required "key" to be prefixed
102
+ // (`api[-_ ]?key`) — the issue's own literal acceptance example, `<your-key>`
103
+ // (bare "key", no "api" prefix), didn't match. Added a bare `key` alternative;
104
+ // still safe because the match is scoped to the WHOLE bracketed value
105
+ // (`^<...>$`), so a real secret that happens to be wrapped in angle brackets
106
+ // AND merely contain the substring "key" still wouldn't collide with this —
107
+ // only a value that is ENTIRELY `<...key...>`-shaped does.
108
+ export const PLACEHOLDER_SECRET_REGEXES = [
109
+ String.raw `(?i)^(your|my|insert|replace|enter|add)[-_ ]?(the[-_ ]?)?(api[-_ ]?)?(key|token|secret|password|credential)s?$`,
110
+ String.raw `(?i)^<[^<>]*(api[-_ ]?key|token|secret|password|credential|key)[^<>]*>$`,
111
+ String.raw `(?i)^x{3,}$`,
112
+ String.raw `(?i)^(changeme|change[-_]me|example|placeholder|dummy|fake|redacted|xxxxxxxx)$`,
113
+ ];
114
+ /**
115
+ * Build a temp gitleaks config that EXTENDS the project's own config (if any,
116
+ * else gitleaks's built-in defaults) with two additions:
117
+ * 1. `[allowlist] paths` — the NARROW secrets-lane scratch exclusion
118
+ * (`scratch-tree-policy.ts`'s `getSecretsLaneAllowlistPaths`, NOT the
119
+ * broader walker-parity `EXCLUDED_DIRS` list — #1562 review-round F1).
120
+ * 2. `[allowlist] regexes` — the placeholder-secret class above.
121
+ *
122
+ * An `[allowlist]` entry is a POST-DETECTION filter (#1562 review-round F4):
123
+ * gitleaks still visits every file under `--source`; the allowlist decides
124
+ * which of the raw hits it reports, it does not shrink the walk itself.
125
+ *
126
+ * gitleaks's `[extend]` allowlist merge is additive (append, not replace —
127
+ * see gitleaks/config/config.go), so a project's own curated allowlist keeps
128
+ * working unchanged; this only ever ADDS exclusions, never removes one the
129
+ * project opted into.
130
+ *
131
+ * Returns the generated file's path; caller owns cleanup (same temp dir as
132
+ * the report, removed together in `runScan`'s `finally`).
133
+ */
134
+ export function writeScopedGitleaksConfig(outDir, cwd) {
135
+ const localConfig = findLocalGitleaksConfig(cwd);
136
+ const extendLine = localConfig
137
+ ? `path = ${JSON.stringify(localConfig)}`
138
+ : "useDefault = true";
139
+ const pathPatterns = getSecretsLaneAllowlistPaths()
140
+ .map((p) => ` ${JSON.stringify(p)},`)
141
+ .join("\n");
142
+ const regexPatterns = PLACEHOLDER_SECRET_REGEXES.map((r) => ` ${JSON.stringify(r)},`).join("\n");
143
+ const toml = `[extend]
144
+ ${extendLine}
145
+
146
+ [allowlist]
147
+ paths = [
148
+ ${pathPatterns}
149
+ ]
150
+ regexes = [
151
+ ${regexPatterns}
152
+ ]
153
+ `;
154
+ const configPath = path.join(outDir, "gitleaks-scoped-config.toml");
155
+ fs.writeFileSync(configPath, toml, "utf-8");
156
+ return configPath;
157
+ }
44
158
  // --- Detection ---
45
159
  /**
46
160
  * Detect whether the project root has opted in to gitleaks via any of the
@@ -51,12 +165,7 @@ const SCAN_TIMEOUT_MS = 120_000;
51
165
  * the client.
52
166
  */
53
167
  export function hasGitleaksSignal(cwd) {
54
- const candidates = [
55
- ".gitleaks.toml",
56
- ".gitleaks.yaml",
57
- ".gitleaks.yml",
58
- ".gitleaksignore",
59
- ];
168
+ const candidates = [...LOCAL_GITLEAKS_CONFIG_NAMES, ".gitleaksignore"];
60
169
  for (const candidate of candidates) {
61
170
  try {
62
171
  if (fs.existsSync(path.join(cwd, candidate)))
@@ -187,11 +296,14 @@ export class GitleaksClient extends SecurityScanClient {
187
296
  const outDir = mkdtempSync(path.join(os.tmpdir(), "pi-lens-gitleaks-"));
188
297
  const reportPath = path.join(outDir, "gitleaks-report.json");
189
298
  try {
299
+ const configPath = writeScopedGitleaksConfig(outDir, cwd);
190
300
  const result = await safeSpawnAsync(bin, [
191
301
  "detect",
192
302
  "--no-git",
193
303
  "--source",
194
304
  cwd,
305
+ "--config",
306
+ configPath,
195
307
  "--report-format",
196
308
  "json",
197
309
  "--report-path",
@@ -219,7 +331,8 @@ export class GitleaksClient extends SecurityScanClient {
219
331
  summary: (result.stderr ?? "").trim().split("\n")[0] || "no report produced",
220
332
  };
221
333
  }
222
- const findings = parseGitleaksReport(fs.readFileSync(reportPath, "utf-8"));
334
+ const rawFindings = parseGitleaksReport(fs.readFileSync(reportPath, "utf-8"));
335
+ const findings = await classifyAndFilterFindings(rawFindings, cwd);
223
336
  return {
224
337
  success: true,
225
338
  findings,
@@ -243,6 +356,66 @@ export class GitleaksClient extends SecurityScanClient {
243
356
  }
244
357
  }
245
358
  }
359
+ // --- Post-scan classification / filter ---
360
+ /**
361
+ * Backstop for the scoped-config secrets-lane exclusion above, plus the
362
+ * `pathStatus` observability field (#1562).
363
+ *
364
+ * Two things happen here, both defense-in-depth relative to the generated
365
+ * gitleaks config:
366
+ * 1. Any finding still under the NARROW secrets-lane scratch tier
367
+ * (`isUnderSecretsLaneScratchTree` — NOT the broader walker-parity
368
+ * `EXCLUDED_DIRS` list) is DEMOTED, never DROPPED (#1562 review-round
369
+ * F2: the observability criterion requires this value to actually be
370
+ * reachable, so a scratch finding survives with `pathStatus: "scratch"`
371
+ * and the adapter (`gitleaksFindingToProjectDiagnostic`) reports it as
372
+ * `severity: "info"`/`semantic: "none"` — visible for an audit read,
373
+ * but it can never surface as a blocking "leaked secret" alarm). We
374
+ * can't exercise gitleaks's own TOML-regex engine in unit tests, only
375
+ * assert the config we hand it, so this backstop guarantees the demotion
376
+ * even if the generated regex ever mismatches gitleaks's own path
377
+ * normalization.
378
+ * 2. Every OTHER finding gets `pathStatus` set from git's tracked/ignored
379
+ * view, so a future false-positive triage is a log read.
380
+ *
381
+ * `collectTrackedFiles`/`collectUntrackedIgnoredIds` degrade to `undefined`
382
+ * when git itself is unavailable (no repo, spawn failure) — findings just
383
+ * keep `pathStatus: undefined` rather than a guessed value (fail-open,
384
+ * matching `git-tracked-ignore.ts`'s own contract). Exported for unit tests.
385
+ */
386
+ export async function classifyAndFilterFindings(findings, cwd) {
387
+ if (findings.length === 0)
388
+ return findings;
389
+ const [trackedIds, untrackedIgnoredIds] = await Promise.all([
390
+ collectTrackedFiles(cwd),
391
+ collectUntrackedIgnoredIds(cwd),
392
+ ]);
393
+ const classified = [];
394
+ for (const finding of findings) {
395
+ if (isUnderSecretsLaneScratchTree(finding.file)) {
396
+ classified.push({ ...finding, pathStatus: "scratch" });
397
+ continue;
398
+ }
399
+ const absPath = path.isAbsolute(finding.file)
400
+ ? finding.file
401
+ : path.resolve(cwd, finding.file);
402
+ let pathStatus;
403
+ if (trackedIds?.has(normalizeEphemeralMapKey(absPath))) {
404
+ pathStatus = "tracked";
405
+ }
406
+ else if (untrackedIgnoredIds?.has(normalizeMapKey(absPath))) {
407
+ pathStatus = "ignored";
408
+ }
409
+ else if (trackedIds !== undefined || untrackedIgnoredIds !== undefined) {
410
+ // At least one of the two git-backed sets resolved, so the absence
411
+ // from both is a real answer ("untracked, not gitignored") rather
412
+ // than an unresolved probe.
413
+ pathStatus = "untracked";
414
+ }
415
+ classified.push(pathStatus ? { ...finding, pathStatus } : finding);
416
+ }
417
+ return classified;
418
+ }
246
419
  // --- Parser ---
247
420
  /**
248
421
  * Map gitleaks's JSON report (a flat array of finding objects) to our
@@ -22,7 +22,8 @@ import * as path from "node:path";
22
22
  import { safeSpawnAsync } from "./safe-spawn.js";
23
23
  import { assertInstallAllowed } from "./project-trust.js";
24
24
  import { SecurityScanClient } from "./security-scan-client.js";
25
- import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
25
+ import { classifyProbeFailure, describeProbeEvidence, INSTALL_TRANSIENT_MAX_ATTEMPTS, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
26
+ import { recordDegradationOnce } from "./degradation-ledger.js";
26
27
  const EMPTY_RESULT = {
27
28
  success: false,
28
29
  findings: [],
@@ -61,9 +62,10 @@ export class GovulncheckClient extends SecurityScanClient {
61
62
  // #1467: a timed-out/killed probe says nothing about whether
62
63
  // govulncheck is on PATH. Latching `false` here disabled it for the
63
64
  // life of the process; a `go install` here would be a heavyweight
64
- // reaction to a host hiccup. Record an expiring verdict, retry later.
65
+ // reaction to a host hiccup. probeVersion has already recorded the
66
+ // expiring verdict and its retry schedule (#1501) — re-marking it
67
+ // here would double-escalate the cooldown. Retry later.
65
68
  this.log("govulncheck probe timed out; retrying later (not installing)");
66
- this.markTransientlyUnavailable(this.lastProbeCause ?? "probe-timeout");
67
69
  return false;
68
70
  }
69
71
  if (!assertInstallAllowed("govulncheck go install")) {
@@ -112,7 +114,15 @@ export class GovulncheckClient extends SecurityScanClient {
112
114
  return false;
113
115
  }
114
116
  this.log("go binary not on PATH — cannot auto-install govulncheck");
115
- this.available = false;
117
+ // Derived from the `go version` probe above, and recorded with it: a
118
+ // reader can see WHY govulncheck went quiet without re-running it (#1500).
119
+ // The evidence names `go`, because that is what was spawned — a row that
120
+ // carried go's errno unlabelled under `tool: "govulncheck"` invited the
121
+ // exact misreading the field exists to prevent.
122
+ this.noteDurableAbsence({
123
+ ...describeProbeEvidence(goOnPath, "go"),
124
+ install: "not-attempted",
125
+ });
116
126
  return false;
117
127
  }
118
128
  this.log("govulncheck not found, attempting auto-install via go install");
@@ -139,23 +149,63 @@ export class GovulncheckClient extends SecurityScanClient {
139
149
  hostStallMs: installHostStallMs,
140
150
  });
141
151
  if (outcome === "transient") {
142
- const retryAfterMs = this.markTransientlyUnavailable(cause);
152
+ // #1497: the retried operation here is a ≤60s network compile,
153
+ // not a cheap probe, so it escalates on the install-class
154
+ // schedule and latches at the attempt ceiling instead of
155
+ // re-compiling every few minutes forever.
156
+ const retryAfterMs = this.markTransientlyUnavailable(cause, {
157
+ operationClass: "install",
158
+ });
159
+ const exhausted = retryAfterMs === 0;
160
+ const ceilingReason = `go install timed out ${INSTALL_TRANSIENT_MAX_ATTEMPTS} times; install retries disabled until the next session`;
161
+ if (exhausted) {
162
+ // The symptom a user notices is an unexplained busy core, not a
163
+ // missing tool — so the terminal verdict gets the louder record.
164
+ // Once per session, matching the latch's own lifetime: both re-arm
165
+ // at `session_start` (review F3).
166
+ recordDegradationOnce({
167
+ kind: "install-retry-exhausted",
168
+ subject: "govulncheck",
169
+ reason: ceilingReason,
170
+ });
171
+ }
143
172
  // One record per decision, so an install that keeps timing out is
144
173
  // readable in latency.log the same day (#1467's forensic trail).
145
174
  logAvailabilityDecision({
146
175
  tool: "govulncheck",
147
176
  verdict: "unavailable",
148
177
  outcome,
149
- cause,
178
+ // At the ceiling the latch rewrote the cause; a row still saying
179
+ // `probe-timeout` would read as "cooling down" (#1497 review F5).
180
+ cause: exhausted ? (this.latchedCause() ?? cause) : cause,
150
181
  elapsedMs: Date.now() - installStartedAt,
151
- latched: false,
182
+ latched: exhausted,
152
183
  hostStallMs: installHostStallMs,
153
184
  ...(retryAfterMs > 0 && { retryAfterMs }),
154
185
  budgetMs: INSTALL_TIMEOUT_MS,
186
+ // The ceiling verdict is an ASSERTION by this call site, not a
187
+ // classification of one spawn, and #1534's convention is that such a
188
+ // row says so and carries the install facts behind it. Every retry
189
+ // DID run a `go install` that failed, so `install: "failed"` is
190
+ // earned, and the reason names the ceiling rather than the spawn.
191
+ ...(exhausted && {
192
+ classifiedBy: "caller",
193
+ evidence: {
194
+ ...describeProbeEvidence(install, "go install"),
195
+ install: "failed",
196
+ installReason: ceilingReason,
197
+ },
198
+ }),
155
199
  });
156
200
  return false;
157
201
  }
158
- this.available = false;
202
+ // A non-transient install failure (module not found, compile error) IS
203
+ // evidence about this machine, so it latches — but the row says the
204
+ // install was tried and failed, which a plain absence never does (#1500).
205
+ this.noteDurableAbsence({
206
+ ...describeProbeEvidence(install, "go install"),
207
+ install: "failed",
208
+ }, { elapsedMs: Date.now() - installStartedAt });
159
209
  return false;
160
210
  }
161
211
  // `go install` writes to `$GOBIN` or `$GOPATH/bin`. The user may not
@@ -176,6 +226,27 @@ export class GovulncheckClient extends SecurityScanClient {
176
226
  if (!reprobe.error && reprobe.status === 0) {
177
227
  this.log("govulncheck auto-installed and found on PATH");
178
228
  this.available = true;
229
+ // The initial PATH probe already wrote a latched `unavailable` row
230
+ // (#1500's own assertion doesn't fire here — the reprobe SUCCEEDED —
231
+ // but that first probe's verdict still logged, before `go install`
232
+ // ever ran). Without a compensating row here, the durable record says
233
+ // govulncheck is off when `go install` just put it on PATH (#1606).
234
+ logAvailabilityDecision({
235
+ tool: "govulncheck",
236
+ verdict: "available",
237
+ outcome: "success",
238
+ cause: "ok",
239
+ elapsedMs: Date.now() - reprobeStartedAt,
240
+ latched: true,
241
+ hostStallMs: reprobeHostStallMs,
242
+ budgetMs: 5000,
243
+ classifiedBy: "caller",
244
+ evidence: {
245
+ install: "succeeded",
246
+ binary: "govulncheck",
247
+ source: "go-install",
248
+ },
249
+ });
179
250
  return true;
180
251
  }
181
252
  // Look in the canonical install locations and remember the absolute
@@ -196,6 +267,24 @@ export class GovulncheckClient extends SecurityScanClient {
196
267
  this.binaryPath = candidate;
197
268
  this.available = true;
198
269
  this.log(`govulncheck auto-installed at ${candidate}`);
270
+ // Same compensating row as the on-PATH reprobe arm above (#1606):
271
+ // the initial probe already latched `unavailable`, and this arm
272
+ // is the OTHER way to recover — the canonical $GOBIN/$GOPATH walk
273
+ // found it after the on-PATH reprobe missed.
274
+ logAvailabilityDecision({
275
+ tool: "govulncheck",
276
+ verdict: "available",
277
+ outcome: "success",
278
+ cause: "ok",
279
+ elapsedMs: Date.now() - installStartedAt,
280
+ latched: true,
281
+ classifiedBy: "caller",
282
+ evidence: {
283
+ install: "succeeded",
284
+ binary: path.basename(candidate),
285
+ source: "go-install",
286
+ },
287
+ });
199
288
  return true;
200
289
  }
201
290
  }
@@ -227,8 +316,19 @@ export class GovulncheckClient extends SecurityScanClient {
227
316
  });
228
317
  return false;
229
318
  }
319
+ // The third silent arm (#1500 review): the install SUCCEEDED and the binary
320
+ // is nowhere the re-probe or the canonical bin dirs could find it. That is a
321
+ // durable, actionable fact — and until now it latched with no record at all,
322
+ // so a $GOBIN misconfiguration was indistinguishable from govulncheck simply
323
+ // not being installed. `install: "succeeded"` also appears on the two success
324
+ // arms above (#1606's compensating `available` rows) — this is the durable
325
+ // FAILURE counterpart, an install that worked but left nothing locatable.
230
326
  this.log("govulncheck auto-install succeeded but binary not locatable — check $GOBIN / $GOPATH");
231
- this.available = false;
327
+ this.noteDurableAbsence({
328
+ ...describeProbeEvidence(reprobe, "govulncheck"),
329
+ install: "succeeded",
330
+ installReason: "installed binary not found on PATH, $GOBIN or $GOPATH/bin",
331
+ }, { elapsedMs: Date.now() - reprobeStartedAt });
232
332
  return false;
233
333
  }
234
334
  /**