@herbertgao/pi-extensions 2026.9.1 → 2026.9.3

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 (341) hide show
  1. package/README.md +3 -2
  2. package/THIRD_PARTY_NOTICES.md +24 -0
  3. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  4. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  5. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  6. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  7. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  8. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  9. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  10. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  11. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  12. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  13. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  14. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  15. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  16. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  17. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  18. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  19. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  20. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  38. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  39. package/node_modules/@narumitw/pi-btw/dist/index.ts +478 -43
  40. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  41. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  42. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  43. package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -3
  44. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  45. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  46. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  47. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  48. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  49. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  50. package/node_modules/pi-lens/CHANGELOG.md +260 -0
  51. package/node_modules/pi-lens/README.md +13 -8
  52. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  53. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
  54. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  55. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  56. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
  57. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -8
  58. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  59. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
  60. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  61. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
  62. package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
  63. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -5
  65. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
  66. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -6
  67. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
  68. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
  69. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  70. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  71. package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
  72. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
  73. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
  74. package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
  75. package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
  76. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +3 -1
  77. package/node_modules/pi-lens/dist/clients/config-resolve.js +10 -7
  78. package/node_modules/pi-lens/dist/clients/config-schema.js +56 -2
  79. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  80. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  81. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  84. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  86. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -41
  87. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  91. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  93. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +7 -2
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +3 -2
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +3 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +15 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +3 -3
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +3 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +5 -3
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  146. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +5 -3
  147. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  148. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  149. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
  150. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  153. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  154. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  155. package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  158. package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/formatters.js +66 -93
  160. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
  161. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  162. package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
  163. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  164. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  165. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  166. package/node_modules/pi-lens/dist/clients/installer/index.js +416 -61
  167. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  168. package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
  169. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  170. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  171. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -11
  172. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  173. package/node_modules/pi-lens/dist/clients/latency-logger.js +7 -7
  174. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
  175. package/node_modules/pi-lens/dist/clients/lens-config.js +23 -0
  176. package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
  177. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
  179. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  180. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
  181. package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/config.js +37 -7
  183. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  184. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +79 -1
  185. package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
  186. package/node_modules/pi-lens/dist/clients/lsp/index.js +491 -64
  187. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  188. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
  189. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
  191. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  192. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
  194. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  195. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  197. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  198. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  200. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  201. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
  202. package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
  204. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
  205. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
  206. package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
  207. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
  208. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  209. package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
  210. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
  212. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  213. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +9 -4
  214. package/node_modules/pi-lens/dist/clients/pipeline.js +5 -4
  215. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  216. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  217. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  218. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  219. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  220. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  221. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  222. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  223. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/project-lens-config.js +49 -1
  225. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  226. package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
  227. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +3 -1
  228. package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
  229. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  230. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
  231. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  232. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  233. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  234. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +10 -1
  235. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  236. package/node_modules/pi-lens/dist/clients/runtime-session.js +58 -14
  237. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
  238. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  239. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  240. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
  241. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  242. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  243. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  244. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  245. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  246. package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
  247. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  248. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  249. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  250. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  251. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  252. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
  253. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  254. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  255. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  256. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  257. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  258. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
  259. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  260. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  261. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  262. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
  263. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  264. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
  265. package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
  266. package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
  267. package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
  268. package/node_modules/pi-lens/dist/index.js +19969 -17481
  269. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  270. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  271. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  272. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
  273. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
  274. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  275. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  276. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  277. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  278. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  279. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  280. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +354 -86
  281. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +54 -112
  282. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  283. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  284. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  285. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  286. package/node_modules/pi-lens/dist/tools/render-compact.js +281 -2
  287. package/node_modules/pi-lens/dist/tools/symbol-search.js +3 -4
  288. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  289. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  290. package/node_modules/pi-lens/docs/configuration.md +9 -0
  291. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  292. package/node_modules/pi-lens/docs/features.md +5 -5
  293. package/node_modules/pi-lens/docs/globalconfig.md +31 -1
  294. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  295. package/node_modules/pi-lens/docs/mcp.md +13 -1
  296. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  297. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  298. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  299. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +41 -0
  300. package/node_modules/pi-lens/docs/pi-lens-subagent.md +21 -2
  301. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  302. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  303. package/node_modules/pi-lens/docs/release-qa-baseline.md +17 -5
  304. package/node_modules/pi-lens/docs/servercapabilities.md +2 -3
  305. package/node_modules/pi-lens/docs/settings.md +37 -2
  306. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  307. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  308. package/node_modules/pi-lens/docs/usage.md +13 -3
  309. package/node_modules/pi-lens/docs/word-index.md +1 -1
  310. package/node_modules/pi-lens/package.json +15 -15
  311. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  312. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  313. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
  314. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  315. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  316. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  317. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  318. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  319. package/node_modules/pi-web-access/README.md +33 -22
  320. package/node_modules/pi-web-access/abortable.ts +17 -0
  321. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  322. package/node_modules/pi-web-access/credential-source.ts +1 -0
  323. package/node_modules/pi-web-access/curator-page.ts +20 -2
  324. package/node_modules/pi-web-access/curator-run.ts +44 -0
  325. package/node_modules/pi-web-access/curator-server.ts +3 -1
  326. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  327. package/node_modules/pi-web-access/extract.ts +57 -12
  328. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  329. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  330. package/node_modules/pi-web-access/index.ts +25 -18
  331. package/node_modules/pi-web-access/openai-search.ts +35 -13
  332. package/node_modules/pi-web-access/package.json +2 -2
  333. package/node_modules/pi-web-access/page-query.ts +5 -2
  334. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  335. package/node_modules/pi-web-access/serpapi.ts +220 -0
  336. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  337. package/node_modules/pi-web-access/storage.ts +10 -0
  338. package/node_modules/pi-web-access/summary-review.ts +34 -21
  339. package/node_modules/pi-web-access/utils.ts +6 -4
  340. package/package.json +7 -4
  341. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -45,7 +45,7 @@ import { BoundedFifoMap } from "./bounded-cache.js";
45
45
  import { getProjectDataDir } from "./file-utils.js";
46
46
  import { getProcessSingleton } from "./process-singletons.js";
47
47
  /** Persisted-file schema version. A file of any other version is ignored. */
48
- export const MUTATION_ATTRIBUTION_FILE_VERSION = 1;
48
+ const MUTATION_ATTRIBUTION_FILE_VERSION = 1;
49
49
  /** File under `getProjectDataDir(cwd)` holding the learned attributions. */
50
50
  export const MUTATION_ATTRIBUTION_FILE = "observed-mutating-tools.json";
51
51
  /**
@@ -55,7 +55,7 @@ export const MUTATION_ATTRIBUTION_FILE = "observed-mutating-tools.json";
55
55
  * co-occurrence — an unrelated tool call that happened to overlap a background
56
56
  * write — cannot durably mislabel a tool for every future session.
57
57
  */
58
- export const PERSIST_AFTER_OBSERVATIONS = 2;
58
+ const PERSIST_AFTER_OBSERVATIONS = 2;
59
59
  /**
60
60
  * Consecutive clean observations after which an UNATTRIBUTED tool stops being
61
61
  * armed for the session. Two, not one: a mutating tool whose FIRST call
@@ -88,7 +88,7 @@ export const CLEAN_OBSERVATION_ARM_LIMIT = 2;
88
88
  */
89
89
  export const DEATTRIBUTE_AFTER_CLEAN_OBSERVATIONS = 3;
90
90
  /** Bound on distinct tool names remembered, in-memory and on disk. */
91
- export const MUTATION_ATTRIBUTION_MAX_TOOLS = 64;
91
+ const MUTATION_ATTRIBUTION_MAX_TOOLS = 64;
92
92
  const ATTRIBUTION_FAMILY = "mutation-attribution";
93
93
  /**
94
94
  * Bumped to 2 (#2449 review round 5, F3): `session` changed shape from a
@@ -127,10 +127,6 @@ export function _exitFlushersForTest() {
127
127
  export function getRegisteredLogFiles() {
128
128
  return registeredLogFiles;
129
129
  }
130
- /** Test-only reset — each test file gets a clean registry (see ndjson-logger.test.ts). */
131
- export function _resetRegisteredLogFilesForTest() {
132
- registeredLogFiles.clear();
133
- }
134
130
  /**
135
131
  * Snapshot of unrecovered write losses, one entry per sink that has any
136
132
  * (#1970). Pure in-memory read — no I/O, so a caller (`degradation-ledger.ts`)
@@ -119,7 +119,7 @@ import { lineContentHash } from "./read-guard.js";
119
119
  * must not turn every first observation into a timeout, which is the least
120
120
  * useful possible outcome.
121
121
  */
122
- export const OBSERVED_CAPTURE_BUDGET_MS = 200;
122
+ const OBSERVED_CAPTURE_BUDGET_MS = 200;
123
123
  /**
124
124
  * Cumulative ceiling for every ARMED capture in one turn. A turn that calls
125
125
  * twenty unclassified tools pays this once, not twenty times. The SETTLE does
@@ -141,7 +141,7 @@ export const OBSERVED_TURN_BUDGET_MS = 600;
141
141
  * holds regardless of the clock. What it CAN cut is a directory target's tail,
142
142
  * and that is reported as `stoppedEarly` rather than scored as clean.
143
143
  */
144
- export const OBSERVED_SETTLE_DEADLINE_MS = 50;
144
+ const OBSERVED_SETTLE_DEADLINE_MS = 50;
145
145
  /**
146
146
  * Entries taken from a DIRECTORY-shaped target path, non-recursively.
147
147
  *
@@ -175,7 +175,7 @@ export const OBSERVED_TRACKED_MAX_FILES = 400;
175
175
  * closes it as soon as anything moves the size or the tick, and the armed
176
176
  * observation (which hashes the target unconditionally) never depended on it.
177
177
  */
178
- export const OBSERVED_LEDGER_SETTLE_MS = 150;
178
+ const OBSERVED_LEDGER_SETTLE_MS = 150;
179
179
  /**
180
180
  * Path keys the "pi-lens already recorded this" set may hold.
181
181
  *
@@ -198,19 +198,19 @@ export const OBSERVED_HANDLED_MAX = 1000;
198
198
  */
199
199
  export const OBSERVED_SWEEP_STAT_WINDOW = 128;
200
200
  /** Cumulative content-hash budget for ONE capture. */
201
- export const OBSERVED_HASH_BUDGET_BYTES = 2 * 1024 * 1024;
201
+ const OBSERVED_HASH_BUDGET_BYTES = 2 * 1024 * 1024;
202
202
  /** Cumulative bytes ONE settled sweep may read to seed or verify a hash. */
203
203
  export const OBSERVED_SWEEP_HASH_BUDGET_BYTES = 2 * 1024 * 1024;
204
204
  /** Cumulative bytes ONE settled sweep may read to derive edit RANGES. */
205
- export const OBSERVED_SWEEP_RANGE_BUDGET_BYTES = 1024 * 1024;
205
+ const OBSERVED_SWEEP_RANGE_BUDGET_BYTES = 1024 * 1024;
206
206
  /** Largest file whose per-line hashes are captured for range derivation. */
207
- export const OBSERVED_LINE_HASH_MAX_BYTES = 512 * 1024;
207
+ const OBSERVED_LINE_HASH_MAX_BYTES = 512 * 1024;
208
208
  /** Ranges reported per file before they collapse to one bounding box. */
209
- export const OBSERVED_MAX_EDIT_RANGES = 32;
209
+ const OBSERVED_MAX_EDIT_RANGES = 32;
210
210
  /** Pending baselines held between `tool_call` and `tool_result`. */
211
- export const OBSERVED_PENDING_MAX = 32;
211
+ const OBSERVED_PENDING_MAX = 32;
212
212
  /** Files remembered by the settled-sweep content ledger. */
213
- export const OBSERVED_LEDGER_MAX = 1000;
213
+ const OBSERVED_LEDGER_MAX = 1000;
214
214
  const OBSERVED_FAMILY = "observed-mutation-net";
215
215
  const OBSERVED_VERSION = 3;
216
216
  function state() {
@@ -43,11 +43,11 @@ import { freshnessFromMtime } from "./freshness.js";
43
43
  import { safeSpawnAsync } from "./safe-spawn.js";
44
44
  import { truncatedByOutputCap } from "./spawn-output-cap.js";
45
45
  /** Stop hashing once this many cumulative bytes were read (per capture). */
46
- export const OPAQUE_HASH_BUDGET_BYTES = 8 * 1024 * 1024;
46
+ const OPAQUE_HASH_BUDGET_BYTES = 8 * 1024 * 1024;
47
47
  /** Hard cap on scanned files — beyond it the verdict is coverage-unknown. */
48
48
  export const OPAQUE_SCAN_MAX_FILES = 2000;
49
49
  /** How far before recorded start an earlier write may still be attributed. */
50
- export const OPAQUE_MTIME_TOLERANCE_MS = 150;
50
+ const OPAQUE_MTIME_TOLERANCE_MS = 150;
51
51
  // `--untracked-files=all` lists untracked files individually instead of
52
52
  // collapsing them per directory (it does NOT add ignored paths — that needs
53
53
  // `--ignored`), so the worst realistic case is a working tree with a large
@@ -220,10 +220,6 @@ export async function isGitWorktree(root) {
220
220
  gitRepoMemo.set(key, isRepo === true);
221
221
  return isRepo === true;
222
222
  }
223
- export function _resetGitWorktreeMemoForTests() {
224
- gitRepoMemo.clear();
225
- gitToplevelMemo.clear();
226
- }
227
223
  const gitToplevelMemo = new Map();
228
224
  /**
229
225
  * The root of the working tree `root` belongs to, or `undefined` when it is
@@ -148,8 +148,10 @@ export class OpengrepClient extends SecurityScanClient {
148
148
  };
149
149
  }
150
150
  const findings = parseOpengrepReport(fs.readFileSync(reportPath, "utf-8"));
151
+ // #2154: the one opengrep site that parsed a scan of this root.
151
152
  return {
152
153
  success: true,
154
+ analyzed: true,
153
155
  findings,
154
156
  scannedAt,
155
157
  };
@@ -3,7 +3,7 @@ import { findLocalToolConfig } from "./path-utils.js";
3
3
  // Opengrep is a fork of Semgrep and natively consumes the same rule format, so
4
4
  // we discover both `.opengrep.*` (preferred) and the de-facto `.semgrep.*` rule
5
5
  // files an existing repo may already carry.
6
- export const LOCAL_OPENGREP_CONFIG_NAMES = [
6
+ const LOCAL_OPENGREP_CONFIG_NAMES = [
7
7
  ".opengrep.yml",
8
8
  ".opengrep.yaml",
9
9
  "opengrep.yml",
@@ -18,6 +18,7 @@ import * as os from "node:os";
18
18
  import * as path from "node:path";
19
19
  import { classifyProbeFailure, createAvailabilityLatch, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
20
20
  import { safeSpawnAsync } from "./safe-spawn.js";
21
+ import { probeToolAsync } from "./tool-probe.js";
21
22
  import { createAvailabilityProbeFlight } from "./availability-probe-flight.js";
22
23
  import { createGenerationSource, } from "./generation-guard.js";
23
24
  import { isAtOrAboveHomeDir, walkUpDirs } from "./path-utils.js";
@@ -114,7 +115,7 @@ async function probeAvailability(pm) {
114
115
  let result;
115
116
  let hostStallMs;
116
117
  try {
117
- result = await safeSpawnAsync(finder, [pm], { timeout: PROBE_TIMEOUT_MS });
118
+ result = await probeToolAsync(finder, [pm], { timeout: PROBE_TIMEOUT_MS });
118
119
  }
119
120
  finally {
120
121
  hostStallMs = sampler.stop();
@@ -10,7 +10,7 @@
10
10
  * - On non-Windows: return path as-is (case-sensitive filesystem)
11
11
  * - Always convert backslashes to forward slashes for Map key consistency
12
12
  */
13
- import { existsSync, realpathSync } from "node:fs";
13
+ import { existsSync, readFileSync, realpathSync, statSync, } from "node:fs";
14
14
  import * as os from "node:os";
15
15
  import * as path from "node:path";
16
16
  import { win32 } from "node:path";
@@ -413,6 +413,25 @@ export function findLocalToolConfig(startDir, names, options = {}) {
413
413
  }
414
414
  return undefined;
415
415
  }
416
+ /**
417
+ * Accept only a real Git repository marker: a directory with HEAD, or a
418
+ * worktree/submodule marker file whose first line starts with `gitdir:`.
419
+ */
420
+ export function isRealGitMarker(markerPath) {
421
+ try {
422
+ const marker = statSync(markerPath);
423
+ if (marker.isDirectory())
424
+ return existsSync(path.join(markerPath, "HEAD"));
425
+ if (!marker.isFile())
426
+ return false;
427
+ return readFileSync(markerPath, "utf8")
428
+ .split(/\r?\n/, 1)[0]
429
+ .startsWith("gitdir:");
430
+ }
431
+ catch {
432
+ return false;
433
+ }
434
+ }
416
435
  /**
417
436
  * Walk up from `startDir` looking for a directory containing any of
418
437
  * `markers`, the same containment-aware climb `knip-client.ts` and
@@ -438,11 +457,13 @@ export function findLocalToolConfig(startDir, names, options = {}) {
438
457
  export function findNearestMarkerRoot(startDir, markers, options = {}) {
439
458
  const boundaries = options.boundaries ?? [];
440
459
  const homeDir = path.resolve(options.homeDir ?? os.homedir());
460
+ const markerPredicate = options.markerPredicate ?? (() => true);
441
461
  let current = path.resolve(startDir);
442
462
  for (let depth = 0; depth < 64; depth++) {
443
463
  if (isAtOrAboveHomeDir(current, homeDir))
444
464
  return null;
445
- if (markers.some((m) => existsSync(path.join(current, m))))
465
+ if (markers.some((m) => existsSync(path.join(current, m)) &&
466
+ markerPredicate(path.join(current, m))))
446
467
  return current;
447
468
  if (boundaries.some((m) => existsSync(path.join(current, m))))
448
469
  return null;
@@ -2,8 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as readline from "node:readline";
3
3
  import { flushLatencyLog, getLatencyLogPath, } from "./latency-logger.js";
4
4
  import { getMaxLogSizeMB } from "./log-cleanup.js";
5
- export const DEFAULT_PERF_TOP_N = 5;
6
- export const MAX_PERF_TOP_N = 50;
5
+ const DEFAULT_PERF_TOP_N = 5;
6
+ const MAX_PERF_TOP_N = 50;
7
7
  export const MAX_PERF_PHASE_SAMPLES = 20_000;
8
8
  const PARSE_YIELD_EVERY = 500;
9
9
  // The window follows the same threshold that rotates the log out from under it.
@@ -91,14 +91,15 @@
91
91
  * gate says "config exists" while the carriage says "not found", silently
92
92
  * dropping the `--config` argv this module exists to carry.
93
93
  */
94
+ import { existsSync } from "node:fs";
94
95
  import * as path from "node:path";
95
- import { findLocalToolConfig } from "./path-utils.js";
96
+ import { resolveToolCwd } from "./tool-cwd.js";
96
97
  /**
97
98
  * In `computeConfigFiles()`'s own precedence order — `.php-cs-fixer.php`
98
99
  * before `.php-cs-fixer.dist.php` — so a same-directory precedence check can
99
100
  * just take the first existing name.
100
101
  */
101
- export const PHP_CS_FIXER_CONFIG_NAMES = [
102
+ const PHP_CS_FIXER_CONFIG_NAMES = [
102
103
  ".php-cs-fixer.php",
103
104
  ".php-cs-fixer.dist.php",
104
105
  ];
@@ -109,6 +110,10 @@ export const PHP_CS_FIXER_CONFIG_NAMES = [
109
110
  * agreeing with this tool's own (unceilinged) detection gates.
110
111
  */
111
112
  export function resolvePhpCsFixerConfig(filePath) {
112
- const startDir = path.dirname(path.resolve(filePath));
113
- return findLocalToolConfig(startDir, PHP_CS_FIXER_CONFIG_NAMES);
113
+ const absolute = path.resolve(filePath);
114
+ const startDir = resolveToolCwd("formatter", "php-cs-fixer", absolute, {
115
+ cwd: path.parse(absolute).root,
116
+ allowHomeMarker: true,
117
+ });
118
+ return PHP_CS_FIXER_CONFIG_NAMES.map((name) => path.join(startDir, name)).find(existsSync);
114
119
  }
@@ -34,6 +34,7 @@ import { clearGraphCache } from "./review-graph/builder.js";
34
34
  import { BoundedLruCache } from "./bounded-cache.js";
35
35
  import { RUNTIME_CONFIG } from "./runtime-config.js";
36
36
  import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
37
+ import { probeToolAsync } from "./tool-probe.js";
37
38
  import { bounded } from "./deadline-utils.js";
38
39
  import { enabledAuxiliaryLspServerIds } from "./dispatch/auxiliary-lsp.js";
39
40
  import { recordDegradationOnce } from "./degradation-ledger.js";
@@ -179,7 +180,7 @@ function createPhaseTracker(toolName, filePath) {
179
180
  };
180
181
  }
181
182
  // --- ESLint autofix helpers ---
182
- export { hasEslintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, };
183
+ export { hasEslintConfig, hasSqlfluffConfig, hasStylelintConfig };
183
184
  /**
184
185
  * eslint autofix availability, per cwd + PATH. The verdict is owned by the
185
186
  * shared availability latch (#1494): a `missing` / `non-installable` outcome
@@ -338,7 +339,7 @@ async function tryDetektFix(filePath, cwd) {
338
339
  const absPath = path.resolve(cwd, filePath);
339
340
  return detectFileChangedAfterCommand(filePath, cmd, ["--auto-correct", "--input", absPath, "--config", configPath], cwd, [1, 2]);
340
341
  }
341
- export async function tryMarkdownlintFix(filePath, cwd) {
342
+ async function tryMarkdownlintFix(filePath, cwd) {
342
343
  const cmd = await resolveToolCommandWithInstallFallback(cwd, "markdownlint");
343
344
  if (!cmd)
344
345
  return 0;
@@ -356,7 +357,7 @@ async function tryOxlintFix(filePath, cwd) {
356
357
  return detectFileChangedAfterCommand(filePath, cmd, ["--fix", filePath], cwd, [1]);
357
358
  }
358
359
  async function tryRustClippyFix(filePath) {
359
- const check = await safeSpawnAsync("cargo", ["--version"], { timeout: 5000 });
360
+ const check = await probeToolAsync("cargo", ["--version"], { timeout: 5000 });
360
361
  if (check.error || check.status !== 0)
361
362
  return [];
362
363
  const cargoDir = findNearestContaining(path.dirname(path.resolve(filePath)), [
@@ -371,7 +372,7 @@ async function tryRustClippyFix(filePath) {
371
372
  return diffProjectSnapshot(cargoDir, before);
372
373
  }
373
374
  async function tryDartFix(filePath) {
374
- const check = await safeSpawnAsync("dart", ["--version"], { timeout: 5000 });
375
+ const check = await probeToolAsync("dart", ["--version"], { timeout: 5000 });
375
376
  if (check.error || check.status !== 0)
376
377
  return [];
377
378
  const pubspecDir = findNearestContaining(path.dirname(path.resolve(filePath)), ["pubspec.yaml"]);
@@ -54,7 +54,7 @@ export function readProjectChanges(cwd) {
54
54
  * version — a missing/corrupt log is the normal cold-start case, not a
55
55
  * caller-visible error.
56
56
  */
57
- export async function readProjectChangesAsync(cwd) {
57
+ async function readProjectChangesAsync(cwd) {
58
58
  const logPath = getProjectChangeLogPath(cwd);
59
59
  try {
60
60
  const content = await fs.promises.readFile(logPath, "utf-8");
@@ -15,7 +15,7 @@ const WARM_TRIGGER = {
15
15
  "dead-code": "runs at session-start (Python projects only)",
16
16
  "test-runner": "fires per-edit at turn_end (only after a source file with a discoverable test companion is edited)",
17
17
  };
18
- export function warmTriggerFor(analyzerId) {
18
+ function warmTriggerFor(analyzerId) {
19
19
  return WARM_TRIGGER[analyzerId] ?? "runs at session-start";
20
20
  }
21
21
  /**
@@ -149,6 +149,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
149
149
  return {
150
150
  diagnostics: [],
151
151
  runners: [],
152
+ analyzed: [],
152
153
  cold: [...ANALYZER_IDS],
153
154
  coldReasons: Object.fromEntries(ANALYZER_IDS.map((id) => [id, unsafeRootReason])),
154
155
  failed: [],
@@ -158,6 +159,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
158
159
  }
159
160
  const diagnostics = [];
160
161
  const runners = [];
162
+ const analyzed = [];
161
163
  const cold = [];
162
164
  // #1623: the specific reason each `cold` id was skipped, captured at the
163
165
  // gate that decided it — see FreshProjectDiagnosticsResult.coldReasons.
@@ -188,7 +190,17 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
188
190
  pushUnique(cold, id);
189
191
  coldReasons[id] = reason;
190
192
  }
191
- function record(id, adapted, elapsedMs) {
193
+ /**
194
+ * `analysedRoot` is the client's own opt-in `AnalysedRootSignal` (#2154),
195
+ * never a property of reaching this function: `success: true` is also what
196
+ * a skipped, crashed-before-reporting or memoised run returns, and only the
197
+ * client knows which of those it is. Findings are recorded either way — an
198
+ * id that did not analyse the root simply carries no authority to retire a
199
+ * retained finding.
200
+ */
201
+ function record(id, adapted, elapsedMs, analysedRoot) {
202
+ if (analysedRoot)
203
+ pushUnique(analyzed, id);
192
204
  timings[id] = (timings[id] ?? 0) + elapsedMs;
193
205
  const kept = applyDispositionsMultiFile(adapted, analysisRoot, (d) => d.filePath);
194
206
  const suppressedHere = adapted.length - kept.length;
@@ -223,10 +235,17 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
223
235
  recordFailed("knip", result);
224
236
  return;
225
237
  }
238
+ // knip reports `success: true` for a run that never happened (no
239
+ // project root, a memo hit, an unparseable exit-0). Those belong in
240
+ // the honest `cold` channel, not in "fetched fresh this call".
241
+ if (result.analyzed !== true) {
242
+ markCold("knip", result.summary ?? "knip did not analyse this root");
243
+ return;
244
+ }
226
245
  cacheManager.writeCache("knip", result, analysisRoot, {
227
246
  scanDurationMs: Date.now() - startMs,
228
247
  });
229
- record("knip", knipIssuesToProjectDiagnostics(analysisRoot, result.issues ?? []), Date.now() - startMs);
248
+ record("knip", knipIssuesToProjectDiagnostics(analysisRoot, result.issues ?? []), Date.now() - startMs, true);
230
249
  }),
231
250
  // jscpd — duplicate code detection. Cache key varies with TS-project
232
251
  // detection, exactly mirroring session_start's own logic.
@@ -243,10 +262,14 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
243
262
  recordFailed("jscpd", result);
244
263
  return;
245
264
  }
265
+ if (result.analyzed !== true) {
266
+ markCold("jscpd", "jscpd did not analyse this root (no source files, or the scan produced no report)");
267
+ return;
268
+ }
246
269
  cacheManager.writeCache(scannerKey, result, analysisRoot, {
247
270
  scanDurationMs: Date.now() - startMs,
248
271
  });
249
- record("jscpd", jscpdResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
272
+ record("jscpd", jscpdResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, true);
250
273
  }),
251
274
  // madge — circular-dependency detection.
252
275
  task("madge", async () => {
@@ -259,7 +282,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
259
282
  cacheManager.writeCache("madge", result, analysisRoot, {
260
283
  scanDurationMs: Date.now() - startMs,
261
284
  });
262
- record("madge", circularDepsToProjectDiagnostics(analysisRoot, result.circular ?? []), Date.now() - startMs);
285
+ record("madge", circularDepsToProjectDiagnostics(analysisRoot, result.circular ?? []), Date.now() - startMs, result.analyzed === true);
263
286
  }),
264
287
  // gitleaks — committed-secrets detection. session_start/per-edit stay
265
288
  // config-gated per #130's strict default (GitleaksClient.hasGitleaksSignal),
@@ -290,7 +313,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
290
313
  cacheManager.writeCache("gitleaks", result, analysisRoot, {
291
314
  scanDurationMs: Date.now() - startMs,
292
315
  });
293
- record("gitleaks", gitleaksResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
316
+ record("gitleaks", gitleaksResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
294
317
  }),
295
318
  // govulncheck — Go module CVE detection. Go-module-gated per #132.
296
319
  task("govulncheck", async () => {
@@ -322,7 +345,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
322
345
  cacheManager.writeCache("govulncheck", result, analysisRoot, {
323
346
  scanDurationMs: Date.now() - startMs,
324
347
  });
325
- record("govulncheck", govulncheckResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
348
+ record("govulncheck", govulncheckResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
326
349
  }),
327
350
  // opengrep — full-workspace semgrep-grade security/quality findings via a
328
351
  // single project-wide CLI scan (#584). Structurally always-on: mirrors
@@ -352,7 +375,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
352
375
  cacheManager.writeCache("opengrep", result, analysisRoot, {
353
376
  scanDurationMs: Date.now() - startMs,
354
377
  });
355
- record("opengrep", opengrepResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
378
+ record("opengrep", opengrepResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
356
379
  }),
357
380
  // trivy — dependency CVE detection. Explicit opt-in per #131.
358
381
  task("trivy", async () => {
@@ -373,7 +396,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
373
396
  cacheManager.writeCache("trivy", result, analysisRoot, {
374
397
  scanDurationMs: Date.now() - startMs,
375
398
  });
376
- record("trivy", trivyResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
399
+ record("trivy", trivyResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
377
400
  }),
378
401
  // dead-code — cross-file dead-code for non-JS/TS languages (#127).
379
402
  // Each client self-gates via detect(); only matching-language projects
@@ -395,7 +418,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
395
418
  cacheManager.writeCache(cacheKey, result, analysisRoot, {
396
419
  scanDurationMs: Date.now() - startMs,
397
420
  });
398
- record("dead-code", deadCodeResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs);
421
+ record("dead-code", deadCodeResultToProjectDiagnostics(analysisRoot, result), Date.now() - startMs, result.analyzed === true);
399
422
  }));
400
423
  }),
401
424
  // test-runner — CACHE-READ only, unlike every task above (#1004). Its
@@ -440,7 +463,10 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
440
463
  // looking indistinguishable from a lane that just executed.
441
464
  cachedAgeMs["test-runner"] =
442
465
  Date.now() - new Date(cached.meta.timestamp).getTime();
443
- record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data, analysisRoot, options.runtime), Date.now() - startMs);
466
+ record("test-runner", testRunnerFindingsToProjectDiagnostics(cached.data, analysisRoot, options.runtime), Date.now() - startMs,
467
+ // Never authoritative: this lane reads the cache turn_end wrote,
468
+ // it never runs a suite over the root this call (#2154).
469
+ false);
444
470
  }),
445
471
  ];
446
472
  // Swallow any later rejection so an aborted-and-abandoned task can never
@@ -470,6 +496,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
470
496
  return {
471
497
  diagnostics,
472
498
  runners,
499
+ analyzed,
473
500
  cold,
474
501
  coldReasons,
475
502
  failed,
@@ -484,6 +511,7 @@ export async function fetchFreshProjectDiagnostics(cacheManager, cwd, clients, s
484
511
  return {
485
512
  diagnostics,
486
513
  runners,
514
+ analyzed,
487
515
  cold,
488
516
  coldReasons,
489
517
  failed,
@@ -3,12 +3,11 @@ import * as path from "node:path";
3
3
  * A gitleaks finding is a leaked secret at a concrete `file:startLine`. Secrets
4
4
  * are treated as **blocking** — a committed credential is not a style nit.
5
5
  *
6
- * Exception: `pathStatus: "scratch"` (#1562 review-round F2) — a finding
7
- * `classifyAndFilterFindings` demoted rather than dropped, so the observability
8
- * criterion ("record carries tracked/ignored/scratch status") stays honest
9
- * about a fourth REACHABLE value instead of silently deleting it. Demoted to
10
- * `severity: "info"`/`semantic: "none"` so it can never read as a blocking
11
- * "leaked secret" alarm, while remaining visible for an audit read.
6
+ * Findings in scratch trees, gitignored files, and nested repositories are
7
+ * retained for explicit audit but demoted to `severity: "info"` / `semantic:
8
+ * "none"`. They are outside the current repository's shippable source, so they
9
+ * must never read as blocking leaked-secret alarms. Tracked files and ordinary
10
+ * untracked files remain blocking.
12
11
  */
13
12
  export function gitleaksFindingToProjectDiagnostic(cwd, finding) {
14
13
  // #1562 observability criterion: surface the git status right in the
@@ -17,14 +16,16 @@ export function gitleaksFindingToProjectDiagnostic(cwd, finding) {
17
16
  const statusSuffix = finding.pathStatus
18
17
  ? ` [git: ${finding.pathStatus}]`
19
18
  : "";
20
- const isScratch = finding.pathStatus === "scratch";
19
+ const isBlocking = finding.pathStatus !== "scratch" &&
20
+ finding.pathStatus !== "ignored" &&
21
+ finding.pathStatus !== "nested-repository";
21
22
  return {
22
23
  filePath: path.isAbsolute(finding.file)
23
24
  ? finding.file
24
25
  : path.resolve(cwd, finding.file),
25
26
  line: finding.startLine,
26
- severity: isScratch ? "info" : "error",
27
- semantic: isScratch ? "none" : "blocking",
27
+ severity: isBlocking ? "error" : "info",
28
+ semantic: isBlocking ? "blocking" : "none",
28
29
  tool: "gitleaks",
29
30
  runner: "gitleaks",
30
31
  rule: `gitleaks:${finding.ruleId}`,
@@ -8,7 +8,7 @@ function resolveClonePath(cwd, file) {
8
8
  * BOTH ends so the duplication surfaces on whichever file the agent is looking
9
9
  * at, each one naming the other end.
10
10
  */
11
- export function jscpdCloneToProjectDiagnostics(cwd, clone) {
11
+ function jscpdCloneToProjectDiagnostics(cwd, clone) {
12
12
  const a = resolveClonePath(cwd, clone.fileA);
13
13
  const b = resolveClonePath(cwd, clone.fileB);
14
14
  const make = (filePath, line, otherFile, otherLine) => ({
@@ -27,7 +27,7 @@ function knipIssueMessage(issue) {
27
27
  }
28
28
  return `${issue.type}: ${issue.name}`;
29
29
  }
30
- export function knipIssueToProjectDiagnostic(cwd, issue) {
30
+ function knipIssueToProjectDiagnostic(cwd, issue) {
31
31
  const blocking = issue.type === "unlisted" || issue.type === "bin";
32
32
  return {
33
33
  filePath: knipIssueFile(cwd, issue),
@@ -12,7 +12,7 @@ function cycleMembers(dep) {
12
12
  * single-point knip finding. Emit a diagnostic on EACH file in the cycle so it
13
13
  * surfaces wherever the agent is looking, each one rendering the whole cycle.
14
14
  */
15
- export function circularDepToProjectDiagnostics(cwd, dep) {
15
+ function circularDepToProjectDiagnostics(cwd, dep) {
16
16
  const members = cycleMembers(dep);
17
17
  const rendered = `${members.map((f) => path.basename(f)).join(" → ")} → ${path.basename(members[0] ?? "")}`;
18
18
  return members.map((file) => ({
@@ -15,7 +15,7 @@ function severityFor(raw) {
15
15
  return { severity: "warning", blocking: false };
16
16
  }
17
17
  }
18
- export function opengrepFindingToProjectDiagnostic(cwd, finding) {
18
+ function opengrepFindingToProjectDiagnostic(cwd, finding) {
19
19
  const { severity, blocking } = severityFor(finding.severity);
20
20
  const cweSuffix = finding.cwe?.length ? ` (${finding.cwe[0]})` : "";
21
21
  return {
@@ -43,7 +43,7 @@ function parseLocation(location) {
43
43
  * caller deserves the same "this may already be superseded" honesty the
44
44
  * per-edit path already gives.
45
45
  */
46
- export function testResultToProjectDiagnostics(result, stale = false) {
46
+ function testResultToProjectDiagnostics(result, stale = false) {
47
47
  if (result.failed === 0 && !result.error)
48
48
  return [];
49
49
  const stalePrefix = stale ? "[stale — from a prior turn] " : "";
@@ -69,6 +69,7 @@ import { deprecationRecords, ignoredRecordCollector, projectLocationFor, readCon
69
69
  import { finalizeRecords, } from "./config-core/records.js";
70
70
  import { homeRelativePath, isAtOrAboveHomeDir, walkUpDirs, } from "./path-utils.js";
71
71
  import { findPiLensConfigMarkerInDir } from "./workspace-topology.js";
72
+ import { readToolConfig } from "./tool-config.js";
72
73
  /**
73
74
  * Project config basenames, in DESCENDING precedence (first match wins), for
74
75
  * this loader's own discovery.
@@ -162,8 +163,15 @@ export function loadPiLensProjectConfig(startDir, preloadedInfo) {
162
163
  // a pi-lens-owned file were then produced by nobody at all. The pi-lens
163
164
  // loader now enumerates those documents itself, for records only: nothing
164
165
  // here is merged, and which file supplies a VALUE is unchanged.
165
- reportPiLensConfigRecords(entry.legacyRecords);
166
166
  const configInfo = preloadedInfo ?? freshInfoFor(entry);
167
+ // The selected legacy document is resolved below and its bounded migration
168
+ // records are cached with that projection. Reporting the discovery copy as
169
+ // well would produce two suppression records with different totals, because
170
+ // the discovery walk and the single-document resolution see different
171
+ // record populations. Keep discovery-only documents here; the selected file
172
+ // is reported by `loadCachedConfigFile`.
173
+ const selectedLegacyPath = configInfo?.path;
174
+ reportPiLensConfigRecords(entry.legacyRecords.filter((record) => record.file !== selectedLegacyPath));
167
175
  if (!configInfo)
168
176
  return EMPTY_PROJECT_CONFIG;
169
177
  return loadCachedConfigFile(configInfo);
@@ -556,6 +564,7 @@ function parseConfigFile(configPath) {
556
564
  for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
557
565
  assignFlagConfigSection(obj, mutations, spec.configKey, note);
558
566
  }
567
+ const toolConfig = readToolConfig(obj, note);
559
568
  const rules = {};
560
569
  if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
561
570
  const rawRules = obj.rules;
@@ -634,6 +643,43 @@ function parseConfigFile(configPath) {
634
643
  }
635
644
  }
636
645
  }
646
+ let startup;
647
+ if (obj.startup !== undefined) {
648
+ if (!obj.startup ||
649
+ typeof obj.startup !== "object" ||
650
+ Array.isArray(obj.startup)) {
651
+ note("startup must be an object");
652
+ }
653
+ else {
654
+ const section = obj.startup;
655
+ if (section.mode === "quick" ||
656
+ section.mode === "full" ||
657
+ section.mode === "minimal") {
658
+ startup = { mode: section.mode };
659
+ }
660
+ else if ("mode" in section) {
661
+ note('startup.mode must be "quick", "full", or "minimal"');
662
+ }
663
+ if (section.scans !== undefined) {
664
+ if (!section.scans ||
665
+ typeof section.scans !== "object" ||
666
+ Array.isArray(section.scans)) {
667
+ note("startup.scans must be an object");
668
+ }
669
+ else if (typeof section.scans.enabled ===
670
+ "boolean") {
671
+ startup ??= {};
672
+ startup.scans = {
673
+ enabled: section.scans
674
+ .enabled,
675
+ };
676
+ }
677
+ else if ("enabled" in section.scans) {
678
+ note("startup.scans.enabled must be a boolean");
679
+ }
680
+ }
681
+ }
682
+ }
637
683
  // #533 hygiene: mirror the global loader's unknown-key warn so a typo in a
638
684
  // shared `.pi-lens.json` (e.g. `maxProjectFile`, `lps`) produces a signal
639
685
  // instead of silently doing nothing. The recognized set is single-sourced
@@ -694,6 +740,8 @@ function parseConfigFile(configPath) {
694
740
  }
695
741
  return {
696
742
  config: {
743
+ ...(toolConfig === undefined ? {} : { tools: toolConfig }),
744
+ ...(startup === undefined ? {} : { startup }),
697
745
  ignore,
698
746
  rules,
699
747
  ...mutations,