mstro-app 0.4.3 → 0.4.4

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 (306) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  5. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  13. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  17. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  18. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.js +10 -807
  20. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  21. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  23. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  25. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  26. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  27. package/dist/server/cli/headless/headless-logger.js +28 -5
  28. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  29. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  31. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  33. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  34. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  35. package/dist/server/cli/headless/stall-assessor.js +65 -457
  36. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  37. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  38. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  39. package/dist/server/cli/improvisation-attachments.js +116 -0
  40. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  41. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  42. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  43. package/dist/server/cli/improvisation-retry.js +434 -0
  44. package/dist/server/cli/improvisation-retry.js.map +1 -0
  45. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  46. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  47. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  48. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  49. package/dist/server/cli/improvisation-types.d.ts +86 -0
  50. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  51. package/dist/server/cli/improvisation-types.js +10 -0
  52. package/dist/server/cli/improvisation-types.js.map +1 -0
  53. package/dist/server/cli/prompt-builders.d.ts +68 -0
  54. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  55. package/dist/server/cli/prompt-builders.js +312 -0
  56. package/dist/server/cli/prompt-builders.js.map +1 -0
  57. package/dist/server/index.js +33 -212
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  60. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  61. package/dist/server/mcp/bouncer-haiku.js +152 -0
  62. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  63. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  64. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  65. package/dist/server/mcp/bouncer-integration.js +50 -196
  66. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  67. package/dist/server/mcp/security-analysis.d.ts +38 -0
  68. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  69. package/dist/server/mcp/security-analysis.js +183 -0
  70. package/dist/server/mcp/security-analysis.js.map +1 -0
  71. package/dist/server/mcp/security-audit.d.ts +1 -1
  72. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  73. package/dist/server/mcp/security-patterns.d.ts +1 -25
  74. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  75. package/dist/server/mcp/security-patterns.js +55 -260
  76. package/dist/server/mcp/security-patterns.js.map +1 -1
  77. package/dist/server/server-setup.d.ts +22 -0
  78. package/dist/server/server-setup.d.ts.map +1 -0
  79. package/dist/server/server-setup.js +101 -0
  80. package/dist/server/server-setup.js.map +1 -0
  81. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  82. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  83. package/dist/server/services/file-explorer-ops.js +211 -0
  84. package/dist/server/services/file-explorer-ops.js.map +1 -0
  85. package/dist/server/services/files.d.ts +2 -85
  86. package/dist/server/services/files.d.ts.map +1 -1
  87. package/dist/server/services/files.js +7 -427
  88. package/dist/server/services/files.js.map +1 -1
  89. package/dist/server/services/plan/composer.d.ts.map +1 -1
  90. package/dist/server/services/plan/composer.js +2 -1
  91. package/dist/server/services/plan/composer.js.map +1 -1
  92. package/dist/server/services/plan/executor.d.ts.map +1 -1
  93. package/dist/server/services/plan/executor.js +3 -1
  94. package/dist/server/services/plan/executor.js.map +1 -1
  95. package/dist/server/services/plan/parser-core.d.ts +20 -0
  96. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  97. package/dist/server/services/plan/parser-core.js +350 -0
  98. package/dist/server/services/plan/parser-core.js.map +1 -0
  99. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  100. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  101. package/dist/server/services/plan/parser-migration.js +124 -0
  102. package/dist/server/services/plan/parser-migration.js.map +1 -0
  103. package/dist/server/services/plan/parser.d.ts +0 -8
  104. package/dist/server/services/plan/parser.d.ts.map +1 -1
  105. package/dist/server/services/plan/parser.js +50 -569
  106. package/dist/server/services/plan/parser.js.map +1 -1
  107. package/dist/server/services/plan/review-gate.d.ts +2 -0
  108. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  109. package/dist/server/services/plan/review-gate.js +2 -2
  110. package/dist/server/services/plan/review-gate.js.map +1 -1
  111. package/dist/server/services/plan/types.d.ts +2 -0
  112. package/dist/server/services/plan/types.d.ts.map +1 -1
  113. package/dist/server/services/platform-credentials.d.ts +24 -0
  114. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  115. package/dist/server/services/platform-credentials.js +68 -0
  116. package/dist/server/services/platform-credentials.js.map +1 -0
  117. package/dist/server/services/platform.d.ts +1 -31
  118. package/dist/server/services/platform.d.ts.map +1 -1
  119. package/dist/server/services/platform.js +10 -119
  120. package/dist/server/services/platform.js.map +1 -1
  121. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  122. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  123. package/dist/server/services/terminal/pty-manager.js +53 -266
  124. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  125. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  126. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  127. package/dist/server/services/terminal/pty-utils.js +141 -0
  128. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  129. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  130. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  131. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  132. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  133. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  134. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  135. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  136. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  137. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  139. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  140. package/dist/server/services/websocket/file-utils.js +3 -3
  141. package/dist/server/services/websocket/file-utils.js.map +1 -1
  142. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  143. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  144. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  145. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  146. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  147. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  148. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  149. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  150. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  151. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  152. package/dist/server/services/websocket/git-handlers.js +35 -541
  153. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  154. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  155. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  156. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  157. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  158. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  160. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  162. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  163. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  164. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  165. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  166. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  167. package/dist/server/services/websocket/git-utils.js +201 -0
  168. package/dist/server/services/websocket/git-utils.js.map +1 -0
  169. package/dist/server/services/websocket/handler.d.ts +2 -0
  170. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  171. package/dist/server/services/websocket/handler.js +37 -126
  172. package/dist/server/services/websocket/handler.js.map +1 -1
  173. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  174. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  175. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  176. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  177. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  178. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  179. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  180. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  181. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  182. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  183. package/dist/server/services/websocket/plan-handlers.js +6 -925
  184. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  185. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  186. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  187. package/dist/server/services/websocket/plan-helpers.js +199 -0
  188. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  189. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  190. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  191. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  192. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  193. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  194. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  195. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  196. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  197. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  198. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  199. package/dist/server/services/websocket/quality-complexity.js +262 -0
  200. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  201. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  202. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  203. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  204. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  205. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  206. package/dist/server/services/websocket/quality-handlers.js +34 -346
  207. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  208. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  209. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  210. package/dist/server/services/websocket/quality-linting.js +178 -0
  211. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  212. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  213. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  214. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  215. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  216. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  217. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  218. package/dist/server/services/websocket/quality-service.js +9 -651
  219. package/dist/server/services/websocket/quality-service.js.map +1 -1
  220. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  221. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  222. package/dist/server/services/websocket/quality-tools.js +208 -0
  223. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  224. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  225. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  226. package/dist/server/services/websocket/quality-types.js +101 -0
  227. package/dist/server/services/websocket/quality-types.js.map +1 -0
  228. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  229. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  230. package/dist/server/services/websocket/session-handlers.js +3 -378
  231. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  232. package/dist/server/services/websocket/session-history.d.ts +4 -0
  233. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  234. package/dist/server/services/websocket/session-history.js +208 -0
  235. package/dist/server/services/websocket/session-history.js.map +1 -0
  236. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  237. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  238. package/dist/server/services/websocket/session-initialization.js +163 -0
  239. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  240. package/dist/server/services/websocket/types.d.ts +12 -2
  241. package/dist/server/services/websocket/types.d.ts.map +1 -1
  242. package/package.json +1 -1
  243. package/server/cli/headless/claude-invoker-process.ts +204 -0
  244. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  245. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  246. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  247. package/server/cli/headless/claude-invoker.ts +15 -1096
  248. package/server/cli/headless/haiku-assessments.ts +365 -0
  249. package/server/cli/headless/headless-logger.ts +26 -5
  250. package/server/cli/headless/native-timeout-detector.ts +117 -0
  251. package/server/cli/headless/stall-assessor.ts +65 -618
  252. package/server/cli/improvisation-attachments.ts +148 -0
  253. package/server/cli/improvisation-retry.ts +602 -0
  254. package/server/cli/improvisation-session-manager.ts +140 -1349
  255. package/server/cli/improvisation-types.ts +98 -0
  256. package/server/cli/prompt-builders.ts +370 -0
  257. package/server/index.ts +35 -246
  258. package/server/mcp/bouncer-haiku.ts +182 -0
  259. package/server/mcp/bouncer-integration.ts +87 -248
  260. package/server/mcp/security-analysis.ts +217 -0
  261. package/server/mcp/security-audit.ts +1 -1
  262. package/server/mcp/security-patterns.ts +60 -283
  263. package/server/server-setup.ts +114 -0
  264. package/server/services/file-explorer-ops.ts +293 -0
  265. package/server/services/files.ts +20 -532
  266. package/server/services/plan/composer.ts +2 -1
  267. package/server/services/plan/executor.ts +3 -1
  268. package/server/services/plan/parser-core.ts +406 -0
  269. package/server/services/plan/parser-migration.ts +128 -0
  270. package/server/services/plan/parser.ts +52 -620
  271. package/server/services/plan/review-gate.ts +4 -2
  272. package/server/services/plan/types.ts +2 -0
  273. package/server/services/platform-credentials.ts +83 -0
  274. package/server/services/platform.ts +15 -141
  275. package/server/services/terminal/pty-manager.ts +66 -313
  276. package/server/services/terminal/pty-utils.ts +176 -0
  277. package/server/services/websocket/file-definition-handlers.ts +165 -0
  278. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  279. package/server/services/websocket/file-search-handlers.ts +291 -0
  280. package/server/services/websocket/file-utils.ts +3 -3
  281. package/server/services/websocket/git-branch-handlers.ts +130 -0
  282. package/server/services/websocket/git-diff-handlers.ts +140 -0
  283. package/server/services/websocket/git-handlers.ts +40 -625
  284. package/server/services/websocket/git-log-handlers.ts +149 -0
  285. package/server/services/websocket/git-pr-handlers.ts +17 -62
  286. package/server/services/websocket/git-tag-handlers.ts +91 -0
  287. package/server/services/websocket/git-utils.ts +230 -0
  288. package/server/services/websocket/handler.ts +39 -126
  289. package/server/services/websocket/plan-board-handlers.ts +277 -0
  290. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  291. package/server/services/websocket/plan-handlers.ts +8 -1114
  292. package/server/services/websocket/plan-helpers.ts +215 -0
  293. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  294. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  295. package/server/services/websocket/quality-complexity.ts +294 -0
  296. package/server/services/websocket/quality-fix-agent.ts +181 -0
  297. package/server/services/websocket/quality-handlers.ts +36 -404
  298. package/server/services/websocket/quality-linting.ts +187 -0
  299. package/server/services/websocket/quality-review-agent.ts +246 -0
  300. package/server/services/websocket/quality-service.ts +11 -762
  301. package/server/services/websocket/quality-tools.ts +209 -0
  302. package/server/services/websocket/quality-types.ts +169 -0
  303. package/server/services/websocket/session-handlers.ts +5 -437
  304. package/server/services/websocket/session-history.ts +222 -0
  305. package/server/services/websocket/session-initialization.ts +209 -0
  306. package/server/services/websocket/types.ts +17 -0
@@ -0,0 +1,165 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { spawn } from 'node:child_process';
5
+ import { join, relative } from 'node:path';
6
+ import type { HandlerContext } from './handler-context.js';
7
+ import type { WebSocketMessage, WSContext } from './types.js';
8
+
9
+ type DefinitionEntry = { filePath: string; line: number; column: number; lineContent: string; kind: string };
10
+
11
+ function classifyDefinitionKind(lineContent: string): string {
12
+ if (/\b(function|def|func|fn)\b/.test(lineContent)) return 'function';
13
+ if (/\bclass\b/.test(lineContent)) return 'class';
14
+ if (/\binterface\b/.test(lineContent)) return 'interface';
15
+ if (/\btype\b/.test(lineContent)) return 'type';
16
+ if (/\b(enum|struct|trait)\b/.test(lineContent)) return 'enum';
17
+ return 'variable';
18
+ }
19
+
20
+ /** Parse a single JSON line from rg definition search. Returns true if max definitions reached. */
21
+ function parseDefinitionLine(line: string, workingDir: string, definitions: DefinitionEntry[]): boolean {
22
+ try {
23
+ const parsed = JSON.parse(line);
24
+ if (parsed.type !== 'match') return false;
25
+
26
+ const filePath = relative(workingDir, join(workingDir, parsed.data.path.text));
27
+ const lineContent = parsed.data.lines.text.replace(/\n$/, '');
28
+ const column = parsed.data.submatches?.[0]?.start ?? 0;
29
+
30
+ definitions.push({
31
+ filePath,
32
+ line: parsed.data.line_number,
33
+ column: column + 1,
34
+ lineContent,
35
+ kind: classifyDefinitionKind(lineContent),
36
+ });
37
+ return definitions.length >= 20;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ function sortDefinitionsByProximity(definitions: DefinitionEntry[], currentFile: string): void {
44
+ const currentDir = currentFile ? currentFile.substring(0, currentFile.lastIndexOf('/')) : '';
45
+ definitions.sort((a, b) => {
46
+ const exactDiff = (a.filePath === currentFile ? 0 : 1) - (b.filePath === currentFile ? 0 : 1);
47
+ if (exactDiff !== 0) return exactDiff;
48
+ const dirDiff = (a.filePath.startsWith(`${currentDir}/`) ? 0 : 1) - (b.filePath.startsWith(`${currentDir}/`) ? 0 : 1);
49
+ if (dirDiff !== 0) return dirDiff;
50
+ return a.filePath.split('/').length - b.filePath.split('/').length;
51
+ });
52
+ }
53
+
54
+ const DEFINITION_PATTERNS: Record<string, (s: string) => string[]> = {
55
+ typescript: (s) => [
56
+ `(function|const|let|var|class|interface|type|enum)\\s+${s}\\b`,
57
+ `export\\s+(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${s}\\b`,
58
+ ],
59
+ javascript: (s) => [
60
+ `(function|const|let|var|class)\\s+${s}\\b`,
61
+ `export\\s+(default\\s+)?(function|const|let|var|class)\\s+${s}\\b`,
62
+ ],
63
+ python: (s) => [
64
+ `(def|class)\\s+${s}\\b`,
65
+ `${s}\\s*=`,
66
+ ],
67
+ go: (s) => [
68
+ `func\\s+(\\(\\w+\\s+\\*?\\w+\\)\\s+)?${s}\\b`,
69
+ `type\\s+${s}\\b`,
70
+ `var\\s+${s}\\b`,
71
+ ],
72
+ rust: (s) => [
73
+ `(fn|struct|enum|trait|type|const|static|mod)\\s+${s}\\b`,
74
+ `impl\\s+${s}\\b`,
75
+ ],
76
+ swift: (s) => [
77
+ `(func|class|struct|enum|protocol|typealias|actor)\\s+${s}\\b`,
78
+ `(let|var)\\s+${s}\\b`,
79
+ `extension\\s+${s}\\b`,
80
+ ],
81
+ kotlin: (s) => [
82
+ `(fun|class|object|interface|typealias|enum\\s+class)\\s+${s}\\b`,
83
+ `(val|var)\\s+${s}\\b`,
84
+ ],
85
+ java: (s) => [
86
+ `(class|interface|enum)\\s+${s}\\b`,
87
+ `(public|private|protected|static)?\\s*(void|int|String|boolean|\\w+)\\s+${s}\\s*\\(`,
88
+ ],
89
+ ruby: (s) => [
90
+ `(def|class|module)\\s+${s}\\b`,
91
+ ],
92
+ };
93
+
94
+ const LANGUAGE_GLOBS: Record<string, string> = {
95
+ typescript: '*.{ts,tsx}',
96
+ javascript: '*.{js,jsx,mjs,cjs}',
97
+ python: '*.py',
98
+ go: '*.go',
99
+ rust: '*.rs',
100
+ swift: '*.swift',
101
+ kotlin: '*.{kt,kts}',
102
+ java: '*.java',
103
+ ruby: '*.rb',
104
+ };
105
+
106
+ export function handleFindDefinition(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
107
+ const symbol = msg.data?.symbol;
108
+ const language = msg.data?.language || 'typescript';
109
+ const currentFile = msg.data?.currentFile || '';
110
+
111
+ if (!symbol) {
112
+ ctx.send(ws, { type: 'definitionResult', tabId, data: { definitions: [], symbol: '' } });
113
+ return;
114
+ }
115
+
116
+ const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
117
+
118
+ const patterns = DEFINITION_PATTERNS[language] || DEFINITION_PATTERNS.typescript;
119
+ const combinedPattern = patterns(escapedSymbol).join('|');
120
+ const fileGlob = LANGUAGE_GLOBS[language] || LANGUAGE_GLOBS.typescript;
121
+
122
+ const args = [
123
+ '--json', '-n',
124
+ '--glob', fileGlob,
125
+ '--glob', '!node_modules/**',
126
+ '--glob', '!dist/**',
127
+ '--glob', '!build/**',
128
+ '--glob', '!.git/**',
129
+ '--glob', '!*.min.*',
130
+ '--glob', '!*.bundle.*',
131
+ '-e', combinedPattern, '.',
132
+ ];
133
+
134
+ const rgProcess = spawn('rg', args, { cwd: workingDir, stdio: ['ignore', 'pipe', 'pipe'] });
135
+ let rgBuffer = '';
136
+ const definitions: Array<{ filePath: string; line: number; column: number; lineContent: string; kind: string }> = [];
137
+
138
+ rgProcess.stdout?.on('data', (chunk: Buffer) => {
139
+ rgBuffer += chunk.toString();
140
+ const lines = rgBuffer.split('\n');
141
+ rgBuffer = lines.pop() || '';
142
+
143
+ for (const line of lines) {
144
+ if (!line.trim()) continue;
145
+ if (parseDefinitionLine(line, workingDir, definitions)) {
146
+ rgProcess.kill();
147
+ return;
148
+ }
149
+ }
150
+ });
151
+
152
+ rgProcess.on('close', () => {
153
+ sortDefinitionsByProximity(definitions, currentFile);
154
+
155
+ ctx.send(ws, {
156
+ type: 'definitionResult',
157
+ tabId,
158
+ data: { definitions: definitions.slice(0, 10), symbol },
159
+ });
160
+ });
161
+
162
+ rgProcess.on('error', (_err) => {
163
+ ctx.send(ws, { type: 'definitionResult', tabId, data: { definitions: [], symbol } });
164
+ });
165
+ }
@@ -1,8 +1,6 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
- import { spawn } from 'node:child_process';
5
- import { join, relative } from 'node:path';
6
4
  import {
7
5
  createDirectory,
8
6
  createFile,
@@ -12,6 +10,8 @@ import {
12
10
  writeFile
13
11
  } from '../files.js';
14
12
  import { validatePathWithinWorkingDir } from '../pathUtils.js';
13
+ import { handleFindDefinition } from './file-definition-handlers.js';
14
+ import { handleCancelSearch, handleSearchFileContents } from './file-search-handlers.js';
15
15
  import { readFileContent } from './file-utils.js';
16
16
  import type { HandlerContext } from './handler-context.js';
17
17
  import type { WebSocketMessage, WebSocketResponse, WSContext } from './types.js';
@@ -64,11 +64,41 @@ export function handleFileExplorerMessage(ctx: HandlerContext, ws: WSContext, ms
64
64
  }
65
65
  handleListDirectory(ctx, ws, msg, tabId, workingDir);
66
66
  },
67
- writeFile: () => handleWriteFile(ctx, ws, msg, tabId, workingDir),
68
- createFile: () => handleCreateFile(ctx, ws, msg, tabId, workingDir),
69
- createDirectory: () => handleCreateDirectory(ctx, ws, msg, tabId, workingDir),
70
- deleteFile: () => handleDeleteFile(ctx, ws, msg, tabId, workingDir),
71
- renameFile: () => handleRenameFile(ctx, ws, msg, tabId, workingDir),
67
+ writeFile: () => {
68
+ if (isSandboxed && msg.data?.filePath) {
69
+ const validation = validatePathWithinWorkingDir(msg.data.filePath, workingDir);
70
+ if (!validation.valid) { ctx.send(ws, { type: 'fileError', tabId, data: { operation: 'writeFile', path: msg.data.filePath, error: 'Sandboxed: path outside project directory' } }); return; }
71
+ }
72
+ handleWriteFile(ctx, ws, msg, tabId, workingDir);
73
+ },
74
+ createFile: () => {
75
+ if (isSandboxed && msg.data?.filePath) {
76
+ const validation = validatePathWithinWorkingDir(msg.data.filePath, workingDir);
77
+ if (!validation.valid) { ctx.send(ws, { type: 'fileError', tabId, data: { operation: 'createFile', path: msg.data.filePath, error: 'Sandboxed: path outside project directory' } }); return; }
78
+ }
79
+ handleCreateFile(ctx, ws, msg, tabId, workingDir);
80
+ },
81
+ createDirectory: () => {
82
+ if (isSandboxed && msg.data?.dirPath) {
83
+ const validation = validatePathWithinWorkingDir(msg.data.dirPath, workingDir);
84
+ if (!validation.valid) { ctx.send(ws, { type: 'fileError', tabId, data: { operation: 'createDirectory', path: msg.data.dirPath, error: 'Sandboxed: path outside project directory' } }); return; }
85
+ }
86
+ handleCreateDirectory(ctx, ws, msg, tabId, workingDir);
87
+ },
88
+ deleteFile: () => {
89
+ if (isSandboxed && msg.data?.filePath) {
90
+ const validation = validatePathWithinWorkingDir(msg.data.filePath, workingDir);
91
+ if (!validation.valid) { ctx.send(ws, { type: 'fileError', tabId, data: { operation: 'deleteFile', path: msg.data.filePath, error: 'Sandboxed: path outside project directory' } }); return; }
92
+ }
93
+ handleDeleteFile(ctx, ws, msg, tabId, workingDir);
94
+ },
95
+ renameFile: () => {
96
+ if (isSandboxed && msg.data?.filePath) {
97
+ const validation = validatePathWithinWorkingDir(msg.data.filePath, workingDir);
98
+ if (!validation.valid) { ctx.send(ws, { type: 'fileError', tabId, data: { operation: 'renameFile', path: msg.data.filePath, error: 'Sandboxed: path outside project directory' } }); return; }
99
+ }
100
+ handleRenameFile(ctx, ws, msg, tabId, workingDir);
101
+ },
72
102
  notifyFileOpened: () => handleNotifyFileOpened(ctx, ws, msg, workingDir),
73
103
  searchFileContents: () => handleSearchFileContents(ctx, ws, msg, tabId, workingDir),
74
104
  cancelSearch: () => handleCancelSearch(ctx, tabId),
@@ -80,9 +110,6 @@ export function handleFileExplorerMessage(ctx: HandlerContext, ws: WSContext, ms
80
110
  try {
81
111
  handler();
82
112
  } catch (error: unknown) {
83
- // Send a domain-specific fileError so the web client can resolve pending
84
- // promises instead of letting the generic handler send { type: 'error' }
85
- // which no file-explorer listener handles (causing orphaned promises).
86
113
  const errorMessage = error instanceof Error ? error.message : String(error);
87
114
  ctx.send(ws, {
88
115
  type: 'fileError',
@@ -178,445 +205,3 @@ function handleNotifyFileOpened(ctx: HandlerContext, ws: WSContext, msg: WebSock
178
205
  });
179
206
  }
180
207
  }
181
-
182
- function appendGlobArgs(args: string[], globStr: string, prefix: string): void {
183
- for (const glob of globStr.split(',')) {
184
- const trimmed = glob.trim();
185
- if (trimmed) args.push('--glob', `${prefix}${trimmed}`);
186
- }
187
- }
188
-
189
- function buildRgArgs(query: string, options: Record<string, unknown>): string[] {
190
- const args: string[] = ['--json', '--no-heading'];
191
- if (!options.caseSensitive) args.push('-i');
192
- if (options.wholeWord) args.push('-w');
193
- if (!options.regex) args.push('-F');
194
- args.push('-C', options.contextLines !== undefined ? String(options.contextLines) : '1');
195
- if (options.includeGlob) appendGlobArgs(args, options.includeGlob as string, '');
196
- if (options.excludeGlob) appendGlobArgs(args, options.excludeGlob as string, '!');
197
- args.push('--', query, '.');
198
- return args;
199
- }
200
-
201
- type SearchMatch = { filePath: string; line: number; column: number; lineContent: string; contextBefore: string[]; contextAfter: string[] };
202
-
203
- /** Process a single JSON line from rg output. Returns true if search should stop (maxResults reached). */
204
- function processRgSearchLine(
205
- line: string,
206
- workingDir: string,
207
- batch: SearchMatch[],
208
- seenFiles: Set<string>,
209
- contextMap: Map<string, { before: string[]; after: string[] }>,
210
- counters: { totalMatches: number; fileCount: number },
211
- maxResults: number,
212
- flushBatch: () => void,
213
- ): boolean {
214
- try {
215
- const parsed = JSON.parse(line);
216
- if (parsed.type === 'match') {
217
- return processRgMatch(parsed, workingDir, batch, seenFiles, contextMap, counters, maxResults, flushBatch);
218
- }
219
- if (parsed.type === 'context') {
220
- appendRgContext(parsed, workingDir, batch);
221
- }
222
- } catch {
223
- // Skip malformed JSON lines
224
- }
225
- return false;
226
- }
227
-
228
- function processRgMatch(
229
- parsed: { data: { path: { text: string }; line_number: number; lines: { text: string }; submatches?: Array<{ start: number }> } },
230
- workingDir: string,
231
- batch: SearchMatch[],
232
- seenFiles: Set<string>,
233
- contextMap: Map<string, { before: string[]; after: string[] }>,
234
- counters: { totalMatches: number; fileCount: number },
235
- maxResults: number,
236
- flushBatch: () => void,
237
- ): boolean {
238
- const filePath = relative(workingDir, parsed.data.path.text);
239
- const lineNumber = parsed.data.line_number;
240
- const lineContent = parsed.data.lines.text.replace(/\n$/, '');
241
- const column = parsed.data.submatches?.[0]?.start ?? 0;
242
-
243
- if (!seenFiles.has(filePath)) {
244
- seenFiles.add(filePath);
245
- counters.fileCount++;
246
- }
247
- counters.totalMatches++;
248
-
249
- const key = `${filePath}:${lineNumber}`;
250
- const ctxLines = contextMap.get(key) || { before: [], after: [] };
251
- batch.push({ filePath, line: lineNumber, column: column + 1, lineContent, contextBefore: ctxLines.before, contextAfter: [] });
252
-
253
- if (counters.totalMatches >= maxResults) {
254
- flushBatch();
255
- return true;
256
- }
257
- if (batch.length >= 50) flushBatch();
258
- return false;
259
- }
260
-
261
- function appendRgContext(
262
- parsed: { data: { path: { text: string }; line_number: number; lines: { text: string } } },
263
- workingDir: string,
264
- batch: SearchMatch[],
265
- ): void {
266
- const filePath = relative(workingDir, parsed.data.path.text);
267
- const lineNumber = parsed.data.line_number;
268
- const lineContent = parsed.data.lines.text.replace(/\n$/, '');
269
-
270
- const lastMatch = batch[batch.length - 1];
271
- if (!lastMatch || lastMatch.filePath !== filePath) return;
272
- if (lineNumber < lastMatch.line) {
273
- lastMatch.contextBefore.push(lineContent);
274
- } else {
275
- lastMatch.contextAfter.push(lineContent);
276
- }
277
- }
278
-
279
- function handleSearchFileContents(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
280
- const query = msg.data?.query;
281
- if (!query) {
282
- ctx.send(ws, { type: 'contentSearchError', tabId, data: { error: 'Search query is required' } });
283
- return;
284
- }
285
-
286
- handleCancelSearch(ctx, tabId);
287
-
288
- const options = msg.data.options || {};
289
- const startTime = Date.now();
290
- let totalMatches = 0;
291
- let fileCount = 0;
292
- const seenFiles = new Set<string>();
293
- const maxResults = options.maxResults || 5000;
294
- let batch: SearchMatch[] = [];
295
-
296
- const args = buildRgArgs(query, options);
297
-
298
- const rgProcess = spawn('rg', args, { cwd: workingDir, stdio: ['ignore', 'pipe', 'pipe'] });
299
- ctx.activeSearches.set(tabId, rgProcess);
300
-
301
- let buffer = '';
302
- const contextMap = new Map<string, { before: string[]; after: string[] }>();
303
-
304
- const flushBatch = () => {
305
- if (batch.length > 0) {
306
- ctx.send(ws, { type: 'contentSearchResults', tabId, data: { matches: batch, partial: true } });
307
- batch = [];
308
- }
309
- };
310
-
311
- const searchState = { totalMatches, fileCount };
312
-
313
- rgProcess.stdout?.on('data', (chunk: Buffer) => {
314
- buffer += chunk.toString();
315
- const lines = buffer.split('\n');
316
- buffer = lines.pop() || '';
317
-
318
- for (const line of lines) {
319
- if (!line.trim()) continue;
320
- if (processRgSearchLine(line, workingDir, batch, seenFiles, contextMap, searchState, maxResults, flushBatch)) {
321
- rgProcess.kill();
322
- return;
323
- }
324
- }
325
- totalMatches = searchState.totalMatches;
326
- fileCount = searchState.fileCount;
327
- });
328
-
329
- rgProcess.stderr?.on('data', (chunk: Buffer) => {
330
- const errText = chunk.toString().trim();
331
- if (errText && !errText.includes('No files were searched')) {
332
- console.error(`[Search] rg stderr: ${errText}`);
333
- }
334
- });
335
-
336
- rgProcess.on('close', (_code) => {
337
- ctx.activeSearches.delete(tabId);
338
- flushBatch();
339
-
340
- ctx.send(ws, {
341
- type: 'contentSearchComplete',
342
- tabId,
343
- data: {
344
- totalMatches,
345
- fileCount,
346
- truncated: totalMatches >= maxResults,
347
- durationMs: Date.now() - startTime,
348
- },
349
- });
350
- });
351
-
352
- rgProcess.on('error', (err) => {
353
- ctx.activeSearches.delete(tabId);
354
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
355
- handleSearchFallback(ctx, ws, query, options, tabId, workingDir);
356
- } else {
357
- ctx.send(ws, { type: 'contentSearchError', tabId, data: { error: err.message } });
358
- }
359
- });
360
- }
361
-
362
- /** Process a single grep output line. Returns true if search should stop. */
363
- function processGrepLine(
364
- line: string,
365
- batch: SearchMatch[],
366
- seenFiles: Set<string>,
367
- counters: { totalMatches: number; fileCount: number },
368
- maxResults: number,
369
- flushBatch: () => void,
370
- ): boolean {
371
- const match = line.match(/^\.\/(.+?):(\d+):(.*)$/);
372
- if (!match) return false;
373
-
374
- const filePath = match[1];
375
- const lineNumber = parseInt(match[2], 10);
376
- const lineContent = match[3];
377
-
378
- if (!seenFiles.has(filePath)) {
379
- seenFiles.add(filePath);
380
- counters.fileCount++;
381
- }
382
- counters.totalMatches++;
383
-
384
- batch.push({ filePath, line: lineNumber, column: 1, lineContent, contextBefore: [], contextAfter: [] });
385
-
386
- if (counters.totalMatches >= maxResults) {
387
- flushBatch();
388
- return true;
389
- }
390
- if (batch.length >= 50) flushBatch();
391
- return false;
392
- }
393
-
394
- function handleSearchFallback(ctx: HandlerContext, ws: WSContext, query: string, options: Record<string, unknown>, tabId: string, workingDir: string): void {
395
- const startTime = Date.now();
396
- const args: string[] = ['-rn'];
397
- if (!options.caseSensitive) args.push('-i');
398
- if (options.includeGlob) {
399
- for (const glob of String(options.includeGlob).split(',')) {
400
- const trimmed = glob.trim();
401
- if (trimmed) args.push(`--include=${trimmed}`);
402
- }
403
- }
404
- args.push('--', query, '.');
405
-
406
- const grepProcess = spawn('grep', args, { cwd: workingDir, stdio: ['ignore', 'pipe', 'pipe'] });
407
- ctx.activeSearches.set(tabId, grepProcess);
408
-
409
- let buffer = '';
410
- let totalMatches = 0;
411
- let fileCount = 0;
412
- const seenFiles = new Set<string>();
413
- const maxResults = (options.maxResults as number) || 5000;
414
- let batch: SearchMatch[] = [];
415
- const grepState = { totalMatches, fileCount };
416
-
417
- const flushGrepBatch = () => {
418
- if (batch.length > 0) {
419
- ctx.send(ws, { type: 'contentSearchResults', tabId, data: { matches: batch, partial: true } });
420
- batch = [];
421
- }
422
- };
423
-
424
- grepProcess.stdout?.on('data', (chunk: Buffer) => {
425
- buffer += chunk.toString();
426
- const lines = buffer.split('\n');
427
- buffer = lines.pop() || '';
428
-
429
- for (const line of lines) {
430
- if (!line.trim()) continue;
431
- if (processGrepLine(line, batch, seenFiles, grepState, maxResults, flushGrepBatch)) {
432
- grepProcess.kill();
433
- return;
434
- }
435
- }
436
- totalMatches = grepState.totalMatches;
437
- fileCount = grepState.fileCount;
438
- });
439
-
440
- grepProcess.on('close', () => {
441
- ctx.activeSearches.delete(tabId);
442
- if (batch.length > 0) {
443
- ctx.send(ws, { type: 'contentSearchResults', tabId, data: { matches: batch, partial: true } });
444
- }
445
- ctx.send(ws, {
446
- type: 'contentSearchComplete',
447
- tabId,
448
- data: { totalMatches, fileCount, truncated: totalMatches >= maxResults, durationMs: Date.now() - startTime },
449
- });
450
- });
451
-
452
- grepProcess.on('error', (err) => {
453
- ctx.activeSearches.delete(tabId);
454
- ctx.send(ws, { type: 'contentSearchError', tabId, data: { error: `Search unavailable: ${err.message}` } });
455
- });
456
- }
457
-
458
- function handleCancelSearch(ctx: HandlerContext, tabId: string): void {
459
- const process = ctx.activeSearches.get(tabId);
460
- if (process) {
461
- process.kill();
462
- ctx.activeSearches.delete(tabId);
463
- }
464
- }
465
-
466
- type DefinitionEntry = { filePath: string; line: number; column: number; lineContent: string; kind: string };
467
-
468
- function classifyDefinitionKind(lineContent: string): string {
469
- if (/\b(function|def|func|fn)\b/.test(lineContent)) return 'function';
470
- if (/\bclass\b/.test(lineContent)) return 'class';
471
- if (/\binterface\b/.test(lineContent)) return 'interface';
472
- if (/\btype\b/.test(lineContent)) return 'type';
473
- if (/\b(enum|struct|trait)\b/.test(lineContent)) return 'enum';
474
- return 'variable';
475
- }
476
-
477
- /** Parse a single JSON line from rg definition search. Returns true if max definitions reached. */
478
- function parseDefinitionLine(line: string, workingDir: string, definitions: DefinitionEntry[]): boolean {
479
- try {
480
- const parsed = JSON.parse(line);
481
- if (parsed.type !== 'match') return false;
482
-
483
- const filePath = relative(workingDir, join(workingDir, parsed.data.path.text));
484
- const lineContent = parsed.data.lines.text.replace(/\n$/, '');
485
- const column = parsed.data.submatches?.[0]?.start ?? 0;
486
-
487
- definitions.push({
488
- filePath,
489
- line: parsed.data.line_number,
490
- column: column + 1,
491
- lineContent,
492
- kind: classifyDefinitionKind(lineContent),
493
- });
494
- return definitions.length >= 20;
495
- } catch {
496
- return false;
497
- }
498
- }
499
-
500
- function sortDefinitionsByProximity(definitions: DefinitionEntry[], currentFile: string): void {
501
- const currentDir = currentFile ? currentFile.substring(0, currentFile.lastIndexOf('/')) : '';
502
- definitions.sort((a, b) => {
503
- const exactDiff = (a.filePath === currentFile ? 0 : 1) - (b.filePath === currentFile ? 0 : 1);
504
- if (exactDiff !== 0) return exactDiff;
505
- const dirDiff = (a.filePath.startsWith(`${currentDir}/`) ? 0 : 1) - (b.filePath.startsWith(`${currentDir}/`) ? 0 : 1);
506
- if (dirDiff !== 0) return dirDiff;
507
- return a.filePath.split('/').length - b.filePath.split('/').length;
508
- });
509
- }
510
-
511
- function handleFindDefinition(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
512
- const symbol = msg.data?.symbol;
513
- const language = msg.data?.language || 'typescript';
514
- const currentFile = msg.data?.currentFile || '';
515
-
516
- if (!symbol) {
517
- ctx.send(ws, { type: 'definitionResult', tabId, data: { definitions: [], symbol: '' } });
518
- return;
519
- }
520
-
521
- const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
522
-
523
- const DEFINITION_PATTERNS: Record<string, (s: string) => string[]> = {
524
- typescript: (s) => [
525
- `(function|const|let|var|class|interface|type|enum)\\s+${s}\\b`,
526
- `export\\s+(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${s}\\b`,
527
- ],
528
- javascript: (s) => [
529
- `(function|const|let|var|class)\\s+${s}\\b`,
530
- `export\\s+(default\\s+)?(function|const|let|var|class)\\s+${s}\\b`,
531
- ],
532
- python: (s) => [
533
- `(def|class)\\s+${s}\\b`,
534
- `${s}\\s*=`,
535
- ],
536
- go: (s) => [
537
- `func\\s+(\\(\\w+\\s+\\*?\\w+\\)\\s+)?${s}\\b`,
538
- `type\\s+${s}\\b`,
539
- `var\\s+${s}\\b`,
540
- ],
541
- rust: (s) => [
542
- `(fn|struct|enum|trait|type|const|static|mod)\\s+${s}\\b`,
543
- `impl\\s+${s}\\b`,
544
- ],
545
- swift: (s) => [
546
- `(func|class|struct|enum|protocol|typealias|actor)\\s+${s}\\b`,
547
- `(let|var)\\s+${s}\\b`,
548
- `extension\\s+${s}\\b`,
549
- ],
550
- kotlin: (s) => [
551
- `(fun|class|object|interface|typealias|enum\\s+class)\\s+${s}\\b`,
552
- `(val|var)\\s+${s}\\b`,
553
- ],
554
- java: (s) => [
555
- `(class|interface|enum)\\s+${s}\\b`,
556
- `(public|private|protected|static)?\\s*(void|int|String|boolean|\\w+)\\s+${s}\\s*\\(`,
557
- ],
558
- ruby: (s) => [
559
- `(def|class|module)\\s+${s}\\b`,
560
- ],
561
- };
562
-
563
- const LANGUAGE_GLOBS: Record<string, string> = {
564
- typescript: '*.{ts,tsx}',
565
- javascript: '*.{js,jsx,mjs,cjs}',
566
- python: '*.py',
567
- go: '*.go',
568
- rust: '*.rs',
569
- swift: '*.swift',
570
- kotlin: '*.{kt,kts}',
571
- java: '*.java',
572
- ruby: '*.rb',
573
- };
574
-
575
- const patterns = DEFINITION_PATTERNS[language] || DEFINITION_PATTERNS.typescript;
576
- const combinedPattern = patterns(escapedSymbol).join('|');
577
- const fileGlob = LANGUAGE_GLOBS[language] || LANGUAGE_GLOBS.typescript;
578
-
579
- const args = [
580
- '--json', '-n',
581
- '--glob', fileGlob,
582
- '--glob', '!node_modules/**',
583
- '--glob', '!dist/**',
584
- '--glob', '!build/**',
585
- '--glob', '!.git/**',
586
- '--glob', '!*.min.*',
587
- '--glob', '!*.bundle.*',
588
- '-e', combinedPattern, '.',
589
- ];
590
-
591
- const rgProcess = spawn('rg', args, { cwd: workingDir, stdio: ['ignore', 'pipe', 'pipe'] });
592
- let rgBuffer = '';
593
- const definitions: Array<{ filePath: string; line: number; column: number; lineContent: string; kind: string }> = [];
594
-
595
- rgProcess.stdout?.on('data', (chunk: Buffer) => {
596
- rgBuffer += chunk.toString();
597
- const lines = rgBuffer.split('\n');
598
- rgBuffer = lines.pop() || '';
599
-
600
- for (const line of lines) {
601
- if (!line.trim()) continue;
602
- if (parseDefinitionLine(line, workingDir, definitions)) {
603
- rgProcess.kill();
604
- return;
605
- }
606
- }
607
- });
608
-
609
- rgProcess.on('close', () => {
610
- sortDefinitionsByProximity(definitions, currentFile);
611
-
612
- ctx.send(ws, {
613
- type: 'definitionResult',
614
- tabId,
615
- data: { definitions: definitions.slice(0, 10), symbol },
616
- });
617
- });
618
-
619
- rgProcess.on('error', (_err) => {
620
- ctx.send(ws, { type: 'definitionResult', tabId, data: { definitions: [], symbol } });
621
- });
622
- }