@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Pure helpers for `scripts/warm-loader-cache.mjs` (#1926).
3
+ *
4
+ * pi loads an extension through jiti (`createJiti(..., { moduleCache: false,
5
+ * alias: … })` in `@earendil-works/pi-coding-agent`'s
6
+ * `dist/core/extensions/loader.js`). jiti transforms the entry with Babel and
7
+ * stores the result in a filesystem cache. pi-lens's `dist/index.js` is a ~4MB
8
+ * esbuild bundle, so that transform costs seconds: the first session after a
9
+ * `git:` install or update measured 4847ms of `module import`, against a 138ms
10
+ * steady state once the cache is warm (#1926 field report).
11
+ *
12
+ * The fix is to pay that transform at install time instead of in the first
13
+ * interactive session. `prepare` already builds `dist/`, so the last step of
14
+ * the chain runs the same transform through the same jiti, writing the same
15
+ * cache file pi will later read.
16
+ *
17
+ * WHY THE WARM PRODUCES A HIT FOR THE REAL LOADER
18
+ *
19
+ * jiti keys a cache entry on the transformed file, not on the jiti instance
20
+ * (see `getCache` in `jiti/dist/jiti.cjs`):
21
+ *
22
+ * <basename(dirname(file))>-<basename(file) up to the first dot>
23
+ * + "+map" if sourceMaps + ".i" if interopDefault
24
+ * + "." + md5(file).slice(0, 8)
25
+ * + ".mjs" for an async import, ".cjs" otherwise
26
+ *
27
+ * and it validates the stored body against a trailing
28
+ * ` /* v<TRANSFORM_VERSION>-<md5(source).slice(0,16)> *\/` marker. So the warm
29
+ * has to agree with pi on three things only: the absolute path of the entry,
30
+ * the cache directory, and the transform itself. Everything else — the alias
31
+ * map, `moduleCache`, which package the jiti instance was created from — is
32
+ * outside the key AND outside the transform. Measured, not assumed: running
33
+ * this warm over the dogfood install's `dist/index.js` produced a cache file
34
+ * byte-identical to the one pi had written, both with pi's alias map and with
35
+ * no alias map at all.
36
+ *
37
+ * The marker is also the safety net. If pi ships a jiti whose transform version
38
+ * or Babel output differs, pi recomputes the marker, does not match, and simply
39
+ * re-transforms. A mismatched warm costs the install a few seconds and buys
40
+ * nothing; it can never hand pi the wrong code.
41
+ */
42
+
43
+ import { createHash, getFips } from "node:crypto";
44
+
45
+ /**
46
+ * Mirror of jiti's `utils_hash`: md5 truncated to `length` hex characters, or
47
+ * sha256 where FIPS mode forbids md5. jiti makes the same substitution, so the
48
+ * two agree on every machine.
49
+ *
50
+ * @param {string} text
51
+ * @param {number} [length]
52
+ * @returns {string}
53
+ */
54
+ export function jitiHash(text, length = 8) {
55
+ let fips = false;
56
+ try {
57
+ fips = Boolean(getFips?.());
58
+ } catch {
59
+ fips = false;
60
+ }
61
+ return createHash(fips ? "sha256" : "md5")
62
+ .update(text)
63
+ .digest("hex")
64
+ .slice(0, length);
65
+ }
66
+
67
+ /**
68
+ * The cache file jiti writes for an async import of `entry`.
69
+ *
70
+ * Mirror of jiti's `getCache` naming:
71
+ * `<basename(dirname(file))>-<basename up to the first dot>.<hash(file)>.mjs`.
72
+ * jiti hashes the POSIX-normalised absolute path, so the separators are
73
+ * converted here. The drive-letter case is left exactly as the caller resolved
74
+ * it, because jiti does not touch it either. Verified against pi's own cache
75
+ * directory: the md5 of the dogfood install's forward-slashed entry path,
76
+ * truncated to 8 characters, is the `db18768f` in the
77
+ * `dist-index.db18768f.mjs` pi had already written.
78
+ *
79
+ * @param {string} entry absolute path to the extension entry
80
+ * @returns {string}
81
+ */
82
+ export function expectedCacheFileName(entry) {
83
+ const posix = entry.split("\\").join("/");
84
+ const segments = posix.split("/");
85
+ const base = segments.pop() ?? posix;
86
+ const parent = segments.pop() ?? "";
87
+ const dot = base.indexOf(".");
88
+ const stem = dot <= 0 ? base : base.slice(0, dot);
89
+ return `${parent}-${stem}.${jitiHash(posix)}.mjs`;
90
+ }
91
+
92
+ /**
93
+ * Did the warm actually leave a cache entry pi can use?
94
+ *
95
+ * Elapsed time is not evidence. jiti can return from an import having written
96
+ * nothing, and every such path exits 0:
97
+ *
98
+ * - the cache directory is not writable. jiti's `prepareCacheDir` catches
99
+ * that, sets `fsCache` to false, and transforms in memory from then on.
100
+ * - the entry imported natively and nothing needed transforming. For an
101
+ * async-imported ESM `.js` file jiti's `evalModule` attempts a native
102
+ * import UNCONDITIONALLY, and only falls back to the transform when that
103
+ * import rejects. So the cache exists at all because native import fails in
104
+ * a real `--omit=dev` install, where the host-provided specifiers do not
105
+ * resolve. That is the same reason pi's own load transforms the bundle,
106
+ * which is why the warm and pi agree — but it is a property of the
107
+ * environment, not of this script.
108
+ * - a cache entry exists but does not match the source. jiti validates the
109
+ * body against a trailing version-and-source-hash marker, so a stale or
110
+ * truncated file is a miss for pi and the session pays the transform.
111
+ *
112
+ * Checking the file is what turns "the import returned" into "the warm's own
113
+ * jiti cached this entry, with these versions". It is not a promise about pi:
114
+ * pi-side drift shows up as a version delta between this record and pi's, which
115
+ * is a thing to read from the log, not something an install can detect.
116
+ * An absent entry always warrants a look, never a shrug.
117
+ *
118
+ * @param {object} args
119
+ * @param {string} args.cacheDir
120
+ * @param {string} args.fileName
121
+ * @param {string} args.source contents of the entry, for the marker check
122
+ * @param {object} args.fsDeps
123
+ * @param {(p: string) => boolean} args.fsDeps.existsSync
124
+ * @param {(p: string) => string} args.fsDeps.readFileSync
125
+ * @param {(p: string) => boolean} args.fsDeps.isWritable
126
+ * @returns {{ok: boolean, reason: string | null, transformVersion: string | null}}
127
+ */
128
+ export function verifyCacheEntry({ cacheDir, fileName, source, fsDeps }) {
129
+ const file = `${cacheDir}/${fileName}`;
130
+ if (!fsDeps.existsSync(file)) {
131
+ if (!fsDeps.isWritable(cacheDir)) {
132
+ return {
133
+ ok: false,
134
+ reason: `cache directory is not writable: ${cacheDir}`,
135
+ transformVersion: null,
136
+ };
137
+ }
138
+ // Deliberately generic. Several routes end here — a native import that
139
+ // needed no transform, a transform that threw before the write — and this
140
+ // check cannot tell them apart, so it does not name one.
141
+ return {
142
+ ok: false,
143
+ reason: `no cache entry was written: ${fileName}`,
144
+ transformVersion: null,
145
+ };
146
+ }
147
+ let body;
148
+ try {
149
+ body = fsDeps.readFileSync(file);
150
+ } catch (err) {
151
+ const message = err instanceof Error ? err.message : String(err);
152
+ return {
153
+ ok: false,
154
+ reason: `cache entry unreadable: ${message}`,
155
+ transformVersion: null,
156
+ };
157
+ }
158
+ const marker = /\/\* v([^-\s]+)-([0-9a-f]+) \*\/\s*$/.exec(body);
159
+ if (!marker) {
160
+ return {
161
+ ok: false,
162
+ reason: "cache entry has no jiti version marker — pi will re-transform",
163
+ transformVersion: null,
164
+ };
165
+ }
166
+ if (marker[2] !== jitiHash(source, marker[2].length)) {
167
+ return {
168
+ ok: false,
169
+ reason:
170
+ "cache entry does not match the built entry — pi will re-transform",
171
+ transformVersion: marker[1],
172
+ };
173
+ }
174
+ return { ok: true, reason: null, transformVersion: marker[1] };
175
+ }
176
+
177
+ /**
178
+ * Mirror of jiti's `prepareCacheDir` fallback branch.
179
+ *
180
+ * jiti picks `<dir of the file that created the jiti instance>/node_modules/
181
+ * .cache/jiti` when that `node_modules` exists, and `<tmpdir>/jiti` otherwise.
182
+ * pi creates its instance from `dist/core/extensions/loader.js` inside the
183
+ * installed `@earendil-works/pi-coding-agent`, which has no `node_modules`
184
+ * sibling, so pi always lands on the tmpdir branch. The warm therefore passes
185
+ * `<tmpdir>/jiti` EXPLICITLY rather than letting jiti derive a directory from
186
+ * this repo's layout: `scripts/` has no `node_modules` sibling today, but a
187
+ * script moved one level up would silently start filling a private cache that
188
+ * pi never reads.
189
+ *
190
+ * The TMPDIR dance below is jiti's, kept verbatim so the two agree on machines
191
+ * where TMPDIR is set to the current directory.
192
+ *
193
+ * @param {object} deps
194
+ * @param {() => string} deps.tmpdir
195
+ * @param {Record<string, string | undefined>} deps.env
196
+ * @param {() => string} deps.cwd
197
+ * @param {(a: string, b: string) => string} deps.join
198
+ * @returns {string}
199
+ */
200
+ export function resolveJitiCacheDir({ tmpdir, env, cwd, join }) {
201
+ let dir = tmpdir();
202
+ if (env.TMPDIR && dir === cwd() && !env.JITI_RESPECT_TMPDIR_ENV) {
203
+ const saved = env.TMPDIR;
204
+ delete env.TMPDIR;
205
+ dir = tmpdir();
206
+ env.TMPDIR = saved;
207
+ }
208
+ return join(dir, "jiti");
209
+ }
210
+
211
+ /**
212
+ * Alias every host-provided specifier to a path that cannot exist.
213
+ *
214
+ * The warm only needs the TRANSFORM of `dist/index.js`; jiti writes the cache
215
+ * entry before it evaluates the module. Evaluation then walks the bundle's
216
+ * external imports, none of which resolve outside pi. Pointing the
217
+ * host-provided ones at a stub makes that walk stop at the first one instead of
218
+ * resolving unrelated packages first, so the warm does the least work it can
219
+ * and always ends the same way. Derived from the shared host-provided list so
220
+ * it cannot drift from what `bundle-dist.mjs` keeps external.
221
+ *
222
+ * @param {readonly string[]} hostProvidedPackages
223
+ * @returns {Record<string, string>}
224
+ */
225
+ export function buildStubAliases(hostProvidedPackages) {
226
+ /** @type {Record<string, string>} */
227
+ const alias = {};
228
+ for (const name of hostProvidedPackages) {
229
+ alias[name] = STUB_TARGET;
230
+ }
231
+ return alias;
232
+ }
233
+
234
+ /** A path no filesystem resolves, so aliased imports fail immediately. */
235
+ export const STUB_TARGET = "/__pi-lens-warm-cache-stub__";
236
+
237
+ /**
238
+ * Decide whether the warm can run, and say why not when it cannot.
239
+ *
240
+ * Every reason is a skip, never a failure: `prepare` also builds `dist/` and
241
+ * downloads grammars, and those steps MUST fail loudly. Cache warming is an
242
+ * optimisation, so it runs last and reports rather than throws — the same
243
+ * posture as `scripts/setup-git-hooks.mjs` (#1804).
244
+ *
245
+ * @param {object} state
246
+ * @param {Record<string, string | undefined>} state.env
247
+ * @param {boolean} state.distEntryExists
248
+ * @param {boolean} state.jitiResolvable
249
+ * @returns {string | null} skip reason, or null to proceed
250
+ */
251
+ export function warmSkipReason({ env, distEntryExists, jitiResolvable }) {
252
+ const optOut = env.PI_LENS_SKIP_WARM_CACHE;
253
+ if (typeof optOut === "string" && optOut.length > 0) {
254
+ return "PI_LENS_SKIP_WARM_CACHE is set";
255
+ }
256
+ if (!distEntryExists) {
257
+ return "dist/index.js is missing — nothing to warm";
258
+ }
259
+ if (!jitiResolvable) {
260
+ // jiti is an optionalDependency, so `--omit=optional` (or a failed
261
+ // optional install) legitimately leaves it absent.
262
+ return "jiti is not installed — install ran without optional dependencies";
263
+ }
264
+ return null;
265
+ }
266
+
267
+ /**
268
+ * Keep the install log bounded: one line per install, newest last.
269
+ *
270
+ * @param {string[]} existingLines
271
+ * @param {string} line
272
+ * @param {number} [max]
273
+ * @returns {string[]}
274
+ */
275
+ export function appendBounded(
276
+ existingLines,
277
+ line,
278
+ max = INSTALL_LOG_MAX_LINES,
279
+ ) {
280
+ const lines = [...existingLines.filter((l) => l.trim().length > 0), line];
281
+ return lines.slice(Math.max(0, lines.length - max));
282
+ }
283
+
284
+ /** Cap for `~/.pi-lens/install.log`. */
285
+ export const INSTALL_LOG_MAX_LINES = 100;
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ // scripts/warm-loader-cache.mjs (#1926)
3
+ //
4
+ // Pay pi's jiti transform of dist/index.js at install time, so no interactive
5
+ // session pays it.
6
+ //
7
+ // pi loads an extension with jiti, which Babel-transforms the entry and caches
8
+ // the result under <tmpdir>/jiti. pi-lens's entry is a ~4MB esbuild bundle, so
9
+ // the transform is the dominant startup cost exactly once per build: the first
10
+ // session after a `git:` install or update measured 4847ms of `module import`,
11
+ // against 138ms once the cache is warm. `prepare` has just built that bundle,
12
+ // so this script transforms it through the same jiti and writes the same cache
13
+ // entry pi will read.
14
+ //
15
+ // See scripts/lib/warm-loader-cache.mjs for why the entry this writes is the
16
+ // entry pi reads — the cache key, the transform-version marker that makes a
17
+ // mismatch a miss instead of a corruption, and the measurement that proved the
18
+ // output byte-identical to pi's own.
19
+ //
20
+ // POSTURE: best-effort, last in the chain, always exit 0. `prepare` also runs
21
+ // `build:dist` and `download-grammars`, which consumers depend on and which
22
+ // MUST fail loudly. A cache warm is an optimisation and must never share their
23
+ // failure path — the same split `scripts/setup-git-hooks.mjs` makes (#1804).
24
+ import * as fs from "node:fs";
25
+ import { createRequire } from "node:module";
26
+ import * as os from "node:os";
27
+ import * as path from "node:path";
28
+ import { fileURLToPath, pathToFileURL } from "node:url";
29
+ import { HOST_PROVIDED_PACKAGES } from "./lib/host-provided-deps.mjs";
30
+ import {
31
+ appendBounded,
32
+ buildStubAliases,
33
+ expectedCacheFileName,
34
+ resolveJitiCacheDir,
35
+ verifyCacheEntry,
36
+ warmSkipReason,
37
+ } from "./lib/warm-loader-cache.mjs";
38
+
39
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
40
+ const require = createRequire(import.meta.url);
41
+
42
+ function log(message) {
43
+ process.stderr.write(`[warm-loader-cache] ${message}\n`);
44
+ }
45
+
46
+ /**
47
+ * Append one JSONL record to ~/.pi-lens/install.log.
48
+ *
49
+ * pi's install output scrolls away, so without this there is no record that the
50
+ * warm ran, was skipped, or failed. One bounded line per install keeps the
51
+ * question answerable after the fact. `PI_LENS_INSTALL_LOG` redirects the file,
52
+ * which is how the tests read the record back.
53
+ */
54
+ function record(entry) {
55
+ try {
56
+ const override = process.env.PI_LENS_INSTALL_LOG;
57
+ const file =
58
+ typeof override === "string" && override.length > 0
59
+ ? override
60
+ : path.join(os.homedir(), ".pi-lens", "install.log");
61
+ fs.mkdirSync(path.dirname(file), { recursive: true });
62
+ const existing = fs.existsSync(file)
63
+ ? fs.readFileSync(file, "utf8").split("\n")
64
+ : [];
65
+ const line = JSON.stringify({
66
+ ts: new Date().toISOString(),
67
+ event: "warm_loader_cache",
68
+ ...entry,
69
+ });
70
+ fs.writeFileSync(file, `${appendBounded(existing, line).join("\n")}\n`);
71
+ } catch {
72
+ // The log is diagnostic. Losing it must not change the install outcome.
73
+ }
74
+ }
75
+
76
+ /** Entries pi will load, read from the manifest rather than hardcoded. */
77
+ function extensionEntries() {
78
+ try {
79
+ const pkg = JSON.parse(
80
+ fs.readFileSync(path.join(root, "package.json"), "utf8"),
81
+ );
82
+ return (pkg.pi?.extensions ?? []).map((entry) => path.resolve(root, entry));
83
+ } catch {
84
+ return [];
85
+ }
86
+ }
87
+
88
+ async function loadCreateJiti() {
89
+ // `jiti/static` is the subpath pi's loader imports, and it is export-mapped
90
+ // for `import` only — `require.resolve` reports it as not exported. Resolve
91
+ // it the ESM way, and fall back to the package root for older runtimes.
92
+ for (const specifier of ["jiti/static", "jiti"]) {
93
+ try {
94
+ const url = import.meta.resolve
95
+ ? import.meta.resolve(specifier)
96
+ : pathToFileURL(require.resolve(specifier)).href;
97
+ const mod = await import(url);
98
+ if (typeof mod.createJiti === "function") return mod.createJiti;
99
+ } catch {
100
+ // Try the next specifier; absence is a skip, not a failure.
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+
106
+ /**
107
+ * The installed jiti's version, so a host jiti bump — which changes the marker
108
+ * and silently turns every warm into a cold session — is diagnosable from the
109
+ * record rather than from a bisect.
110
+ */
111
+ function jitiVersion() {
112
+ try {
113
+ const url = import.meta.resolve
114
+ ? import.meta.resolve("jiti/package.json")
115
+ : pathToFileURL(require.resolve("jiti/package.json")).href;
116
+ return JSON.parse(fs.readFileSync(fileURLToPath(url), "utf8")).version;
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /** Real-filesystem probes for `verifyCacheEntry`. */
123
+ const fsDeps = {
124
+ existsSync: (p) => fs.existsSync(p),
125
+ readFileSync: (p) => fs.readFileSync(p, "utf8"),
126
+ isWritable: (p) => {
127
+ try {
128
+ fs.accessSync(p, fs.constants.W_OK);
129
+ return true;
130
+ } catch {
131
+ return false;
132
+ }
133
+ },
134
+ };
135
+
136
+ export async function main() {
137
+ const entries = extensionEntries();
138
+ const createJiti = await loadCreateJiti();
139
+ const skip = warmSkipReason({
140
+ env: process.env,
141
+ distEntryExists:
142
+ entries.length > 0 && entries.every((e) => fs.existsSync(e)),
143
+ jitiResolvable: typeof createJiti === "function",
144
+ });
145
+ if (skip) {
146
+ log(`skipped: ${skip}`);
147
+ record({ status: "skipped", reason: skip });
148
+ return;
149
+ }
150
+
151
+ const cacheDir = resolveJitiCacheDir({
152
+ tmpdir: os.tmpdir,
153
+ env: process.env,
154
+ cwd: process.cwd,
155
+ join: path.join,
156
+ });
157
+ const alias = buildStubAliases(HOST_PROVIDED_PACKAGES);
158
+ const version = jitiVersion();
159
+
160
+ for (const entry of entries) {
161
+ const started = Date.now();
162
+ const cacheFile = expectedCacheFileName(entry);
163
+ const jiti = createJiti(pathToFileURL(entry).href, {
164
+ // Mirror pi's loader: no in-process module cache, aliases present.
165
+ // Neither affects the transform or the cache key, but staying close to
166
+ // pi's call keeps the two easy to compare.
167
+ moduleCache: false,
168
+ fsCache: cacheDir,
169
+ alias,
170
+ });
171
+ try {
172
+ // jiti writes the cache entry BEFORE it evaluates the module, so the
173
+ // warm is complete even though evaluation then fails on the first
174
+ // host-provided import — those only resolve inside pi.
175
+ await jiti.import(entry, { default: true });
176
+ } catch {
177
+ // Expected. Evaluation is not the goal; the transform is.
178
+ }
179
+ const ms = Date.now() - started;
180
+ const relative = path.relative(root, entry);
181
+
182
+ // The import returning proves nothing. Several exit-0 paths leave no usable
183
+ // entry — an unwritable cache directory, an import that never reached the
184
+ // transform, a stale file that fails jiti's marker check — so read the file
185
+ // back before claiming anything was cached.
186
+ const verdict = verifyCacheEntry({
187
+ cacheDir,
188
+ fileName: cacheFile,
189
+ source: fs.readFileSync(entry, "utf8"),
190
+ fsDeps,
191
+ });
192
+ const common = {
193
+ entry: relative,
194
+ ms,
195
+ cacheDir,
196
+ cacheFile,
197
+ jitiVersion: version,
198
+ };
199
+ if (verdict.ok) {
200
+ log(`warmed ${relative} in ${ms}ms (cache: ${cacheDir}/${cacheFile})`);
201
+ record({
202
+ status: "warmed",
203
+ ...common,
204
+ transformVersion: verdict.transformVersion,
205
+ });
206
+ continue;
207
+ }
208
+ log(`not cached: ${relative} after ${ms}ms — ${verdict.reason}`);
209
+ record({ status: "not_cached", ...common, reason: verdict.reason });
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Run `work`, absorbing any failure. Exported so a test can inject a throwing
215
+ * body and assert the process still exits 0 and still leaves a record.
216
+ */
217
+ export async function run(work = main) {
218
+ try {
219
+ await work();
220
+ } catch (err) {
221
+ const message = err instanceof Error ? err.message : String(err);
222
+ log(`failed: ${message}`);
223
+ record({ status: "failed", reason: message });
224
+ }
225
+ }
226
+
227
+ const invokedPath = process.argv[1];
228
+ const invokedDirectly =
229
+ typeof invokedPath === "string" &&
230
+ pathToFileURL(path.resolve(invokedPath)).href === import.meta.url;
231
+ if (invokedDirectly) {
232
+ await run();
233
+ }
@@ -62,6 +62,8 @@ Use these instead of writing raw YAML:
62
62
 
63
63
  `hasKind` and `hasDescendantKind` are mutually exclusive on both tools — combining them errors.
64
64
 
65
+ ⚠ `insideKind` searches ALL ancestors (`stopBy: end`) with no boundary of its own — on a deeply nested file it can escalate past the enclosing function you meant and match against an unrelated outer scope; scope with `paths` or a raw YAML `rule:` with its own `stopBy` boundary if that matters.
66
+
65
67
  ```
66
68
  # console.log only inside functions
67
69
  ast_grep_search pattern="console.log($MSG)" lang="typescript" insideKind="function_declaration"
@@ -13,7 +13,7 @@ Same `id` as a built-in overrides it. Multiple rules per file: separate with `--
13
13
  ```yaml
14
14
  id: no-foo-bar
15
15
  language: TypeScript # PascalCase — see languages below
16
- severity: warning # error | warning | info
16
+ severity: warning # error | warning | hint | info — pick by evidence, see below
17
17
  message: "Avoid foo.bar() — use baz() instead"
18
18
  note: |
19
19
  Longer explanation / fix guidance here.
@@ -21,6 +21,39 @@ rule:
21
21
  pattern: foo.bar($ARG)
22
22
  ```
23
23
 
24
+ **Pick the severity by the evidence behind the rule, not by feel:**
25
+
26
+ - **`error`** — only with a documented zero-false-positive audit in the `note`.
27
+ Only `error` maps to semantic `blocking` and stops a turn.
28
+ - **`warning`** — a real finding with a known, bounded false-positive rate.
29
+ - **`hint` / `info`** — style opinions. They render as advisory text, never
30
+ block, and lose the report budget to warnings when a report is capped.
31
+
32
+ Full policy and the `error`-promotion procedure: AGENTS.md's "Severity policy
33
+ (#1777)" section. Read it before shipping anything above `warning`.
34
+
35
+ ## Before promoting to error
36
+
37
+ Three things must all be true, and the rule's `note` must record them:
38
+
39
+ 1. **A multi-corpus false-positive census**, not a single-tree count. Run the
40
+ rule over pi-lens's `clients/`/`tests/` plus at least one real external
41
+ codebase of the kind the rule targets, classify every hit, and put the
42
+ table in the note.
43
+ 2. **Structural narrowing before exemption.** Suppress a legitimate idiom
44
+ with a relational constraint (`inside`/`has`/`follows`, scoped path
45
+ globs), not with prose telling readers to ignore the hit. Narrow first —
46
+ don't reach for a lower tier as the easy way out. `no-non-null-assertion`
47
+ was demoted to `hint` (`0124608a`), then reverted back to `warning` once
48
+ the same false positives were closed with a structural exclusion instead
49
+ (`b3e1fd79`).
50
+ 3. **Self-scan wiring.** Tag the rule `metadata.category: pi-lens-self-scan`
51
+ so `npm run astgrep:self-scan` holds this tree at zero in CI. An `error`
52
+ rule that never runs against pi-lens's own source is an unaudited claim.
53
+
54
+ If the post-narrowing residual is still tens of legitimate hits, stop and
55
+ report the numbers instead of shipping at `error`.
56
+
24
57
  ## Language values
25
58
 
26
59
  `TypeScript` `JavaScript` `Python` `Go` `Rust` `Java` `C` `Cpp` `CSharp` `Kotlin` `Ruby` `Php`
@@ -69,6 +102,13 @@ constraints: # metavariable regex constraints work
69
102
  recursive descendant/ancestor search add `stopBy: end`. This is the #1 migration
70
103
  gotcha — see the `has` note in `reference.md`.
71
104
 
105
+ ⚠ **`stopBy: end` alone is not a boundary.** It's a search-depth control — how far
106
+ the walk searches — not a stop condition; the `any:` kind list only decides what
107
+ CAN satisfy the match, not where the walk halts. To scope a relation to the
108
+ nearest enclosing function (or any other boundary), give `stopBy` its own rule.
109
+ See "Scoping to the nearest enclosing X" in `reference.md` (#1794 F1 — this bug
110
+ shipped twice in one window).
111
+
72
112
  ## YAML quoting — REQUIRED (js-yaml will reject the rule otherwise)
73
113
 
74
114
  The parser is a real YAML parser, so unquoted special chars throw and the rule is
@@ -104,13 +144,50 @@ The parser is a real YAML parser, so unquoted special chars throw and the rule i
104
144
 
105
145
  `reference.md` (same directory) covers: ReDoS-safe regex authoring, node-text
106
146
  string-escape quirks, `has`/`inside` `stopBy` defaults (and when to override
107
- them), the `-js` twin dedup behavior (#657), boolean-parameter matching
108
- across the TS/JS grammars, and precision-over-recall heuristics for
147
+ them), scoping a relation to the nearest enclosing node with a `stopBy`
148
+ boundary rule, the `-js` twin dedup behavior (#657), boolean-parameter
149
+ matching across the TS/JS grammars, and precision-over-recall heuristics for
109
150
  denylist-shaped rules. Read it when a rule isn't matching (or over-matching)
110
151
  the way you expect, or before shipping a `regex`/`has`-heavy rule.
111
152
 
153
+ ## Testing a suppression
154
+
155
+ Every relational suppression (a `has`/`inside`/`not` exclusion for a
156
+ legitimate idiom) needs two fixtures per bound metavariable, not one:
157
+
158
+ 1. **A same-binding valid case** — the idiom the exclusion is meant to
159
+ suppress, with the metavariable bound consistently (same receiver, same
160
+ key).
161
+ 2. **A mutation-guard invalid case** — the same shape with the binding
162
+ broken: a different key, a different receiver, or a boundary-crossing
163
+ lookalike (the guard lives in the wrong scope). This proves the
164
+ exclusion checks the BINDING, not just the pattern's presence somewhere
165
+ in the file.
166
+
167
+ `rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml` is the model: alongside the
168
+ get-after-has/pop-after-length valid cases, it fixtures a different-key
169
+ guard, a different-map guard, and an outer-function guard that must NOT
170
+ suppress an inner closure — each one red if the corresponding binding check
171
+ is deleted.
172
+
173
+ Corpus silence is not evidence. A valve with zero corpus hits needs
174
+ adversarial fixtures MORE, not less — `redundant-unsafe-function` targets
175
+ Rust, so it has no pi-lens corpus to census against, and its `# Safety`
176
+ valve still shipped an unbounded backward scan that over-suppressed real
177
+ detections, caught only by adversarial fixtures in review (`00284bcc`). A
178
+ documented blind spot (an idiom the rule knowingly can't distinguish from a
179
+ bug) becomes an `invalid`-direction fixture with a comment naming the gap,
180
+ never silence.
181
+
182
+ Regenerate the catalog doc with `npm run docs:rule-catalogs` after adding or
183
+ changing a rule — never hand-edit the generated catalog.
184
+
112
185
  ## Validating a candidate rule against the REAL engine (not the warm MCP cache)
113
186
 
187
+ Live-binary discipline (AGENTS.md shape 16) applies here too: verify parsing
188
+ and match behavior against a real `ast-grep` run before you write it into a
189
+ rule note or a test fixture — a hand-written fixture pins a guess, not a fact.
190
+
114
191
  ```
115
192
 
116
193
  # inspect how a PATTERN parses → find the node kind you actually need
@@ -92,6 +92,47 @@ grammars, and precision-over-recall heuristics for denylist-shaped rules.
92
92
  the partition unique; flag only a single quantified atom inside the group).
93
93
  ```
94
94
 
95
+ ## Scoping to the nearest enclosing X (#1794 F1)
96
+
97
+ `stopBy` controls how far the ancestor/descendant walk searches. It is not a
98
+ stop CONDITION. An `any:` kind list inside `inside`/`has` decides which
99
+ nodes the walk is ALLOWED to match — it does not tell the walk where to
100
+ halt. `stopBy: end` alone searches every ancestor up to the file root, so a
101
+ guard several scopes out can suppress a finding inside an unrelated closure.
102
+
103
+ To scope a relation to the nearest enclosing function (or any other
104
+ boundary), give `stopBy` its OWN rule — the same kind list used as the
105
+ match target — so the walk stops at the first satisfying ancestor and can
106
+ never escalate past it:
107
+
108
+ ```yaml
109
+ inside:
110
+ stopBy: # the BOUNDARY: halts the walk here
111
+ any:
112
+ - kind: function_declaration
113
+ - kind: method_definition
114
+ - kind: arrow_function
115
+ - kind: function_expression
116
+ any: # the MATCH TARGET: same list, different job
117
+ - kind: function_declaration
118
+ - kind: method_definition
119
+ - kind: arrow_function
120
+ - kind: function_expression
121
+ has:
122
+ stopBy: end
123
+ pattern: $M.has($K)
124
+ ```
125
+
126
+ This is the fix for #1794 F1: an earlier `no-non-null-assertion` exclusion
127
+ used `stopBy: end` with only the `any:` kind list as the match target, so a
128
+ `.has()`/`.length` guard in an OUTER function suppressed a closure's `!`
129
+ several scopes in. The same defect shape — `stopBy: end` mistaken for a
130
+ boundary — shipped twice in the same 2026-08-20 window: once here, and once
131
+ in `redundant-unsafe-function`'s `# Safety` valve (an unbounded backward
132
+ comment scan, fixed in `00284bcc`). See
133
+ `rules/ast-grep-rules/rules/no-non-null-assertion.yml` for the full working
134
+ rule.
135
+
95
136
  ## Matching things a pattern can't express (#305)
96
137
 
97
138
  ```