@herbertgao/pi-extensions 2026.8.5 → 2026.8.7

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 (389) hide show
  1. package/README.md +5 -5
  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 +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -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 +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  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 +22 -0
  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-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -9,12 +9,18 @@
9
9
  * Docs: https://knip.dev/
10
10
  */
11
11
  import { createSubsystemLogger } from "./extension-log.js";
12
+ import { incrementDegradationCount } from "./degradation-ledger.js";
12
13
  import * as fs from "node:fs";
13
14
  import * as path from "node:path";
14
15
  import { getProjectDataDir } from "./file-utils.js";
15
16
  import { findNearestMarkerRoot } from "./path-utils.js";
16
17
  import { safeSpawnAsync } from "./safe-spawn.js";
17
- import { createAvailabilityChecker, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
18
+ import { createAvailabilityChecker, findManagedNodeToolBinary, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
19
+ import { createAvailabilityLatch, describeUnavailability, } from "./dispatch/runners/utils/availability-policy.js";
20
+ import { spawnFailedWithNoOutput } from "./dispatch/runners/utils/spawn-outcome.js";
21
+ import { formatToolFailure } from "./dispatch/runners/utils/tool-failure.js";
22
+ import { findLocalBinUpwards } from "./package-manager.js";
23
+ import { logSessionStart } from "./sessionstart-logger.js";
18
24
  const EMPTY_RESULT = {
19
25
  success: false,
20
26
  issues: [],
@@ -56,13 +62,118 @@ export function readOverridePinnedPackageNames(targetDir) {
56
62
  collectKeys(pkg.pnpm?.overrides);
57
63
  return names;
58
64
  }
65
+ /**
66
+ * knip's own config-file names, in knip's discovery order (knip 6
67
+ * `configFilesLookup`). Used for REPORTING which config a run resolved — the
68
+ * runner never passes `--config`, so knip still does its own discovery from the
69
+ * spawn cwd. Reading the same list here keeps the recorded answer honest
70
+ * instead of guessing "knip.json or nothing".
71
+ */
72
+ const KNIP_CONFIG_FILENAMES = [
73
+ "knip.json",
74
+ "knip.jsonc",
75
+ "knip.ts",
76
+ "knip.js",
77
+ "knip.config.ts",
78
+ "knip.config.js",
79
+ ".knip.json",
80
+ ".knip.jsonc",
81
+ ".knip.ts",
82
+ ".knip.js",
83
+ ];
84
+ /**
85
+ * The knip config this project ships, relative to `targetDir`, or null when the
86
+ * project ships none (knip then runs on its built-in defaults).
87
+ *
88
+ * `package.json` is reported last and only when it carries a `knip` field —
89
+ * knip reads that field as a config source, and a project that configures knip
90
+ * there is just as configured as one with a `knip.json`.
91
+ */
92
+ export function resolveProjectKnipConfig(targetDir) {
93
+ for (const name of KNIP_CONFIG_FILENAMES) {
94
+ if (fs.existsSync(path.join(targetDir, name)))
95
+ return name;
96
+ }
97
+ try {
98
+ const pkg = JSON.parse(fs.readFileSync(path.join(targetDir, "package.json"), "utf-8"));
99
+ if (pkg && typeof pkg === "object" && pkg.knip !== undefined) {
100
+ return "package.json#knip";
101
+ }
102
+ }
103
+ catch {
104
+ // No/malformed package.json — "no project config" is the honest answer.
105
+ }
106
+ return null;
107
+ }
108
+ /**
109
+ * The knip the PROJECT itself would run: its own `node_modules/.bin/knip`,
110
+ * walking up so a workspace package finds the monorepo's hoisted copy. Null
111
+ * when the project installs no knip and pi-lens's managed shim is all there is.
112
+ *
113
+ * A project that installs knip pins the version its config is written against,
114
+ * and that version is what the project's `npx knip` / `npm run knip` runs.
115
+ * Preferring pi-lens's managed shim instead makes the lens report a DIFFERENT
116
+ * tool's verdict under the project's config (#1721): a managed knip 6.4.1
117
+ * flagged 62 unused type exports on a tree that knip 6.32.2 leaves clean, and
118
+ * every one of those flags is destructive advice.
119
+ *
120
+ * The walk is the shared `findLocalBinUpwards`, the same one behind jscpd's
121
+ * (`clients/jscpd-client.ts:269`) and madge's
122
+ * (`clients/dependency-checker.ts:420`) resolution, rather than a fourth copy.
123
+ * It stops at the filesystem half deliberately: the global-bin step those two
124
+ * add spawns a probe per package manager, and this runs on every analyze().
125
+ */
126
+ export function resolveProjectKnipBinary(targetDir) {
127
+ return findLocalBinUpwards("knip", targetDir) ?? null;
128
+ }
129
+ /**
130
+ * Classify a resolved shim for the toolchain record: inside the project tree,
131
+ * elsewhere on the user's machine, or pi-lens's own managed/PATH fallback.
132
+ */
133
+ export function classifyKnipBinary(binary, targetDir) {
134
+ if (binary === null)
135
+ return "managed-or-path";
136
+ const relative = path.relative(path.resolve(targetDir), path.resolve(binary));
137
+ return relative === "" || relative.startsWith("..") || path.isAbsolute(relative)
138
+ ? "global"
139
+ : "project";
140
+ }
141
+ /**
142
+ * The version of the knip package behind a resolved shim, read from disk (no
143
+ * spawn — a `--version` probe per project root would add a process to a hot
144
+ * path for a telemetry field). Returns null for a bare PATH command, whose
145
+ * package location is unknown.
146
+ */
147
+ function readKnipShimVersion(binary) {
148
+ if (!path.isAbsolute(binary))
149
+ return null;
150
+ const binDir = path.dirname(binary);
151
+ if (path.basename(binDir) !== ".bin")
152
+ return null;
153
+ try {
154
+ const pkg = JSON.parse(fs.readFileSync(path.join(path.dirname(binDir), "knip", "package.json"), "utf-8"));
155
+ const version = pkg.version;
156
+ return typeof version === "string" ? version : null;
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ /** Distinct toolchain records kept per client instance (bounded telemetry). */
163
+ const MAX_RECORDED_TOOLCHAINS = 32;
59
164
  // --- Client ---
60
165
  export class KnipClient {
61
166
  knipAvailability = createAvailabilityChecker("knip", ".cmd", ["--version"], {
62
167
  environment: (cwd) => getManagedToolEnvironment("knip", cwd),
63
168
  unclassifiedFailureOutcome: "missing",
169
+ fastPath: () => findManagedNodeToolBinary("knip"),
64
170
  });
65
- knipAvailable = null;
171
+ /**
172
+ * Client-side memo. Only a DURABLE verdict is latched — a transient probe
173
+ * failure expires, so an installed knip becomes available again without a
174
+ * host restart (#1467).
175
+ */
176
+ availabilityLatch = createAvailabilityLatch();
66
177
  knipCommand = "knip";
67
178
  ensureInFlight = null;
68
179
  log;
@@ -80,11 +191,22 @@ export class KnipClient {
80
191
  * in-flight promise; completing clears the slot.
81
192
  */
82
193
  inFlight = new Map();
194
+ /** Last successful result per project and runtime content generation. */
195
+ completedByProject = new Map();
196
+ /**
197
+ * (project root, binary, config) triples already recorded, so the toolchain
198
+ * row is written once per distinct resolution instead of once per run.
199
+ */
200
+ recordedToolchains = new Set();
83
201
  constructor(verbose = false) {
84
202
  this.log = verbose
85
203
  ? createSubsystemLogger("knip")
86
204
  : () => { };
87
205
  }
206
+ /** Re-arm content-keyed reuse at the session boundary. */
207
+ resetSessionState() {
208
+ this.completedByProject.clear();
209
+ }
88
210
  /**
89
211
  * Find the nearest directory with a project/knip config marker.
90
212
  *
@@ -104,12 +226,17 @@ export class KnipClient {
104
226
  return findNearestMarkerRoot(startDir, ["package.json", "knip.json", "knip.ts", "knip.config.js", "knip.config.ts"], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
105
227
  }
106
228
  /**
107
- * Check if knip CLI is available, auto-install if not
229
+ * Check if knip CLI is available, auto-install if not.
230
+ *
231
+ * The memo returns `null` when the last verdict was transient and its
232
+ * cooldown has expired, which re-enters the probe. That is the difference
233
+ * between "knip is missing" (a fact worth caching) and "the probe timed out"
234
+ * (a moment worth retrying).
108
235
  */
109
236
  async ensureAvailable() {
110
- // Fast path: already checked
111
- if (this.knipAvailable !== null)
112
- return this.knipAvailable;
237
+ const memo = this.availabilityLatch.read();
238
+ if (memo !== null)
239
+ return memo;
113
240
  if (this.ensureInFlight)
114
241
  return this.ensureInFlight;
115
242
  this.ensureInFlight = this.doEnsureAvailable();
@@ -121,11 +248,39 @@ export class KnipClient {
121
248
  }
122
249
  }
123
250
  async doEnsureAvailable() {
124
- const resolved = await resolveAvailableOrInstall(this.knipAvailability, "knip", process.cwd());
125
- this.knipAvailable = resolved !== null;
126
- if (resolved)
251
+ const cwd = process.cwd();
252
+ const resolved = await resolveAvailableOrInstall(this.knipAvailability, "knip", cwd);
253
+ if (resolved !== null) {
127
254
  this.knipCommand = resolved;
128
- return this.knipAvailable;
255
+ this.availabilityLatch.noteAvailable();
256
+ return true;
257
+ }
258
+ const verdict = this.knipAvailability.getVerdict(cwd);
259
+ this.availabilityLatch.noteUnavailable(verdict.outcome ?? "missing", verdict.cause ?? "not-found");
260
+ return false;
261
+ }
262
+ /**
263
+ * The single place a knip-unavailable result is worded. A transient probe
264
+ * failure must never be reported as "install knip" — knip is on disk.
265
+ */
266
+ unavailableResult() {
267
+ const verdict = this.knipAvailability.getVerdict(process.cwd());
268
+ const transient = verdict.outcome === "transient";
269
+ const retryAfterMs = verdict.retryAtMs
270
+ ? Math.max(0, verdict.retryAtMs - Date.now())
271
+ : undefined;
272
+ return {
273
+ ...EMPTY_RESULT,
274
+ failureKind: transient ? "unavailable-transient" : "unavailable-missing",
275
+ summary: describeUnavailability({
276
+ tool: "Knip",
277
+ installHint: "npm install -D knip",
278
+ outcome: verdict.outcome,
279
+ cause: verdict.cause,
280
+ elapsedMs: verdict.elapsedMs,
281
+ retryAfterMs,
282
+ }),
283
+ };
129
284
  }
130
285
  /**
131
286
  * Run knip analysis on the project.
@@ -137,8 +292,13 @@ export class KnipClient {
137
292
  *
138
293
  * Re-entrancy safe: concurrent calls resolving to the same project
139
294
  * root share a single knip process via `inFlight`.
295
+ *
296
+ * Successful memo hits validate package.json and the resolved Knip config
297
+ * with bounded metadata checks. The two statSync calls replace a 10-23
298
+ * second project scan. Source-only external edits remain undetected until
299
+ * pi observes a write or the session resets.
140
300
  */
141
- async analyze(cwd, _ignore) {
301
+ async analyze(cwd, _ignore, options = {}) {
142
302
  const targetDir = this.resolveProjectRoot(cwd || process.cwd());
143
303
  if (!targetDir) {
144
304
  // No package.json / knip config anywhere up the tree. Running knip
@@ -151,33 +311,97 @@ export class KnipClient {
151
311
  summary: "No project root found; knip skipped",
152
312
  };
153
313
  }
154
- if (!(await this.ensureAvailable())) {
155
- return {
156
- ...EMPTY_RESULT,
157
- summary: "Knip not available. Install with: npm install -D knip",
158
- };
159
- }
160
314
  const key = path.resolve(targetDir);
315
+ const completed = this.completedByProject.get(key);
316
+ if (options.projectSeq !== undefined &&
317
+ completed?.projectSeq === options.projectSeq &&
318
+ this.matchesMemoSignal(targetDir, completed.signal)) {
319
+ this.log(`Analysis cache hit for ${key} at projectSeq ${options.projectSeq}`);
320
+ return { ...completed.result, execution: "cache" };
321
+ }
322
+ // A project that ships its own knip needs no managed install and no
323
+ // managed probe — the shim is already on disk, and gating on
324
+ // `ensureAvailable()` would tell such a project to "npm install -D knip"
325
+ // whenever the MANAGED copy is missing (#1721).
326
+ if (resolveProjectKnipBinary(targetDir) === null &&
327
+ !(await this.ensureAvailable())) {
328
+ return this.unavailableResult();
329
+ }
161
330
  const existing = this.inFlight.get(key);
162
331
  if (existing) {
163
332
  this.log(`Analysis already in flight for ${key}; sharing result`);
164
333
  return existing;
165
334
  }
166
- const promise = this.runAnalyze(key).finally(() => {
335
+ const promise = this.runAnalyze(key)
336
+ .then((result) => {
337
+ const executed = { ...result, execution: "executed" };
338
+ if (result.success && options.projectSeq !== undefined) {
339
+ this.completedByProject.set(key, {
340
+ projectSeq: options.projectSeq,
341
+ result: executed,
342
+ signal: this.readMemoSignal(key),
343
+ });
344
+ }
345
+ return executed;
346
+ })
347
+ .finally(() => {
167
348
  this.inFlight.delete(key);
168
349
  });
169
350
  this.inFlight.set(key, promise);
170
351
  return promise;
171
352
  }
353
+ readMemoFileSignal(filePath) {
354
+ try {
355
+ const stat = fs.statSync(filePath);
356
+ return { path: filePath, mtimeMs: stat.mtimeMs, size: stat.size };
357
+ }
358
+ catch {
359
+ return null;
360
+ }
361
+ }
362
+ readMemoSignal(targetDir) {
363
+ const config = resolveProjectKnipConfig(targetDir);
364
+ return {
365
+ packageJson: this.readMemoFileSignal(path.join(targetDir, "package.json")),
366
+ config: config && config !== "package.json#knip"
367
+ ? this.readMemoFileSignal(path.join(targetDir, config))
368
+ : null,
369
+ };
370
+ }
371
+ matchesMemoSignal(targetDir, signal) {
372
+ const current = {
373
+ packageJson: this.readMemoFileSignal(path.join(targetDir, "package.json")),
374
+ config: signal.config ? this.readMemoFileSignal(signal.config.path) : null,
375
+ };
376
+ return (JSON.stringify(current.packageJson) === JSON.stringify(signal.packageJson) &&
377
+ JSON.stringify(current.config) === JSON.stringify(signal.config));
378
+ }
172
379
  async runAnalyze(targetDir) {
173
380
  // Cache dir is routed through pi-lens's project-data-dir convention (NOT
174
381
  // knip's own default `./node_modules/.cache/knip`) so it lives alongside
175
382
  // every other project cache (see cache-manager.ts, call-graph.ts) and is
176
383
  // covered by the existing `.pi-lens/` gitignore entry.
177
384
  //
178
- // Caveat (per knip's docs): a cached run does NOT pick up newly-added
179
- // `.gitignore` files automatically — the cache must be deleted to detect
180
- // them. Not auto-handled here; this is a documented tradeoff, not a bug.
385
+ // Documented cache-invalidation gaps (both live in knip's own cache, not
386
+ // in pi-lens):
387
+ //
388
+ // 1. `.gitignore` (per knip's docs): a cached run does NOT pick up
389
+ // newly-added `.gitignore` files automatically — the cache must be
390
+ // deleted to detect them. Not auto-handled here; documented tradeoff.
391
+ // 2. Consumer-side imports (#1630): knip's glob cache records the mtime of
392
+ // every directory that CONTRIBUTED a matched path, and revalidates
393
+ // against those. A directory that matched nothing at cache time is not
394
+ // recorded, so a file later added to it is invisible to the cached
395
+ // glob. A new test that imports an export therefore never reaches the
396
+ // graph, and knip keeps reporting that export as unused — a stale
397
+ // verdict pi-lens then renders as fresh. Fixed upstream in knip 6.28.0
398
+ // (glob cache now tracks every directory it reads), but the managed
399
+ // install floats and older versions are common in the field, so
400
+ // `pruneStaleGlobCache()` below drops the glob cache on every run
401
+ // regardless of version. Measured on this repo with knip 6.4.1:
402
+ // uncached 3.3s, fully cached 1.3s, glob-cache-dropped 1.4s. The glob
403
+ // cache is worth ~0.1s of the ~2.0s the cache saves, so dropping it
404
+ // buys correctness for almost nothing.
181
405
  const cacheLocation = path.join(getProjectDataDir(targetDir), "cache", "knip");
182
406
  // knip (verified against 6.26.0) silently fails to persist the cache when
183
407
  // `--cache-location` points at a directory that doesn't exist yet: its
@@ -193,6 +417,7 @@ export class KnipClient {
193
417
  catch (err) {
194
418
  this.log(`Failed to pre-create knip cache dir ${cacheLocation}: ${err}`);
195
419
  }
420
+ this.pruneStaleGlobCache(cacheLocation);
196
421
  const args = [
197
422
  "--reporter=json",
198
423
  "--include",
@@ -205,7 +430,14 @@ export class KnipClient {
205
430
  "--cache-location",
206
431
  cacheLocation,
207
432
  ];
208
- const result = await safeSpawnAsync(this.knipCommand, args, {
433
+ // Project toolchain first. #1199 already put `<targetDir>/node_modules/.bin`
434
+ // at the front of the child PATH for exactly this preference, but the spawn
435
+ // command is an ABSOLUTE managed path, so PATH order never got a say
436
+ // (#1721). Resolving the command here is what makes that intent real.
437
+ const projectBinary = resolveProjectKnipBinary(targetDir);
438
+ const command = projectBinary ?? this.knipCommand;
439
+ this.recordToolchain(targetDir, command, classifyKnipBinary(projectBinary, targetDir));
440
+ const result = await safeSpawnAsync(command, args, {
209
441
  timeout: ANALYSIS_TIMEOUT_MS,
210
442
  cwd: targetDir,
211
443
  env: await getManagedToolEnvironment("knip", targetDir),
@@ -217,13 +449,55 @@ export class KnipClient {
217
449
  summary: `Error: ${result.error.message}`,
218
450
  };
219
451
  }
220
- // Knip exits 0 on success (even with issues), 1 on errors
452
+ // Empirical exit-code table (knip 6.4.1, verified live for #1736): 0 =
453
+ // clean run, JSON stdout `{"issues":[]}`; 1 = clean run WITH findings,
454
+ // JSON stdout `{"issues":[...]}`; 2 = config/load error, empty or
455
+ // non-JSON stdout with the error on stderr. A genuinely clean run's
456
+ // stdout is therefore NEVER empty — knip always prints at least
457
+ // `{"issues":[]}` on exit 0. Empty stdout only ever shows up paired
458
+ // with a NONZERO exit (a broken shim, a crash, or knip's own load
459
+ // error), so `status !== 0 && !output.trim()` is unambiguous evidence
460
+ // of a failed run, never a clean one.
221
461
  const output = result.stdout || "";
222
- this.log(`Knip output length: ${output.length}`);
462
+ this.log(`Knip output length: ${output.length}, exit status: ${result.status}`);
223
463
  if (output.length < 500) {
224
464
  this.log(`Knip output sample: ${output}`);
225
465
  }
226
466
  if (!output.trim()) {
467
+ // Reuses the SAME discriminator every dispatch/runners linter uses
468
+ // (`spawnFailedWithNoOutput`, `clients/dispatch/runners/utils/
469
+ // spawn-outcome.ts`) rather than a parallel hand-rolled check — a
470
+ // nonzero exit with nothing to parse is never a clean run, here or
471
+ // there.
472
+ if (spawnFailedWithNoOutput(result, output)) {
473
+ // #1816: one shared wording, one truncation, signal named. The
474
+ // binary-source discriminator (#1721's whole point — WHICH knip
475
+ // ran) survives as a named field rather than as prose.
476
+ const reason = formatToolFailure({
477
+ tool: "knip",
478
+ status: result.status,
479
+ signal: result.signal,
480
+ stderr: result.stderr,
481
+ fields: {
482
+ command,
483
+ source: classifyKnipBinary(projectBinary, targetDir),
484
+ },
485
+ });
486
+ this.log(reason);
487
+ incrementDegradationCount({
488
+ kind: "runner-empty-result",
489
+ subject: "knip",
490
+ reason,
491
+ });
492
+ return {
493
+ ...EMPTY_RESULT,
494
+ summary: `Knip exited ${result.status} with no output; run skipped`,
495
+ };
496
+ }
497
+ // status === 0 with empty stdout has never been observed against a
498
+ // real knip binary (a genuine clean run still prints
499
+ // `{"issues":[]}`), but a defensive fallback is cheap insurance
500
+ // against a future knip release that changes this.
227
501
  return {
228
502
  ...EMPTY_RESULT,
229
503
  success: true,
@@ -232,6 +506,69 @@ export class KnipClient {
232
506
  }
233
507
  return this.dropOverridePinnedDeps(this.parseOutput(output), targetDir);
234
508
  }
509
+ /**
510
+ * Record WHICH knip a run used and WHICH config it ran under (#1721).
511
+ *
512
+ * The dogfood failure this fixes was invisible from the outside: the lens
513
+ * reported 62 unused type exports and the project's own `npx knip` reported
514
+ * none, with nothing in any log to say the two ran different binaries. This
515
+ * row is that missing evidence — it names the shim, its version, the config
516
+ * knip will discover, and whether the shim came from the project or from
517
+ * pi-lens.
518
+ *
519
+ * Bounded twice over: one row per distinct (project root, binary, config)
520
+ * triple, and at most `MAX_RECORDED_TOOLCHAINS` distinct triples per client.
521
+ * Steady-state cost is therefore one line per project per session, not one
522
+ * per turn_end.
523
+ */
524
+ recordToolchain(targetDir, command, source) {
525
+ const config = resolveProjectKnipConfig(targetDir);
526
+ const key = `${targetDir} ${command} ${config ?? ""}`;
527
+ if (this.recordedToolchains.has(key))
528
+ return;
529
+ if (this.recordedToolchains.size >= MAX_RECORDED_TOOLCHAINS)
530
+ return;
531
+ this.recordedToolchains.add(key);
532
+ const version = readKnipShimVersion(command);
533
+ logSessionStart(`knip toolchain ${targetDir}: binary=${command}` +
534
+ `${version ? ` version=${version}` : ""}` +
535
+ ` source=${source}` +
536
+ ` config=${config ?? "none (knip defaults)"}`);
537
+ }
538
+ /**
539
+ * Delete knip's glob cache (`glob-<version>.cache`) before every run (#1630).
540
+ *
541
+ * knip revalidates a cached glob against the mtimes of the directories that
542
+ * contributed a matched path. Directories that matched nothing are never
543
+ * recorded, so files later added to them stay invisible and knip keeps
544
+ * reporting an export as unused after a new consumer imports it. Removing
545
+ * this one file forces a fresh file walk while the module and plugin caches —
546
+ * which carry nearly all of the cache's speed — survive.
547
+ *
548
+ * Failures are logged and ignored: a glob cache we could not delete degrades
549
+ * to the pre-#1630 behavior, which is stale but not fatal.
550
+ */
551
+ pruneStaleGlobCache(cacheLocation) {
552
+ let entries;
553
+ try {
554
+ entries = fs.readdirSync(cacheLocation);
555
+ }
556
+ catch (err) {
557
+ this.log(`Failed to read knip cache dir ${cacheLocation}: ${err}`);
558
+ return;
559
+ }
560
+ for (const entry of entries) {
561
+ if (!entry.startsWith("glob-") || !entry.endsWith(".cache"))
562
+ continue;
563
+ try {
564
+ fs.rmSync(path.join(cacheLocation, entry), { force: true });
565
+ this.log(`Pruned knip glob cache ${entry}`);
566
+ }
567
+ catch (err) {
568
+ this.log(`Failed to prune knip glob cache ${entry}: ${err}`);
569
+ }
570
+ }
571
+ }
235
572
  /**
236
573
  * Drop `dependency`/`devDependency` issues for a package that's also
237
574
  * referenced as an npm `overrides` (or Yarn `resolutions` / pnpm
@@ -61,6 +61,7 @@ export const LANGUAGE_POLICY = {
61
61
  gleam: { lspCapable: true },
62
62
  ocaml: { lspCapable: true },
63
63
  clojure: { lspCapable: true },
64
+ cue: { lspCapable: true },
64
65
  terraform: { lspCapable: true },
65
66
  terragrunt: { lspCapable: false },
66
67
  nix: { lspCapable: true },
@@ -119,7 +120,7 @@ const PRIMARY_DISPATCH_GROUPS = {
119
120
  },
120
121
  yaml: {
121
122
  mode: "all",
122
- runnerIds: ["lsp", "yamllint", "trivy-config", "helm-lint"],
123
+ runnerIds: ["lsp", "yamllint", "trivy-config", "helm-lint", "helm-render"],
123
124
  filterKinds: ["yaml"],
124
125
  },
125
126
  sql: {
@@ -183,7 +184,9 @@ const PRIMARY_DISPATCH_GROUPS = {
183
184
  haskell: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["haskell"] },
184
185
  "helm-template": {
185
186
  mode: "all",
186
- runnerIds: ["helm-lint"],
187
+ // helm-render is opt-in (`helm.renderValidation.enabled`) and skips itself
188
+ // when the switch is absent, so it costs nothing when unconfigured (#1283).
189
+ runnerIds: ["helm-lint", "helm-render"],
187
190
  filterKinds: ["helm-template"],
188
191
  },
189
192
  elixir: {
@@ -198,6 +201,9 @@ const PRIMARY_DISPATCH_GROUPS = {
198
201
  },
199
202
  ocaml: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["ocaml"] },
200
203
  clojure: { mode: "fallback", runnerIds: ["lsp"], filterKinds: ["clojure"] },
204
+ // #1522: cuelsp reports syntax/parse errors only; cue-vet covers the
205
+ // evaluation-error class the language server deliberately doesn't publish.
206
+ cue: { mode: "all", runnerIds: ["lsp", "cue-vet"], filterKinds: ["cue"] },
201
207
  terraform: {
202
208
  mode: "all",
203
209
  runnerIds: ["lsp", "tflint", "trivy-config"],
@@ -35,6 +35,7 @@ const PROJECT_MARKERS_BY_KIND = {
35
35
  swift: ["Package.swift"],
36
36
  dart: ["pubspec.yaml"],
37
37
  elixir: ["mix.exs"],
38
+ cue: ["cue.mod"],
38
39
  gleam: ["gleam.toml"],
39
40
  terraform: [".terraform.lock.hcl"],
40
41
  terragrunt: TERRAGRUNT_FILENAMES,
@@ -80,6 +81,7 @@ const ROOT_MARKERS_BY_KIND = {
80
81
  swift: ["Package.swift"],
81
82
  dart: ["pubspec.yaml"],
82
83
  elixir: ["mix.exs"],
84
+ cue: ["cue.mod", ".git"],
83
85
  gleam: ["gleam.toml"],
84
86
  terraform: [".terraform.lock.hcl"],
85
87
  terragrunt: TERRAGRUNT_FILENAMES,