@herbertgao/pi-extensions 2026.9.0 → 2026.9.1

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 (293) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  11. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  12. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  13. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  14. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  15. package/node_modules/pi-lens/CHANGELOG.md +988 -0
  16. package/node_modules/pi-lens/README.md +3 -0
  17. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  18. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  19. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -2
  20. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  21. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  22. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  23. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  24. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -2
  25. package/node_modules/pi-lens/dist/clients/bash-file-access.js +1 -3
  26. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  27. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -0
  28. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  29. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  30. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -4
  31. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  32. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +2 -2
  33. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  34. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  35. package/node_modules/pi-lens/dist/clients/cache-observability.js +44 -18
  36. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  37. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -2
  38. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  39. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  40. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  41. package/node_modules/pi-lens/dist/clients/config-core/index.js +47 -0
  42. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  43. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  44. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  45. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +171 -0
  46. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  47. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  48. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  49. package/node_modules/pi-lens/dist/clients/config-core/schema.js +167 -0
  50. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  51. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  52. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  53. package/node_modules/pi-lens/dist/clients/config-schema.js +197 -0
  54. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  55. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  56. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  58. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  59. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  60. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  63. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  64. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +14 -4
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -18
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -7
  68. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  69. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +380 -26
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -1
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +13 -18
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  121. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -3
  122. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  123. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  124. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  125. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  126. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/file-time.js +6 -1
  128. package/node_modules/pi-lens/dist/clients/file-utils.js +112 -9
  129. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  130. package/node_modules/pi-lens/dist/clients/format-service.js +6 -1
  131. package/node_modules/pi-lens/dist/clients/formatters.js +276 -102
  132. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  133. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +114 -19
  134. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  135. package/node_modules/pi-lens/dist/clients/git-guard.js +1 -1
  136. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  137. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  138. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  139. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  140. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  141. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  142. package/node_modules/pi-lens/dist/clients/installer/index.js +71 -52
  143. package/node_modules/pi-lens/dist/clients/instance-reaper.js +104 -230
  144. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  145. package/node_modules/pi-lens/dist/clients/language-registry.js +595 -0
  146. package/node_modules/pi-lens/dist/clients/latency-logger.js +160 -2
  147. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +13 -0
  148. package/node_modules/pi-lens/dist/clients/lens-config.js +141 -30
  149. package/node_modules/pi-lens/dist/clients/lens-engine.js +30 -37
  150. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +59 -6
  151. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  153. package/node_modules/pi-lens/dist/clients/lsp/client.js +168 -30
  154. package/node_modules/pi-lens/dist/clients/lsp/config.js +483 -158
  155. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/lsp/edits.js +15 -2
  157. package/node_modules/pi-lens/dist/clients/lsp/index.js +676 -71
  158. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -169
  159. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  160. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -16
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  162. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  163. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  164. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +9 -2
  165. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  167. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  168. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  171. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +2 -4
  172. package/node_modules/pi-lens/dist/clients/module-report.js +20 -45
  173. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  174. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  175. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -20
  177. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  178. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  179. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +68 -22
  180. package/node_modules/pi-lens/dist/clients/opengrep-config.js +4 -0
  181. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  182. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  183. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  184. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  185. package/node_modules/pi-lens/dist/clients/path-utils.js +428 -13
  186. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  187. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  188. package/node_modules/pi-lens/dist/clients/pipeline.js +77 -16
  189. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  190. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  191. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  195. package/node_modules/pi-lens/dist/clients/project-lens-config.js +393 -122
  196. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  197. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  198. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  199. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  200. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  201. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +15 -4
  203. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  204. package/node_modules/pi-lens/dist/clients/read-guard.js +21 -0
  205. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  206. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +152 -109
  207. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  208. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  209. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  210. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  211. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +18 -2
  212. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  213. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  214. package/node_modules/pi-lens/dist/clients/runtime-session.js +139 -22
  215. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +168 -35
  216. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  217. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  218. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  219. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  220. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -1
  221. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  222. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  223. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  224. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  225. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  226. package/node_modules/pi-lens/dist/clients/sgconfig.js +6 -1
  227. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  228. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  229. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  230. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  231. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  232. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +239 -0
  233. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  234. package/node_modules/pi-lens/dist/clients/tool-policy.js +91 -21
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +86 -48
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  241. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  242. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -2
  243. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  244. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -2
  245. package/node_modules/pi-lens/dist/clients/word-index-store.js +20 -5
  246. package/node_modules/pi-lens/dist/clients/word-index.js +243 -31
  247. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  248. package/node_modules/pi-lens/dist/clients/zizmor-config.js +3 -0
  249. package/node_modules/pi-lens/dist/index.js +64335 -54300
  250. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  251. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  252. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  253. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  254. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  255. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +95 -24
  256. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +67 -80
  257. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  258. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  259. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  260. package/node_modules/pi-lens/docs/configuration.md +222 -0
  261. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  262. package/node_modules/pi-lens/docs/features.md +44 -5
  263. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  264. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  265. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  266. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +27 -0
  267. package/node_modules/pi-lens/docs/pi-lens-subagent.md +38 -0
  268. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  269. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  270. package/node_modules/pi-lens/docs/release-qa-baseline.md +182 -0
  271. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  272. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  273. package/node_modules/pi-lens/package.json +15 -23
  274. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  275. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  276. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  277. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  278. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  279. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  280. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  281. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  282. package/node_modules/pi-web-access/README.md +62 -15
  283. package/node_modules/pi-web-access/curator-page.ts +3 -1
  284. package/node_modules/pi-web-access/curator-server.ts +5 -1
  285. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  286. package/node_modules/pi-web-access/github-extract.ts +242 -1
  287. package/node_modules/pi-web-access/index.ts +118 -64
  288. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  289. package/node_modules/pi-web-access/package.json +2 -2
  290. package/node_modules/pi-web-access/perplexity.ts +14 -1
  291. package/node_modules/pi-web-access/utils.ts +23 -6
  292. package/node_modules/pi-web-access/xai-search.ts +96 -33
  293. package/package.json +5 -5
@@ -221,6 +221,35 @@ function createState(files) {
221
221
  progress: [],
222
222
  },
223
223
  },
224
+ // #2526: the config stack's positive-observability rows. `resolved`
225
+ // counts `config_resolved` phase records; `legacyWithoutRecords` counts
226
+ // the rows that carry a deprecated document but produced no
227
+ // migration/notice record — the deprecation machinery gone silent.
228
+ //
229
+ // Round 2, F2: the session->row relation is a JOIN on the session id both
230
+ // sides carry, not a subtraction of two counts.
231
+ //
232
+ // Round 3, S1: the session-side half of the join is no longer a
233
+ // PREDICTION. `pendingSessions` holds every session id that published a
234
+ // `config_resolution_pending` mark — written by `loadLSPConfig` itself,
235
+ // at the instant a resolution is actually attempted, never by a
236
+ // `runtime-session.ts` handler guessing from `no-lsp`/subagent/warm-attach
237
+ // flags ahead of time. A session that never reaches that call (quick or
238
+ // minimal mode's second-and-later session in a process, for the same
239
+ // root) never gets a mark, so it is silently excluded rather than
240
+ // counted against. `resolvedSessions` holds every session id a
241
+ // resolution actually reported, from EITHER sink (the latency row or the
242
+ // sessionstart line — one call writes both, so this is one fact
243
+ // surviving an independent rotation of either log, not a second source
244
+ // of truth).
245
+ config: {
246
+ resolved: 0,
247
+ pendingSessions: new Map(),
248
+ resolvedSessions: new Set(),
249
+ legacyDocuments: 0,
250
+ legacyWithoutRecords: 0,
251
+ examples: [],
252
+ },
224
253
  diagnostics: {
225
254
  bySeverity: counter(),
226
255
  byTool: counter(),
@@ -351,7 +380,44 @@ async function analyzeLatency(files, state) {
351
380
  } else if (entry.type === "phase") {
352
381
  const phase = entry.phase ?? "unknown";
353
382
  state.latency.phaseCounts.inc(phase);
354
- if (/_timeout$/.test(phase)) state.latency.phaseTimeouts.inc(phase);
383
+ if (phase.endsWith("_timeout")) state.latency.phaseTimeouts.inc(phase);
384
+ if (phase === "config_resolved") {
385
+ // #2526. Counted here rather than derived from phaseCounts so the
386
+ // legacy/record cross-check reads the same row it counts.
387
+ state.config.resolved += 1;
388
+ const md = entry.metadata ?? {};
389
+ // #2526 R2 F2 / #2552 R4: the join key is (session, root), not
390
+ // session alone — see `configJoinKey`'s doc comment. `entry.filePath`
391
+ // is this row's root (the same `configResolutionKey(cwd)` value the
392
+ // pending mark's `root=` carries); a row from a build that predates
393
+ // the session id carries none, and simply joins to nothing.
394
+ if (
395
+ typeof md.sessionId === "string" &&
396
+ md.sessionId.length > 0 &&
397
+ typeof entry.filePath === "string" &&
398
+ entry.filePath.length > 0
399
+ ) {
400
+ state.config.resolvedSessions.add(
401
+ configJoinKey(md.sessionId, entry.filePath),
402
+ );
403
+ }
404
+ const documents = Array.isArray(md.documents) ? md.documents : [];
405
+ const legacy = documents.filter((doc) => doc?.legacy === true);
406
+ state.config.legacyDocuments += legacy.length;
407
+ // A deprecated document that produced NO record means the
408
+ // migration notices went silent — the user is on a removal
409
+ // schedule and is never told. Zero records with zero legacy
410
+ // documents is the correct canonical-only answer, not a smell.
411
+ if (legacy.length > 0 && Number(md.recordCount ?? 0) === 0) {
412
+ state.config.legacyWithoutRecords += 1;
413
+ pushTop(
414
+ state.config.examples,
415
+ summarizeConfigResolved(entry, "legacy document with 0 records"),
416
+ limit * 3,
417
+ byDuration,
418
+ );
419
+ }
420
+ }
355
421
  if (
356
422
  phase === "total" &&
357
423
  (entry.durationMs ?? 0) >= thresholds.totalSlowMs
@@ -636,6 +702,40 @@ async function analyzeSessionStart(files, state) {
636
702
  trackProject(state, cwd);
637
703
  }
638
704
 
705
+ // #2526 R3 S1: this session's config-resolution PENDING mark, published
706
+ // by `loadLSPConfig` itself at the instant a resolution is actually
707
+ // attempted — never a start-line prediction from `no-lsp`/subagent/
708
+ // warm-attach flags. A session that never calls `loadLSPConfig` (quick
709
+ // or minimal mode's second-and-later session in a process, for the
710
+ // same root) never writes this line, and is correctly absent from the
711
+ // join rather than counted against.
712
+ //
713
+ // #2552 R4: `root=` is REQUIRED in the match — a warm MCP process keeps
714
+ // one session id for its whole life but marks once per served root
715
+ // (`configJoinKey`'s doc comment), so a line missing it cannot be
716
+ // attributed to a specific root and is left out of the join rather than
717
+ // guessed into a wrong one.
718
+ const pending =
719
+ /session_start config_resolution_pending session=(\S+) root=(.*)$/.exec(
720
+ message,
721
+ );
722
+ if (pending) {
723
+ const [, sessionId, root] = pending;
724
+ state.config.pendingSessions.set(configJoinKey(sessionId, root), {
725
+ ts: iso(ts),
726
+ sessionId,
727
+ root,
728
+ });
729
+ }
730
+ // The loader's own line is the second half of the join, so a rotated
731
+ // latency.log cannot manufacture a deficit on its own.
732
+ const resolvedSession =
733
+ /config resolved .*\bsession=(\S+) root=(.*)$/.exec(message);
734
+ if (resolvedSession) {
735
+ const [, sessionId, root] = resolvedSession;
736
+ state.config.resolvedSessions.add(configJoinKey(sessionId, root));
737
+ }
738
+
639
739
  const total = /session_start total:\s*(\d+)ms/.exec(message);
640
740
  if (total && Number(total[1]) >= thresholds.startupSlowMs) {
641
741
  state.smellTotals.inc("slow-session-start");
@@ -899,6 +999,23 @@ function iso(date) {
899
999
  return date?.toISOString?.() ?? "unknown";
900
1000
  }
901
1001
 
1002
+ /**
1003
+ * The config-resolution join key (#2552 review round 4). A warm MCP process
1004
+ * keeps ONE session id for its entire life but calls `loadLSPConfig` once per
1005
+ * SERVED ROOT (#2526 review round 2, F3's premise, reintroduced one layer up
1006
+ * here) — joining on session id alone let one root's row silently clear every
1007
+ * OTHER root's deficit under the same session id. `root` is whatever string
1008
+ * the producer already wrote (the pending mark's `root=`, or the row's own
1009
+ * `filePath` — both come from the SAME `configResolutionKey(cwd)` call in
1010
+ * `clients/lsp/config.ts`, which folds separators, canonicalizes, then folds
1011
+ * again — `normalizeFilePath(path.resolve(normalizeFilePath(cwd)))`, in that
1012
+ * order (#2518 review F6; resolving first breaks POSIX backslash names), so they compare equal without this script re-deriving any
1013
+ * path normalization of its own).
1014
+ */
1015
+ function configJoinKey(sessionId, root) {
1016
+ return `${sessionId} ${root}`;
1017
+ }
1018
+
902
1019
  function counter() {
903
1020
  const map = new Map();
904
1021
  return {
@@ -985,6 +1102,33 @@ function summarizeLatency(entry) {
985
1102
  };
986
1103
  }
987
1104
 
1105
+ /**
1106
+ * A `config_resolved` row (#2526), summarised for a smell example.
1107
+ *
1108
+ * Deliberately NOT `summarizeLatency`: that helper's metadata whitelist would
1109
+ * drop every field this row carries, so the example would print a phase name
1110
+ * and nothing a reader could act on.
1111
+ */
1112
+ function summarizeConfigResolved(entry, note) {
1113
+ const md = entry.metadata ?? {};
1114
+ const documents = Array.isArray(md.documents) ? md.documents : [];
1115
+ return {
1116
+ ts: entry.ts,
1117
+ durationMs: entry.durationMs,
1118
+ phase: entry.phase,
1119
+ project: projectOf(entry.filePath),
1120
+ note,
1121
+ documents: documents
1122
+ .map(
1123
+ (doc) =>
1124
+ `${doc?.tier ?? "?"}:${doc?.file ?? "?"}${doc?.legacy ? " (legacy)" : ""}`,
1125
+ )
1126
+ .slice(0, 8),
1127
+ recordCount: md.recordCount,
1128
+ deniedServers: md.deniedServers,
1129
+ };
1130
+ }
1131
+
988
1132
  function summarizeWorkspaceSweep(entry) {
989
1133
  const md = entry.metadata ?? {};
990
1134
  const bits = [];
@@ -1193,6 +1337,43 @@ function buildReport(state) {
1193
1337
  `session_start background tasks >= ${thresholds.backgroundSlowMs}ms`,
1194
1338
  state.session.slowTasks.slice(0, limit),
1195
1339
  );
1340
+ // #2526: config resolution has to prove it HAPPENED. Two shapes, one smell:
1341
+ //
1342
+ // (a) a session that has a `config_resolution_pending` mark and produced no
1343
+ // `config_resolved` row. Round 3, S1: the pending mark is written by
1344
+ // `loadLSPConfig` itself, at the instant a resolution is actually
1345
+ // attempted — never predicted from a handler's own flags ahead of time.
1346
+ // Round 2, F2 established the JOIN itself: on the session id both sides
1347
+ // carry, never a subtraction of two counts (subtracting counted quick
1348
+ // sessions' rows against full sessions' start lines — different
1349
+ // populations — which both masked total silence and charged
1350
+ // `--no-lsp`/subagent sessions with a permanent deficit they could never
1351
+ // clear).
1352
+ // Round 4, #2552 review: the join is on (session, root), not session
1353
+ // alone — a warm MCP process keeps one session id for its whole life but
1354
+ // marks once per SERVED ROOT (`configJoinKey`'s doc comment), so one
1355
+ // root's row must not clear another root's deficit under the same id.
1356
+ // (b) a legacy document present with zero records — the deprecation
1357
+ // machinery went silent while the user is on a removal schedule.
1358
+ const pendingConfigResolution = [...state.config.pendingSessions.entries()];
1359
+ const unresolvedSessions = pendingConfigResolution.filter(
1360
+ ([key]) => !state.config.resolvedSessions.has(key),
1361
+ );
1362
+ for (const [, value] of unresolvedSessions.slice(0, limit * 3)) {
1363
+ state.config.examples.push({
1364
+ ts: value.ts,
1365
+ session: value.sessionId,
1366
+ root: value.root,
1367
+ note: "session had a config_resolution_pending mark and produced no config_resolved row",
1368
+ });
1369
+ }
1370
+ addSmell(
1371
+ smells,
1372
+ "config-resolution",
1373
+ unresolvedSessions.length + state.config.legacyWithoutRecords,
1374
+ `Sessions with a config_resolution_pending mark but no config_resolved row (${unresolvedSessions.length} of ${pendingConfigResolution.length}) or a legacy config document that produced no migration record (${state.config.legacyWithoutRecords})`,
1375
+ state.config.examples.slice(0, limit),
1376
+ );
1196
1377
  addSmell(
1197
1378
  smells,
1198
1379
  "cascade-fallbacks",
@@ -1352,6 +1533,16 @@ function buildReport(state) {
1352
1533
  rotations: state.session.rotations.toJSON(),
1353
1534
  errors: state.session.errors.slice(0, limit),
1354
1535
  },
1536
+ // #2526: the positive-observability counters behind the
1537
+ // `config-resolution` smell, so a reader can see WHY it fired (or that
1538
+ // it correctly did not) without re-deriving the deficit.
1539
+ config: {
1540
+ resolved: state.config.resolved,
1541
+ sessionsPendingResolution: pendingConfigResolution.length,
1542
+ sessionsWithoutResolution: unresolvedSessions.length,
1543
+ legacyDocuments: state.config.legacyDocuments,
1544
+ legacyWithoutRecords: state.config.legacyWithoutRecords,
1545
+ },
1355
1546
  actionable: {
1356
1547
  events: state.actionable.events.toJSON(),
1357
1548
  reports: state.actionable.reports,
@@ -17,7 +17,9 @@
17
17
  * Force-imports the modules whose TOP-LEVEL bare imports are the documented
18
18
  * failure points, plus the bare specifiers directly, and checks the two
19
19
  * build-script-provided assets (ast-grep CLI binary + tree-sitter grammars)
20
- * that pnpm/bun skip by default. It runs no model and needs no credentials.
20
+ * that pnpm/bun skip by default. It also resolves the `pi.skills` manifest
21
+ * entries the way pi does, from the installed package root (#2587). It runs no
22
+ * model and needs no credentials.
21
23
  *
22
24
  * WHAT IT NO LONGER COVERS (#1926)
23
25
  * pi supplies `typebox` and `@earendil-works/pi-tui` from its own runtime, so
@@ -47,6 +49,7 @@ import { createRequire } from "node:module";
47
49
  import * as path from "node:path";
48
50
  import { fileURLToPath } from "node:url";
49
51
  import { HOST_PROVIDED_PACKAGES } from "./lib/host-provided-deps.mjs";
52
+ import { collectSkillEntryPaths } from "./lib/skills-predicate.mjs";
50
53
 
51
54
  const here = path.dirname(fileURLToPath(import.meta.url));
52
55
  const pkgRoot = path.resolve(here, "..");
@@ -242,6 +245,56 @@ try {
242
245
  }
243
246
  record("tree-sitter grammars", "asset", hasCoreGrammar, grammarDetail);
244
247
 
248
+ // --- 4. pi.skills manifest resolution, AS INSTALLED (#2587) ----------------
249
+ // Guards the recurrence of #2587: a `pi.skills` entry that resolves outside the
250
+ // installed package, so pi registers none of the shipped skills. This probe runs
251
+ // from the INSTALLED package (`require.resolve("pi-lens/scripts/…")`), so it sees
252
+ // the real layout — hoisted node_modules, pnpm's symlink store, bun, yarn — which
253
+ // is exactly where `../../skills` escaped and where a static check on the source
254
+ // tree cannot look. Replicates pi's own resolver, `PackageManager#collectFiles\
255
+ // FromManifestEntries` in `@earendil-works/pi-coding-agent`
256
+ // `dist/core/package-manager.js`: a non-glob entry is `resolve(packageRoot, entry)`
257
+ // (verified identical in 0.78.1 / 0.84.1 / 0.85.1).
258
+ //
259
+ // #2626 review round 2 (F2): the "does this dir have a skill" walk used to be
260
+ // a hand-rolled `countSkillFiles` here AND a second, differently-wrong one in
261
+ // `clients/skills-resolver.ts`. Both now derive from the ONE structural
262
+ // predicate in `./lib/skills-predicate.mjs`, which replicates pi's actual
263
+ // `loadSkillsFromDirInternal` walk (see that module's header for the exact
264
+ // discovery rules and the two documented scope gaps).
265
+ {
266
+ const pkgJson = JSON.parse(
267
+ fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"),
268
+ );
269
+ const entries = pkgJson.pi?.skills ?? [];
270
+ if (entries.length === 0) {
271
+ record("pi.skills declared", "manifest", false, "pi.skills is empty");
272
+ }
273
+ for (const entry of entries) {
274
+ const label = `pi.skills "${entry}"`;
275
+ // pi globs entries containing * or ? through a different branch; this
276
+ // probe only replicates the non-glob one.
277
+ if (/[*?]/.test(entry)) {
278
+ record(label, "manifest", true, "glob entry — not probed");
279
+ continue;
280
+ }
281
+ const resolved = path.resolve(pkgRoot, entry);
282
+ if (resolved !== pkgRoot && !resolved.startsWith(pkgRoot + path.sep)) {
283
+ record(label, "manifest", false, `escapes the package: ${resolved}`);
284
+ continue;
285
+ }
286
+ const found = collectSkillEntryPaths(resolved).length;
287
+ record(
288
+ label,
289
+ "manifest",
290
+ found > 0,
291
+ found > 0
292
+ ? `${found} SKILL.md under ${resolved}`
293
+ : `no SKILL.md under ${resolved}`,
294
+ );
295
+ }
296
+ }
297
+
245
298
  // --- Report ----------------------------------------------------------------
246
299
  const pad = Math.max(...results.map((r) => r.name.length));
247
300
  let hardFail = 0;
@@ -249,7 +302,10 @@ let softFail = 0;
249
302
  for (const r of results) {
250
303
  const soft = (r.kind === "asset" || r.kind === "tool") && allowSoft;
251
304
  const status = r.ok ? "PASS" : soft ? "WARN" : "FAIL";
252
- if (!r.ok) soft ? softFail++ : hardFail++;
305
+ if (!r.ok) {
306
+ if (soft) softFail++;
307
+ else hardFail++;
308
+ }
253
309
  console.log(
254
310
  ` [${status}] ${r.name.padEnd(pad)} ${r.detail ? "— " + r.detail : ""}`,
255
311
  );
@@ -0,0 +1,129 @@
1
+ // The ONE structural replica of pi's own skill-loading walk (#2626 review
2
+ // round 2, F1/F2).
3
+ //
4
+ // Two independent detectors used to answer "does this directory tree hold a
5
+ // skill pi would load" with two DIFFERENT, hand-rolled heuristics that both
6
+ // diverged from pi's real resolver:
7
+ // - `clients/skills-resolver.ts` (a `SKILL.md` one level down only, no
8
+ // recursion, no root `.md` acceptance, no symlink-follow, no dot/
9
+ // node_modules skip)
10
+ // - `scripts/install-selftest.mjs`'s `countSkillFiles` (recursed
11
+ // UNBOUNDED — would double-count a `SKILL.md` sitting inside another
12
+ // skill's own resource subdirectory — with no dot/node_modules skip, no
13
+ // root `.md` acceptance, no symlink handling)
14
+ // Neither matched `loadSkillsFromDirInternal` in
15
+ // `@earendil-works/pi-coding-agent` `dist/core/skills.js` (mirrored by
16
+ // `collectSkillEntries` in `dist/core/package-manager.js`, byte-identical
17
+ // walk, confirmed against the 0.85.1 tarball). This module is the fold: BOTH
18
+ // consumers now derive from `collectSkillEntryPaths` below.
19
+ //
20
+ // Discovery rules, replicated from `loadSkillsFromDirInternal` (verified
21
+ // against the extracted 0.85.1 `skills.js`):
22
+ // 1. If `dir` directly contains a (non-symlink-broken) `SKILL.md`, `dir` IS
23
+ // one skill root — return immediately, do NOT look at `dir`'s other
24
+ // entries or recurse into its subdirectories (a `SKILL.md` inside a
25
+ // skill's own reference/ subfolder is the skill's OWN resource, not a
26
+ // second skill).
27
+ // 2. Otherwise, skip dot-prefixed entries and `node_modules`; recurse into
28
+ // every remaining subdirectory (same two rules, one level down); at the
29
+ // OUTERMOST call only (`isRoot`), a loose `*.md` file directly in `dir`
30
+ // also counts (pi's `includeRootFiles`, true only for the top call).
31
+ // 3. A symlink is followed via `statSync` (broken symlink = skip) for BOTH
32
+ // the `SKILL.md` check and the directory/file classification in step 2.
33
+ //
34
+ // Deliberately NOT replicated (out of #2626's scope — packaging/path
35
+ // resolution, not skill-content validation):
36
+ // - `.gitignore`/`.ignore`/`.fdignore` matching inside the skills tree.
37
+ // - frontmatter parsing: pi additionally requires a non-empty `description`
38
+ // field before a discovered file becomes a loaded skill. A `SKILL.md`
39
+ // file that exists but fails THAT validation is a content-authoring
40
+ // defect a maintainer would see in normal use, not a silent zero.
41
+ // Both gaps mean this predicate can be a bit more OPTIMISTIC than pi's true
42
+ // count in a pathological case (an ignored or description-less file still
43
+ // counts as "found" here); it is never more PESSIMISTIC — every real skill
44
+ // pi loads is found by this walk too — which is the direction #2626 cares
45
+ // about (never claim "no skills" when pi would load one).
46
+
47
+ import * as fs from "node:fs";
48
+ import * as path from "node:path";
49
+
50
+ /**
51
+ * Symlink-aware entry classification, mirroring `loadSkillsFromDirInternal`'s
52
+ * own `entry.isSymbolicLink() ? statSync(fullPath) : entry`.
53
+ * @param {string} fullPath
54
+ * @param {import("node:fs").Dirent} entry
55
+ * @returns {"file" | "dir" | "other"}
56
+ */
57
+ export function resolvedEntryKind(fullPath, entry) {
58
+ if (entry.isSymbolicLink()) {
59
+ try {
60
+ const stats = fs.statSync(fullPath);
61
+ if (stats.isDirectory()) return "dir";
62
+ if (stats.isFile()) return "file";
63
+ return "other";
64
+ } catch {
65
+ return "other"; // broken symlink
66
+ }
67
+ }
68
+ if (entry.isDirectory()) return "dir";
69
+ if (entry.isFile()) return "file";
70
+ return "other";
71
+ }
72
+
73
+ /**
74
+ * The structural walk over an ALREADY-READ directory listing — split out
75
+ * from `collectSkillEntryPaths` so a caller that already has `entries` (a
76
+ * top-level ENOENT/EACCES distinction, say) is not forced into a second
77
+ * `readdirSync`.
78
+ * @param {string} dir
79
+ * @param {import("node:fs").Dirent[]} entries
80
+ * @param {boolean} isRoot
81
+ * @returns {string[]} absolute paths of every `SKILL.md` (or, at the root,
82
+ * loose `.md`) file pi's own resolver would treat as a skill entry point.
83
+ */
84
+ export function scanEntriesForSkills(dir, entries, isRoot) {
85
+ for (const entry of entries) {
86
+ if (
87
+ entry.name === "SKILL.md" &&
88
+ resolvedEntryKind(path.join(dir, entry.name), entry) === "file"
89
+ ) {
90
+ return [path.join(dir, entry.name)];
91
+ }
92
+ }
93
+ const found = [];
94
+ for (const entry of entries) {
95
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
96
+ continue;
97
+ }
98
+ const fullPath = path.join(dir, entry.name);
99
+ const kind = resolvedEntryKind(fullPath, entry);
100
+ if (kind === "dir") {
101
+ found.push(...collectSkillEntryPaths(fullPath, false));
102
+ continue;
103
+ }
104
+ if (isRoot && kind === "file" && entry.name.endsWith(".md")) {
105
+ found.push(fullPath);
106
+ }
107
+ }
108
+ return found;
109
+ }
110
+
111
+ /**
112
+ * Every skill entry point under `dir`, recursively, per pi's discovery
113
+ * rules. Fail-closed: a `readdirSync` error at `dir` (absent, unreadable) or
114
+ * any recursed-into subdirectory returns `[]` for that branch rather than
115
+ * throwing — matching `loadSkillsFromDirInternal`'s own bare `try { … }
116
+ * catch {}` per directory.
117
+ * @param {string} dir
118
+ * @param {boolean} [isRoot]
119
+ * @returns {string[]}
120
+ */
121
+ export function collectSkillEntryPaths(dir, isRoot = true) {
122
+ let entries;
123
+ try {
124
+ entries = fs.readdirSync(dir, { withFileTypes: true });
125
+ } catch {
126
+ return [];
127
+ }
128
+ return scanEntriesForSkills(dir, entries, isRoot);
129
+ }
@@ -61,7 +61,9 @@ function handle(line) {
61
61
  if (m.type === "response" && m.command === "get_commands") {
62
62
  const cmds = (m.data && m.data.commands) || [];
63
63
  const lens = cmds.filter(
64
- (c) => /^lens-/.test(c.name) || String(c.path || "").includes("pi-lens"),
64
+ (c) =>
65
+ String(c.name || "").startsWith("lens-") ||
66
+ String(c.path || "").includes("pi-lens"),
65
67
  );
66
68
  console.log(
67
69
  `total commands: ${cmds.length}; pi-lens commands: ${lens.map((c) => c.name).join(", ") || "(none)"}`,
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.28.0] - 2026-09-04
8
+
9
+ ### Highlights
10
+
11
+ - Search X posts through xAI or choose Mistral for web searches.
12
+ - Batch searches run in parallel for faster multi-query research.
13
+ - Follow-up retrieval can access stored search results, and Perplexity keeps all cited sources.
14
+ - Web-tool proxy settings no longer affect unrelated Pi requests.
15
+
16
+ ### Added
17
+
18
+ - Documented the Linux `xdg-utils` dependency for automatic curator browser launch and the manual URL fallback. Thanks to [@wickedTangent](https://github.com/wickedTangent) for issue #336 and PR #337.
19
+ - Added opt-in X post search through xAI alongside web search, configured with `xaiSearchTools`. Thanks to [@Jerry2003sky](https://github.com/Jerry2003sky) for issue #342.
20
+ - Added Mistral web search with `provider: "mistral"`, using `mistralApiKey` / `MISTRAL_API_KEY` credentials. Premium web search is available as a separate opt-in. Thanks to [@jaudiger](https://github.com/jaudiger) for issue #346.
21
+
22
+ ### Changed
23
+
24
+ - Run batch searches in parallel with a concurrency limit, keeping results in query order and preserving provider fallback. Thanks to [@Noir-Lime](https://github.com/Noir-Lime) for PR #345.
25
+
26
+ ### Fixed
27
+
28
+ - Included `responseId` in search output so the model can retrieve stored results with `get_search_content`. Thanks to [@axelbaumlisto](https://github.com/axelbaumlisto) for PR #354.
29
+ - Fixed flaky tests on slower Node startups. Thanks to [@axelbaumlisto](https://github.com/axelbaumlisto) for PR #353.
30
+ - Kept all sources cited in Perplexity answers, even beyond `numResults`. Answers without citations still respect the result limit. Thanks to [@schlessera](https://github.com/schlessera) for issue #340 and PR #341.
31
+ - Limited configured proxies to web-tool requests so unrelated Pi requests are unaffected. Thanks to [@alexei-ciobanu](https://github.com/alexei-ciobanu) for PR #339 and [@mystery4f](https://github.com/mystery4f) for PR #343.
32
+ - Cleaned up abandoned GitHub clone directories after crashes, only when their owning process is confirmed dead. Thanks to [@yazanabuashour](https://github.com/yazanabuashour) for issue #331.
33
+ - Kept using an existing `~/.pi/web-search.json` when `XDG_CONFIG_HOME` is set but no config file exists there. Thanks to [@hu3rror](https://github.com/hu3rror) for issue #333.
34
+
7
35
  ## [0.27.0] - 2026-08-28
8
36
 
9
37
  ### Highlights