@herbertgao/pi-extensions 2026.9.2 → 2026.9.4

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 (255) hide show
  1. package/README.md +4 -2
  2. package/THIRD_PARTY_NOTICES.md +49 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
  8. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  9. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  10. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  11. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  12. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  13. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  14. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  15. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  16. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  17. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  18. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  19. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  20. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  38. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  39. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  40. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  41. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  42. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  43. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  44. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
  45. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  46. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  47. package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
  48. package/node_modules/pi-antigravity/LICENSE +21 -0
  49. package/node_modules/pi-antigravity/README.md +194 -0
  50. package/node_modules/pi-antigravity/package.json +67 -0
  51. package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
  52. package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
  53. package/node_modules/pi-antigravity/src/client/client.ts +561 -0
  54. package/node_modules/pi-antigravity/src/client/index.ts +1 -0
  55. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  56. package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +336 -0
  58. package/node_modules/pi-antigravity/src/image/index.ts +1 -0
  59. package/node_modules/pi-antigravity/src/index.ts +280 -0
  60. package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
  61. package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
  62. package/node_modules/pi-antigravity/src/models/index.ts +3 -0
  63. package/node_modules/pi-antigravity/src/models/models.ts +500 -0
  64. package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
  65. package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
  66. package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
  67. package/node_modules/pi-antigravity/src/types/index.ts +2 -0
  68. package/node_modules/pi-antigravity/src/types/types.ts +292 -0
  69. package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
  71. package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
  72. package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
  73. package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
  74. package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
  75. package/node_modules/pi-antigravity/tsconfig.json +21 -0
  76. package/node_modules/pi-lens/CHANGELOG.md +176 -0
  77. package/node_modules/pi-lens/README.md +13 -8
  78. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  79. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  80. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  82. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
  83. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
  84. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  85. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  86. package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
  87. package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
  88. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  89. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  90. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
  92. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  143. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  144. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  145. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  146. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  147. package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
  148. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  149. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  150. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  151. package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  154. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
  155. package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
  157. package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
  158. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  163. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  164. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  165. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  166. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  167. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
  168. package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
  169. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  170. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  171. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  172. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
  173. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  174. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  175. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
  176. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  177. package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
  178. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  179. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  180. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  181. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  182. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  183. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  184. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  185. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  186. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  187. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  188. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  189. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  190. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  191. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  192. package/node_modules/pi-lens/dist/index.js +21969 -20005
  193. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  194. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  195. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  196. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  197. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  198. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  199. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  200. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  201. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
  202. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
  203. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  204. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  205. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  206. package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
  207. package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
  208. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  209. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  210. package/node_modules/pi-lens/docs/configuration.md +9 -0
  211. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  212. package/node_modules/pi-lens/docs/features.md +5 -5
  213. package/node_modules/pi-lens/docs/globalconfig.md +11 -1
  214. package/node_modules/pi-lens/docs/mcp.md +13 -1
  215. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  216. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  217. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  218. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
  219. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
  220. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  221. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  222. package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
  223. package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
  224. package/node_modules/pi-lens/docs/settings.md +37 -2
  225. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  226. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  227. package/node_modules/pi-lens/docs/usage.md +13 -3
  228. package/node_modules/pi-lens/package.json +10 -1
  229. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  230. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  231. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  232. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  233. package/node_modules/pi-web-access/README.md +33 -22
  234. package/node_modules/pi-web-access/abortable.ts +17 -0
  235. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  236. package/node_modules/pi-web-access/credential-source.ts +1 -0
  237. package/node_modules/pi-web-access/curator-page.ts +20 -2
  238. package/node_modules/pi-web-access/curator-run.ts +44 -0
  239. package/node_modules/pi-web-access/curator-server.ts +3 -1
  240. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  241. package/node_modules/pi-web-access/extract.ts +57 -12
  242. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  243. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  244. package/node_modules/pi-web-access/index.ts +25 -18
  245. package/node_modules/pi-web-access/openai-search.ts +35 -13
  246. package/node_modules/pi-web-access/package.json +2 -2
  247. package/node_modules/pi-web-access/page-query.ts +5 -2
  248. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  249. package/node_modules/pi-web-access/serpapi.ts +220 -0
  250. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  251. package/node_modules/pi-web-access/storage.ts +10 -0
  252. package/node_modules/pi-web-access/summary-review.ts +34 -21
  253. package/node_modules/pi-web-access/utils.ts +6 -4
  254. package/package.json +11 -5
  255. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -1,7 +1,7 @@
1
1
  // Compact tool-result rendering (refs #345).
2
2
  //
3
3
  // The navigable/structural tools (module_report, read_symbol, read_enclosing,
4
- // ast_grep_search, ast_grep_dump, ast_grep_outline) return large bodies that are
4
+ // ast_grep_search, ast_grep_outline) return large bodies that are
5
5
  // useful to the MODEL but flood the user's terminal. The pi host renders a tool's
6
6
  // `content` verbatim only when the tool defines no `renderResult` (the
7
7
  // createResultFallback path in tool-execution.ts). By supplying a `renderResult`
@@ -19,6 +19,285 @@
19
19
  // (overrideExistingRenderers === false), so these renderers win and still coexist
20
20
  // with a globally-installed renderer extension.
21
21
  import { Text } from "../clients/deps/pi-tui.js";
22
+ import * as fs from "node:fs";
23
+ import * as path from "node:path";
24
+ import { randomUUID } from "node:crypto";
25
+ import { recordDegradationOnce } from "../clients/degradation-ledger.js";
26
+ import { getGlobalPiLensLogDir } from "../clients/probe-home-state.js";
27
+ /** The per-result delivery bound (#2848). Exported so surface gates and their
28
+ * pins assert the real budget instead of restating the literal. */
29
+ export const MAX_RESULT_BYTES = 40 * 1024;
30
+ // 2026-09-10: cap the complete MCP payload before it can retain or log an
31
+ // unbounded result; ordinary results keep the complete-log contract below it.
32
+ export const COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES = 8 * 1024 * 1024;
33
+ // #2800 item 7: the footer is stamped AFTER the payload bound, so the
34
+ // footer's own maximum size is reserved inside MAX_RESULT_BYTES. The reserve
35
+ // is computed from the footer's widest literal: the `result error` verdict, a
36
+ // bounded diag severity section, and maximum-width numeric fields with the
37
+ // wider `truncated=false` value.
38
+ const FOOTER_MAX_DIGITS = String(Number.MAX_SAFE_INTEGER).length;
39
+ /** One `diag severity=` line is width-bounded so the footer's maximum size
40
+ * stays finite and the reserve above stays sound. */
41
+ const FOOTER_DIAG_LINE_MAX_CHARS = 200;
42
+ const FOOTER_DIAG_SECTION_MAX_BYTES = 1024;
43
+ export const RESULT_FOOTER_RESERVE_BYTES = Buffer.byteLength(`\n\nresult error\n${"x".repeat(FOOTER_DIAG_SECTION_MAX_BYTES)}\nusage tokens=${"9".repeat(FOOTER_MAX_DIGITS)} elapsed-ms=${"9".repeat(FOOTER_MAX_DIGITS)} bytes=${"9".repeat(FOOTER_MAX_DIGITS)} truncated=false`, "utf8");
44
+ // The literal reserve intentionally leaves about 1 KiB below MAX_RESULT_BYTES
45
+ // for footer growth. Keep this conservative slack: deriving the bound by
46
+ // iterating over a changing footer caused both overflows and repeated log writes
47
+ // (round 2 F2/F7, refs #2862 and #2864).
48
+ /** The payload byte budget the footer is stamped into: the delivered result
49
+ * budget minus the reserved footer maximum (#2800 item 7). */
50
+ export const RESULT_PAYLOAD_BUDGET_BYTES = MAX_RESULT_BYTES - RESULT_FOOTER_RESERVE_BYTES;
51
+ function renderHeadTail(text, maxBytes, markerFor) {
52
+ const render = (kept) => {
53
+ const head = Math.floor(kept / 2);
54
+ const tail = kept - head;
55
+ return `${text.slice(0, head)}${markerFor(head, tail)}${text.slice(text.length - tail)}`;
56
+ };
57
+ let low = 0;
58
+ let high = text.length;
59
+ while (low < high) {
60
+ const kept = Math.ceil((low + high) / 2);
61
+ if (Buffer.byteLength(render(kept), "utf8") <= maxBytes)
62
+ low = kept;
63
+ else
64
+ high = kept - 1;
65
+ }
66
+ return { text: render(low), keptCharacters: low };
67
+ }
68
+ /** Bound model-facing result text while retaining both the useful head and tail.
69
+ * `maxBytes` defaults to the full result budget; the footer gate passes the
70
+ * payload budget that leaves room for the stamped footer (#2800 item 7). */
71
+ export function boundToolText(text, maxBytes = MAX_RESULT_BYTES) {
72
+ const totalBytes = Buffer.byteLength(text, "utf8");
73
+ if (totalBytes <= maxBytes) {
74
+ return { text, truncated: false, omittedCharacters: 0 };
75
+ }
76
+ const fullOutputPath = path.join(getGlobalPiLensLogDir(), `tool-result-${Date.now()}-${randomUUID()}.log`);
77
+ fs.mkdirSync(path.dirname(fullOutputPath), { recursive: true });
78
+ if (totalBytes > COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES) {
79
+ const omittedBytes = totalBytes - COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES;
80
+ recordDegradationOnce({
81
+ kind: "mcp-complete-result-budget-exceeded",
82
+ subject: "complete-result",
83
+ reason: `${totalBytes} input bytes exceeded ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}-byte budget`,
84
+ metadata: {
85
+ totalBytes,
86
+ budgetBytes: COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES,
87
+ },
88
+ });
89
+ const logText = renderHeadTail(text, COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES, (head, tail) => {
90
+ const keptBytes = Buffer.byteLength(`${text.slice(0, head)}${text.slice(text.length - tail)}`, "utf8");
91
+ return `\n\n[incomplete: ${totalBytes - keptBytes} bytes omitted, budget ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}]\n\n[Full output: ${fullOutputPath}]\n\n`;
92
+ });
93
+ fs.writeFileSync(fullOutputPath, logText.text, "utf8");
94
+ const output = renderHeadTail(logText.text, maxBytes, () => `\n\n[incomplete: ${omittedBytes} bytes omitted, budget ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}]\n\n[Full output: ${fullOutputPath}]\n\n`);
95
+ return {
96
+ text: output.text,
97
+ truncated: true,
98
+ omittedCharacters: text.length - logText.keptCharacters,
99
+ fullOutputPath,
100
+ };
101
+ }
102
+ fs.writeFileSync(fullOutputPath, text, "utf8");
103
+ const output = renderHeadTail(text, maxBytes, (head, tail) => `\n\n[${text.length - head - tail} characters omitted. Full output: ${fullOutputPath}]\n\n`);
104
+ return {
105
+ text: output.text,
106
+ truncated: true,
107
+ omittedCharacters: text.length - output.keptCharacters,
108
+ fullOutputPath,
109
+ };
110
+ }
111
+ /** Matches an already-stamped contract footer at the end of the joined text.
112
+ * A result re-entering the gate must not gain a second footer (refs #2852 N4).
113
+ * The byte and truncated groups let the gate read the kept footer's own
114
+ * delivery figures on re-entry (round 2 F1). */
115
+ const CONTRACT_FOOTER_TAIL_RE = /(?:^|\n)result (?:ok|error)\n(?:diag severity=[^\n]*\n)*usage tokens=\d+ elapsed-ms=\d+ bytes=(\d+) truncated=(true|false)$/;
116
+ /**
117
+ * Add the stable, model-facing result footer shared by pi and MCP.
118
+ *
119
+ * The host adapters own transport and terminal styling; this function owns the
120
+ * textual contract. Defaults are deliberately deterministic because elapsed
121
+ * time is not a property of a projection and must not make parity tests flaky.
122
+ * Idempotent: a result whose text already ends with the footer is only
123
+ * `isError`-normalized, never stamped twice.
124
+ *
125
+ * When `delivery` is given (the gate path), `bytes=`/`truncated=` describe the
126
+ * already-bound payload; otherwise they describe this function's input text,
127
+ * which is the delivered payload because no bound has run.
128
+ */
129
+ export function renderToolResultContract(result, delivery) {
130
+ const normalized = {
131
+ ...result,
132
+ isError: result.isError === true,
133
+ };
134
+ const content = result.content ?? [];
135
+ const textBlocks = content
136
+ .filter((block) => block.type === "text" && typeof block.text === "string")
137
+ .map((block) => block.text);
138
+ if (textBlocks.length === 0)
139
+ return normalized;
140
+ const text = textBlocks.join("\n");
141
+ if (CONTRACT_FOOTER_TAIL_RE.test(text))
142
+ return normalized;
143
+ const details = normalized.details;
144
+ // The diag section is width- and byte-bounded (#2800 item 7) so the
145
+ // footer's maximum size — and therefore the reserved budget above — stays
146
+ // finite regardless of how many diagnostics a result carries.
147
+ const diagLines = [];
148
+ let diagSectionBytes = 0;
149
+ if (Array.isArray(details?.diagnostics)) {
150
+ for (const value of details.diagnostics) {
151
+ if (!value || typeof value !== "object")
152
+ continue;
153
+ const severity = value.severity;
154
+ if (typeof severity !== "string")
155
+ continue;
156
+ const line = `diag severity=${severity.slice(0, FOOTER_DIAG_LINE_MAX_CHARS)}`;
157
+ const lineBytes = Buffer.byteLength(line, "utf8") + 1;
158
+ if (diagSectionBytes + lineBytes > FOOTER_DIAG_SECTION_MAX_BYTES)
159
+ break;
160
+ diagLines.push(line);
161
+ diagSectionBytes += lineBytes;
162
+ }
163
+ }
164
+ const tokens = normalized.usage?.tokens ?? Math.ceil(Buffer.byteLength(text, "utf8") / 4);
165
+ const elapsedMs = normalized.usage?.elapsedMs ?? 0;
166
+ const deliveredBytes = delivery?.bytes ?? Buffer.byteLength(text, "utf8");
167
+ const truncated = delivery?.truncated === true;
168
+ const contractLines = [
169
+ `result ${normalized.isError ? "error" : "ok"}`,
170
+ ...diagLines,
171
+ `usage tokens=${tokens} elapsed-ms=${elapsedMs} bytes=${deliveredBytes} truncated=${truncated ? "true" : "false"}`,
172
+ ];
173
+ let lastTextIndex = -1;
174
+ for (let index = content.length - 1; index >= 0; index--) {
175
+ const block = content[index];
176
+ if (block?.type === "text" && typeof block.text === "string") {
177
+ lastTextIndex = index;
178
+ break;
179
+ }
180
+ }
181
+ if (lastTextIndex < 0)
182
+ return normalized;
183
+ return {
184
+ ...normalized,
185
+ content: content.map((block, index) => index === lastTextIndex && block.type === "text"
186
+ ? { ...block, text: `${block.text}\n\n${contractLines.join("\n")}` }
187
+ : block),
188
+ };
189
+ }
190
+ /** Bound the payload text blocks so the footer stamped afterwards still fits
191
+ * inside MAX_RESULT_BYTES (refs #2800 item 7): each block is bounded to the
192
+ * result budget minus the reserved footer maximum, and the delivered byte
193
+ * count plus the bound's truncated flag travel with the result so the footer
194
+ * can report them. Per-block bounding is inherited from #2852; production
195
+ * results carry a single text block (renderToolText). */
196
+ export function boundResultPayload(result) {
197
+ if (!result.content) {
198
+ return { result, deliveredBytes: 0, truncated: false };
199
+ }
200
+ const joined = fullTextOf(result);
201
+ // Reserve the widest footer once. This keeps the MAX_RESULT_BYTES invariant
202
+ // independent of payload contents and gives boundToolText one log write.
203
+ const bound = boundToolText(joined, RESULT_PAYLOAD_BUDGET_BYTES);
204
+ const firstText = result.content.findIndex((block) => block.type === "text" && typeof block.text === "string");
205
+ let retainedText = false;
206
+ const content = result.content
207
+ .filter((block, index) => block.type !== "text" ||
208
+ typeof block.text !== "string" ||
209
+ index === firstText)
210
+ .map((block) => {
211
+ if (block.type === "text" &&
212
+ typeof block.text === "string" &&
213
+ !retainedText) {
214
+ retainedText = true;
215
+ return { ...block, text: bound.text };
216
+ }
217
+ return block;
218
+ });
219
+ const deliveredBytes = Buffer.byteLength(fullTextOf({ content }), "utf8");
220
+ return {
221
+ result: { ...result, content },
222
+ deliveredBytes,
223
+ truncated: bound.truncated,
224
+ };
225
+ }
226
+ /** Build the raw result envelope shared by both host adapters: the summary is
227
+ * joined with the structured payload's fenced JSON, and the payload also rides
228
+ * along as `details` for surface-side consumers (pi's compact-line summarizer,
229
+ * the MCP gate's `diag severity=` footer lines). The contract footer and the
230
+ * #2848 byte bound are NOT applied here — each surface stamps them once, after
231
+ * the tool's own result exists (`finalizeToolResult` / the MCP dispatcher). */
232
+ export function renderToolText(summary, structured, compact = false) {
233
+ const rawText = structured === undefined
234
+ ? summary
235
+ : `${summary}\n\n\`\`\`json\n${JSON.stringify(structured, compact ? undefined : null, compact ? undefined : 2)}\n\`\`\``;
236
+ return {
237
+ content: [{ type: "text", text: rawText }],
238
+ details: structured,
239
+ };
240
+ }
241
+ /** Drop the structured `details` field from a finished result. The MCP gate
242
+ * consumes `details` for the footer's `diag severity=` lines and then strips
243
+ * it before delivery, so the wire carries only the bounded text blocks
244
+ * (refs #2852 N1); pi keeps `details` for its compact-line summarizer. */
245
+ export function stripResultDetails(result) {
246
+ const { details: _details, ...rest } = result;
247
+ return rest;
248
+ }
249
+ /** Finish a host-adapter result after its status and all warnings exist
250
+ * (#2800 item 7): the payload bound runs FIRST with the footer's own maximum
251
+ * size reserved inside MAX_RESULT_BYTES, then the footer is stamped LAST with
252
+ * the delivered payload's byte count and the bound's truncated flag. So
253
+ * `bytes=`/`truncated=` describe what the model actually receives, and the
254
+ * delivered text — footer included — never exceeds MAX_RESULT_BYTES.
255
+ *
256
+ * Re-entry (refs #2852 N4, round 2 F1): the bound still runs on an
257
+ * already-stamped result — master applied the bound after the stamp-skip, and
258
+ * the kept tail carries the footer through it — so re-entry is never delivered
259
+ * unbounded. A stamped result within the delivered budget is kept as-is; the
260
+ * figures are the kept footer's own prior values, never a footer-inclusive
261
+ * re-measure and never a hard-coded `false`. */
262
+ export function finalizeToolResultWithDelivery(result) {
263
+ const normalized = { ...result, isError: result.isError === true };
264
+ const existingText = fullTextOf(normalized);
265
+ const existingFooter = CONTRACT_FOOTER_TAIL_RE.exec(existingText);
266
+ if (existingFooter &&
267
+ Buffer.byteLength(existingText, "utf8") <= MAX_RESULT_BYTES) {
268
+ return {
269
+ result: normalized,
270
+ deliveredBytes: Number(existingFooter[1]),
271
+ truncated: existingFooter[2] === "true",
272
+ };
273
+ }
274
+ const bound = boundResultPayload(normalized);
275
+ const text = fullTextOf(bound.result);
276
+ const keptFooter = CONTRACT_FOOTER_TAIL_RE.exec(text);
277
+ if (keptFooter) {
278
+ // The bound ran and the kept tail still carries the footer, so there is
279
+ // nothing to stamp; the kept footer's figures stay the delivery
280
+ // contract (row 6: prior value kept).
281
+ return {
282
+ result: bound.result,
283
+ deliveredBytes: Number(keptFooter[1]),
284
+ truncated: keptFooter[2] === "true",
285
+ };
286
+ }
287
+ const stamped = renderToolResultContract(bound.result, {
288
+ bytes: bound.deliveredBytes,
289
+ truncated: bound.truncated,
290
+ });
291
+ return {
292
+ result: stamped,
293
+ deliveredBytes: bound.deliveredBytes,
294
+ truncated: bound.truncated,
295
+ };
296
+ }
297
+ /** Finish a host-adapter result after its status and all warnings exist. */
298
+ export function finalizeToolResult(result) {
299
+ return finalizeToolResultWithDelivery(result).result;
300
+ }
22
301
  // pi-lens brand colour: blue characters on whatever background the pi tool shell
23
302
  // paints (default success/error background is left untouched). Truecolor bold
24
303
  // foreground, theme-independent so the summary reads as ours regardless of the
@@ -23,8 +23,8 @@ export function createSymbolSearchTool(getProjectRoot) {
23
23
  return {
24
24
  name: "symbol_search",
25
25
  label: "Symbol Search",
26
- description: "Ranked identifier search over the persisted word index (BM25 + priors demoting tests/vendor/docs) — answers 'which files are most relevant to <query>' by identifier. First step of the discovery funnel: symbol_search finds candidates, module_report explains the file, read_symbol reads the body. Complements grep (raw substrings) and lsp_navigation (exact references). Each hit's startLine/endLine mark its best-matching line (offset=startLine, limit=endLine-startLine+1 for a one-line peek); use module_report on `file` for the real outline. Returns available:false with a retry hint if the index isn't built yet — it self-builds in the background (never blocks this call).",
27
- promptSnippet: "Ranked identifier search — find relevant files by name/usage",
26
+ description: "Find relevant files by ranked identifier search. On a cold cache, project_report and symbol_search return available: false with a retry hint and start a non-blocking background build; module_report degrades to outline-only with cache freshness explicit. Example: search `authenticate user` before module_report.",
27
+ promptSnippet: "Find files by identifier",
28
28
  renderResult: compactRenderResult(({ details, isError }) => {
29
29
  if (isError || details?.available === false) {
30
30
  return `symbol_search "${details?.query ?? ""}" — unavailable${details?.hint ? `: ${details.hint}` : ""}`;
@@ -278,8 +278,7 @@ Registered as pi agent tools. Verified names:
278
278
 
279
279
  | Tool | What it does | Use it to… |
280
280
  |---|---|---|
281
- | `lens_diagnostics` | Query pi-lens diagnostic state. `mode=delta` (default, this turn) / `mode=all` (cache-wide) / `mode=full` (fresh whole-project scan). Optional `paths` scope. | Check what pi-lens found; confirm clean before "done" (use `mode=full`). |
282
- | `lsp_diagnostics` | LSP diagnostics for explicit files/dirs (per-file `clean`/`unavailable`/`unconfirmed`/… outcomes). | Targeted LSP check on specific files. |
281
+ | `lens_diagnostics` | Session-cache or LSP-probe diagnostics with `source` and `scope` selectors. | Use `source=lsp` with `scope=paths` for targeted checks; an empty cache is not proof of clean. |
283
282
  | `lsp_navigation` | LSP navigation (definition/references/etc.). | Trace symbols semantically. |
284
283
  | `symbol_search` | Ranked identifier search over the warm word index (BM25 + priors). | Entry point of the discovery funnel. |
285
284
  | `module_report` | Navigable outline + signatures + decorators + imports + callbacks for a file; optional `blastRadius`. | Understand a module without reading the whole body. |
@@ -287,7 +286,7 @@ Registered as pi agent tools. Verified names:
287
286
  | `read_enclosing` | Smallest enclosing symbol/callback body for a file+line (records coverage). | Bridge a diagnostic location → exact body. |
288
287
  | `project_report` | Project-level structural report. | Orient in an unfamiliar project. |
289
288
  | `ast_grep_search` / `ast_grep_replace` | Structural AST search / replace. | Find or rewrite by code shape, not regex. |
290
- | `ast_grep_outline` / `ast_grep_dump` | Outline / AST dump. | Inspect structure. |
289
+ | `ast_grep_outline` / `ast_grep_search` (`dump=true`) | Outline / AST dump. | Inspect structure. |
291
290
  | `lens_diagnostic_mark` | Mark a finding false-positive / suppressed / deferred / flagged-to-fix (honored across surfaces). | Triage a finding you've judged. |
292
291
 
293
292
  Funnel discipline: **`symbol_search` → `module_report` → `read_symbol`/`read_enclosing`**
@@ -315,7 +314,7 @@ pi-lens is also an MCP server. The same capabilities are mirrored under a `pilen
315
314
  prefix: `pilens_diagnostics`, `pilens_analyze`, `pilens_module_report`,
316
315
  `pilens_symbol_search`, `pilens_read_symbol`, `pilens_read_enclosing`,
317
316
  `pilens_project_report`, `pilens_project_scan`, `pilens_lsp_navigation`,
318
- `pilens_lsp_diagnostics`, `pilens_ast_grep_search`/`pilens_ast_grep_replace`,
317
+ `pilens_ast_grep_search`/`pilens_ast_grep_replace`,
319
318
  `pilens_session_start`/`pilens_turn_end`, `pilens_health`, `pilens_latency`,
320
319
  `pilens_rebuild` (source checkouts only). Note MCP has **no read-guard** — mirror reads
321
320
  don't record edit coverage. Prefer the **warm** review path; MCP `fresh` mode
@@ -2,43 +2,67 @@
2
2
 
3
3
  pi-lens registers the following tools with the pi agent. Most are also exposed
4
4
  through the MCP mirror (`clients/lens-engine.ts` is the seam both adapters
5
- share) — current exceptions: `ast_grep_outline` and `ast_grep_dump`
5
+ share) — current exception: `ast_grep_outline`
6
6
  (module_report supersedes them for discovery), and `lens_diagnostic_mark`
7
7
  (pi-lens-internal for now). `read_enclosing` gained MCP parity
8
- (`pilens_read_enclosing`) as of #536, closing #522 item 1.
8
+ (`pilens_read_enclosing`) as of #536, closing #522 item 1. The standalone
9
+ `lsp_diagnostics` tool was folded into `lens_diagnostics` (`source=lsp`,
10
+ #2860). The retired MCP name remains a one-release compatibility redirect to
11
+ `pilens_diagnostics` with `source=lsp` and `scope=paths` (`mcp/server.ts`),
12
+ logging one `lsp-diagnostics-compatibility` degradation per session. Callers
13
+ should move to `pilens_diagnostics`.
9
14
 
10
- **Dynamic tooling.** Six tools stay always-active: `lens_diagnostics`,
11
- `lsp_diagnostics`, `module_report`, `read_symbol`, `read_enclosing`,
12
- `symbol_search`. Six situational tools — `ast_grep_search`, `ast_grep_replace`,
13
- `ast_grep_outline`, `ast_grep_dump`, `lsp_navigation`, `lens_diagnostic_mark` —
15
+
16
+ **Dynamic tooling.** Five tools stay always-active: `lens_diagnostics`,
17
+ `module_report`, `read_symbol`, `read_enclosing`,
18
+ `symbol_search`. Five situational tools — `ast_grep_search`, `ast_grep_replace`,
19
+ `ast_grep_outline`, `lsp_navigation`, `lens_diagnostic_mark` —
14
20
  are registered but
15
21
  inactive by default; the model activates the ones it needs via the always-active
16
22
  loader tool `pi_lens_activate_tools`, per pi's dynamic-tool-loading API
17
23
  (`pi.setActiveTools`/`pi.getActiveTools`). The loader explicitly reports
18
24
  "Available starting next turn"; do not retry the tool in the same turn.
19
- Feature-detected: on hosts without that API, the six situational tools fall back
25
+ Feature-detected: on hosts without that API, the five situational tools fall back
20
26
  to being statically active, exactly as before (`tools/activate-tools.ts`, wired
21
27
  in `index.ts`).
22
28
 
29
+ Tool descriptions contain the contract sentence and one example. Operational
30
+ guidance, including cache state, scan scope, safety details, and lifecycle
31
+ results, belongs in the returned result so it is paid only when the tool runs.
32
+
33
+ **Result contract.** One post-result gate per surface — `finalizeToolResult` /
34
+ `finalizeToolResultWithDelivery` in `tools/render-compact.ts`, wired into every
35
+ pi tool's `execute` wrapper in `index.ts` and into the MCP `tools/call`
36
+ dispatcher in `mcp/server.ts` — bounds every delivered result to 40 KiB
37
+ (`MAX_RESULT_BYTES`) and stamps a trailing usage footer describing what was
38
+ actually sent: `usage tokens=<n> elapsed-ms=<n> bytes=<n> truncated=<true|false>`.
39
+ A rejected call (e.g. MCP's "Unknown or disabled tool") is rendered through the
40
+ same gate rather than bypassing it.
41
+
23
42
  ## Per-edit
24
43
 
25
- - **`lens_diagnostics`** — Cached diagnostic state for the current session.
26
- Modes: `delta` (current turn), `all` (resurfaces stale blockers dropped from
27
- turn context), `full` (project-wide scan).
44
+ - **`lens_diagnostics`** — Session-cache or LSP-probe diagnostic state, selected
45
+ by `source` (`session` default, or `lsp`) and `scope` (`paths` or
46
+ `workspace`; explicit `paths` always win over `scope`). `severity` is a
47
+ threshold, not an exact filter: `error` shows only errors; `warning` adds
48
+ warnings; `information` adds information; `hint`/`all` (default) show every
49
+ tier. Legacy `mode`: `delta` (current turn), `all` (resurfaces stale
50
+ blockers dropped from turn context), `full` (project-wide scan).
28
51
  - **`lens_diagnostic_mark`** — Triage a diagnostic: `false-positive` /
29
52
  `suppress` (writes an inline `pi-lens-ignore` comment) / `defer`
30
53
  (session-only) / `flagged` (persists, rendered `📌 flagged-to-fix`).
31
54
  Content-anchored so marks survive edits; every mark is logged and published
32
55
  on the bus. See [dispositions.md](dispositions.md).
33
- - **`lsp_diagnostics`** — File- or directory-scoped LSP diagnostics via the
34
- active language server.
35
- - **`lsp_navigation`** IDE-style navigation: `definition`, `references`,
36
- `implementation`, `typeDefinition`, `declaration`, `rename`, `rename_file`,
37
- `hover`, `documentSymbol`, `workspaceSymbol`, `signatureHelp`,
38
- `prepareCallHierarchy`, `incomingCalls`, `outgoingCalls`, `executeCommand`,
39
- and `capabilities`. Position-based operations accept a `path`/`line`/`character`
40
- triple. `documentSymbol` accepts a `kinds` filter (e.g. `function`, `class`)
41
- and a `maxResults` cap (default 20, max 100) to keep large files bounded.
56
+ - **`lsp_navigation`** — IDE-style navigation, 19 operations: `definition`,
57
+ `typeDefinition`, `declaration`, `references`, `hover`, `signatureHelp`,
58
+ `documentSymbol`, `findSymbol`, `workspaceSymbol`, `codeAction`, `rename`,
59
+ `rename_file`, `implementation`, `prepareCallHierarchy`, `incomingCalls`,
60
+ `outgoingCalls`, `executeCommand`, `workspaceDiagnostics`, and `capabilities`
61
+ (`tools/lsp-navigation.ts` operation description). Position-based operations
62
+ accept a `path`/`line`/`character` triple. `documentSymbol` accepts a `kinds`
63
+ filter (e.g. `function`, `class`) and a `maxResults` cap (default 20, max 100)
64
+ to keep large files bounded. Full per-operation parameter reference:
65
+ [skills/pi-lens-lsp-navigation/SKILL.md](../skills/pi-lens-lsp-navigation/SKILL.md).
42
66
  - **`ast_grep_search`** — AST-aware structural search across ~40 languages via
43
67
  the `sg` CLI. Supports metavariables (`$VAR`, `$$$ARGS`), `strictness`
44
68
  modes (`smart`, `relaxed`, `ast`, `cst`, `signature`, `template`), structural
@@ -48,7 +72,7 @@ in `index.ts`).
48
72
  (per-call cap, default 50, max 200; also sets the pagination step).
49
73
  `nodeKind` is an expert grammar-specific escape hatch: it finds every node of
50
74
  the exact kind used by the target grammar. Node kinds are not universal across
51
- languages; use `ast_grep_dump` to discover the kind in the target language. It is mutually exclusive with `pattern` and `rule`.
75
+ languages; use `dump=true` with the representative snippet in `pattern` to discover the kind in the target language. It is mutually exclusive with `rule`.
52
76
  `hasKind` retains ast-grep's immediate-child semantics; use
53
77
  `hasDescendantKind` for an explicit recursive descendant search. A future
54
78
  canonical `find`/`query` facade (call/function/import/etc.) should map to
@@ -60,11 +84,11 @@ in `index.ts`).
60
84
  searches. `pattern` is optional when a `rule` or `nodeKind` is given.
61
85
  Results include `details.matchLocations[]` — each hit carries a ready
62
86
  `readSlice` (`path`/`offset`/`limit`) for a bounded context read; zero-match
63
- results include a `suggestedDump` hint pointing at `ast_grep_dump`.
87
+ results include a `suggestedDump` hint pointing at `ast_grep_search` with `dump=true`.
64
88
  - **`ast_grep_replace`** — AST-aware structural replace. Re-validates the pattern
65
89
  against the current file before writing and reports a clear error if the
66
90
  file changed since the preview.
67
- - **`ast_grep_dump`** — Dumps the raw tree-sitter AST for a source snippet. Use
91
+ - **`ast_grep_search` with `dump=true`** — Dumps the raw tree-sitter AST for a source snippet. Use
68
92
  this when an `ast_grep_search` or `ast_grep_replace` pattern returns zero
69
93
  matches and the correct node kind or field name is unknown. `includeAnonymous`
70
94
  shows punctuation/CST nodes.
@@ -36,6 +36,15 @@ everything LSP-related lives under an `lsp` namespace inside them.
36
36
  }
37
37
  ```
38
38
 
39
+ Each model-facing tool accepts `tools.<name>.enabled` in the config file. Valid
40
+ names include `ast_grep_search`, `ast_grep_replace`, `ast_grep_outline`,
41
+ `lsp_navigation`, `lens_diagnostics`,
42
+ `lens_diagnostic_mark`, `symbol_search`, `module_report`, `project_report`,
43
+ `read_symbol`, `read_enclosing`, `effective_config`, `analyze`, `health`,
44
+ `latency`, `project_scan`, and `rebuild`. The activation loader and MCP
45
+ lifecycle tools `session_start`, `turn_end`, and `session_end` remain enabled
46
+ because their host protocols require them.
47
+
39
48
  **Some settings are global-only.** A handful of switches — `lsp.enabled`
40
49
  (`--no-lsp`), `tests.enabled`, `delta.enabled` and the other session-wide
41
50
  toggles — are decided once for the machine, not per project, so writing one in a
@@ -123,11 +123,6 @@ the `dispatch.runnerTimeoutFloorMs` config key, which wins when both are set.
123
123
 
124
124
  ## Install control
125
125
 
126
- ### `PI_LENS_AUTO_INSTALL`
127
-
128
- Set to `1` to auto-approve tool installs non-interactively (same as
129
- `--auto-install`). Off by default — installs prompt interactively.
130
-
131
126
  ### `PI_LENS_DISABLE_LSP_INSTALL`
132
127
 
133
128
  Set to `1` to skip auto-installing language servers. Off by default.
@@ -12,20 +12,20 @@ pi-lens includes **45 language server definitions** (including four cross-cuttin
12
12
  { "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
13
13
  ```
14
14
 
15
- **Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Key operations:
15
+ **Agent LSP tools:** `lens_diagnostics` with `source=lsp` can check one file, a directory, or explicit paths with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Key operations:
16
16
 
17
17
  - **`rename`** — renames a symbol across all references; `apply: true` writes workspace edits to disk with per-file LSP re-sync.
18
18
  - **`rename_file`** — LSP-aware file rename: sends `workspace/willRenameFiles` to collect import-path rewrites, applies them, renames the file on disk, and notifies servers via `workspace/didRenameFiles`. `apply: false` previews the workspace edits without touching the filesystem.
19
19
  - **`capabilities`** — shows which operations are supported by the active LSP server(s) for a file, read directly from the cached `initialize` response (no round-trip).
20
20
  - **Symbol column resolution** — passing `symbol: "myFunc"` instead of an exact `character` position resolves the correct column automatically. Use `symbol: "foo#2"` for the second occurrence of `foo` on the line.
21
21
 
22
- LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java (JDT LS, with Lombok javaagent support when a Lombok jar is available), Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, CUE (syntax and parse diagnostics; evaluation errors via the cue-vet auxiliary runner), Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, CSS.
22
+ LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, PowerShell, C# (omnisharp), F#, Java (JDT LS, with Lombok javaagent support when a Lombok jar is available), Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, Markdown (marksman), OCaml, Clojure, CUE (syntax and parse diagnostics; evaluation errors via the cue-vet auxiliary runner), Terraform, Nix, Bash, Fish, CMake, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, CSS.
23
23
 
24
24
  ### Formatters
25
25
 
26
26
  pi-lens auto-detects and runs **34 formatters** based on project config:
27
27
 
28
- biome, prettier, oxfmt, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo, fish_indent, google-java-format, cljfmt, cmake-format, cue fmt, psscriptanalyzer-format
28
+ biome, prettier, oxfmt, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, ktfmt, rubocop, standardrb, gleam format, terraform fmt, terragrunt-hcl, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo, google-java-format, cljfmt, cmake-format, cue fmt, psscriptanalyzer-format
29
29
 
30
30
  Detection rules:
31
31
 
@@ -310,7 +310,7 @@ Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake. A TypeSc
310
310
  - **Strictness modes** — `strictness: "relaxed"` ignores optional punctuation (trailing commas, semicolons) that causes zero matches in `smart` mode. Also supports `ast`, `cst`, `signature`, `template`.
311
311
  - **Pagination** — `skip: N` offsets into large result sets; truncated results include a next-page hint.
312
312
  - **Stale-preview detection** — `ast_grep_replace` re-validates the pattern before writing; returns a clear error if files changed since the preview instead of applying against wrong content.
313
- - **`ast_grep_dump`** — dumps the full tree-sitter AST for a source snippet. Use this when a pattern returns zero matches and the correct node kind or field name is unknown.
313
+ - **`ast_grep_search` with `dump=true`** — dumps the full tree-sitter AST for a source snippet. Use this when a pattern returns zero matches and the correct node kind or field name is unknown.
314
314
 
315
315
  ### Tree-sitter Rules
316
316
 
@@ -397,7 +397,7 @@ pi-lens ships an MCP (Model Context Protocol) server so Claude Code — or any M
397
397
 
398
398
  | Layer | MCP tools | What they expose |
399
399
  |---|---|---|
400
- | **Per-edit** | `pilens_analyze`, `pilens_lsp_diagnostics`, `pilens_lsp_navigation`, `pilens_ast_grep_search`, `pilens_ast_grep_replace`, `pilens_module_report`, `pilens_read_symbol`, `pilens_read_enclosing` | The fast pipeline (format → autofix → LSP diagnostics → parallel runners) plus the structured read-substitute pair. `analyze` accepts `mode: warm \| fresh` — `warm` reuses the server's in-process LSP, `fresh` forks a worker that loads freshly-built code from disk so the result reflects the latest commit. |
400
+ | **Per-edit** | `pilens_analyze`, `pilens_lsp_navigation`, `pilens_ast_grep_search`, `pilens_ast_grep_replace`, `pilens_module_report`, `pilens_read_symbol`, `pilens_read_enclosing` | The fast pipeline (format → autofix → LSP diagnostics → parallel runners) plus the structured read-substitute pair. `analyze` accepts `mode: warm \| fresh` — `warm` reuses the server's in-process LSP, `fresh` forks a worker that loads freshly-built code from disk so the result reflects the latest commit. |
401
401
  | **Per-turn** | `pilens_turn_end` | Drives the **real** `handleTurnEnd` (knip incremental, dep-circular, cascade, tests, actionable+code-quality warnings) — not a re-implementation. Caller-supplied edited files are auto-registered into turn-state via `addModifiedRange`. |
402
402
  | **Per-session** | `pilens_session_start` | Drives the **real** `handleSessionStart` — full jscpd/knip/madge/govulncheck/gitleaks/trivy scans + complexity baselines + LSP warm. The error-debt baseline is not currently populated by the production session-start path. |
403
403
  | **Project / observability** | `pilens_project_scan`, `pilens_project_report`, `pilens_diagnostics`, `pilens_health`, `pilens_latency`, `pilens_symbol_search`, `pilens_effective_config` | Cheap project-wide scans, cached diagnostic state, latency telemetry, ranked identifier search (BM25 over the persisted word index — see [docs/word-index.md](word-index.md)). Cross-file blast radius now lives in `pilens_module_report`'s `blastRadius` option. `pilens_health` (and its pi-side `/lens-health` counterpart) also reports a bounded, process-local **degradation ledger** — trust refusals, mode suppressions, LSP breaker trips, formatter skips/failures, TypeScript/word-index/review-graph/project-snapshot idle evictions, WASM aborts, and diagnostics-timeout tallies — so silently degraded behavior stays visible instead of vanishing into a log. `pilens_effective_config` answers **“why is X running / why is X not running”** from one query — the resolved configuration with the provenance of every setting, and for a file you name, its language plus every LSP server with the reason it was selected or denied and the runners that would dispatch. It reports sources, never values. |
@@ -18,6 +18,7 @@ Each runtime toggle is settable from the CLI *and* from `config.json`. The two a
18
18
  | `--no-autofix` | `autofix.enabled` | `true` |
19
19
  | `--no-lens-context` | `contextInjection.enabled` | `true` |
20
20
  | `--lens-guard` | `guard.enabled` | `false` |
21
+ | `--lens-checkout-guard` | `guard.sharedCheckout` | `false` |
21
22
  | `--immediate-format` | `format.mode` (`"immediate"`) | `"deferred"` |
22
23
  | `--lens-turn-summary` | `turnSummary.enabled` | `false` |
23
24
  | `--lens-actionable-warnings` | `actionableWarnings.enabled` | `false` |
@@ -42,7 +43,7 @@ Each runtime toggle is settable from the CLI *and* from `config.json`. The two a
42
43
  | `startup.mode` | `full` | `quick`, `full`, or `minimal`; `PI_LENS_STARTUP_MODE` wins |
43
44
  | `startup.scans.enabled` | `true` | `true` or `false` |
44
45
 
45
- By default pi-lens registers six situational tools (the `ast_grep_*` family,
46
+ By default pi-lens registers five situational tools (the `ast_grep_*` family,
46
47
  `lsp_navigation`, `lens_diagnostic_mark`) inactive and exposes a small loader,
47
48
  `pi_lens_activate_tools`, that the model calls to activate the ones it needs.
48
49
  `--no-lazy-tools` turns that off: every pi-lens tool is active from the first
@@ -123,6 +124,15 @@ Turn subsystems off globally instead of retyping flags every session:
123
124
 
124
125
  `lens.enabled: false` starts every session with pi-lens off (the `--no-lens` equivalent); `/lens-toggle` still re-enables it for one session. `lsp.enabled: false` falls back to language-specific checkers such as pyright. `tests.enabled: false` skips the on-write test runner. `delta.enabled: false` reports every diagnostic rather than only ones introduced this turn. `opengrep.enabled: false` detaches the Opengrep security scanner. `readGuard.enabled: false` turns off the read-before-edit monitor. `guard.enabled: true` opts into the experimental commit/push blocker.
125
126
 
127
+ The `tools.<name>.enabled` setting controls each model-facing tool. Valid names
128
+ include `ast_grep_search`, `ast_grep_replace`, `ast_grep_outline`,
129
+ `lsp_navigation`, `lens_diagnostics`,
130
+ `lens_diagnostic_mark`, `symbol_search`, `module_report`, `project_report`,
131
+ `read_symbol`, `read_enclosing`, `effective_config`, `analyze`, `health`,
132
+ `latency`, `project_scan`, and `rebuild`. The activation loader and MCP
133
+ lifecycle tools `session_start`, `turn_end`, and `session_end` cannot be
134
+ disabled.
135
+
126
136
  ## Project Config
127
137
 
128
138
  In addition to the user-level `~/.pi-lens/config.json` above, pi-lens reads a per-project `.pi-lens.json` (or `pi-lens.json`) at the project root. Walked upward from the cwd, so a monorepo can keep the config at the repo root and have every subdir pick it up. The schema is intentionally small — only fields pi-lens actually honors:
@@ -113,11 +113,23 @@ New files:
113
113
  | tool | maps to | purpose |
114
114
  |------|---------|---------|
115
115
  | `pilens_analyze` | `analyzeFile` (warm) / worker (fresh) | run the per-edit pipeline on a file; returns diagnostics + timing. `mode: warm\|fresh`. **Correctness + mechanism probe.** |
116
- | `pilens_diagnostics` | lens-diagnostics formatters | delta/all/full project/session diagnostic state. |
116
+ | `pilens_ast_grep_search` | `ast_grep_search` | AST-aware structural search. |
117
+ | `pilens_ast_grep_replace` | `ast_grep_replace` | AST-aware structural replace. |
118
+ | `pilens_diagnostics` | `lens_diagnostics` | session or LSP diagnostics, selected by `source`, `scope`, and `severity`. |
119
+ | `pilens_effective_config` | `effective_config` | resolved configuration and provenance. |
117
120
  | `pilens_project_scan` | `scanProjectDiagnostics` | project-wide cheap scan. |
118
121
  | `pilens_latency` | `getLatencyReports` | latency records (latency.log schema). **Review-loop measurement.** |
119
122
  | `pilens_health` | `/lens-health` internals | runtime health snapshot. |
123
+ | `pilens_lsp_navigation` | `lsp_navigation` | IDE-style LSP navigation. |
124
+ | `pilens_module_report` | `module_report` | navigable module outline and dependents. |
125
+ | `pilens_project_report` | `project_report` | project-level structure and diagnostics report. |
126
+ | `pilens_read_enclosing` | `read_enclosing` | read the smallest enclosing symbol or callback. |
127
+ | `pilens_read_symbol` | `read_symbol` | read one symbol body. |
120
128
  | `pilens_rebuild` | `npm run build:dist` | rebuild dist so `fresh` reflects the latest commit. Makes the loop self-contained. |
129
+ | `pilens_session_end` | session lifecycle | close the MCP session and flush lifecycle state. |
130
+ | `pilens_session_start` | session lifecycle | initialize session state and caches. |
131
+ | `pilens_symbol_search` | `symbol_search` | search the persisted symbol index. |
132
+ | `pilens_turn_end` | turn lifecycle | settle turn-end work and return findings. |
121
133
 
122
134
  ## Packaging / wiring
123
135