@herbertgao/pi-extensions 2026.9.2 → 2026.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +3 -2
  2. package/THIRD_PARTY_NOTICES.md +24 -0
  3. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  4. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  5. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  6. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  7. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  8. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  9. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  10. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  11. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  12. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  13. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  14. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  15. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  16. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  17. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  18. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  19. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  20. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  38. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
  39. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  40. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  41. package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
  42. package/node_modules/pi-lens/CHANGELOG.md +176 -0
  43. package/node_modules/pi-lens/README.md +13 -8
  44. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  45. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  46. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
  47. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  48. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
  49. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
  50. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  51. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  52. package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
  53. package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
  54. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  55. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  56. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  57. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
  58. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  59. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  60. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  61. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  62. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
  63. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  65. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  66. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  67. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  110. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  111. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  112. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  113. package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
  114. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  115. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  116. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  117. package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
  118. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  119. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  120. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
  121. package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
  122. package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
  123. package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
  124. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  125. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
  126. package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
  127. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  128. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  129. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  130. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  131. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  132. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
  134. package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
  135. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  136. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  137. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
  139. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  140. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  141. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
  142. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  143. package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
  144. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  145. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  146. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  148. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  149. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  150. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  151. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  152. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  153. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  154. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  155. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  156. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  157. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  158. package/node_modules/pi-lens/dist/index.js +21969 -20005
  159. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  160. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  161. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  162. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  163. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  164. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  165. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  166. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  167. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
  168. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
  169. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  170. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  171. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  172. package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
  173. package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
  174. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  175. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  176. package/node_modules/pi-lens/docs/configuration.md +9 -0
  177. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  178. package/node_modules/pi-lens/docs/features.md +5 -5
  179. package/node_modules/pi-lens/docs/globalconfig.md +11 -1
  180. package/node_modules/pi-lens/docs/mcp.md +13 -1
  181. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  182. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  183. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  184. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
  185. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
  186. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  187. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  188. package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
  189. package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
  190. package/node_modules/pi-lens/docs/settings.md +37 -2
  191. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  192. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  193. package/node_modules/pi-lens/docs/usage.md +13 -3
  194. package/node_modules/pi-lens/package.json +10 -1
  195. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  196. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  197. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  198. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  199. package/node_modules/pi-web-access/README.md +33 -22
  200. package/node_modules/pi-web-access/abortable.ts +17 -0
  201. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  202. package/node_modules/pi-web-access/credential-source.ts +1 -0
  203. package/node_modules/pi-web-access/curator-page.ts +20 -2
  204. package/node_modules/pi-web-access/curator-run.ts +44 -0
  205. package/node_modules/pi-web-access/curator-server.ts +3 -1
  206. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  207. package/node_modules/pi-web-access/extract.ts +57 -12
  208. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  209. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  210. package/node_modules/pi-web-access/index.ts +25 -18
  211. package/node_modules/pi-web-access/openai-search.ts +35 -13
  212. package/node_modules/pi-web-access/package.json +2 -2
  213. package/node_modules/pi-web-access/page-query.ts +5 -2
  214. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  215. package/node_modules/pi-web-access/serpapi.ts +220 -0
  216. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  217. package/node_modules/pi-web-access/storage.ts +10 -0
  218. package/node_modules/pi-web-access/summary-review.ts +34 -21
  219. package/node_modules/pi-web-access/utils.ts +6 -4
  220. package/package.json +7 -4
  221. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-btw",
3
- "version": "0.58.0",
3
+ "version": "0.58.1",
4
4
  "description": "Pi extension that adds a /btw side-question command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -146,7 +146,12 @@ export async function resolveBtwModel({
146
146
  : `falling back to ${fallback}`;
147
147
  try {
148
148
  const auth = await modelRegistry.getApiKeyAndHeaders(configuredModel);
149
- if (auth.ok && hasRequestAuth(auth)) return { model: configuredModel, auth };
149
+ if (auth.ok && hasRequestAuth(auth)) {
150
+ return {
151
+ model: auth.baseUrl ? { ...configuredModel, baseUrl: auth.baseUrl } : configuredModel,
152
+ auth,
153
+ };
154
+ }
150
155
  const reason = auth.ok ? "has no request credentials" : auth.error;
151
156
  reportWarning(
152
157
  `pi-btw model ${settings.model} is unavailable (${reason}); ${fallbackAction}.`,
@@ -163,7 +168,13 @@ export async function resolveBtwModel({
163
168
  if (!currentModel) return undefined;
164
169
  try {
165
170
  const auth = await modelRegistry.getApiKeyAndHeaders(currentModel);
166
- if (auth.ok && hasRequestAuth(auth)) return { model: currentModel, auth };
171
+ if (auth.ok && hasRequestAuth(auth)) {
172
+ // Direct provider streams bypass Pi's request preparation, including OAuth routing.
173
+ return {
174
+ model: auth.baseUrl ? { ...currentModel, baseUrl: auth.baseUrl } : currentModel,
175
+ auth,
176
+ };
177
+ }
167
178
  } catch {
168
179
  // The caller reports the final lack of an available model.
169
180
  }
@@ -16,6 +16,182 @@ All notable changes to pi-lens will be documented in this file.
16
16
 
17
17
  ### Security
18
18
 
19
+ ## [4.1.6] - 2026-09-10
20
+
21
+ ### Added
22
+
23
+ - **Add an advisory incremental Stryker lane for changed scripts (refs #1844)** — `npm run mutation:diff` and the pull-request mutation lane use Stryker's command runner over changed `scripts/**/*.mjs` files and their related tests, report uncovered files, retain incremental results under ignored paths, and publish the mutation report for review.
24
+
25
+ - **CI failures now distinguish infrastructure outages from real test failures and arm one rerun** (refs #2103, #2784) — the classifier recognizes exit-137 kills, CodeQL/SARIF outages, codeload throttling, and npm CI timeouts, while preserving assertion and TypeScript failures as real. It updates one PR comment, applies the manifest-managed CI label, and lets the verdict report an armed infrastructure rerun as pending; **Terminal CI rerun finalization now covers master push and repository-dispatch runs** (refs #2103, #2784) — the workflow finalizes second-attempt classifications for every supported event and records a bounded step summary when no associated PR exists.
26
+
27
+ - Add an AST governance sweep for `vi.mock` factories that omit production exports. Refs #2281.
28
+
29
+ - **Add an advisory Windows Vitest lane (refs #2536)** — Run the path-shape and configuration test subset on `windows-latest`.
30
+
31
+ - **Add the tool-smoke install lane to the release-QA baseline (refs #2663, #2784)** — a `tool-smoke-install` row installs every npm/pip installer-registry entry through the real smoke harness, so a dead registry entry is a red do-not-ship row, a registry-unreachable lane is INCONCLUSIVE, and neither reads as green.
32
+
33
+ - **Add a TypeScript strictness spike with scratch configs, per-directory counts, a two-sided ratchet, and an advisory report lane (refs #2697)** — the migration remains staged by directory instead of enabling either flag globally.
34
+
35
+ - **Add a cwd-sensitive yamllint tool-smoke row (refs #2697, refs #2691)** — the real runner proves yamllint uses the dispatch cwd instead of the host process cwd for config discovery.
36
+
37
+ - **Add tool-smoke cwd rows for nested discovery (refs #2777)** — the runner, formatter, and LSP smoke shapes assert their resolved cwd and reason log, while missing binaries remain visible skips.
38
+
39
+ - **Add one command for every mechanical PR gate (refs #2790, #2784)** — `npm run preflight` runs the ordered local build, lint, formatting, changelog, lockfile, governance, and PR-document checks.
40
+
41
+ - Stamp one session-scoped turn identifier on observability sink rows (refs #2815).
42
+
43
+ - **Add a real pi RPC harness for load, tool-set restore, and read-guard scenarios (refs #2825)** — the serialized `real-harness` lane drives the built extension with a deterministic scripted provider and hermetic sinks.
44
+
45
+ - **Make Knip a hard local preflight gate and cover every admitted test lane** (refs #2837) — `npm run preflight` runs Knip, while the Knip entry census covers real-harness, Windows, wall-clock, and workflow-invoked script populations with documented same-file export admissions.
46
+
47
+ - **Gate nightly LSP smoke rows on primary diagnostics** (refs #2780) — the tool-smoke workflow now drives opted-in seeded fixtures through the real `lsp_diagnostics` handler, reports unavailable toolchains correctly, and tracks gate failures.
48
+
49
+ - **`complexity (advisory)` CI job dogfoods pi-lens's own complexity client over `clients/`, `tools/` and `mcp/` (refs #2697)** — `npm run complexity` writes a Markdown report (top functions by cyclomatic/cognitive complexity, files over 1,000 lines, split candidates at the dispatch threshold of 15) to the step summary and an artifact; `ComplexityClient` exposes the per-function metrics it already computes, uses the dispatch cyclomatic metric for JS/TS, and fails when analysis produces no files or throws.
50
+
51
+ - **One session-end line names the tools never activated or called (refs #2800)** — a bounded debug record per session on both surfaces, keyed by the tool registry, so roster decisions can read which tools carried no weight in a session; the monitor readout gains the line. MCP owns the row for the connection and emits it at `pilens_session_end` or transport close, whichever comes first; repeated `pilens_session_start` calls refresh without clearing observations. Pi records the row for fresh sessions only: a reload, resume, or fork start suppresses the session's row entirely (conversation-owned accounting tracked in #2858).
52
+
53
+ - **Per-call tool-result observability**: the `usage tokens=<n> elapsed-ms=<n>` contract footer gains `bytes=<delivered payload bytes> truncated=<true|false>` on both host surfaces, the payload byte bound runs before the footer is stamped with the footer's own maximum size reserved so the delivered text (footer included) never exceeds 40 KiB for the single-text-block results production emits (the bound is per text block, inherited from #2852), and the per-turn `cache_usage` latency row aggregates `toolResultBytes` and `toolResultsTruncated` over the turn's tool calls (refs #2800)
54
+
55
+ - **Guard workflow shell portability (refs #2625, #2784)** — Reject bash 4-only constructs in steps that can run on macOS runners, including matrix and reusable-workflow input expansion and executable command substitutions nested inside double quotes.
56
+
57
+ - **Record injected bytes and repeated findings per turn (refs #2838)** — the existing cache attribution row records UTF-8 bytes by model-facing injection source and counts repeated finding identities with bounded per-session state.
58
+
59
+ - **Check lockfile completeness under CI's npm (refs #2803, #2784)** — preflight and `check:lockfile --complete` reject optional dependency drift before merge; **Pin the clean production install to npm@11.18.0 (refs #2803, #2784)** — use
60
+ the repository's exact npm pin so a different npm version cannot rewrite the
61
+ committed lockfile.
62
+
63
+ - **`tools.<name>.enabled` proven through a real pi RPC session (refs #2800)** — real-harness scenarios show a disabled tool absent from the tool list pi reports, the loader and MCP lifecycle tools refusing to be disabled with the config diagnostic, and `--no-tool` winning over config; the roster bytes pi receives per tool are measured on the wire.
64
+
65
+ ### Changed
66
+
67
+ - Keep admission registries sorted and enforce declaration order with one shared sweep helper (refs #2671, #2784).
68
+
69
+ - **Skip the PR title, body and close-keyword checks for dependabot PRs (refs #2714)** — the `pr-title-lint` and `pr-body-lint` jobs in lint.yml and the lint job in close-keywords.yml now carry a `dependabot[bot]` skip condition. A bump can never carry an issue ref or the PR-body template, so these checks no longer go red on every bump and no merge-train pass has to ignore them by hand. Lint, Unit tests, the install tests and the advisory lanes still run on bumps.
70
+
71
+ - **Trim the three largest tool parameter schemas and pin both host rosters (refs #2800).**
72
+
73
+ - **Adaptively demote budget-hitting auxiliaries, re-promote fast late answers, and deliver findings through the deferred runner path (refs #2810).**
74
+
75
+ - **Tool probes and hand-derived package roots resolve through shared seams (refs #2894)** — a `probeToolAsync` seam owns the "a presence/version probe never gets a `cwd`" contract for 23 spawn sites that each decided it locally, `rust-clippy` takes its package root from `resolveRunnerCwd` (with `Cargo.toml` registered as its runner marker) instead of an uncapped `findNearestContaining` walk, and `ruff-client`'s autofix resolves its config root through `resolveToolCwd` so a file in a nested package gets that package's `pyproject.toml` rather than the workspace one.
76
+
77
+ - **Preserve per-row auxiliary publication evidence semantics (refs #2892)** — demoted waits accept an advanced per-path stamp alongside a content binding, while sibling and aggregate rows remain binding-only.
78
+
79
+ - **`ast_grep_dump` folded into `ast_grep_search` (refs #2800)** — AST inspection is `ast_grep_search` with `dump=true`, with the same result contract; the retired name answers for one session through a bounded compatibility response on both surfaces (one `ast-grep-dump-compatibility` record per session) and is absent from the tool registry and every tool listing.
80
+
81
+ - **Audit and update user-facing and contributor docs against master for the v4.1.6 cut.** `docs/agent-tools.md` now describes `lens_diagnostics`' `source`/`scope`/`severity`-threshold contract, the one-release MCP compatibility redirect, the `lsp_navigation` 19-operation roster with a link to its SKILL.md parameter reference, and the result-delivery contract (`finalizeToolResult`, 40 KiB bound, `bytes=`/`truncated=` usage footer). `docs/settings.md`, `docs/globalconfig.md`, and `docs/usage.md` now agree on all registry flags, including the seven analyzer toggles. `docs/features.md`'s formatter list is corrected to the current 34-entry autoformat registry (drops `fish_indent`, which is a check-only dispatch runner rather than an entry in that registry, and adds `ktfmt` and `terragrunt-hcl`). `docs/environment-variables.md` drops `PI_LENS_AUTO_INSTALL`, which no longer exists in code. (refs #2800)
82
+
83
+ - **Fold `lsp_diagnostics` into `lens_diagnostics` with source and scope modifiers; retain the MCP compatibility shim for one release.** (refs #2800)
84
+ Deprecated: `pilens_lsp_diagnostics` remains accepted by MCP for one release and maps to `source=lsp`.
85
+
86
+ - **Make the oxfmt format check a CI gate (refs #2790, #2784)** — the workflow job now uses the gating name `oxfmt format check`, matching the hard `fmt:check` preflight gate and keeping formatting drift out of master.
87
+
88
+ - **Fold `allowStamp` out of auxiliary coverage (closes #2914)** — The stamp-union predicate keeps one behaviour; the sibling and aggregate rows call the binding predicate directly.
89
+
90
+ - **One result renderer for pi tool results and the MCP mirror (refs #2800)** — result status, diagnostic severity and usage lines come from one shared renderer on both surfaces, pinned by a both-surfaces governance test that drives real inputs for every paired registry tool and explicitly covers pi-only rows.
91
+ The renderer runs after each tool has set `isError`, then applies stale warnings, and bounds the final MCP payload to 40 KiB.
92
+
93
+ - **Dropped a no-op string replacement in the tsgolint preflight test (refs #2709)** — CodeQL alert 49 (`js/identity-replacement`): the helper replaced `/package.json` with itself; the key lookup now only strips the `/tmp/` prefix.
94
+
95
+ - **Pin the tool-roster description budget and trim paid metadata (refs #2800)** — pi and MCP tool registration now use concise contract-plus-example descriptions, with a two-sided baseline covering descriptions and schemas on both surfaces; the model-facing contract sentences (read coverage, diagnostic suppression ordering, configuration redaction, cold-cache guidance) stay in the paid description and are pinned per surface so a later trim cannot drop them.
96
+
97
+ - **Add per-tool enablement (refs #2800)** — `tools.<name>.enabled` and repeatable `--no-tool=<name>` prevent disabled lens tools from registering on the pi and MCP surfaces. One registry now covers every model-facing tool, keeps the activation and MCP lifecycle tools non-disableable, and emits stable diagnostic code `PILENS_CFG_0009` for unknown or non-disableable keys. The resolved state appears in `effective_config`.
98
+
99
+ - **`sql-injection` (TypeScript tree-sitter rule) is a warning, not a blocking error.** The query keys on the callee name only (`query|execute|exec|run`), so any template literal passed to a same-named non-SQL function fired a blocking finding; it stays as a warning until the rule gains a receiver/type signal.
100
+
101
+ ### Fixed
102
+
103
+ - **Windows lane enumerated zero files because six scripts detected "am I the entry module" by string-comparing `import.meta.url` with `file://${process.argv[1]}`** (refs #2536) — on Windows the two spellings never match, so the main block never ran; every gate now compares against `pathToFileURL(process.argv[1]).href`, the win32 gate scanner excludes fixtures by the platform separator, and a governance test rejects the hand-built comparison.
104
+
105
+ - **Bound turn-end Gitleaks classification (refs #2575)** — timed-out classification keeps raw findings fail-open. Thanks @stekman08.
106
+
107
+ - **Prune merged clean worktrees with a per-run cap; never a tree with untracked files (refs #2631, #2538, #2784)** — the agent-worktree hygiene sweep now also removes any registered worktree whose branch tip is an ancestor of `origin/master` and whose checkout is clean, clears `.claude/worktrees/agent-*` leftovers that hold nothing but git's own `.git` gitlink, and caps removals per run with `--max` (default 10). An untracked file counts as a deliverable: only an empty `git status --porcelain` is clean, and the keep record names the first entry it protected.
108
+
109
+ - **The format smoke lane installs the managed formatters it selects (refs #2767)** —
110
+ The `--install` path prefetches configured formatters through `ensureTool`
111
+ and the formatter pipeline resolves those managed binaries, with venv/local
112
+ → PATH → managed precedence; every managed resolver returns the typed
113
+ unavailable outcome instead of falling through to a bare command; GitHub and
114
+ archive installs record a typed unavailable outcome when no asset exists for
115
+ the host platform and architecture; the formatter-absence tests pin the
116
+ installer's independent PATH lookup so npm's `node_modules/.bin` prefix on
117
+ CI cannot resolve a dev-dependency binary.
118
+
119
+ - **Unify tool cwd and root resolution (refs #2777)** — Runners, formatters, and LSP configuration walks now use one bounded, logged resolution seam.
120
+
121
+ - **Fix runner no-config advisories logging once per tool and root at debug level (refs #2811)** — Dispatch now bounds constant runner advisories to one record per tool and resolved root per session.
122
+
123
+ - **Key vi.mock export admissions by factory content and ratchet missing counts across line moves.** (refs #2816)
124
+
125
+ - Make vi.mock export sweep warnings visible in default Vitest output and keep baseline regeneration successful when admissions change.
126
+
127
+ - **Harden real pi harness waits and isolation (refs #2825)** — consume cursor-based events, honor scenario scripts, pin lane membership, and sweep shared scratch directories.
128
+
129
+ - **Verify explicit existing records in PR-body observability lint (refs #2855)** — require the named kind in the named source file and verify its cited line is within 20 lines of the literal.
130
+
131
+ - **Report peak RSS for exit-137 classifications (refs #2856)** — retain master’s real-failure precedence and include the three heaviest `[mem-file]` peaks in kill details.
132
+
133
+ - **Attribute pi situational-tool observations by session file and emit on replacement.** A shutdown with `targetSessionFile` emits the conversation that is ending; reload re-runs the extension factory but keeps the same file and row. (refs #2858)
134
+
135
+ - **Fail session-start tests when the awaited handler crashes or exceeds its test budget, instead of passing vacuously.** (refs #2859)
136
+
137
+ - **Bound joined tool-result text with one conservative footer reserve, write one complete-result log, and convert rejecting pi tool executions into contract errors.** (refs #2862)
138
+
139
+ - **Derive lens diagnostics limits and scope metadata from the LSP probe, preserve single-file names, and honor session severity tiers.** (refs #2864)
140
+
141
+ - **Test-runner selection is per file kind and per module root, and a go `[setup failed]` result is advisory (refs #2870)** — a repo carrying both a `go.mod` and a Gradle build no longer hands every `.java` file to `go test` (the `RUNNERS` declaration order used to decide), a `README.md` or `.yaml` under a test directory no longer becomes its own test target, a nested Gradle or Go module anchors at its own build while a single-language repo keeps the runner it selected before, and go's `FAIL <pkg> [setup failed]` verdict reports as "could not run tests" instead of a fabricated blocking failure whose classification depended on whether go printed the word "error".
142
+
143
+ - **The test runner spawns its child through `resolveToolCwd`, so a nested module's tests run from that module (refs #2871)** — a `.go` file in a nested `go.mod` module now runs `go test ./internal/…` from the module that owns the package instead of a root-relative path the root module cannot resolve, with the shared seam supplying marker discovery, the `.git`/dispatch-root fallbacks, the `$HOME` ceiling and one `tool-cwd` log line per resolution key; a whole-project build launches from the directory that owns its wrapper (`gradle`, `maven`), a child is never anchored on a config file the detector itself rejected as evidence (`pytest`'s `pyproject.toml`, `phpunit`'s `composer.json`), and the failed-target ledger stays keyed on the dispatch root.
144
+
145
+ - **Make the runner spawn-cwd sweep verify resolver origin across the child-spawn population (refs #2872).** Bindings resolve by the grammar's own scope rather than a list of node kinds, any later rebinding leaves the value unproven, promoted resolvers must be a single seam return, and each admitted site is keyed by its cwd expression and the locals that expression reads, so a laundered or edited cwd cannot inherit an admission.
146
+
147
+ - **Keep diagnostics visible for tolerant MCP severity inputs and apply one threshold across session and LSP sources, including error-only files at the warning threshold.** (refs #2864)
148
+
149
+ - **Surface a crashed pi hook handler instead of swallowing it silently (refs #2884)** — the nine `index.ts` catch sites that absorb a handler crash (`session_start`, `session_before_fork`, `observed_settled_sweep`, `observed_ledger_refresh`, `agent_end`, `turn_end`, the `agent_settled` deferred-mutation drain, `quiet_window`, and `message_end`) now route through one shared `surfaceHandlerCrash` guard: production keeps the swallow but leaves one bounded `hook-handler-crash` degradation row per handler per session, and under the test runner the crash is rethrown so the awaiting test fails instead of resolving as if the handler had completed. The fire-and-forget `quiet_window` catch records the row without rethrowing, so a crash cannot terminate the pi host with an unhandled rejection.
150
+
151
+ - **Resolve aliased child-process bindings (closes #2888)** — Extend the spawn-cwd test sweep to resolve aliased and namespaced child-process bindings.
152
+
153
+ - **Make pi RPC `session_start` idempotent per reason and session ID, with a session-file fallback (refs #2890)** — repeated replacement events no longer repeat tool restoration or session-state resets, while a different session identity starts a new mutation pass.
154
+
155
+ - **Deduplicate repeated RPC session starts (refs #2890)** — use the stable session ID, fall back to the session file, and re-run the restore when the host's active tool posture drifted.
156
+
157
+ - **`lens_diagnostics mode=full` retires a stale finding only when the result that replaces it is genuinely authoritative: an LSP write the ordering guard accepted, or a project runner that actually analysed the root this call. A runner that reported success without running — no project root, no source files, a scan that crashed before writing its report — is reported cold instead of silently deleting the finding, and a retained result that could not be reconciled is labelled stale rather than served as a current blocker (refs #2154).**
158
+
159
+ - **Require Windows-only test gates to name and enter the Windows Vitest lane (refs #2536, #2784)** — one shared population script drives the Windows job and governance sweep, with a job summary recording the executed gate count.
160
+
161
+ - **Require runtime Observability sections to name a diff record (refs #2543, #2784)** — the PR-body checker matches observability record literals against added runtime lines and rejects unsupported no-failure claims.
162
+
163
+ - **Make the merge-train warden label `red-ci` for any failing non-advisory check on the exact pull-request head (refs #2616, #2784)**.
164
+
165
+ - **CI failure classifier: a test timeout beside registry network failures is infra, not real (refs #2839)** — A Unit-tests job whose only test-level failure is a vitest `Test timed out in Nms` line (no `AssertionError`, no compiler diagnostic) now classifies `infra-net` when the log also carries registry-unreachable evidence (`npm error code ECONNRESET` through every `npm-retry` attempt, codeload/SARIF 429/503, `npm ci` ETIMEDOUT, `npm-retry: attempt N network error`), so the once-per-SHA automatic rerun arms instead of the job sitting `ci:real`; PRs #2834 and #2835 both hit this and were re-run by hand. An `AssertionError` or a TypeScript `error TS` line beside the same network noise still classifies `real`, and a lone timeout with no network evidence is unchanged.
166
+
167
+ - **`detectFileRole` recognises Go `_test.go` and the other table conventions (closes #2880)** — Editing `pkg/foo_test.go` produced no test target because the shared role classifier only knew `.test.`/`.spec.`/prefix/dir patterns. It now also matches the `_test.`/`_spec.` suffix infixes and the case-sensitive `*Test(s).<ext>` CamelCase suffix for Java/Kotlin/C#/F#/PHP, informed by the test-runner client's `SOURCE_TO_TEST_PATTERNS` + `RUNNERS` table; hand-written, see #2928 for the single-classifier fold, so an edited Go test file runs its own package tests.
168
+
169
+ - **Route helm-lint through the runner cwd resolver (closes #2882)** — Helm lint now runs with the cwd selected by the shared dispatch runner seam.
170
+
171
+ - **Preserve situational tool activations across pi rebuilds (refs #2889)** — Remember activations by session file so reload, resume, and fork restore the conversation's tool posture.
172
+
173
+ - Run the installed-registry release-QA smoke with a scratch-home pip policy and keep the measured registry deterministic.
174
+
175
+ - **Diagnostic verification guidance (closes #2792).** Agent and MCP descriptions distinguish cache-only reporting from active scans. Use `mode=full` with `paths` when changed files have absent or stale cached findings.
176
+
177
+ - **Partition LSP diagnostics by server provenance (refs #2776)** — custom language servers now render their diagnostics as primary findings even when their server-authored `source` value differs from the registered server id.
178
+
179
+ - **Re-sync LSP diagnostics after Git tree changes (refs #2817)** — open documents and bounded cached import dependencies are touched before scoped full diagnostics serve cached results; files without import-fact coverage receive a fresh requested-file touch; **Prove language-neutral LSP dependency resync through the real server path** (refs #2817); the language matrix derives its facts and fallback rows from the production import-facts predicate; the shared drift pass gives a Git-queued file one resync opportunity even when ordinary stat drift identifies the same file in that pass.
180
+
181
+ - **Bound MCP tool results through the shared pi result seam (refs #2799, #2800)** — oversized results retain head and tail context, report omitted characters, and preserve the complete payload in the session log; the roster budget rejects duplicate tool names; results above the documented 8 MiB input budget keep a bounded head, an explicit incomplete marker and the tail in the session log, with one `mcp-complete-result-budget-exceeded` degradation per session.
182
+
183
+ - **Flake-shape ratchet governs support-helper waits (refs #2563, #2784)** — the contention ratchet scans `.ts` and `.mts` support helpers, resolves aliased timers, and requires every support baseline row to carry a detector-specific reason, header, and serialized-lane proof through its importing tests; **Reduce flake-shape ratchet AST scanning cost (refs #2563, #2784)** — the whole-tree scan uses a comment/string-blanked textual pre-pass and parses each timer-bearing source file at most once per run.
184
+
185
+ - **Label-sync post-merge validation fails when the syncer deletes or loses any label (refs #2614)** — the `labels.yml` sync job now snapshots the live label set before `action-label-syncer` runs and re-validates after: any before-vs-after deletion, or any manifest label missing from the live set, fails the job through `::error::label sync deleted or missing labels:` with the names printed, instead of a silent success while prune deletes labels (the #2553 recurrence, twice for the priority labels).
186
+
187
+ - **Route built-in LSP roots through the tool-cwd seam and expose resolved cwd in `lens_diagnostics` (refs #2777)** — Built-in server marker tables now use the shared bounded resolver, and diagnostic rows identify the cwd used for their language server. Workspace-priority roots keep their complete marker tables through the seam (Go, JSON), and a server-computed root is the seam's input, never replaced (review rounds 2 and 4).
188
+
189
+ - **Run the release-QA tool smoke from the export root (closes #2893)** — the install lane now executes the smoke harness from the archived source tree while loading the installer registry from the installed package, and measures pip entries under its scratch-pinned package policy.
190
+
191
+ - **Docs membership guard asserts list members, not counts (closes #2919).** `tests/docs/features-counts.test.ts` now compares the `docs/features.md` formatter list member-by-member against `ALL_FORMATTERS`, the LSP language list against non-auxiliary `LSP_SERVERS` ids, and the `docs/mcp.md` tool table against `TOOL_REGISTRY`, and reports missing and extra names, so reinstating a removed member or swapping in a stale name fails the suite.
192
+
193
+ - **Route tool configuration diagnostics through the shared note seam (refs #2800)** — unknown and non-disableable tool names use the bounded config notice path, and `tools.<name>.enabled` is documented across the config references.
194
+
19
195
  ## [4.1.5] - 2026-09-08
20
196
 
21
197
  ### Added
@@ -155,6 +155,14 @@ pi install git:github.com/apmantza/pi-lens
155
155
  Requires Node.js >=22.19.0 (matches the pi host's own floor — see `engines`
156
156
  in `package.json`).
157
157
 
158
+ Letting a coding agent do it: paste this into its prompt.
159
+
160
+ ```text
161
+ Install pi-lens (npm:pi-lens) into pi, then read AGENTS.md in
162
+ https://github.com/apmantza/pi-lens for how the extension works and how it
163
+ expects to be operated.
164
+ ```
165
+
158
166
  > **npm v12 users:** dependency lifecycle scripts (e.g. `@ast-grep/cli`'s
159
167
  > `postinstall`) now require explicit approval — if `npm install` warns about
160
168
  > unreviewed install scripts, review and allow them with
@@ -336,6 +344,11 @@ Thanks goes to these wonderful people:
336
344
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
337
345
  <td align="center" valign="top" width="14.28%"><a href="https://aeturnal.io/"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
338
346
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/floatGray"><img src="https://avatars.githubusercontent.com/u/44295302?v=4" width="100px;" alt=""/><br /><sub><b>floatGray</b></sub></a><br /><a href="#code-floatGray" title="Code">💻</a></td>
347
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/thomasfsteeples"><img src="https://avatars.githubusercontent.com/u/32720359?v=4" width="100px;" alt=""/><br /><sub><b>Thomas F Steeples</b></sub></a><br /><a href="#code-thomasfsteeples" title="Code">💻</a></td>
348
+ </tr>
349
+ <tr>
350
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/amasen02"><img src="https://avatars.githubusercontent.com/u/97525823?v=4" width="100px;" alt=""/><br /><sub><b>Ama Senevirathne</b></sub></a><br /><a href="#code-amasen02" title="Code">💻</a></td>
351
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/AcademyKUB8"><img src="https://avatars.githubusercontent.com/u/217174854?v=4" width="100px;" alt=""/><br /><sub><b>AcademyKUB8</b></sub></a><br /><a href="#code-AcademyKUB8" title="Code">💻</a></td>
339
352
  </tr>
340
353
  </tbody>
341
354
  </table>
@@ -344,11 +357,3 @@ Thanks goes to these wonderful people:
344
357
  <!-- prettier-ignore-end -->
345
358
 
346
359
  <!-- ALL-CONTRIBUTORS-LIST:END -->
347
-
348
- The following commit identities also appear in `git shortlog -sne HEAD` but are
349
- not represented in the generated table above: Anas Alsbei, Claude,
350
- Christopher Patti,
351
- dependabot[bot], Fabio-D, github-actions[bot], JSup, Kenny McCormick,
352
- Max Lupus, Moritz Hofmann, ricardo, and root.
353
-
354
- If you land a pull request or report an issue that gets fixed, we'll add you here.
@@ -26,7 +26,6 @@
26
26
  "./clients/format-events-publish.js",
27
27
  "./clients/format-service.js",
28
28
  "./clients/formatters-lazy.js",
29
- "./clients/git-guard.js",
30
29
  "./clients/host-ports.js",
31
30
  "./clients/installer/index.js",
32
31
  "./clients/instance-reaper.js",
@@ -75,13 +74,16 @@
75
74
  "./clients/session-start-observability.js",
76
75
  "./clients/session-state-store.js",
77
76
  "./clients/sessionstart-logger.js",
77
+ "./clients/situational-tool-telemetry.js",
78
78
  "./clients/skills-resolver.js",
79
79
  "./clients/smells-rollup.js",
80
80
  "./clients/startup-timing.js",
81
81
  "./clients/test-runner-delivery.js",
82
+ "./clients/tool-config.js",
82
83
  "./clients/tool-definition.js",
83
84
  "./clients/tool-render.js",
84
85
  "./clients/tool-set-policy.js",
86
+ "./clients/turn-context.js",
85
87
  "./clients/turn-summary-render.js",
86
88
  "./clients/turn-summary.js",
87
89
  "./clients/user-notify.js",
@@ -0,0 +1 @@
1
+ export {};
@@ -99,7 +99,7 @@ export function astGrepRemediationHint(kind) {
99
99
  case "json_parse_failed":
100
100
  return "Hint: ast-grep produced output that could not be parsed — retry with a simpler pattern; the installed CLI version may be incompatible.";
101
101
  default:
102
- return "Hint: verify the pattern is a single valid AST node for this `lang` (use ast_grep_dump to discover node kinds), or fall back to grep for plain-text search.";
102
+ return "Hint: verify the pattern is a single valid AST node for this `lang` (use ast_grep_search with dump=true to discover node kinds), or fall back to grep for plain-text search.";
103
103
  }
104
104
  }
105
105
  export function logAstGrepToolEvent(event) {
@@ -14,8 +14,10 @@ import { isFileKind } from "./file-kinds.js";
14
14
  import { getGlobalPiLensDir } from "./file-utils.js";
15
15
  import { findGlobalBinary } from "./package-manager.js";
16
16
  import { safeSpawnAsync } from "./safe-spawn.js";
17
+ import { probeToolAsync } from "./tool-probe.js";
17
18
  import { createSingleFlight } from "./single-flight.js";
18
19
  import { biomeConfigArgs } from "./tool-policy.js";
20
+ import { resolveToolCwd } from "./tool-cwd.js";
19
21
  import { resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
20
22
  import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
21
23
  // --- Client ---
@@ -169,7 +171,14 @@ export class BiomeClient {
169
171
  let result;
170
172
  let hostStallMs;
171
173
  try {
172
- result = await this.spawnBiomeAsync(["--version"], PROBE_TIMEOUT_MS);
174
+ // The presence probe asks whether biome answers at all, so it goes
175
+ // through the probe seam (no cwd) rather than through
176
+ // `spawnBiomeAsync`, whose optional `cwd` exists for the analysis
177
+ // spawns that DO resolve a project config (#2894).
178
+ const { cmd, args: prefix } = await this.getBiomeBinary();
179
+ result = await probeToolAsync(cmd, [...prefix, "--version"], {
180
+ timeout: PROBE_TIMEOUT_MS,
181
+ });
173
182
  }
174
183
  finally {
175
184
  hostStallMs = sampler.stop();
@@ -314,7 +323,9 @@ export class BiomeClient {
314
323
  }
315
324
  try {
316
325
  const before = await fs.promises.readFile(absolutePath, "utf-8");
317
- const configCwd = cwd ?? path.dirname(absolutePath);
326
+ const configCwd = resolveToolCwd("runner", "biome", absolutePath, {
327
+ ...(cwd !== undefined && { cwd }),
328
+ });
318
329
  // Shared config-args seam (#1247): the lint runner consumes the same
319
330
  // builder, so `lint --write` can never drift to biome's default
320
331
  // config when a user config or the package fallback exists.
@@ -50,6 +50,8 @@ import { logLatency } from "./latency-logger.js";
50
50
  * must not also be written by a raw `logLatency` call somewhere else.
51
51
  */
52
52
  export const BOUNDED_TELEMETRY_PHASES = [
53
+ /** #2890: a duplicate pi RPC session_start was admitted but suppressed. */
54
+ "session_start_duplicate_suppressed",
53
55
  /**
54
56
  * #2467: a demand for the analyzer bootstrap clients was not served and
55
57
  * the caller proceeded without them. Rising-edge per demand reason, with
@@ -176,9 +176,50 @@ function newAttributionState() {
176
176
  injectedCharsSinceLastUsage: 0,
177
177
  newTranscriptCharsSinceLastUsage: 0,
178
178
  attributionCharsCapped: false,
179
+ injectedBytes: emptyInjectedBytes(),
180
+ injectedFindingsRepeated: 0,
181
+ findingIdentities: new Set(),
182
+ findingIdentityCapRecorded: false,
183
+ turnEndAdvisoryBytes: 0,
184
+ toolResultBytes: 0,
185
+ toolResultsTruncated: 0,
179
186
  summary: newCacheUsageSummary(),
180
187
  };
181
188
  }
189
+ const MAX_FINDING_IDENTITIES = 2_000;
190
+ function emptyInjectedBytes() {
191
+ return {
192
+ sessionGuidance: 0,
193
+ turnFindings: 0,
194
+ testFindings: 0,
195
+ agentNudge: 0,
196
+ turnEndAdvisory: 0,
197
+ other: 0,
198
+ };
199
+ }
200
+ function sourceKey(source) {
201
+ return {
202
+ "session-guidance": "sessionGuidance",
203
+ "turn-findings": "turnFindings",
204
+ "test-findings": "testFindings",
205
+ "agent-nudge": "agentNudge",
206
+ }[source];
207
+ }
208
+ /** Best-effort, language-neutral identity extraction from rendered findings. */
209
+ function findingIdentities(text) {
210
+ const out = [];
211
+ for (const line of text.split(/\r?\n/)) {
212
+ const location = line.match(/(?:^|\s)([^\s:]+):(\d+)(?::\d+)?/);
213
+ if (!location)
214
+ continue;
215
+ const rule = line.match(/(?:rule|code)\s*[:=]\s*([\w./-]+)/i)?.[1] ??
216
+ line.match(/\(([\w./-]+)\)\s*$/)?.[1] ??
217
+ line.match(/:\d+(?::\d+)?\s+([\w./-]+)/)?.[1];
218
+ if (rule)
219
+ out.push(`${location[1]}:${location[2]}:${rule}`);
220
+ }
221
+ return out;
222
+ }
182
223
  /**
183
224
  * Fetch (creating if absent) one session's attribution state, refreshing LRU
184
225
  * recency and evicting the oldest entries past the cap — same bounding as
@@ -688,10 +729,37 @@ export function observeCacheContext(args) {
688
729
  countsCapped: sliceSize.capped,
689
730
  };
690
731
  });
732
+ const state = attributionFor(attributionKey(args.sessionId, args.sessionRole));
733
+ for (const slice of injectionSlices) {
734
+ const key = sourceKey(slice.source);
735
+ const bytes = measureInjectedMessages(slice.messages).bytes;
736
+ state.injectedBytes[key] += bytes;
737
+ for (const message of slice.messages) {
738
+ for (const identity of findingIdentities(typeof message.content === "string" ? message.content : "")) {
739
+ if (state.findingIdentities.has(identity)) {
740
+ state.injectedFindingsRepeated += 1;
741
+ continue;
742
+ }
743
+ if (state.findingIdentities.size >= MAX_FINDING_IDENTITIES) {
744
+ if (!state.findingIdentityCapRecorded) {
745
+ state.findingIdentityCapRecorded = true;
746
+ logLatency({
747
+ type: "phase",
748
+ filePath: "<pi-lens>",
749
+ phase: "injected_finding_identity_cap",
750
+ durationMs: 0,
751
+ metadata: { cap: MAX_FINDING_IDENTITIES },
752
+ });
753
+ }
754
+ continue;
755
+ }
756
+ state.findingIdentities.add(identity);
757
+ }
758
+ }
759
+ }
691
760
  const messageCountCapped = existingMessages.length > MAX_REPORTED_MESSAGES ||
692
761
  resultMessages.length > MAX_REPORTED_MESSAGES;
693
762
  recordContextAttribution(attributionKey(args.sessionId, args.sessionRole), existingMessages, sizes);
694
- const state = attributionFor(attributionKey(args.sessionId, args.sessionRole));
695
763
  state.sequenceHashTruncatedSinceLastUsage ||=
696
764
  beforeSequence.truncated ||
697
765
  beforeSequence.contentTruncated ||
@@ -721,6 +789,10 @@ export function observeCacheContext(args) {
721
789
  injectedMessageCountCapped: injectedMessages.length > MAX_REPORTED_MESSAGES,
722
790
  injectedChars: sizes.chars,
723
791
  injectedBytes: sizes.bytes,
792
+ injectedBytesBySource: Object.fromEntries(sourceBreakdown.map((entry) => [
793
+ sourceKey(entry.source),
794
+ entry.bytes,
795
+ ])),
724
796
  injectedEstimatedTokens: estimateTokens(sizes.chars),
725
797
  injectedTokenBasis: "chars-per-token-4-estimate-not-provider-measured",
726
798
  injectedCountsCapped: sizes.capped,
@@ -881,6 +953,13 @@ export function logCacheUsage(message, dbg, context) {
881
953
  const injectedCharsSinceLastTurn = state.injectedCharsSinceLastUsage;
882
954
  const newTranscriptCharsSinceLastTurn = state.newTranscriptCharsSinceLastUsage;
883
955
  const attributionCharsCapped = state.attributionCharsCapped;
956
+ const injectedBytes = {
957
+ ...state.injectedBytes,
958
+ turnEndAdvisory: state.turnEndAdvisoryBytes + state.injectedBytes.turnEndAdvisory,
959
+ };
960
+ const injectedFindingsRepeated = state.injectedFindingsRepeated;
961
+ const toolResultBytes = state.toolResultBytes;
962
+ const toolResultsTruncated = state.toolResultsTruncated;
884
963
  // This record is the turn boundary: reset the per-turn accumulators and
885
964
  // re-arm the prefix-break flag so the next verdict describes the NEXT gap.
886
965
  state.lastUsageAtMs = nowMs;
@@ -898,6 +977,11 @@ export function logCacheUsage(message, dbg, context) {
898
977
  state.injectedCharsSinceLastUsage = 0;
899
978
  state.newTranscriptCharsSinceLastUsage = 0;
900
979
  state.attributionCharsCapped = false;
980
+ state.injectedBytes = emptyInjectedBytes();
981
+ state.injectedFindingsRepeated = 0;
982
+ state.turnEndAdvisoryBytes = 0;
983
+ state.toolResultBytes = 0;
984
+ state.toolResultsTruncated = 0;
901
985
  // Clear the request stamp too: the next turn measures from ITS request, and
902
986
  // a turn whose `context` call pi-lens never saw must fall back rather than
903
987
  // reuse this one.
@@ -939,6 +1023,13 @@ export function logCacheUsage(message, dbg, context) {
939
1023
  injectedCharsSinceLastTurn,
940
1024
  newTranscriptCharsSinceLastTurn,
941
1025
  attributionCharsCapped,
1026
+ injectedBytes,
1027
+ injectedFindingsRepeated,
1028
+ // Delivered tool-result aggregation over the turn (#2800 item 7):
1029
+ // the sum of each delivered footer's bytes= figure and the count
1030
+ // of results whose bound fired.
1031
+ toolResultBytes,
1032
+ toolResultsTruncated,
942
1033
  ...(context
943
1034
  ? {
944
1035
  // MessageEndEvent has no request/context id in the host API. These
@@ -1126,6 +1217,29 @@ export function clearCachePrefixSession(sessionId, sessionRole) {
1126
1217
  // a stale gap baseline and a stale prefix-break flag.
1127
1218
  attributionBySession.delete(key);
1128
1219
  }
1220
+ /** Record the exact quiet-window summary content handed to pi.sendMessage. */
1221
+ export function recordTurnEndAdvisoryBytes(sessionId, bytes, sessionRole = "primary") {
1222
+ const state = attributionFor(attributionKey(sessionId, sessionRole));
1223
+ state.turnEndAdvisoryBytes = Math.min(MAX_INJECTED_BYTES, state.turnEndAdvisoryBytes + Math.max(0, bytes));
1224
+ }
1225
+ /**
1226
+ * Fold one delivered tool result into the per-turn aggregation the
1227
+ * `cache_usage` row reports as `toolResultBytes` / `toolResultsTruncated`
1228
+ * (#2800 item 7). `bytes` is the delivered payload's UTF-8 byte count as
1229
+ * stamped in the result's own footer, and `truncated` is the footer's
1230
+ * truncated flag. Called from each host adapter's delivery seam; the row is
1231
+ * emitted by `logCacheUsage`, which resets both figures at the turn boundary.
1232
+ * The byte sum saturates at the attribution bound so the row stays bounded.
1233
+ */
1234
+ export function recordToolResultDelivery(args) {
1235
+ const state = attributionFor(attributionKey(args.sessionId, args.sessionRole));
1236
+ const bytes = Number.isFinite(args.bytes)
1237
+ ? Math.max(0, Math.floor(args.bytes))
1238
+ : 0;
1239
+ state.toolResultBytes = Math.min(MAX_ATTRIBUTION_BYTES, state.toolResultBytes + bytes);
1240
+ if (args.truncated === true)
1241
+ state.toolResultsTruncated += 1;
1242
+ }
1129
1243
  /**
1130
1244
  * Emit one bounded session summary and retire its attribution state. The
1131
1245
  * summary contains fixed-key counters only; it never serializes transcript or
@@ -1165,6 +1279,13 @@ export function resetCachePrefixObservation() {
1165
1279
  prefixHashBySession.clear();
1166
1280
  attributionBySession.clear();
1167
1281
  }
1282
+ export function resetCacheFindingIdentitiesSession(sessionId, sessionRole) {
1283
+ const state = attributionBySession.get(attributionKey(sessionId, sessionRole));
1284
+ if (!state)
1285
+ return;
1286
+ state.findingIdentities.clear();
1287
+ state.findingIdentityCapRecorded = false;
1288
+ }
1168
1289
  export function _attributionBySessionHasForTests(key) {
1169
1290
  return attributionBySession.has(key);
1170
1291
  }
@@ -13,6 +13,7 @@
13
13
  import { createSubsystemLogger } from "./extension-log.js";
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
+ import { calcCyclomaticComplexity } from "./dispatch/facts/function-facts.js";
16
17
  import { firstChildOfType, withTreeSitterRoot, isComplexityLanguageId, isComplexitySupportedFile, resolveTreeSitterLanguage, walk, } from "./tree-sitter-shared.js";
17
18
  // JS/TS/JSX share one grammar shape.
18
19
  const JSTS = {
@@ -190,6 +191,9 @@ function isLogicalOp(node, nodes) {
190
191
  }
191
192
  /** Cyclomatic contribution of a subtree: decision points + logical operators. */
192
193
  function subtreeCyclomatic(root, nodes) {
194
+ // Dispatch uses 1-based McCabe; this client exposes 0-based contributions (#2697).
195
+ if (nodes === JSTS)
196
+ return calcCyclomaticComplexity(root) - 1;
193
197
  let cc = 0;
194
198
  walk(root, (n) => {
195
199
  if (nodes.decision.has(n.type))
@@ -438,6 +442,7 @@ export class ComplexityClient {
438
442
  aiCommentPatterns: countAICommentPatterns(content),
439
443
  singleUseFunctions: countSingleUseFunctions(functions),
440
444
  tryCatchCount: countTryCatch(root, nodes),
445
+ functions,
441
446
  };
442
447
  }
443
448
  aggregateFunctionStats(functions) {
@@ -171,6 +171,8 @@ export const CONFIG_DIAGNOSTIC_CODES = {
171
171
  * suppressed" about a configuration that applied nothing at all.
172
172
  */
173
173
  PILENS_CFG_0008: "config resolution failed; whole configuration ignored",
174
+ /** A tool key is unknown or names a required, non-disableable tool. */
175
+ PILENS_CFG_0009: "unknown or non-disableable tool config key ignored",
174
176
  };
175
177
  /** The shape every code in the namespace must match. */
176
178
  export const CONFIG_DIAGNOSTIC_CODE_PATTERN = /^PILENS_CFG_\d{4}$/;
@@ -740,8 +740,8 @@ export function reportPiLensConfigRecords(records) {
740
740
  */
741
741
  export function ignoredRecordCollector(configPath, tier) {
742
742
  const noted = [];
743
- const note = (reason) => {
744
- noted.push({
743
+ const note = (reason, code = "PILENS_CFG_0001") => {
744
+ let notedRecord = {
745
745
  code: "PILENS_CFG_0001",
746
746
  file: configPath,
747
747
  key: "",
@@ -752,7 +752,10 @@ export function ignoredRecordCollector(configPath, tier) {
752
752
  sourceText: reason.sourceText,
753
753
  }),
754
754
  tier,
755
- });
755
+ };
756
+ if (code !== "PILENS_CFG_0001")
757
+ notedRecord.code = code;
758
+ noted.push(notedRecord);
756
759
  };
757
760
  return {
758
761
  note,