@herbertgao/pi-extensions 2026.8.4 → 2026.8.5

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 (288) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  13. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  14. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  15. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  16. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  20. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  21. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  22. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  30. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  31. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  32. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  33. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  34. package/node_modules/pi-lens/README.md +13 -0
  35. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  36. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  37. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  39. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  40. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  41. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  42. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  43. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  44. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  45. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  46. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  47. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  51. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  52. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  53. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  54. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  56. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  57. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  58. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  59. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  60. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  63. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  64. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  66. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  67. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  68. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  69. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  70. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  71. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  72. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  75. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  99. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  100. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  101. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  102. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  103. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  104. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  105. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  106. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  107. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  108. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  109. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  111. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  112. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  113. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  114. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  116. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  117. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  118. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  119. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  120. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  121. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  122. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  123. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  124. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  125. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  126. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  127. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  128. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  129. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  130. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  131. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  132. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  134. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  135. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  136. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  137. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  138. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  139. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  140. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  141. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  142. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  143. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  144. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  145. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  146. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  147. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  148. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  149. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  150. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  151. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  152. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  153. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  154. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  155. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  157. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  158. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  159. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  160. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  161. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  162. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  163. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  164. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  165. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  166. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  167. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  168. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  169. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  170. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  171. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  172. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  173. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  174. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  175. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  177. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  178. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  179. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  180. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  181. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  182. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  183. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  184. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  185. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  186. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  187. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  188. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  189. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  190. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  191. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  192. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  193. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  194. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  196. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  197. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  198. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  199. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  200. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  201. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  202. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  203. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  204. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  205. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  210. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  211. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  212. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  213. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  214. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  215. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  216. package/node_modules/pi-lens/dist/index.js +49361 -36888
  217. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  218. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  219. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  220. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  221. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  222. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  223. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  224. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  225. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  226. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  227. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  228. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  229. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  230. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  231. package/node_modules/pi-lens/docs/fable.md +27 -0
  232. package/node_modules/pi-lens/docs/features.md +31 -2
  233. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  234. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  235. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  236. package/node_modules/pi-lens/docs/mcp.md +37 -4
  237. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  238. package/node_modules/pi-lens/docs/settings.md +12 -1
  239. package/node_modules/pi-lens/docs/tools.md +4 -1
  240. package/node_modules/pi-lens/docs/usage.md +18 -0
  241. package/node_modules/pi-lens/package.json +10 -10
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  252. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  253. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  254. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  255. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  256. package/node_modules/pi-mcp-adapter/README.md +34 -3
  257. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  258. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  259. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  260. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  261. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  262. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  263. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  264. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  265. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  266. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  267. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  268. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  269. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  270. package/node_modules/pi-mcp-adapter/package.json +3 -3
  271. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  272. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  273. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  274. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  275. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  276. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  277. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  278. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  279. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  280. package/package.json +11 -12
  281. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  282. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  283. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  284. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  285. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  286. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  287. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  288. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -1,8 +1,30 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
+ import { TERRAGRUNT_FILENAMES } from "./file-kinds.js";
3
4
  import { logLatency } from "./latency-logger.js";
5
+ import { resolvePackagePath } from "./package-root.js";
4
6
  import { findNearestContaining, walkUpDirs } from "./path-utils.js";
5
- import { loadProjectSnapshot } from "./project-snapshot.js";
7
+ import { loadProjectSnapshotWithoutWordIndex } from "./project-snapshot.js";
8
+ // Extension → formatter policy. This map, `FORMATTER_POLICY_BY_FILENAME`, and
9
+ // `AUTO_INSTALLABLE_DEFAULT_FORMATTERS` are the inverse of the formatter
10
+ // definitions in `clients/formatters.ts` (formatter → extensions). The two are
11
+ // bound by `tests/clients/formatter-policy-consistency.test.ts` (#1135, the
12
+ // #883/#209 single-source-of-truth class) so they cannot drift silently. All
13
+ // three are re-exported (read-only intent) via a single `export {}` below —
14
+ // deliberately NOT inline on these declarations, to keep the pre-existing,
15
+ // structural lookup-table duplication out of the PR's "new code" (SonarCloud).
16
+ //
17
+ // Two deliberate #1135 decisions live in the entries below, documented here so
18
+ // the rationale sits outside the duplicated run:
19
+ // - `.sass` offers ["biome", "prettier"] only — NOT oxfmt: oxfmt is absent
20
+ // from OXFMT_SUPPORTED_EXTENSIONS (oxfmt/prettier handle .css/.scss/.less,
21
+ // not the indented `.sass` syntax). It was previously hand-listed here,
22
+ // diverging from the oxfmt definition (inert, because `matching` filtered
23
+ // it out); dropped to bind the two.
24
+ // - `.fish` intentionally has NO entry. `fish-indent` is a LINT runner
25
+ // (getLinterPolicyForFile), not a FormatterInfo — a formatter policy naming
26
+ // it was a dead, unsatisfiable entry; fish reformatting runs via the
27
+ // linter/autofix path. Removed to keep extension→formatter consistent.
6
28
  const FORMATTER_POLICY_BY_EXTENSION = new Map([
7
29
  [
8
30
  ".js",
@@ -133,7 +155,8 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
133
155
  [
134
156
  ".sass",
135
157
  {
136
- formatterNames: ["biome", "prettier", "oxfmt"],
158
+ // #1135: no oxfmt (see map header comment).
159
+ formatterNames: ["biome", "prettier"],
137
160
  defaultFormatter: "biome",
138
161
  defaultWhenUnconfigured: true,
139
162
  gate: "smart-default",
@@ -230,7 +253,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
230
253
  {
231
254
  formatterNames: ["ktfmt", "ktlint"],
232
255
  defaultFormatter: "ktlint",
233
- defaultWhenUnconfigured: true,
256
+ defaultWhenUnconfigured: false,
234
257
  gate: "smart-default",
235
258
  },
236
259
  ],
@@ -239,7 +262,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
239
262
  {
240
263
  formatterNames: ["ktfmt", "ktlint"],
241
264
  defaultFormatter: "ktlint",
242
- defaultWhenUnconfigured: true,
265
+ defaultWhenUnconfigured: false,
243
266
  gate: "smart-default",
244
267
  },
245
268
  ],
@@ -248,7 +271,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
248
271
  {
249
272
  formatterNames: ["swiftformat"],
250
273
  defaultFormatter: "swiftformat",
251
- defaultWhenUnconfigured: true,
274
+ defaultWhenUnconfigured: false,
252
275
  gate: "smart-default",
253
276
  },
254
277
  ],
@@ -257,7 +280,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
257
280
  {
258
281
  formatterNames: ["fantomas"],
259
282
  defaultFormatter: "fantomas",
260
- defaultWhenUnconfigured: true,
283
+ defaultWhenUnconfigured: false,
261
284
  gate: "smart-default",
262
285
  },
263
286
  ],
@@ -266,7 +289,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
266
289
  {
267
290
  formatterNames: ["fantomas"],
268
291
  defaultFormatter: "fantomas",
269
- defaultWhenUnconfigured: true,
292
+ defaultWhenUnconfigured: false,
270
293
  gate: "smart-default",
271
294
  },
272
295
  ],
@@ -275,7 +298,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
275
298
  {
276
299
  formatterNames: ["fantomas"],
277
300
  defaultFormatter: "fantomas",
278
- defaultWhenUnconfigured: true,
301
+ defaultWhenUnconfigured: false,
279
302
  gate: "smart-default",
280
303
  },
281
304
  ],
@@ -284,7 +307,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
284
307
  {
285
308
  formatterNames: ["nixfmt"],
286
309
  defaultFormatter: "nixfmt",
287
- defaultWhenUnconfigured: true,
310
+ defaultWhenUnconfigured: false,
288
311
  gate: "smart-default",
289
312
  },
290
313
  ],
@@ -293,7 +316,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
293
316
  {
294
317
  formatterNames: ["mix"],
295
318
  defaultFormatter: "mix",
296
- defaultWhenUnconfigured: true,
319
+ defaultWhenUnconfigured: false,
297
320
  gate: "smart-default",
298
321
  },
299
322
  ],
@@ -302,7 +325,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
302
325
  {
303
326
  formatterNames: ["mix"],
304
327
  defaultFormatter: "mix",
305
- defaultWhenUnconfigured: true,
328
+ defaultWhenUnconfigured: false,
306
329
  gate: "smart-default",
307
330
  },
308
331
  ],
@@ -311,7 +334,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
311
334
  {
312
335
  formatterNames: ["mix"],
313
336
  defaultFormatter: "mix",
314
- defaultWhenUnconfigured: true,
337
+ defaultWhenUnconfigured: false,
315
338
  gate: "smart-default",
316
339
  },
317
340
  ],
@@ -320,7 +343,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
320
343
  {
321
344
  formatterNames: ["mix"],
322
345
  defaultFormatter: "mix",
323
- defaultWhenUnconfigured: true,
346
+ defaultWhenUnconfigured: false,
324
347
  gate: "smart-default",
325
348
  },
326
349
  ],
@@ -329,7 +352,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
329
352
  {
330
353
  formatterNames: ["mix"],
331
354
  defaultFormatter: "mix",
332
- defaultWhenUnconfigured: true,
355
+ defaultWhenUnconfigured: false,
333
356
  gate: "smart-default",
334
357
  },
335
358
  ],
@@ -419,7 +442,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
419
442
  {
420
443
  formatterNames: ["csharpier"],
421
444
  defaultFormatter: "csharpier",
422
- defaultWhenUnconfigured: true,
445
+ defaultWhenUnconfigured: false,
423
446
  gate: "smart-default",
424
447
  },
425
448
  ],
@@ -437,7 +460,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
437
460
  {
438
461
  formatterNames: ["ormolu"],
439
462
  defaultFormatter: "ormolu",
440
- defaultWhenUnconfigured: true,
463
+ defaultWhenUnconfigured: false,
441
464
  gate: "smart-default",
442
465
  },
443
466
  ],
@@ -446,7 +469,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
446
469
  {
447
470
  formatterNames: ["ormolu"],
448
471
  defaultFormatter: "ormolu",
449
- defaultWhenUnconfigured: true,
472
+ defaultWhenUnconfigured: false,
450
473
  gate: "smart-default",
451
474
  },
452
475
  ],
@@ -504,21 +527,12 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
504
527
  gate: "smart-default",
505
528
  },
506
529
  ],
507
- [
508
- ".fish",
509
- {
510
- formatterNames: ["fish-indent"],
511
- defaultFormatter: "fish-indent",
512
- defaultWhenUnconfigured: true,
513
- gate: "smart-default",
514
- },
515
- ],
516
530
  [
517
531
  ".toml",
518
532
  {
519
533
  formatterNames: ["taplo"],
520
534
  defaultFormatter: "taplo",
521
- defaultWhenUnconfigured: true,
535
+ defaultWhenUnconfigured: false,
522
536
  gate: "smart-default",
523
537
  },
524
538
  ],
@@ -527,7 +541,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
527
541
  {
528
542
  formatterNames: ["terraform"],
529
543
  defaultFormatter: "terraform",
530
- defaultWhenUnconfigured: true,
544
+ defaultWhenUnconfigured: false,
531
545
  gate: "smart-default",
532
546
  },
533
547
  ],
@@ -536,7 +550,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
536
550
  {
537
551
  formatterNames: ["terraform"],
538
552
  defaultFormatter: "terraform",
539
- defaultWhenUnconfigured: true,
553
+ defaultWhenUnconfigured: false,
540
554
  gate: "smart-default",
541
555
  },
542
556
  ],
@@ -617,7 +631,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
617
631
  {
618
632
  formatterNames: ["psscriptanalyzer-format"],
619
633
  defaultFormatter: "psscriptanalyzer-format",
620
- defaultWhenUnconfigured: true,
634
+ defaultWhenUnconfigured: false,
621
635
  gate: "smart-default",
622
636
  },
623
637
  ],
@@ -626,7 +640,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
626
640
  {
627
641
  formatterNames: ["psscriptanalyzer-format"],
628
642
  defaultFormatter: "psscriptanalyzer-format",
629
- defaultWhenUnconfigured: true,
643
+ defaultWhenUnconfigured: false,
630
644
  gate: "smart-default",
631
645
  },
632
646
  ],
@@ -635,7 +649,7 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
635
649
  {
636
650
  formatterNames: ["psscriptanalyzer-format"],
637
651
  defaultFormatter: "psscriptanalyzer-format",
638
- defaultWhenUnconfigured: true,
652
+ defaultWhenUnconfigured: false,
639
653
  gate: "smart-default",
640
654
  },
641
655
  ],
@@ -643,10 +657,15 @@ const FORMATTER_POLICY_BY_EXTENSION = new Map([
643
657
  // oxfmt supports these extensions — registered as a candidate formatter for each.
644
658
  // Using a post-processing pass avoids repeating the same modification across
645
659
  // many map entries (and keeps SonarCloud's duplication gate happy).
646
- const OXFMT_SUPPORTED_EXTENSIONS = new Set([
660
+ // This is the SINGLE SOURCE OF TRUTH for oxfmt's supported extensions — do not
661
+ // hand-maintain a second copy; `clients/formatters.ts`'s `oxfmtFormatter.extensions`
662
+ // imports and spreads this same Set (#1134; previously two parallel hand-maintained
663
+ // lists, the #883 single-source-of-truth class).
664
+ export const OXFMT_SUPPORTED_EXTENSIONS = new Set([
647
665
  ".js", ".jsx", ".mjs", ".cjs",
648
666
  ".ts", ".tsx", ".mts", ".cts",
649
667
  ".vue",
668
+ ".svelte",
650
669
  ".css", ".scss", ".less",
651
670
  ".html", ".htm",
652
671
  ".json", ".jsonc",
@@ -662,6 +681,22 @@ FORMATTER_POLICY_BY_EXTENSION.set(".vue", {
662
681
  defaultWhenUnconfigured: false,
663
682
  gate: "config-first",
664
683
  });
684
+ // Add .svelte entry (no prior formatter policy existed for this extension —
685
+ // docs/language-coverage.md previously listed no formatter). oxfmt is the only
686
+ // candidate; the loop below appends it since ".svelte" is in
687
+ // OXFMT_SUPPORTED_EXTENSIONS. Unlike the other oxfmt extensions, oxfmt's
688
+ // .svelte support is conditional on more than "a config file exists" — see
689
+ // `hasOxfmtSvelteConfig` below, gated in `formatters.ts`'s
690
+ // `hasExplicitFormatterConfig` (verified empirically against the real oxfmt
691
+ // npm binary; oxfmt exits non-zero on .svelte without BOTH the `svelte`
692
+ // package installed and the config's `svelte` flag enabled — an unconditional
693
+ // offer would guarantee a formatter failure, not a no-op).
694
+ FORMATTER_POLICY_BY_EXTENSION.set(".svelte", {
695
+ formatterNames: [],
696
+ defaultFormatter: "oxfmt",
697
+ defaultWhenUnconfigured: false,
698
+ gate: "config-first",
699
+ });
665
700
  for (const [ext, policy] of FORMATTER_POLICY_BY_EXTENSION) {
666
701
  if (OXFMT_SUPPORTED_EXTENSIONS.has(ext) && !policy.formatterNames.includes("oxfmt")) {
667
702
  policy.formatterNames.push("oxfmt");
@@ -675,10 +710,30 @@ const AUTO_INSTALLABLE_DEFAULT_FORMATTERS = new Map([
675
710
  ["taplo", "taplo"],
676
711
  ["ktlint", "ktlint"],
677
712
  ]);
713
+ // `gate: "smart-default"` so a matched file is formatted only when nothing else
714
+ // claims it. Misdetection edge: `root.hcl` is filename-detected as terragrunt, so
715
+ // a non-terragrunt `root.hcl` gets smart-default formatted with `terragrunt hcl
716
+ // fmt`'s generic HCL canonicalization — a soft outcome (canonical HCL, no config
717
+ // semantics assumed), not a hard failure.
718
+ const TERRAGRUNT_FORMATTER_POLICY = {
719
+ formatterNames: ["terragrunt-hcl"],
720
+ defaultFormatter: "terragrunt-hcl",
721
+ defaultWhenUnconfigured: true,
722
+ gate: "smart-default",
723
+ };
724
+ const FORMATTER_POLICY_BY_FILENAME = new Map(TERRAGRUNT_FILENAMES.map((name) => [name, TERRAGRUNT_FORMATTER_POLICY]));
725
+ // Re-exported (read-only intent) for the #1135 drift guard. Kept as a single
726
+ // separate statement — NOT inline on the declarations above — so the touched
727
+ // lines don't fall at the head of the pre-existing lookup-table duplication and
728
+ // get counted as new duplicated code. Bindings are live, so position is safe.
729
+ export { AUTO_INSTALLABLE_DEFAULT_FORMATTERS, FORMATTER_POLICY_BY_EXTENSION, FORMATTER_POLICY_BY_FILENAME, };
678
730
  export function getFormatterPolicyForExtension(ext) {
679
731
  return FORMATTER_POLICY_BY_EXTENSION.get(ext.toLowerCase());
680
732
  }
681
733
  export function getFormatterPolicyForFile(filePath) {
734
+ const byFilename = FORMATTER_POLICY_BY_FILENAME.get(path.basename(filePath).toLowerCase());
735
+ if (byFilename)
736
+ return byFilename;
682
737
  return getFormatterPolicyForExtension(path.extname(filePath));
683
738
  }
684
739
  export function getSmartDefaultFormatterName(filePath) {
@@ -773,8 +828,21 @@ const AUTOFIX_CAPABILITIES = new Map([
773
828
  ],
774
829
  ]);
775
830
  const TOOL_EXECUTION_POLICY = new Map([
831
+ // Dispatch/LSP runners use the shared availability/install seam. These
832
+ // managed tools are missing-command repair candidates; probe failures still
833
+ // fail closed before this policy is consulted.
834
+ ["pyright", { gate: "smart-default", autoInstall: true }],
835
+ ["shellcheck", { gate: "smart-default", autoInstall: true }],
836
+ ["shfmt", { gate: "smart-default", autoInstall: true }],
837
+ ["tflint", { gate: "smart-default", autoInstall: true }],
838
+ ["terragrunt", { gate: "smart-default", autoInstall: true }],
839
+ ["trivy", { gate: "config-first", autoInstall: true }],
776
840
  ["biome", { gate: "smart-default", autoInstall: true }],
777
841
  ["ruff", { gate: "smart-default", autoInstall: true }],
842
+ ["jscpd", { gate: "smart-default", autoInstall: true }],
843
+ ["madge", { gate: "smart-default", autoInstall: true }],
844
+ ["knip", { gate: "smart-default", autoInstall: true }],
845
+ ["ast-grep", { gate: "smart-default", autoInstall: true }],
778
846
  ["oxlint", { gate: "smart-default", autoInstall: true }],
779
847
  ["stylelint", { gate: "smart-default", autoInstall: true }],
780
848
  ["sqlfluff", { gate: "smart-default", autoInstall: true }],
@@ -785,6 +853,7 @@ const TOOL_EXECUTION_POLICY = new Map([
785
853
  ["mypy", { gate: "config-first", autoInstall: true }],
786
854
  ["taplo", { gate: "smart-default", autoInstall: true }],
787
855
  ["hadolint", { gate: "smart-default", autoInstall: true }],
856
+ ["helm", { gate: "smart-default", autoInstall: true }],
788
857
  ["htmlhint", { gate: "smart-default", autoInstall: true }],
789
858
  ["ktlint", { gate: "smart-default", autoInstall: true }],
790
859
  // ktfmt is opt-in (a project's explicit formatting choice), so it only runs
@@ -1096,8 +1165,8 @@ export function getLinterPolicyForFile(filePath, context = {}) {
1096
1165
  runnerNames: ["sqlfluff"],
1097
1166
  preferredRunners: ["sqlfluff"],
1098
1167
  defaultRunner: "sqlfluff",
1099
- defaultWhenUnconfigured: true,
1100
- gate: "smart-default",
1168
+ defaultWhenUnconfigured: false,
1169
+ gate: "config-first",
1101
1170
  };
1102
1171
  }
1103
1172
  if ([".rb", ".rake", ".gemspec", ".ru"].includes(ext)) {
@@ -1145,6 +1214,15 @@ export function getLinterPolicyForFile(filePath, context = {}) {
1145
1214
  gate: "smart-default",
1146
1215
  };
1147
1216
  }
1217
+ if (TERRAGRUNT_FILENAMES.includes(path.basename(filePath).toLowerCase())) {
1218
+ return {
1219
+ runnerNames: ["terragrunt"],
1220
+ preferredRunners: ["terragrunt"],
1221
+ defaultRunner: "terragrunt",
1222
+ defaultWhenUnconfigured: true,
1223
+ gate: "smart-default",
1224
+ };
1225
+ }
1148
1226
  if ([".kt", ".kts"].includes(ext)) {
1149
1227
  // When the project opts into ktfmt, ktfmt (a pure formatter wired as a safe
1150
1228
  // autofix) owns Kotlin formatting; ktlint's lint steps aside so its style
@@ -1227,7 +1305,7 @@ export function getLinterPolicyForFile(filePath, context = {}) {
1227
1305
  preferredRunners: ["tflint"],
1228
1306
  defaultRunner: "tflint",
1229
1307
  defaultWhenUnconfigured: true,
1230
- gate: "smart-default",
1308
+ gate: context.hasTflintConfig ? "config-first" : "smart-default",
1231
1309
  };
1232
1310
  }
1233
1311
  if ([".ex", ".exs", ".eex", ".heex", ".leex"].includes(ext)) {
@@ -1296,7 +1374,7 @@ export function getLinterPolicyForFile(filePath, context = {}) {
1296
1374
  * start / project-seq bumps rather than on every read.
1297
1375
  */
1298
1376
  export function getCachedProjectConventions(cwd) {
1299
- const snapshot = loadProjectSnapshot(cwd);
1377
+ const snapshot = loadProjectSnapshotWithoutWordIndex(cwd);
1300
1378
  return snapshot?.conventions;
1301
1379
  }
1302
1380
  export function getLinterPolicyForCwd(filePath, cwd) {
@@ -1314,6 +1392,9 @@ export function getLinterPolicyForCwd(filePath, cwd) {
1314
1392
  hasMypyConfig: hasMypyConfig(cwd),
1315
1393
  hasDetektConfig: hasDetektConfig(cwd),
1316
1394
  hasKtfmtConfig: hasKtfmtConfig(cwd),
1395
+ // From the file's directory, not cwd: a `.tflint.hcl` in a terraform
1396
+ // subdirectory is invisible to an upward walk that starts at the repo root.
1397
+ hasTflintConfig: hasTflintConfig(path.dirname(path.resolve(cwd, filePath))),
1317
1398
  };
1318
1399
  const policy = getLinterPolicyForFile(filePath, context);
1319
1400
  if (policy) {
@@ -1412,8 +1493,8 @@ export function getAutofixPolicyForFile(filePath, context = {}) {
1412
1493
  toolNames: ["sqlfluff"],
1413
1494
  preferredTools: ["sqlfluff"],
1414
1495
  defaultTool: "sqlfluff",
1415
- defaultWhenUnconfigured: true,
1416
- gate: "smart-default",
1496
+ defaultWhenUnconfigured: false,
1497
+ gate: "config-first",
1417
1498
  safe: true,
1418
1499
  };
1419
1500
  }
@@ -1428,6 +1509,16 @@ export function getAutofixPolicyForFile(filePath, context = {}) {
1428
1509
  };
1429
1510
  }
1430
1511
  if ([".kt", ".kts"].includes(ext)) {
1512
+ if (context.hasKtlintConfig) {
1513
+ return {
1514
+ toolNames: ["ktlint", "ktfmt", "detekt"],
1515
+ preferredTools: ["ktlint"],
1516
+ defaultTool: "ktlint",
1517
+ defaultWhenUnconfigured: false,
1518
+ gate: "config-first",
1519
+ safe: true,
1520
+ };
1521
+ }
1431
1522
  // ktfmt is config-first and the project's explicit formatting choice, so it
1432
1523
  // wins over both detekt and the ktlint smart-default when opted in (#129).
1433
1524
  if (context.hasKtfmtConfig) {
@@ -1625,6 +1716,69 @@ export function hasOxfmtConfig(cwd) {
1625
1716
  }
1626
1717
  return false;
1627
1718
  }
1719
+ // Empirically verified against the real `oxfmt` npm package (0.62.0), a
1720
+ // scratch fixture, and a plain `Component.svelte` — see PR body for the full
1721
+ // four-cell matrix. Both conditions are required; either alone always fails:
1722
+ // - no `svelte` package, no config flag -> exit 2 ("excluded by ignore rules")
1723
+ // - no `svelte` package, config flag on -> exit 2 ("Cannot find module 'svelte/compiler'")
1724
+ // - `svelte` package installed, no flag -> exit 2 ("excluded by ignore rules")
1725
+ // - `svelte` package installed, flag on -> exit 0, formats the file
1726
+ // Per https://oxc.rs/docs/guide/usage/formatter/language-support.html, oxfmt
1727
+ // also requires the npm-distributed binary (not the standalone GitHub-release
1728
+ // binary) for `.svelte` — pi-lens already resolves oxfmt via `findInNodeModules`/
1729
+ // `which`, which favors the npm-installed binary, so that requirement is not
1730
+ // separately re-checked here.
1731
+ //
1732
+ // Known limits of this line-match heuristic (#1134 P3 tail 1): it tolerates a
1733
+ // trailing `#`-comment after the value (`svelte = true # enable`), but it is
1734
+ // NOT a real TOML parser — a `svelte = true` occurrence nested under a
1735
+ // `[table]` section, or one embedded inside a multi-line/triple-quoted string
1736
+ // value, would still match and false-positive. Both are considered acceptable
1737
+ // risk: an oxfmt.toml sectioning `svelte` under a table is not a realistic
1738
+ // config shape for this single top-level boolean key, and a false positive
1739
+ // here only causes oxfmt to be OFFERED (still gated by oxfmt actually running
1740
+ // and the `svelte` package check above), never a silent formatter failure.
1741
+ const OXFMT_SVELTE_TOML_TRUE = /(^|\n)\s*svelte\s*=\s*true\s*(\s*#.*)?(\r?\n|$)/;
1742
+ export function hasOxfmtSvelteConfig(cwd) {
1743
+ // Monorepo asymmetry (#1134 P3 tail 2): this dependency check stops at the
1744
+ // NEAREST package.json (`hasNearestPackageJsonDependency`), while the
1745
+ // config walk below (`walkUpDirs`) continues all the way to the repo root.
1746
+ // A root-level `svelte` dependency combined with an oxfmt config at the
1747
+ // root, but invoked with a sub-package `cwd` that has its own
1748
+ // (svelte-less) package.json, under-offers oxfmt for that sub-package —
1749
+ // the same nearest-vs-root-walk asymmetry as the `.tflint.hcl` note in
1750
+ // `getLinterPolicyForCwd` above. This is untested/deliberately unfixed:
1751
+ // failing to offer a valid formatter is safe (never mis-offers one that
1752
+ // then fails at runtime), unlike the inverse.
1753
+ if (!hasNearestPackageJsonDependency(cwd, "svelte"))
1754
+ return false;
1755
+ for (const dir of walkUpDirs(cwd)) {
1756
+ const rcPath = path.join(dir, ".oxfmtrc.json");
1757
+ if (fs.existsSync(rcPath)) {
1758
+ try {
1759
+ const cfg = JSON.parse(fs.readFileSync(rcPath, "utf-8"));
1760
+ if (cfg.svelte === true)
1761
+ return true;
1762
+ }
1763
+ catch { }
1764
+ }
1765
+ // oxfmt.toml is TOML; pi-lens has no TOML parser dependency, so this is
1766
+ // a targeted line match for the single boolean key rather than a full
1767
+ // parse (same pragmatic style as hasVitePlusConfig's content.includes
1768
+ // check above) — presence of the file alone is NOT sufficient, since a
1769
+ // TOML config can omit `svelte` or set it false.
1770
+ const tomlPath = path.join(dir, "oxfmt.toml");
1771
+ if (fs.existsSync(tomlPath)) {
1772
+ try {
1773
+ const content = fs.readFileSync(tomlPath, "utf-8");
1774
+ if (OXFMT_SVELTE_TOML_TRUE.test(content))
1775
+ return true;
1776
+ }
1777
+ catch { }
1778
+ }
1779
+ }
1780
+ return false;
1781
+ }
1628
1782
  export function hasStylelintConfig(cwd) {
1629
1783
  for (const dir of walkUpDirs(cwd)) {
1630
1784
  if (STYLELINT_CONFIGS.some((cfg) => fs.existsSync(path.join(dir, cfg)))) {
@@ -1832,9 +1986,61 @@ export function hasRuffConfig(cwd) {
1832
1986
  }
1833
1987
  return false;
1834
1988
  }
1989
+ /**
1990
+ * Shared config-args seam for the markdownlint lint AND autofix surfaces
1991
+ * (#1247). The lint runner (`markdownlint.ts`) and the autofix path
1992
+ * (`pipeline.ts` `tryMarkdownlintFix`) MUST both consume this builder so the
1993
+ * package-owned sensible-defaults config (`config/markdownlint/core.json`) can
1994
+ * never drift apart from the bare `--fix` invocation again.
1995
+ */
1996
+ /**
1997
+ * Shared fallback shape for the config-args builders (#1247): when the
1998
+ * project has its own config the lint/autofix surfaces pass NO args (the
1999
+ * tool discovers it); otherwise both surfaces point at the package-owned
2000
+ * fallback so the tool never silently runs its default ruleset. One shape
2001
+ * here means the per-tool builders cannot drift from each other.
2002
+ */
2003
+ function configArgsWithFallback(hasConfig, toolArgs, packageConfigPath) {
2004
+ return hasConfig
2005
+ ? []
2006
+ : [...toolArgs, resolvePackagePath(import.meta.url, packageConfigPath)];
2007
+ }
2008
+ export function markdownlintConfigArgs(cwd) {
2009
+ return configArgsWithFallback(hasMarkdownlintConfig(cwd), ["--config"], "config/markdownlint/core.json");
2010
+ }
2011
+ /**
2012
+ * Shared config-args seam for the ruff lint AND autofix surfaces (#1247).
2013
+ * The lint runner (`ruff.ts`) and `ruffClient.fixFileAsync` MUST both consume
2014
+ * this builder so `config/ruff/core.toml` applies on `--fix` too.
2015
+ */
2016
+ export function ruffConfigArgs(cwd) {
2017
+ return configArgsWithFallback(hasRuffConfig(cwd), ["--config"], "config/ruff/core.toml");
2018
+ }
2019
+ /**
2020
+ * Shared config-args seam for the biome lint AND autofix surfaces (#1247).
2021
+ * The lint runner (`biome-check.ts`) and `biomeClient.fixFileAsync` MUST both
2022
+ * consume this builder so the user's `biome.json(c)` — or the package-owned
2023
+ * `config/biome/core.jsonc` fallback — applies on `lint --write` too.
2024
+ */
2025
+ export function biomeConfigArgs(cwd) {
2026
+ return [
2027
+ "--config-path=" +
2028
+ (getBiomeConfigPath(cwd) ??
2029
+ resolvePackagePath(import.meta.url, "config/biome/core.jsonc")),
2030
+ ];
2031
+ }
1835
2032
  export function hasGolangciConfig(cwd) {
1836
2033
  return findNearestContaining(cwd, GOLANGCI_CONFIGS) !== undefined;
1837
2034
  }
2035
+ // tflint ships built-in rules and runs without config, so `.tflint.hcl` is not
2036
+ // a prerequisite — it is the project electing tflint as its terraform linter,
2037
+ // which promotes the policy from smart-default to config-first. Takes a start
2038
+ // directory rather than the project cwd because tflint resolves config
2039
+ // per-directory: callers pass the EDITED FILE's directory so this agrees with
2040
+ // what the runner will actually hand tflint via `--config`.
2041
+ export function hasTflintConfig(startDir) {
2042
+ return findNearestContaining(startDir, [".tflint.hcl"]) !== undefined;
2043
+ }
1838
2044
  export function hasClangFormatConfig(cwd) {
1839
2045
  return (findNearestContaining(cwd, [".clang-format", "_clang-format"]) !== undefined);
1840
2046
  }
@@ -1897,7 +2103,150 @@ const KTFMT_GRADLE_FILES = [
1897
2103
  "settings.gradle.kts",
1898
2104
  "settings.gradle",
1899
2105
  ];
2106
+ const KOTLIN_GRADLE_FILES = [
2107
+ "build.gradle.kts",
2108
+ "build.gradle",
2109
+ "settings.gradle.kts",
2110
+ "settings.gradle",
2111
+ ];
2112
+ const spotlessKotlinConfigCache = new Map();
2113
+ let spotlessGradleReadCount = 0;
2114
+ /** Test-only observability for asserting the hot-path I/O bound. */
2115
+ export function _getSpotlessGradleReadCountForTests() {
2116
+ return spotlessGradleReadCount;
2117
+ }
2118
+ /**
2119
+ * Blank comments and quoted strings while preserving braces and newlines in
2120
+ * executable Gradle source. This is deliberately a small lexical pre-pass,
2121
+ * not a Groovy/Kotlin parser. In particular, statically disabled constructs
2122
+ * such as `if (false) { ktlint() }` remain a documented false-positive.
2123
+ */
2124
+ function stripGradleCommentsAndStrings(source) {
2125
+ let result = "";
2126
+ let state = "code";
2127
+ let quote = "";
2128
+ for (let index = 0; index < source.length; index += 1) {
2129
+ const char = source[index];
2130
+ const next = source[index + 1];
2131
+ if (state === "code") {
2132
+ if (char === "/" && next === "/") {
2133
+ result += " ";
2134
+ index += 1;
2135
+ state = "line-comment";
2136
+ }
2137
+ else if (char === "/" && next === "*") {
2138
+ result += " ";
2139
+ index += 1;
2140
+ state = "block-comment";
2141
+ }
2142
+ else if (char === '"' || char === "'") {
2143
+ result += " ";
2144
+ quote = char;
2145
+ state = "string";
2146
+ }
2147
+ else {
2148
+ result += char;
2149
+ }
2150
+ continue;
2151
+ }
2152
+ if (state === "line-comment") {
2153
+ if (char === "\n" || char === "\r") {
2154
+ result += char;
2155
+ state = "code";
2156
+ }
2157
+ else {
2158
+ result += " ";
2159
+ }
2160
+ continue;
2161
+ }
2162
+ if (state === "block-comment") {
2163
+ if (char === "*" && next === "/") {
2164
+ result += " ";
2165
+ index += 1;
2166
+ state = "code";
2167
+ }
2168
+ else {
2169
+ result += char === "\n" || char === "\r" ? char : " ";
2170
+ }
2171
+ continue;
2172
+ }
2173
+ if (char === "\\") {
2174
+ result += " ";
2175
+ if (index + 1 < source.length) {
2176
+ result += source[index + 1] === "\n" ? "\n" : " ";
2177
+ index += 1;
2178
+ }
2179
+ }
2180
+ else if (char === quote) {
2181
+ result += " ";
2182
+ state = "code";
2183
+ }
2184
+ else {
2185
+ result += char === "\n" || char === "\r" ? char : " ";
2186
+ }
2187
+ }
2188
+ return result;
2189
+ }
2190
+ function namedGradleBlockBodies(source, name) {
2191
+ const bodies = [];
2192
+ const startPattern = new RegExp(`\\b${name}\\s*\\{`, "g");
2193
+ for (const match of source.matchAll(startPattern)) {
2194
+ const open = source.indexOf("{", match.index);
2195
+ let depth = 1;
2196
+ for (let index = open + 1; index < source.length; index += 1) {
2197
+ if (source[index] === "{")
2198
+ depth += 1;
2199
+ if (source[index] === "}")
2200
+ depth -= 1;
2201
+ if (depth === 0) {
2202
+ bodies.push(source.slice(open + 1, index));
2203
+ break;
2204
+ }
2205
+ }
2206
+ }
2207
+ return bodies;
2208
+ }
2209
+ /**
2210
+ * Resolve the formatter elected by a Spotless `kotlin { ... }` block.
2211
+ * ktlint is the deterministic tie-break if a malformed project names both.
2212
+ */
2213
+ export function getSpotlessKotlinFormatter(cwd) {
2214
+ for (const dir of walkUpDirs(cwd)) {
2215
+ for (const gradle of KOTLIN_GRADLE_FILES) {
2216
+ const filePath = path.join(dir, gradle);
2217
+ if (!fs.existsSync(filePath))
2218
+ continue;
2219
+ try {
2220
+ const mtime = fs.statSync(filePath).mtimeMs;
2221
+ let config = spotlessKotlinConfigCache.get(filePath);
2222
+ if (!config || config.mtime !== mtime) {
2223
+ const source = stripGradleCommentsAndStrings(fs.readFileSync(filePath, "utf-8"));
2224
+ spotlessGradleReadCount += 1;
2225
+ const kotlinBodies = namedGradleBlockBodies(source, "spotless").flatMap((spotless) => namedGradleBlockBodies(spotless, "kotlin"));
2226
+ config = {
2227
+ mtime,
2228
+ ktlintConfig: kotlinBodies.some((body) => /\bktlint\s*\(/.test(body)),
2229
+ ktfmtConfig: kotlinBodies.some((body) => /\bktfmt\s*\(/.test(body)),
2230
+ };
2231
+ spotlessKotlinConfigCache.set(filePath, config);
2232
+ }
2233
+ if (config.ktlintConfig)
2234
+ return "ktlint";
2235
+ if (config.ktfmtConfig)
2236
+ return "ktfmt";
2237
+ }
2238
+ catch { }
2239
+ }
2240
+ }
2241
+ return undefined;
2242
+ }
2243
+ export function hasKtlintConfig(cwd) {
2244
+ return getSpotlessKotlinFormatter(cwd) === "ktlint";
2245
+ }
1900
2246
  export function hasKtfmtConfig(cwd) {
2247
+ const spotlessFormatter = getSpotlessKotlinFormatter(cwd);
2248
+ if (spotlessFormatter)
2249
+ return spotlessFormatter === "ktfmt";
1901
2250
  for (const dir of walkUpDirs(cwd)) {
1902
2251
  if (KTFMT_CONFIG_FILES.some((cfg) => fs.existsSync(path.join(dir, cfg))))
1903
2252
  return true;