@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
@@ -60,8 +60,9 @@ function formatReport(result, cwd) {
60
60
  if (result.diagnostics.length > 30) {
61
61
  lines.push(` … ${result.diagnostics.length - 30} more`);
62
62
  }
63
- if (result.lsp && result.lsp.status === "skipped") {
64
- lines.push(" (LSP type-check skipped run pilens_analyze on the warm MCP server for type errors)");
63
+ if (result.lsp &&
64
+ (result.lsp.status === "skipped" || result.lsp.status === "deferred")) {
65
+ lines.push(" (LSP diagnostics were skipped or deferred — run pilens_analyze on the warm MCP server for type errors)");
65
66
  }
66
67
  return lines.join("\n");
67
68
  }
@@ -22,15 +22,20 @@ import * as fs from "node:fs";
22
22
  import * as net from "node:net";
23
23
  import * as path from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
+ import { finalizeToolResult, finalizeToolResultWithDelivery, renderToolText as toolText, stripResultDetails, } from "../tools/render-compact.js";
25
26
  import { AstGrepClient } from "../clients/ast-grep-client.js";
26
27
  import { CacheManager } from "../clients/cache-manager.js";
27
- import { getDegradationSummary, renderDegradationLines, } from "../clients/degradation-ledger.js";
28
+ import { getDegradationSummary, recordDegradationOnce, renderDegradationLines, } from "../clients/degradation-ledger.js";
28
29
  import { acknowledgeTurnEnd, analyzeFile, analyzeFileFresh, canRebuildPiLens, createMcpHost, createWarmIpcLineReader, createWarmIpcRequestQueue, diagnosticStats, effectiveConfig, ensureLspConfig, generatedSkipNotice, ipcPathForCwd, isEffectiveFileViewError, lspStatus, moduleReport, projectReport, projectScan, readEnclosing, readSymbol, readTurnEndStatus, recentLatency, renderCompactModuleReport, renderCompactProjectReport, renderLspBrokenStatusLines, resolveRebuildScript, resourceFootprint, runRebuild, runSessionStart, runTurnEnd, runTurnEndForIpc, scanTruncationNotice, summarizeScan, symbolSearch, treeSitterRuntimeStatus, WARM_TURN_END_SCHEMA_VERSION, } from "../clients/lens-engine.js";
29
30
  import { createAstGrepReplaceTool } from "../tools/ast-grep-replace.js";
30
- import { createAstGrepSearchTool } from "../tools/ast-grep-search.js";
31
+ import { astGrepDumpCompatibilityResult, createAstGrepSearchTool, } from "../tools/ast-grep-search.js";
31
32
  import { createLensDiagnosticsTool } from "../tools/lens-diagnostics.js";
32
33
  import { peekMcpSessionRuntime } from "../clients/mcp/session.js";
33
- import { createLspDiagnosticsTool } from "../tools/lsp-diagnostics.js";
34
+ import { loadPiLensGlobalConfig } from "../clients/lens-config.js";
35
+ import { loadPiLensProjectConfig } from "../clients/project-lens-config.js";
36
+ import { resolveLensToolEnabled, toolRegistryEntryForMcp, } from "../clients/tool-config.js";
37
+ import { endSituationalToolTelemetry, observeSituationalToolCall, startSituationalToolTelemetrySession, } from "../clients/situational-tool-telemetry.js";
38
+ import { flushExtensionLog } from "../clients/extension-log.js";
34
39
  import { createLspNavigationTool } from "../tools/lsp-navigation.js";
35
40
  import { shouldInitializeSessionRoot } from "../clients/lsp/session-roots.js";
36
41
  import { computeBuildStamp, STALE_SERVED_BY_FRESH, STALE_WARN_ONLY, StalenessGate, stalenessCheckEnabled, } from "./build-staleness.js";
@@ -354,18 +359,6 @@ function sendResult(id, result) {
354
359
  function sendError(id, code, message) {
355
360
  send({ jsonrpc: "2.0", id, error: { code, message } });
356
361
  }
357
- /**
358
- * A tool result: human-readable text first, full JSON appended for the agent.
359
- * `compact` omits indentation (#512) — for token-efficient tools like
360
- * module_report the ~30% saved on the wire is worth losing pretty-printing
361
- * for a payload the agent parses, not reads formatted.
362
- */
363
- function toolText(summary, structured, compact = false) {
364
- const text = structured === undefined
365
- ? summary
366
- : `${summary}\n\n\`\`\`json\n${JSON.stringify(structured, compact ? undefined : null, compact ? undefined : 2)}\n\`\`\``;
367
- return { content: [{ type: "text", text }] };
368
- }
369
362
  // --- Graph-staleness signal (#536) -------------------------------------------
370
363
  //
371
364
  // Extends #514's honesty-warning shape from "missing node" (module_report's
@@ -417,12 +410,20 @@ const cacheManager = new CacheManager();
417
410
  // nothing — a no-op dressed as a fix, not a real parity gap. The 4th arg is left
418
411
  // at its default (`async () => {}`, already a no-op) rather than importing and
419
412
  // wiring a flush with nothing to flush.
413
+ const isLensGuardEnabled = () => Boolean(createMcpHost(undefined, DEFAULT_CWD).getFlag("lens-guard"));
420
414
  const lensDiagnosticsTool = createLensDiagnosticsTool(cacheManager, () => DEFAULT_CWD, undefined, undefined, undefined, undefined,
421
415
  // #1413 surface parity: validate cached test-runner findings against the
422
416
  // same session identity the in-process path uses. Resolved lazily — until
423
417
  // an MCP session context exists there is no session to compare against and
424
418
  // validation skips the check, which is the honest classification.
425
- () => peekMcpSessionRuntime());
419
+ () => peekMcpSessionRuntime(),
420
+ // #2860: without this, the default `() => true` applies on the MCP
421
+ // surface and every confirmed-clean LSP probe unconditionally resyncs the
422
+ // commit-gate `turn-end-findings` record even when the project has
423
+ // `lens-guard` off — a config bypass on a durable cross-surface store.
424
+ // Matches index.ts:1656 and the two sibling readers
425
+ // (clients/runtime-tool-call.ts, clients/runtime-tool-result.ts).
426
+ () => isLensGuardEnabled());
426
427
  const astGrepClient = new AstGrepClient();
427
428
  const astGrepSearchTool = createAstGrepSearchTool(astGrepClient);
428
429
  const astGrepReplaceTool = createAstGrepReplaceTool(astGrepClient);
@@ -434,7 +435,6 @@ const astGrepReplaceTool = createAstGrepReplaceTool(astGrepClient);
434
435
  // Resolve lazily against the call's own `cwd` instead (config loads are
435
436
  // mtime-cached, so this stays cheap).
436
437
  const lspNavigationTool = createLspNavigationTool((name, cwd) => createMcpHost(undefined, cwd ?? DEFAULT_CWD).getFlag(name));
437
- const lspDiagnosticsTool = createLspDiagnosticsTool();
438
438
  // Wrapped pi tools already declare their params as typebox (which IS JSON
439
439
  // Schema). Emit that directly as the MCP inputSchema (+ the MCP-only `cwd`)
440
440
  // instead of hand-restating it — no drift between the tool and its schema.
@@ -455,10 +455,7 @@ function schemaWithCwd(parameters) {
455
455
  const ALL_TOOLS = [
456
456
  {
457
457
  name: "pilens_analyze",
458
- description: "Run pi-lens's per-edit dispatch pipeline (LSP + linters + structural " +
459
- "rules) on a single file and return its diagnostics plus the latency " +
460
- "record for that dispatch (same schema as latency.log). The core review " +
461
- "probe: shows a change's real behavioral + perf impact on a real file.",
458
+ description: "Run pi-lens's per-edit dispatch pipeline on one file. Example: analyze `src/app.ts` after an edit.",
462
459
  inputSchema: {
463
460
  type: "object",
464
461
  properties: {
@@ -485,15 +482,12 @@ const ALL_TOOLS = [
485
482
  },
486
483
  {
487
484
  name: "pilens_diagnostics",
488
- description: "Query pi-lens's diagnostic state across ALL runners (not just LSP). " +
489
- "mode=delta (current turn, instant), mode=all (every dispatched file this " +
490
- "session), mode=full (expensive project-wide active scan).",
485
+ description: lensDiagnosticsTool.description,
491
486
  inputSchema: schemaWithCwd(lensDiagnosticsTool.parameters),
492
487
  },
493
488
  {
494
489
  name: "pilens_latency",
495
- description: "Return recent dispatch latency reports (latency.log schema: per-file " +
496
- "total duration + per-runner timings). The review-loop measurement surface.",
490
+ description: "Return recent dispatch latency reports. Example: limit results to 5.",
497
491
  inputSchema: {
498
492
  type: "object",
499
493
  properties: {
@@ -510,18 +504,12 @@ const ALL_TOOLS = [
510
504
  },
511
505
  {
512
506
  name: "pilens_rebuild",
513
- description: "Rebuild pi-lens so subsequent `pilens_analyze mode=fresh` runs reflect " +
514
- "the latest commit. Runs `npm run build` (in-place dev layout) or " +
515
- "`npm run build:dist` (precompiled dist layout), matching how this server " +
516
- "was launched. The missing link that makes the review loop honest: " +
517
- "commit → pilens_rebuild → pilens_analyze mode=fresh.",
507
+ description: "Rebuild pi-lens so later fresh analyses use the latest commit. Example: rebuild after changing a tool.",
518
508
  inputSchema: { type: "object", properties: {} },
519
509
  },
520
510
  {
521
511
  name: "pilens_project_scan",
522
- description: "Cheap project-wide scan (tree-sitter + fact rules) across source files, " +
523
- "returning structural/quality diagnostics. Complements pilens_diagnostics " +
524
- "mode=full (which adds active LSP).",
512
+ description: "Scan project files for structural and quality diagnostics. Example: cap the scan with `maxFiles: 20`.",
525
513
  inputSchema: {
526
514
  type: "object",
527
515
  properties: {
@@ -539,17 +527,7 @@ const ALL_TOOLS = [
539
527
  },
540
528
  {
541
529
  name: "pilens_symbol_search",
542
- description: "Ranked identifier search over the persisted word index (BM25 + priors " +
543
- "that demote tests/vendor and doc files). Answers 'which files are most " +
544
- "relevant to <query>' by identifier — first step of the discovery funnel: " +
545
- "symbol_search finds candidate files, pilens_module_report explains one, " +
546
- "pilens_read_symbol reads a body. Complements grep (raw substrings) and " +
547
- "LSP (exact symbols). Each hit's `startLine`/`endLine` mark its best-matching " +
548
- "line (offset=startLine, limit=endLine-startLine+1 for a one-line peek) — " +
549
- "use pilens_module_report on `file` for the real outline. Returns " +
550
- "`available: false` with a retry hint if the index isn't built yet for this " +
551
- "workspace (pilens_session_start builds it, or it self-builds in the background " +
552
- "on first query).",
530
+ 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 pilens_module_report.",
553
531
  inputSchema: {
554
532
  type: "object",
555
533
  properties: {
@@ -577,23 +555,7 @@ const ALL_TOOLS = [
577
555
  },
578
556
  {
579
557
  name: "pilens_module_report",
580
- description: "Structured, navigable overview of a source module a token-efficient " +
581
- "substitute for reading the whole file. Returns each symbol's " +
582
- "name/kind/signature/line-range (plus a first-line `doc` summary when a " +
583
- "doc comment is attached), plus who-uses-this, risk flags, and ranked " +
584
- "recommendedReads. To read a symbol's body: call pilens_read_symbol (or " +
585
- "read) with offset=startLine, limit=endLine-startLine+1 on THIS report's " +
586
- "`file` — those aren't repeated per symbol. Prefer this before a full " +
587
- "read; then use pilens_read_symbol for the exact body. Single mode: " +
588
- "tree-sitter outline + review-graph who-uses-this + inline executable " +
589
- "extraction; degrades to outline-only when no cached graph is available " +
590
- "(this path never calls LSP). `semantic.source` reports whether graph " +
591
- "data was used. Pass `blastRadius: true` for the cross-file blast radius " +
592
- "(transitive dependents as ranked file reads, read-only over the cached " +
593
- "graph). Pass `callGraph: true` for bounded derived callers/callees; a " +
594
- "cold or stale call-graph cache is reported explicitly, never as zero calls. " +
595
- '`view: "compact"` returns a line-oriented text rendering ' +
596
- "(cheapest option) instead of JSON. An outline shows shape, not bodies.",
558
+ description: "Return a navigable source-module outline with references and read handles. An outline shows shape, not bodies, and does not satisfy read-before-edit; `read_symbol` and `read_enclosing` return body text and record read coverage. 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: use pilens_module_report on `src/app.ts` before pilens_read_symbol.",
597
559
  inputSchema: {
598
560
  type: "object",
599
561
  properties: {
@@ -637,24 +599,7 @@ const ALL_TOOLS = [
637
599
  },
638
600
  {
639
601
  name: "pilens_project_report",
640
- description: "Project-level orientation from the review graph 'orient me in this " +
641
- "project' before drilling into any one file. First step of a wider " +
642
- "discovery funnel: pilens_project_report orients, pilens_module_report " +
643
- "explains a file, pilens_read_symbol reads a body. Six capped, ranked " +
644
- "sections: a trust header (graph freshness, file coverage, " +
645
- "edge-resolution-quality mix), hubs (top fan-in files — the repo's " +
646
- "contract surface), entry points (near-zero fan-in / high fan-out files " +
647
- "— activation/CLI/mains), a directory-level subsystem map (import " +
648
- "cycles + layering violations, e.g. a forbidden clients/ -> tools/ " +
649
- "edge), risk hotspots (fan-in × max per-symbol cyclomatic complexity), " +
650
- "and suspected dead weight (zero-importer files, shipped with a " +
651
- "low-confidence disclaimer). Every file line carries a `suggestedNext` " +
652
- "module_report call. No per-symbol detail and no prose summary — " +
653
- "structural facts only. Read-only over the cached graph: returns " +
654
- "`available: false` with a retry hint on a cold cache and kicks off a " +
655
- 'background build (never blocks this call). `view: "compact"` returns ' +
656
- "a line-oriented text rendering instead of JSON. Pass `focus` to " +
657
- "re-rank every section toward a task hint.",
602
+ description: "Orient in a project from its review graph. 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: use pilens_project_report before choosing a file.",
658
603
  inputSchema: {
659
604
  type: "object",
660
605
  properties: {
@@ -677,15 +622,7 @@ const ALL_TOOLS = [
677
622
  },
678
623
  {
679
624
  name: "pilens_read_symbol",
680
- description: "Return the verbatim source of a single named symbol " +
681
- "(function/class/method/interface/type) in a file — a targeted, cheap " +
682
- "alternative to reading the whole file. Pair with pilens_module_report: it " +
683
- "finds the symbol, this shows its body. Includes an attached doc comment " +
684
- "when one exists. Accepts a dotted `Class.method` name to resolve a " +
685
- "member, falling back to a plain lookup when the qualifier doesn't " +
686
- "resolve. A miss embeds the ~3 nearest symbol names in the file. When " +
687
- "multiple same-file symbols share a name, the first is returned with an " +
688
- "ambiguity note; pass `kind` to pick a specific one.",
625
+ description: "Return one symbol's verbatim source. An outline shows shape, not bodies, and does not satisfy read-before-edit; `read_symbol` and `read_enclosing` return body text and record read coverage. Example: use pilens_read_symbol after pilens_module_report identifies `parseConfig`.",
689
626
  inputSchema: {
690
627
  type: "object",
691
628
  properties: {
@@ -705,12 +642,7 @@ const ALL_TOOLS = [
705
642
  },
706
643
  {
707
644
  name: "pilens_read_enclosing",
708
- description: "Return the verbatim source for the smallest useful symbol/callback " +
709
- "enclosing a line in a file. Use after pilens_ast_grep_search, " +
710
- "pilens_diagnostics, or pilens_lsp_navigation locations when you need " +
711
- "exact body text without reading the whole file. Uses tree-sitter only — " +
712
- "no LSP or graph build. MCP has no read-guard, so unlike the pi tool this " +
713
- "does not record edit-coverage.",
645
+ description: "Return the smallest symbol or callback enclosing a line. An outline shows shape, not bodies, and does not satisfy read-before-edit; `read_symbol` and `read_enclosing` return body text and record read coverage. Example: use pilens_read_enclosing after a diagnostic points to line 42.",
714
646
  inputSchema: {
715
647
  type: "object",
716
648
  properties: {
@@ -747,25 +679,12 @@ const ALL_TOOLS = [
747
679
  },
748
680
  {
749
681
  name: "pilens_health",
750
- description: "pi-lens runtime health for THIS server: alive LSP servers, last dispatch " +
751
- "summary, session diagnostic counts, how many resolved config leaves each " +
752
- "source tier decided, and the total CPU/RAM footprint attributable to " +
753
- "pi-lens across every process it owns (host + LSP children) machine-wide, " +
754
- "from the shared instance registry.",
682
+ description: "Return pi-lens runtime health for this server. Example: call it after a slow analysis.",
755
683
  inputSchema: { type: "object", properties: {} },
756
684
  },
757
685
  {
758
686
  name: "pilens_effective_config",
759
- description: "The resolved pi-lens configuration with the provenance of every " +
760
- "decision: which file and source tier each setting came from, the trust " +
761
- "decision that applied, and which config files contributed. Pass `file` " +
762
- "to also get, for that path, its canonical language, EVERY LSP server " +
763
- "with the reason it was selected or denied — including which tier's " +
764
- "config denied it, which a nearer file cannot lift — and the lint/format " +
765
- "runners that would dispatch. This is the answer to 'why is X running / " +
766
- "why is X not running' without reading logs. Redacted by construction: " +
767
- "it reports sources, never values — no environment values, no command " +
768
- "arguments beyond the binary itself, and config paths are home-relative.",
687
+ description: "Explain resolved configuration and provenance. Response is redacted by construction: it contains no environment values, no command arguments beyond the binary, and home-relative paths; a tier-denied LSP decision cannot be lifted by a nearer config. Example: pass `file` to explain one selection.",
769
688
  inputSchema: {
770
689
  type: "object",
771
690
  properties: {
@@ -783,12 +702,7 @@ const ALL_TOOLS = [
783
702
  },
784
703
  {
785
704
  name: "pilens_session_start",
786
- description: "Run pi-lens's real session_start lifecycle: warm the dominant-language " +
787
- "LSP (so subsequent pilens_analyze is LSP-complete), establish the " +
788
- "error-debt baseline (tests/build pass-state) + complexity baselines, and " +
789
- "kick off knip/jscpd/type-coverage/dep/secrets project scans. Returns " +
790
- "project guidance + baseline; scan results land in caches (query via " +
791
- "pilens_diagnostics afterwards). Run once per workspace before reviewing.",
705
+ description: "Initialize pi-lens for a workspace. Example: run once before reviewing a project.",
792
706
  inputSchema: {
793
707
  type: "object",
794
708
  properties: { cwd: { type: "string" } },
@@ -796,13 +710,7 @@ const ALL_TOOLS = [
796
710
  },
797
711
  {
798
712
  name: "pilens_turn_end",
799
- description: "Run pi-lens's real turn_end lifecycle over the files changed this turn: " +
800
- "knip dead-code + jscpd duplication (incremental), circular-dep checks, " +
801
- "tests on affected targets, cascade to dependents, and the actionable/" +
802
- "code-quality warning aggregation. Returns the turn-end advisory + test " +
803
- "findings. `files` is OPTIONAL — pilens_analyze (and the PostToolUse hook) " +
804
- "auto-register edited files into turn-state, so you can call this with no " +
805
- "args after a series of edits; pass `files` to add any not analyzed.",
713
+ description: "Summarize checks for files changed this turn. Example: pass `files` for an unanalysed file.",
806
714
  inputSchema: {
807
715
  type: "object",
808
716
  properties: {
@@ -815,39 +723,29 @@ const ALL_TOOLS = [
815
723
  },
816
724
  },
817
725
  },
726
+ {
727
+ name: "pilens_session_end",
728
+ description: "End the MCP connection's telemetry session and write its bounded situational-tool telemetry line. This is terminal for the connection.",
729
+ inputSchema: {
730
+ type: "object",
731
+ properties: {},
732
+ },
733
+ },
818
734
  {
819
735
  name: "pilens_ast_grep_search",
820
- description: "Structural (AST) code search via ast-grep match by code structure, not " +
821
- "text. Use complete patterns with arguments (e.g. 'console.log($MSG)', " +
822
- "'function $NAME($$$ARGS) { $$$BODY }'), or use nodeKind to find every " +
823
- "node of a known grammar kind. Metavariables do not match text inside " +
824
- "quoted string literals; use an exact string or grep for wildcard text. " +
825
- "This is the same schema and synthesis path as the pi tool.",
736
+ description: "Search code by AST structure rather than text. Example: find calls with `console.log($MSG)`.",
826
737
  inputSchema: schemaWithCwd(astGrepSearchTool.parameters),
827
738
  },
828
739
  {
829
740
  name: "pilens_ast_grep_replace",
830
- description: "Structural (AST) find-and-rewrite via ast-grep, e.g. pattern='var $X' " +
831
- "rewrite='let $X'. DRY-RUN by default (apply=false shows the diff); set " +
832
- "apply=true to write the changes to disk.",
741
+ description: "Find and rewrite code by AST structure; preview by default. Example: set `apply: false` to inspect a diff.",
833
742
  inputSchema: schemaWithCwd(astGrepReplaceTool.parameters),
834
743
  },
835
744
  {
836
745
  name: "pilens_lsp_navigation",
837
- description: "LSP code navigation: definition, typeDefinition, declaration, " +
838
- "references, hover, documentSymbol, " +
839
- "workspaceSymbol, implementation, call hierarchy (prepareCallHierarchy/" +
840
- "incomingCalls/outgoingCalls), rename, codeAction, executeCommand " +
841
- "(allowlisted, dry-run by default) — exact + type-aware, " +
842
- "~50ms. Use before changing a signature to see every caller.",
746
+ description: 'Navigate source with language-server operations. Example: use `{operation: "references", path: "src/app.ts", line: 12}`.',
843
747
  inputSchema: schemaWithCwd(lspNavigationTool.parameters),
844
748
  },
845
- {
846
- name: "pilens_lsp_diagnostics",
847
- description: "Pure LSP diagnostics for a file, directory, or batch of files (type " +
848
- "errors only — narrower than pilens_diagnostics, which spans all runners).",
849
- inputSchema: schemaWithCwd(lspDiagnosticsTool.parameters),
850
- },
851
749
  ];
852
750
  // #920: published packages cannot rebuild themselves safely because their
853
751
  // build config is intentionally not shipped. Do not advertise the destructive
@@ -855,6 +753,15 @@ const ALL_TOOLS = [
855
753
  const TOOLS = canRebuildPiLens(REPO_ROOT)
856
754
  ? ALL_TOOLS
857
755
  : ALL_TOOLS.filter((tool) => tool.name !== "pilens_rebuild");
756
+ function enabledToolsForCwd(cwd) {
757
+ const global = loadPiLensGlobalConfig();
758
+ const project = loadPiLensProjectConfig(cwd);
759
+ return TOOLS.filter((tool) => {
760
+ const entry = toolRegistryEntryForMcp(tool.name);
761
+ return (entry !== undefined &&
762
+ resolveLensToolEnabled(entry.name, global, project.raw));
763
+ });
764
+ }
858
765
  function formatAnalyze(result, cwd, mode, servedBy) {
859
766
  // Surface the LSP outcome so a cold/indexing server's "0" is never silently
860
767
  // read as "clean" — a known limit on large projects (warm mode / re-run once
@@ -920,6 +827,38 @@ function effectiveDocumentLine(document) {
920
827
  return ` ${document.tier}: ${document.file}${legacy}`;
921
828
  }
922
829
  async function callTool(name, args) {
830
+ if (name === "pilens_lsp_diagnostics") {
831
+ recordDegradationOnce({
832
+ kind: "lsp-diagnostics-compatibility",
833
+ subject: "lsp_diagnostics",
834
+ reason: "retired tool name redirected to pilens_diagnostics source=lsp",
835
+ });
836
+ name = "pilens_diagnostics";
837
+ args = {
838
+ ...args,
839
+ source: "lsp",
840
+ // #2860: the retired tool's contract was always "path or paths is
841
+ // required" (tools/lsp-diagnostics.ts's own early return) — it never
842
+ // had a bare "sweep the whole workspace" mode. `scope: "paths"` is a
843
+ // no-op for source=lsp (lens-diagnostics.ts only special-cases
844
+ // scope==="workspace"), so whatever `path`/`paths` the caller sent
845
+ // (or didn't) passes straight through to the probe's own path/paths
846
+ // handling unchanged, reproducing master's behavior exactly —
847
+ // including its error when neither is present. Mapping absent
848
+ // `paths` to `scope:"workspace"` here previously caused every
849
+ // no-args call to substitute `cwd` and run a whole-project LSP sweep
850
+ // instead (root-eviction smoke: 129 sweeps, 180s timeout x3).
851
+ scope: "paths",
852
+ };
853
+ }
854
+ if (name === "pilens_ast_grep_dump") {
855
+ recordDegradationOnce({
856
+ kind: "ast-grep-dump-compatibility",
857
+ subject: "ast_grep_dump",
858
+ reason: "retired tool name redirected to ast_grep_search dump mode",
859
+ });
860
+ return astGrepDumpCompatibilityResult(args, "mcp");
861
+ }
923
862
  if (name === "pilens_analyze") {
924
863
  const file = args.file;
925
864
  if (typeof file !== "string" || file.length === 0) {
@@ -1254,7 +1193,10 @@ async function callTool(name, args) {
1254
1193
  const header = `${result.kind} ${result.name}${ambiguityNote}${sigSuffix} ${path.relative(cwd, result.path)}:${result.startLine}-${result.endLine}`;
1255
1194
  return {
1256
1195
  content: [
1257
- { type: "text", text: `${header}\n\n${result.source ?? ""}` },
1196
+ {
1197
+ type: "text",
1198
+ text: `${header}\n\n${result.source ?? ""}`,
1199
+ },
1258
1200
  ],
1259
1201
  };
1260
1202
  }
@@ -1311,7 +1253,10 @@ async function callTool(name, args) {
1311
1253
  const header = `${result.kind} ${result.name} ${path.relative(cwd, result.path)}:${range}`;
1312
1254
  return {
1313
1255
  content: [
1314
- { type: "text", text: `${header}\n\n${result.source ?? ""}` },
1256
+ {
1257
+ type: "text",
1258
+ text: `${header}\n\n${result.source ?? ""}`,
1259
+ },
1315
1260
  ],
1316
1261
  };
1317
1262
  }
@@ -1431,7 +1376,7 @@ async function callTool(name, args) {
1431
1376
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
1432
1377
  await ensureReady(cwd);
1433
1378
  const out = (await lensDiagnosticsTool.execute("mcp", args, new AbortController().signal, undefined, { cwd }));
1434
- return { content: out.content };
1379
+ return out;
1435
1380
  }
1436
1381
  if (name === "pilens_latency") {
1437
1382
  const limit = typeof args.limit === "number" && Number.isFinite(args.limit)
@@ -1462,6 +1407,10 @@ async function callTool(name, args) {
1462
1407
  ];
1463
1408
  return toolText(lines.filter(Boolean).join("\n"), outcome);
1464
1409
  }
1410
+ if (name === "pilens_session_end") {
1411
+ endSituationalToolTelemetry();
1412
+ return toolText("Session ended.");
1413
+ }
1465
1414
  if (name === "pilens_turn_end") {
1466
1415
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
1467
1416
  await ensureReady(cwd);
@@ -1481,15 +1430,15 @@ async function callTool(name, args) {
1481
1430
  const tool = name === "pilens_ast_grep_search"
1482
1431
  ? astGrepSearchTool
1483
1432
  : astGrepReplaceTool;
1484
- const out = (await tool.execute("mcp", args, new AbortController().signal, undefined, { cwd }));
1485
- return { content: out.content };
1433
+ const out = (await tool.execute("mcp", args, new AbortController().signal, undefined, { cwd, resultMaxItems: Number.POSITIVE_INFINITY }));
1434
+ return out;
1486
1435
  }
1487
- if (name === "pilens_lsp_navigation" || name === "pilens_lsp_diagnostics") {
1436
+ if (name === "pilens_lsp_navigation") {
1488
1437
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
1489
1438
  await ensureReady(cwd);
1490
- const tool = name === "pilens_lsp_navigation" ? lspNavigationTool : lspDiagnosticsTool;
1439
+ const tool = lspNavigationTool;
1491
1440
  const out = (await tool.execute("mcp", args, new AbortController().signal, undefined, { cwd }));
1492
- return { content: out.content };
1441
+ return out;
1493
1442
  }
1494
1443
  return { ...toolText(`Unknown tool: ${name}`), isError: true };
1495
1444
  }
@@ -1520,7 +1469,7 @@ async function callTool(name, args) {
1520
1469
  // pilens_session_start, pilens_turn_end — mutate warm LSP/graph state;
1521
1470
  // must run in-process, can't be forked fresh.
1522
1471
  // pilens_ast_grep_search, pilens_ast_grep_replace,
1523
- // pilens_lsp_navigation, pilens_lsp_diagnostics — depend on the warm LSP
1472
+ // pilens_lsp_navigation, pilens_diagnostics — depend on the warm LSP
1524
1473
  // fleet / ast-grep client instances; no fresh-fork machinery exists for
1525
1474
  // them today (only pilens_analyze's worker.ts loads a fresh dispatch
1526
1475
  // graph) and the LSP fleet specifically CANNOT be recreated cheaply per
@@ -1549,7 +1498,6 @@ const WARN_ONLY_STALE_TOOLS = new Set([
1549
1498
  "pilens_ast_grep_search",
1550
1499
  "pilens_ast_grep_replace",
1551
1500
  "pilens_lsp_navigation",
1552
- "pilens_lsp_diagnostics",
1553
1501
  "pilens_read_symbol",
1554
1502
  "pilens_read_enclosing",
1555
1503
  "pilens_effective_config",
@@ -1582,6 +1530,7 @@ async function handleRequest(request) {
1582
1530
  const isNotification = id === undefined;
1583
1531
  switch (method) {
1584
1532
  case "initialize": {
1533
+ startSituationalToolTelemetrySession("mcp");
1585
1534
  const requested = params?.protocolVersion;
1586
1535
  sendResult(id ?? null, {
1587
1536
  protocolVersion: typeof requested === "string" ? requested : FALLBACK_PROTOCOL_VERSION,
@@ -1599,7 +1548,7 @@ async function handleRequest(request) {
1599
1548
  sendResult(id ?? null, {});
1600
1549
  return;
1601
1550
  case "tools/list":
1602
- sendResult(id ?? null, { tools: TOOLS });
1551
+ sendResult(id ?? null, { tools: enabledToolsForCwd(DEFAULT_CWD) });
1603
1552
  return;
1604
1553
  case "tools/call": {
1605
1554
  const name = params?.name;
@@ -1610,6 +1559,21 @@ async function handleRequest(request) {
1610
1559
  sendError(id ?? null, -32602, "tools/call requires a string 'name'");
1611
1560
  return;
1612
1561
  }
1562
+ const enabledName = name === "pilens_lsp_diagnostics" ? "pilens_diagnostics" : name;
1563
+ if (name !== "pilens_ast_grep_dump" &&
1564
+ name !== "pilens_rebuild" &&
1565
+ !enabledToolsForCwd(typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD).some((tool) => tool.name === enabledName)) {
1566
+ sendResult(id ?? null, stripResultDetails(finalizeToolResult({
1567
+ ...toolText(`Unknown or disabled tool: ${name}`),
1568
+ isError: true,
1569
+ })));
1570
+ return;
1571
+ }
1572
+ const entry = toolRegistryEntryForMcp(name);
1573
+ if (entry && "situational" in entry && entry.situational) {
1574
+ startSituationalToolTelemetrySession("mcp");
1575
+ observeSituationalToolCall(entry.name);
1576
+ }
1613
1577
  // #544 self-heal: if auto-session was supposed to fire on `initialize`
1614
1578
  // (PI_LENS_MCP_AUTO_SESSION=1) but never completed successfully — never
1615
1579
  // attempted, still in flight, or threw — nudge it here too. Cheap no-op
@@ -1623,20 +1587,29 @@ async function handleRequest(request) {
1623
1587
  // see the forcedFresh branch inside callTool. Every other tool that
1624
1588
  // depends on warm-only process state gets an honest-degrade warning
1625
1589
  // instead, so the warm boundary never silently serves old code.
1590
+ // The warning precedes the #2800 item 7 gate, so it is part of the
1591
+ // payload the byte bound protects (kept in the retained tail) and
1592
+ // its bytes land in the footer's delivered figure.
1626
1593
  if (WARN_ONLY_STALE_TOOLS.has(name) &&
1627
1594
  !result.isError &&
1628
1595
  isWarmBuildStale()) {
1629
1596
  result = withStaleWarning(result);
1630
1597
  }
1631
- sendResult(id ?? null, result);
1598
+ // #2800 item 7: the payload bound runs first with the footer's own
1599
+ // size reserved, then the footer is stamped with the delivered
1600
+ // payload's byte count and the bound's truncated flag.
1601
+ const delivery = finalizeToolResultWithDelivery(result);
1602
+ // The gate consumed `details` for the footer's diag lines above;
1603
+ // strip it so the wire carries only the bounded text (#2852 N1).
1604
+ sendResult(id ?? null, stripResultDetails(delivery.result));
1632
1605
  }
1633
1606
  catch (err) {
1634
1607
  // Surface as a tool error (isError), not a transport error, so the
1635
1608
  // agent sees the message instead of a dead request.
1636
- sendResult(id ?? null, {
1609
+ sendResult(id ?? null, stripResultDetails(finalizeToolResult({
1637
1610
  ...toolText(`pi-lens tool '${name}' failed: ${err.message}`),
1638
1611
  isError: true,
1639
- });
1612
+ })));
1640
1613
  }
1641
1614
  return;
1642
1615
  }
@@ -1669,6 +1642,9 @@ process.stdin.on("data", (chunk) => {
1669
1642
  newlineIndex = buffer.indexOf("\n");
1670
1643
  }
1671
1644
  });
1672
- process.stdin.on("end", () => process.exit(0));
1645
+ process.stdin.on("end", () => {
1646
+ endSituationalToolTelemetry();
1647
+ void flushExtensionLog().finally(() => process.exit(0));
1648
+ });
1673
1649
  startIpcServer();
1674
1650
  console.error(`[pi-lens-mcp] ready (cwd=${DEFAULT_CWD})`);
@@ -21,24 +21,27 @@ import { Type } from "../clients/deps/typebox.js";
21
21
  export function createActivateToolsTool(pi, lazyTools, options = {}) {
22
22
  const lazyNames = lazyTools.map((t) => t.name);
23
23
  const lazyNameSet = new Set(lazyNames);
24
- const catalog = lazyTools.map((t) => `${t.name} — ${t.summary}`).join("\n");
25
24
  return {
26
25
  name: "pi_lens_activate_tools",
27
26
  label: "Activate pi-lens Tools",
28
- description: "Activate one or more situational pi-lens tools that stay registered but inactive by default, so the default tool list stays lean. " +
29
- "Call this ONCE with the tools you need before using them — they become callable starting the NEXT turn. " +
30
- `Available:\n${catalog}`,
31
- promptSnippet: "Activate situational ast-grep / lsp_navigation tools before using them",
27
+ description: 'Activate registered situational tools for the next turn. Example: `{tools: ["lsp_navigation"]}`.',
28
+ promptSnippet: "Activate a situational tool",
32
29
  parameters: Type.Object({
33
30
  tools: Type.Array(Type.String({ enum: lazyNames }), {
34
31
  minItems: 1,
35
- description: "Names of situational tools to activate (see this tool's description for the catalog).",
32
+ description: "Names of situational tools to activate, e.g. `lsp_navigation`.",
36
33
  }),
37
34
  }),
38
35
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
39
36
  const requested = Array.isArray(params.tools)
40
37
  ? params.tools.filter((t) => typeof t === "string" && lazyNameSet.has(t))
41
38
  : [];
39
+ if (Array.isArray(params.tools)) {
40
+ for (const name of params.tools) {
41
+ if (typeof name === "string" && !lazyNameSet.has(name))
42
+ options.onRejected?.(name);
43
+ }
44
+ }
42
45
  if (requested.length === 0) {
43
46
  return {
44
47
  content: [
@@ -56,7 +59,7 @@ export function createActivateToolsTool(pi, lazyTools, options = {}) {
56
59
  // Remember every requested tool, not just the newly-added ones: a
57
60
  // tool that is already active still has to survive the next
58
61
  // fork/reload/resume restore.
59
- options.onActivated?.(requested);
62
+ options.onActivated?.(requested, ctx);
60
63
  const active = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
61
64
  const activeSet = new Set(active);
62
65
  const added = requested.filter((name) => !activeSet.has(name));
@@ -29,19 +29,8 @@ export function createAstGrepOutlineTool(astGrepClient) {
29
29
  return {
30
30
  name: "ast_grep_outline",
31
31
  label: "AST-Grep Outline",
32
- description: "Syntax-only code structure (symbols, imports, exports, and members) via " +
33
- "`ast-grep outline`. Fast, local, no index/LSP/cross-file semantics — useful " +
34
- "for languages or structures where pi-lens's own extractor is weak, or for a " +
35
- "raw second opinion.\n\n" +
36
- "Prefer module_report for pi-lens-aware navigation (who-uses-this, " +
37
- "complexity/fanout, recommendedReads, blast radius, callback handles). Use " +
38
- "ast_grep_outline when you want the syntax tree's own view of a file or a " +
39
- "whole directory.\n\n" +
40
- "Returns JSON: per file, `items[]` with name/symbolType/signature/range, " +
41
- "`isExported`/`isImport`, nested `members[]` (with `isPublic`), and ready " +
42
- "`read` args on every entry. NOTE: structure only — an outline is NOT a read " +
43
- "of a symbol's body (use read_symbol/read_enclosing for that).",
44
- promptSnippet: "Syntax-only code outline via ast-grep (no index/LSP); module_report is the richer default",
32
+ description: "Return syntax-only symbols, imports, exports, and members with ast-grep. An outline shows structure, not a symbol body, and does not satisfy read-before-edit; use read_symbol or read_enclosing for body coverage. Example: outline `src/` before choosing a symbol to read.",
33
+ promptSnippet: "Inspect syntax without index or LSP",
45
34
  renderResult: compactRenderResult(({ details, isError, text }) => {
46
35
  if (isError) {
47
36
  return `ast_grep_outline — ${text.split("\n")[0] ?? "error"}`;