codesift-mcp 0.14.0 → 0.15.0

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 (454) hide show
  1. package/README.md +5 -1
  2. package/dist/cli/commands-admin.d.ts +18 -0
  3. package/dist/cli/commands-admin.d.ts.map +1 -0
  4. package/dist/cli/commands-admin.js +187 -0
  5. package/dist/cli/commands-admin.js.map +1 -0
  6. package/dist/cli/commands-daemon.d.ts +42 -0
  7. package/dist/cli/commands-daemon.d.ts.map +1 -0
  8. package/dist/cli/commands-daemon.js +163 -0
  9. package/dist/cli/commands-daemon.js.map +1 -0
  10. package/dist/cli/commands-index.d.ts +12 -0
  11. package/dist/cli/commands-index.d.ts.map +1 -0
  12. package/dist/cli/commands-index.js +133 -0
  13. package/dist/cli/commands-index.js.map +1 -0
  14. package/dist/cli/commands-maintenance.d.ts +12 -0
  15. package/dist/cli/commands-maintenance.d.ts.map +1 -0
  16. package/dist/cli/commands-maintenance.js +409 -0
  17. package/dist/cli/commands-maintenance.js.map +1 -0
  18. package/dist/cli/commands-query.d.ts +21 -0
  19. package/dist/cli/commands-query.d.ts.map +1 -0
  20. package/dist/cli/commands-query.js +270 -0
  21. package/dist/cli/commands-query.js.map +1 -0
  22. package/dist/cli/commands.d.ts +2 -36
  23. package/dist/cli/commands.d.ts.map +1 -1
  24. package/dist/cli/commands.js +8 -867
  25. package/dist/cli/commands.js.map +1 -1
  26. package/dist/cli/embed-child.js +13 -2
  27. package/dist/cli/embed-child.js.map +1 -1
  28. package/dist/instructions.d.ts +1 -1
  29. package/dist/instructions.d.ts.map +1 -1
  30. package/dist/instructions.js +5 -2
  31. package/dist/instructions.js.map +1 -1
  32. package/dist/parser/extractors/php-class-metadata.d.ts +10 -0
  33. package/dist/parser/extractors/php-class-metadata.d.ts.map +1 -0
  34. package/dist/parser/extractors/php-class-metadata.js +65 -0
  35. package/dist/parser/extractors/php-class-metadata.js.map +1 -0
  36. package/dist/parser/extractors/php-doc.d.ts +13 -0
  37. package/dist/parser/extractors/php-doc.d.ts.map +1 -0
  38. package/dist/parser/extractors/php-doc.js +39 -0
  39. package/dist/parser/extractors/php-doc.js.map +1 -0
  40. package/dist/parser/extractors/php-node-metadata.d.ts +26 -0
  41. package/dist/parser/extractors/php-node-metadata.d.ts.map +1 -0
  42. package/dist/parser/extractors/php-node-metadata.js +132 -0
  43. package/dist/parser/extractors/php-node-metadata.js.map +1 -0
  44. package/dist/parser/extractors/php-symbol-builders.d.ts +13 -0
  45. package/dist/parser/extractors/php-symbol-builders.d.ts.map +1 -0
  46. package/dist/parser/extractors/php-symbol-builders.js +58 -0
  47. package/dist/parser/extractors/php-symbol-builders.js.map +1 -0
  48. package/dist/parser/extractors/php-walker.d.ts +4 -0
  49. package/dist/parser/extractors/php-walker.d.ts.map +1 -0
  50. package/dist/parser/extractors/php-walker.js +238 -0
  51. package/dist/parser/extractors/php-walker.js.map +1 -0
  52. package/dist/parser/extractors/php.d.ts +2 -33
  53. package/dist/parser/extractors/php.d.ts.map +1 -1
  54. package/dist/parser/extractors/php.js +2 -747
  55. package/dist/parser/extractors/php.js.map +1 -1
  56. package/dist/register-tool-loaders.d.ts +11 -11
  57. package/dist/register-tool-loaders.d.ts.map +1 -1
  58. package/dist/register-tools/runtime.d.ts +11 -0
  59. package/dist/register-tools/runtime.d.ts.map +1 -1
  60. package/dist/register-tools/runtime.js +40 -0
  61. package/dist/register-tools/runtime.js.map +1 -1
  62. package/dist/register-tools.d.ts.map +1 -1
  63. package/dist/register-tools.js +5 -1
  64. package/dist/register-tools.js.map +1 -1
  65. package/dist/retrieval/semantic-handlers.d.ts.map +1 -1
  66. package/dist/retrieval/semantic-handlers.js +10 -3
  67. package/dist/retrieval/semantic-handlers.js.map +1 -1
  68. package/dist/search/semantic.d.ts +2 -0
  69. package/dist/search/semantic.d.ts.map +1 -1
  70. package/dist/search/semantic.js +41 -9
  71. package/dist/search/semantic.js.map +1 -1
  72. package/dist/storage/chunk-store.d.ts +14 -2
  73. package/dist/storage/chunk-store.d.ts.map +1 -1
  74. package/dist/storage/chunk-store.js +214 -34
  75. package/dist/storage/chunk-store.js.map +1 -1
  76. package/dist/storage/embedding-store.d.ts +0 -12
  77. package/dist/storage/embedding-store.d.ts.map +1 -1
  78. package/dist/storage/embedding-store.js +69 -9
  79. package/dist/storage/embedding-store.js.map +1 -1
  80. package/dist/storage/index-cache.d.ts +12 -0
  81. package/dist/storage/index-cache.d.ts.map +1 -0
  82. package/dist/storage/index-cache.js +89 -0
  83. package/dist/storage/index-cache.js.map +1 -0
  84. package/dist/storage/index-json-mutations.d.ts +11 -0
  85. package/dist/storage/index-json-mutations.d.ts.map +1 -0
  86. package/dist/storage/index-json-mutations.js +93 -0
  87. package/dist/storage/index-json-mutations.js.map +1 -0
  88. package/dist/storage/index-migration.d.ts +22 -0
  89. package/dist/storage/index-migration.d.ts.map +1 -0
  90. package/dist/storage/index-migration.js +80 -0
  91. package/dist/storage/index-migration.js.map +1 -0
  92. package/dist/storage/index-store.d.ts +5 -61
  93. package/dist/storage/index-store.d.ts.map +1 -1
  94. package/dist/storage/index-store.js +33 -414
  95. package/dist/storage/index-store.js.map +1 -1
  96. package/dist/storage/index-version.d.ts +39 -0
  97. package/dist/storage/index-version.d.ts.map +1 -0
  98. package/dist/storage/index-version.js +87 -0
  99. package/dist/storage/index-version.js.map +1 -0
  100. package/dist/storage/registry.d.ts +9 -1
  101. package/dist/storage/registry.d.ts.map +1 -1
  102. package/dist/storage/registry.js +169 -8
  103. package/dist/storage/registry.js.map +1 -1
  104. package/dist/storage/shared-embedding-cache.d.ts +23 -3
  105. package/dist/storage/shared-embedding-cache.d.ts.map +1 -1
  106. package/dist/storage/shared-embedding-cache.js +300 -45
  107. package/dist/storage/shared-embedding-cache.js.map +1 -1
  108. package/dist/storage/telemetry/config.d.ts.map +1 -1
  109. package/dist/storage/telemetry/config.js +3 -2
  110. package/dist/storage/telemetry/config.js.map +1 -1
  111. package/dist/storage/telemetry/retro-aggregator.d.ts +21 -0
  112. package/dist/storage/telemetry/retro-aggregator.d.ts.map +1 -1
  113. package/dist/storage/telemetry/retro-aggregator.js +58 -16
  114. package/dist/storage/telemetry/retro-aggregator.js.map +1 -1
  115. package/dist/storage/telemetry/sanitizer.d.ts +16 -0
  116. package/dist/storage/telemetry/sanitizer.d.ts.map +1 -1
  117. package/dist/storage/telemetry/sanitizer.js +2 -0
  118. package/dist/storage/telemetry/sanitizer.js.map +1 -1
  119. package/dist/storage/telemetry/uploader.d.ts +3 -1
  120. package/dist/storage/telemetry/uploader.d.ts.map +1 -1
  121. package/dist/storage/telemetry/uploader.js +158 -21
  122. package/dist/storage/telemetry/uploader.js.map +1 -1
  123. package/dist/tools/cross-repo-contract-adapters.d.ts +39 -0
  124. package/dist/tools/cross-repo-contract-adapters.d.ts.map +1 -0
  125. package/dist/tools/cross-repo-contract-adapters.js +70 -0
  126. package/dist/tools/cross-repo-contract-adapters.js.map +1 -0
  127. package/dist/tools/cross-repo-contract-group.d.ts +17 -0
  128. package/dist/tools/cross-repo-contract-group.d.ts.map +1 -0
  129. package/dist/tools/cross-repo-contract-group.js +89 -0
  130. package/dist/tools/cross-repo-contract-group.js.map +1 -0
  131. package/dist/tools/cross-repo-contract-matcher.d.ts +24 -0
  132. package/dist/tools/cross-repo-contract-matcher.d.ts.map +1 -0
  133. package/dist/tools/cross-repo-contract-matcher.js +134 -0
  134. package/dist/tools/cross-repo-contract-matcher.js.map +1 -0
  135. package/dist/tools/cross-repo-contract-resolver.d.ts +9 -0
  136. package/dist/tools/cross-repo-contract-resolver.d.ts.map +1 -0
  137. package/dist/tools/cross-repo-contract-resolver.js +86 -0
  138. package/dist/tools/cross-repo-contract-resolver.js.map +1 -0
  139. package/dist/tools/cross-repo-contract-tools.d.ts +12 -135
  140. package/dist/tools/cross-repo-contract-tools.d.ts.map +1 -1
  141. package/dist/tools/cross-repo-contract-tools.js +8 -589
  142. package/dist/tools/cross-repo-contract-tools.js.map +1 -1
  143. package/dist/tools/cross-repo-contract-types.d.ts +30 -0
  144. package/dist/tools/cross-repo-contract-types.d.ts.map +1 -0
  145. package/dist/tools/cross-repo-contract-types.js +3 -0
  146. package/dist/tools/cross-repo-contract-types.js.map +1 -0
  147. package/dist/tools/cross-repo-outbound-calls.d.ts +28 -0
  148. package/dist/tools/cross-repo-outbound-calls.d.ts.map +1 -0
  149. package/dist/tools/cross-repo-outbound-calls.js +347 -0
  150. package/dist/tools/cross-repo-outbound-calls.js.map +1 -0
  151. package/dist/tools/cross-repo-outbound-lexer/call-detectors.d.ts.map +1 -1
  152. package/dist/tools/cross-repo-outbound-lexer/call-detectors.js +2 -0
  153. package/dist/tools/cross-repo-outbound-lexer/call-detectors.js.map +1 -1
  154. package/dist/tools/cross-repo-outbound-lexer/types.d.ts +2 -0
  155. package/dist/tools/cross-repo-outbound-lexer/types.d.ts.map +1 -1
  156. package/dist/tools/index-tools/file-indexer.js +3 -3
  157. package/dist/tools/index-tools/file-indexer.js.map +1 -1
  158. package/dist/tools/index-tools/folder-indexer.d.ts.map +1 -1
  159. package/dist/tools/index-tools/folder-indexer.js +47 -2
  160. package/dist/tools/index-tools/folder-indexer.js.map +1 -1
  161. package/dist/tools/index-tools/parse.d.ts +12 -3
  162. package/dist/tools/index-tools/parse.d.ts.map +1 -1
  163. package/dist/tools/index-tools/parse.js +33 -8
  164. package/dist/tools/index-tools/parse.js.map +1 -1
  165. package/dist/tools/index-tools/registry.d.ts +19 -0
  166. package/dist/tools/index-tools/registry.d.ts.map +1 -1
  167. package/dist/tools/index-tools/registry.js +88 -2
  168. package/dist/tools/index-tools/registry.js.map +1 -1
  169. package/dist/tools/index-tools/state.d.ts +31 -0
  170. package/dist/tools/index-tools/state.d.ts.map +1 -1
  171. package/dist/tools/index-tools/state.js +53 -0
  172. package/dist/tools/index-tools/state.js.map +1 -1
  173. package/dist/tools/index-tools/types.d.ts +9 -0
  174. package/dist/tools/index-tools/types.d.ts.map +1 -1
  175. package/dist/tools/index-tools/watcher.js +3 -3
  176. package/dist/tools/index-tools/watcher.js.map +1 -1
  177. package/dist/tools/index-tools/worktree-seed.d.ts +44 -0
  178. package/dist/tools/index-tools/worktree-seed.d.ts.map +1 -0
  179. package/dist/tools/index-tools/worktree-seed.js +302 -0
  180. package/dist/tools/index-tools/worktree-seed.js.map +1 -0
  181. package/dist/tools/index-tools.d.ts.map +1 -1
  182. package/dist/tools/index-tools.js +3 -1
  183. package/dist/tools/index-tools.js.map +1 -1
  184. package/dist/tools/nest-ext-tools/graphql.d.ts +17 -0
  185. package/dist/tools/nest-ext-tools/graphql.d.ts.map +1 -0
  186. package/dist/tools/nest-ext-tools/graphql.js +54 -0
  187. package/dist/tools/nest-ext-tools/graphql.js.map +1 -0
  188. package/dist/tools/nest-ext-tools/microservice.d.ts +17 -0
  189. package/dist/tools/nest-ext-tools/microservice.d.ts.map +1 -0
  190. package/dist/tools/nest-ext-tools/microservice.js +51 -0
  191. package/dist/tools/nest-ext-tools/microservice.js.map +1 -0
  192. package/dist/tools/nest-ext-tools/openapi.d.ts +81 -0
  193. package/dist/tools/nest-ext-tools/openapi.d.ts.map +1 -0
  194. package/dist/tools/nest-ext-tools/openapi.js +254 -0
  195. package/dist/tools/nest-ext-tools/openapi.js.map +1 -0
  196. package/dist/tools/nest-ext-tools/queue.d.ts +27 -0
  197. package/dist/tools/nest-ext-tools/queue.d.ts.map +1 -0
  198. package/dist/tools/nest-ext-tools/queue.js +95 -0
  199. package/dist/tools/nest-ext-tools/queue.js.map +1 -0
  200. package/dist/tools/nest-ext-tools/schedule.d.ts +19 -0
  201. package/dist/tools/nest-ext-tools/schedule.d.ts.map +1 -0
  202. package/dist/tools/nest-ext-tools/schedule.js +89 -0
  203. package/dist/tools/nest-ext-tools/schedule.js.map +1 -0
  204. package/dist/tools/nest-ext-tools/scope.d.ts +19 -0
  205. package/dist/tools/nest-ext-tools/scope.d.ts.map +1 -0
  206. package/dist/tools/nest-ext-tools/scope.js +151 -0
  207. package/dist/tools/nest-ext-tools/scope.js.map +1 -0
  208. package/dist/tools/nest-ext-tools/shared.d.ts +33 -0
  209. package/dist/tools/nest-ext-tools/shared.d.ts.map +1 -0
  210. package/dist/tools/nest-ext-tools/shared.js +279 -0
  211. package/dist/tools/nest-ext-tools/shared.js.map +1 -0
  212. package/dist/tools/nest-ext-tools/typeorm.d.ts +22 -0
  213. package/dist/tools/nest-ext-tools/typeorm.d.ts.map +1 -0
  214. package/dist/tools/nest-ext-tools/typeorm.js +59 -0
  215. package/dist/tools/nest-ext-tools/typeorm.js.map +1 -0
  216. package/dist/tools/nest-ext-tools/websocket.d.ts +21 -0
  217. package/dist/tools/nest-ext-tools/websocket.d.ts.map +1 -0
  218. package/dist/tools/nest-ext-tools/websocket.js +70 -0
  219. package/dist/tools/nest-ext-tools/websocket.js.map +1 -0
  220. package/dist/tools/nest-ext-tools.d.ts +10 -204
  221. package/dist/tools/nest-ext-tools.d.ts.map +1 -1
  222. package/dist/tools/nest-ext-tools.js +10 -749
  223. package/dist/tools/nest-ext-tools.js.map +1 -1
  224. package/dist/tools/perf-tools.js +2 -2
  225. package/dist/tools/python-audit/checks/dead-code.d.ts +1 -1
  226. package/dist/tools/python-audit/checks/dead-code.d.ts.map +1 -1
  227. package/dist/tools/python-constants/constant-match.d.ts +5 -0
  228. package/dist/tools/python-constants/constant-match.d.ts.map +1 -0
  229. package/dist/tools/python-constants/constant-match.js +24 -0
  230. package/dist/tools/python-constants/constant-match.js.map +1 -0
  231. package/dist/tools/python-constants/file-context.d.ts +5 -0
  232. package/dist/tools/python-constants/file-context.d.ts.map +1 -0
  233. package/dist/tools/python-constants/file-context.js +91 -0
  234. package/dist/tools/python-constants/file-context.js.map +1 -0
  235. package/dist/tools/python-constants/function-defaults.d.ts +5 -0
  236. package/dist/tools/python-constants/function-defaults.d.ts.map +1 -0
  237. package/dist/tools/python-constants/function-defaults.js +98 -0
  238. package/dist/tools/python-constants/function-defaults.js.map +1 -0
  239. package/dist/tools/python-constants/model.d.ts +78 -0
  240. package/dist/tools/python-constants/model.d.ts.map +1 -0
  241. package/dist/tools/python-constants/model.js +2 -0
  242. package/dist/tools/python-constants/model.js.map +1 -0
  243. package/dist/tools/python-constants/syntax.d.ts +22 -0
  244. package/dist/tools/python-constants/syntax.d.ts.map +1 -0
  245. package/dist/tools/python-constants/syntax.js +93 -0
  246. package/dist/tools/python-constants/syntax.js.map +1 -0
  247. package/dist/tools/python-constants/value-resolver.d.ts +6 -0
  248. package/dist/tools/python-constants/value-resolver.d.ts.map +1 -0
  249. package/dist/tools/python-constants/value-resolver.js +320 -0
  250. package/dist/tools/python-constants/value-resolver.js.map +1 -0
  251. package/dist/tools/python-constants-tools.d.ts +3 -45
  252. package/dist/tools/python-constants-tools.d.ts.map +1 -1
  253. package/dist/tools/python-constants-tools.js +2 -505
  254. package/dist/tools/python-constants-tools.js.map +1 -1
  255. package/dist/tools/route-tools/django.d.ts +8 -0
  256. package/dist/tools/route-tools/django.d.ts.map +1 -0
  257. package/dist/tools/route-tools/django.js +52 -0
  258. package/dist/tools/route-tools/django.js.map +1 -0
  259. package/dist/tools/route-tools/express.d.ts +5 -0
  260. package/dist/tools/route-tools/express.d.ts.map +1 -0
  261. package/dist/tools/route-tools/express.js +33 -0
  262. package/dist/tools/route-tools/express.js.map +1 -0
  263. package/dist/tools/route-tools/file-sources.d.ts +7 -0
  264. package/dist/tools/route-tools/file-sources.d.ts.map +1 -0
  265. package/dist/tools/route-tools/file-sources.js +15 -0
  266. package/dist/tools/route-tools/file-sources.js.map +1 -0
  267. package/dist/tools/route-tools/handler-discovery.d.ts +4 -0
  268. package/dist/tools/route-tools/handler-discovery.d.ts.map +1 -0
  269. package/dist/tools/route-tools/handler-discovery.js +38 -0
  270. package/dist/tools/route-tools/handler-discovery.js.map +1 -0
  271. package/dist/tools/route-tools/hono.d.ts +5 -0
  272. package/dist/tools/route-tools/hono.d.ts.map +1 -0
  273. package/dist/tools/route-tools/hono.js +61 -0
  274. package/dist/tools/route-tools/hono.js.map +1 -0
  275. package/dist/tools/route-tools/ktor.d.ts +5 -0
  276. package/dist/tools/route-tools/ktor.d.ts.map +1 -0
  277. package/dist/tools/route-tools/ktor.js +76 -0
  278. package/dist/tools/route-tools/ktor.js.map +1 -0
  279. package/dist/tools/route-tools/laravel.d.ts +5 -0
  280. package/dist/tools/route-tools/laravel.d.ts.map +1 -0
  281. package/dist/tools/route-tools/laravel.js +45 -0
  282. package/dist/tools/route-tools/laravel.js.map +1 -0
  283. package/dist/tools/route-tools/mermaid-call-chain.d.ts +3 -0
  284. package/dist/tools/route-tools/mermaid-call-chain.d.ts.map +1 -0
  285. package/dist/tools/route-tools/mermaid-call-chain.js +50 -0
  286. package/dist/tools/route-tools/mermaid-call-chain.js.map +1 -0
  287. package/dist/tools/route-tools/nest.d.ts +5 -0
  288. package/dist/tools/route-tools/nest.d.ts.map +1 -0
  289. package/dist/tools/route-tools/nest.js +75 -0
  290. package/dist/tools/route-tools/nest.js.map +1 -0
  291. package/dist/tools/route-tools/next-trace.d.ts +4 -0
  292. package/dist/tools/route-tools/next-trace.d.ts.map +1 -0
  293. package/dist/tools/route-tools/next-trace.js +48 -0
  294. package/dist/tools/route-tools/next-trace.js.map +1 -0
  295. package/dist/tools/route-tools/next.d.ts +7 -0
  296. package/dist/tools/route-tools/next.d.ts.map +1 -0
  297. package/dist/tools/route-tools/next.js +77 -0
  298. package/dist/tools/route-tools/next.js.map +1 -0
  299. package/dist/tools/route-tools/python-decorators.d.ts +7 -0
  300. package/dist/tools/route-tools/python-decorators.d.ts.map +1 -0
  301. package/dist/tools/route-tools/python-decorators.js +53 -0
  302. package/dist/tools/route-tools/python-decorators.js.map +1 -0
  303. package/dist/tools/route-tools/route-mermaid.d.ts +4 -0
  304. package/dist/tools/route-tools/route-mermaid.d.ts.map +1 -0
  305. package/dist/tools/route-tools/route-mermaid.js +53 -0
  306. package/dist/tools/route-tools/route-mermaid.js.map +1 -0
  307. package/dist/tools/route-tools/spring-kotlin.d.ts +5 -0
  308. package/dist/tools/route-tools/spring-kotlin.d.ts.map +1 -0
  309. package/dist/tools/route-tools/spring-kotlin.js +52 -0
  310. package/dist/tools/route-tools/spring-kotlin.js.map +1 -0
  311. package/dist/tools/route-tools/trace-analysis.d.ts +4 -0
  312. package/dist/tools/route-tools/trace-analysis.d.ts.map +1 -0
  313. package/dist/tools/route-tools/trace-analysis.js +37 -0
  314. package/dist/tools/route-tools/trace-analysis.js.map +1 -0
  315. package/dist/tools/route-tools/trace-route.d.ts +6 -0
  316. package/dist/tools/route-tools/trace-route.d.ts.map +1 -0
  317. package/dist/tools/route-tools/trace-route.js +53 -0
  318. package/dist/tools/route-tools/trace-route.js.map +1 -0
  319. package/dist/tools/route-tools/types.d.ts +35 -0
  320. package/dist/tools/route-tools/types.d.ts.map +1 -0
  321. package/dist/tools/route-tools/types.js +2 -0
  322. package/dist/tools/route-tools/types.js.map +1 -0
  323. package/dist/tools/route-tools/yii2.d.ts +5 -0
  324. package/dist/tools/route-tools/yii2.d.ts.map +1 -0
  325. package/dist/tools/route-tools/yii2.js +73 -0
  326. package/dist/tools/route-tools/yii2.js.map +1 -0
  327. package/dist/tools/route-tools.d.ts +7 -49
  328. package/dist/tools/route-tools.d.ts.map +1 -1
  329. package/dist/tools/route-tools.js +5 -978
  330. package/dist/tools/route-tools.js.map +1 -1
  331. package/dist/tools/symbol-analysis-tools.d.ts +81 -0
  332. package/dist/tools/symbol-analysis-tools.d.ts.map +1 -0
  333. package/dist/tools/symbol-analysis-tools.js +327 -0
  334. package/dist/tools/symbol-analysis-tools.js.map +1 -0
  335. package/dist/tools/symbol-context-tools.d.ts +68 -0
  336. package/dist/tools/symbol-context-tools.d.ts.map +1 -0
  337. package/dist/tools/symbol-context-tools.js +232 -0
  338. package/dist/tools/symbol-context-tools.js.map +1 -0
  339. package/dist/tools/symbol-lookup-tools.d.ts +77 -0
  340. package/dist/tools/symbol-lookup-tools.d.ts.map +1 -0
  341. package/dist/tools/symbol-lookup-tools.js +305 -0
  342. package/dist/tools/symbol-lookup-tools.js.map +1 -0
  343. package/dist/tools/symbol-reference-tools.d.ts +38 -0
  344. package/dist/tools/symbol-reference-tools.d.ts.map +1 -0
  345. package/dist/tools/symbol-reference-tools.js +253 -0
  346. package/dist/tools/symbol-reference-tools.js.map +1 -0
  347. package/dist/tools/symbol-tool-internals.d.ts +9 -0
  348. package/dist/tools/symbol-tool-internals.d.ts.map +1 -0
  349. package/dist/tools/symbol-tool-internals.js +47 -0
  350. package/dist/tools/symbol-tool-internals.js.map +1 -0
  351. package/dist/tools/symbol-tools.d.ts +11 -270
  352. package/dist/tools/symbol-tools.d.ts.map +1 -1
  353. package/dist/tools/symbol-tools.js +7 -1131
  354. package/dist/tools/symbol-tools.js.map +1 -1
  355. package/dist/tools/taint/python-context.d.ts +10 -0
  356. package/dist/tools/taint/python-context.d.ts.map +1 -0
  357. package/dist/tools/taint/python-context.js +150 -0
  358. package/dist/tools/taint/python-context.js.map +1 -0
  359. package/dist/tools/taint/taint-analyzer.d.ts +2 -0
  360. package/dist/tools/taint/taint-analyzer.d.ts.map +1 -0
  361. package/dist/tools/taint/taint-analyzer.js +2 -0
  362. package/dist/tools/taint/taint-analyzer.js.map +1 -0
  363. package/dist/tools/taint/taint-expression.d.ts +18 -0
  364. package/dist/tools/taint/taint-expression.d.ts.map +1 -0
  365. package/dist/tools/taint/taint-expression.js +265 -0
  366. package/dist/tools/taint/taint-expression.js.map +1 -0
  367. package/dist/tools/taint/taint-flow.d.ts +10 -0
  368. package/dist/tools/taint/taint-flow.d.ts.map +1 -0
  369. package/dist/tools/taint/taint-flow.js +178 -0
  370. package/dist/tools/taint/taint-flow.js.map +1 -0
  371. package/dist/tools/taint/taint-model.d.ts +113 -0
  372. package/dist/tools/taint/taint-model.d.ts.map +1 -0
  373. package/dist/tools/taint/taint-model.js +277 -0
  374. package/dist/tools/taint/taint-model.js.map +1 -0
  375. package/dist/tools/taint-tools.d.ts +2 -34
  376. package/dist/tools/taint-tools.d.ts.map +1 -1
  377. package/dist/tools/taint-tools.js +2 -852
  378. package/dist/tools/taint-tools.js.map +1 -1
  379. package/dist/tools/typescript-constants/file-context.d.ts +7 -0
  380. package/dist/tools/typescript-constants/file-context.d.ts.map +1 -0
  381. package/dist/tools/typescript-constants/file-context.js +219 -0
  382. package/dist/tools/typescript-constants/file-context.js.map +1 -0
  383. package/dist/tools/typescript-constants/symbol-resolver.d.ts +7 -0
  384. package/dist/tools/typescript-constants/symbol-resolver.d.ts.map +1 -0
  385. package/dist/tools/typescript-constants/symbol-resolver.js +165 -0
  386. package/dist/tools/typescript-constants/symbol-resolver.js.map +1 -0
  387. package/dist/tools/typescript-constants/types.d.ts +44 -0
  388. package/dist/tools/typescript-constants/types.d.ts.map +1 -0
  389. package/dist/tools/typescript-constants/types.js +2 -0
  390. package/dist/tools/typescript-constants/types.js.map +1 -0
  391. package/dist/tools/typescript-constants/value-evaluator.d.ts +8 -0
  392. package/dist/tools/typescript-constants/value-evaluator.d.ts.map +1 -0
  393. package/dist/tools/typescript-constants/value-evaluator.js +404 -0
  394. package/dist/tools/typescript-constants/value-evaluator.js.map +1 -0
  395. package/dist/tools/typescript-constants-tools.d.ts.map +1 -1
  396. package/dist/tools/typescript-constants-tools.js +19 -709
  397. package/dist/tools/typescript-constants-tools.js.map +1 -1
  398. package/dist/utils/import-graph/collect.d.ts +5 -0
  399. package/dist/utils/import-graph/collect.d.ts.map +1 -0
  400. package/dist/utils/import-graph/collect.js +47 -0
  401. package/dist/utils/import-graph/collect.js.map +1 -0
  402. package/dist/utils/import-graph/edge-accumulator.d.ts +7 -0
  403. package/dist/utils/import-graph/edge-accumulator.d.ts.map +1 -0
  404. package/dist/utils/import-graph/edge-accumulator.js +47 -0
  405. package/dist/utils/import-graph/edge-accumulator.js.map +1 -0
  406. package/dist/utils/import-graph/language-imports.d.ts +6 -0
  407. package/dist/utils/import-graph/language-imports.d.ts.map +1 -0
  408. package/dist/utils/import-graph/language-imports.js +84 -0
  409. package/dist/utils/import-graph/language-imports.js.map +1 -0
  410. package/dist/utils/import-graph/metrics.d.ts +6 -0
  411. package/dist/utils/import-graph/metrics.d.ts.map +1 -0
  412. package/dist/utils/import-graph/metrics.js +51 -0
  413. package/dist/utils/import-graph/metrics.js.map +1 -0
  414. package/dist/utils/import-graph/path-map.d.ts +7 -0
  415. package/dist/utils/import-graph/path-map.d.ts.map +1 -0
  416. package/dist/utils/import-graph/path-map.js +56 -0
  417. package/dist/utils/import-graph/path-map.js.map +1 -0
  418. package/dist/utils/import-graph/python-php-edge-collectors.d.ts +5 -0
  419. package/dist/utils/import-graph/python-php-edge-collectors.d.ts.map +1 -0
  420. package/dist/utils/import-graph/python-php-edge-collectors.js +56 -0
  421. package/dist/utils/import-graph/python-php-edge-collectors.js.map +1 -0
  422. package/dist/utils/import-graph/source-edge-collector.d.ts +13 -0
  423. package/dist/utils/import-graph/source-edge-collector.d.ts.map +1 -0
  424. package/dist/utils/import-graph/source-edge-collector.js +42 -0
  425. package/dist/utils/import-graph/source-edge-collector.js.map +1 -0
  426. package/dist/utils/import-graph/source-imports.d.ts +5 -0
  427. package/dist/utils/import-graph/source-imports.d.ts.map +1 -0
  428. package/dist/utils/import-graph/source-imports.js +33 -0
  429. package/dist/utils/import-graph/source-imports.js.map +1 -0
  430. package/dist/utils/import-graph/types.d.ts +15 -0
  431. package/dist/utils/import-graph/types.d.ts.map +1 -0
  432. package/dist/utils/import-graph/types.js +2 -0
  433. package/dist/utils/import-graph/types.js.map +1 -0
  434. package/dist/utils/import-graph/typescript-edge-collector.d.ts +7 -0
  435. package/dist/utils/import-graph/typescript-edge-collector.d.ts.map +1 -0
  436. package/dist/utils/import-graph/typescript-edge-collector.js +58 -0
  437. package/dist/utils/import-graph/typescript-edge-collector.js.map +1 -0
  438. package/dist/utils/import-graph/workspace-alias.d.ts +7 -0
  439. package/dist/utils/import-graph/workspace-alias.d.ts.map +1 -0
  440. package/dist/utils/import-graph/workspace-alias.js +95 -0
  441. package/dist/utils/import-graph/workspace-alias.js.map +1 -0
  442. package/dist/utils/import-graph/workspace-entry.d.ts +3 -0
  443. package/dist/utils/import-graph/workspace-entry.d.ts.map +1 -0
  444. package/dist/utils/import-graph/workspace-entry.js +81 -0
  445. package/dist/utils/import-graph/workspace-entry.js.map +1 -0
  446. package/dist/utils/import-graph.d.ts +10 -94
  447. package/dist/utils/import-graph.d.ts.map +1 -1
  448. package/dist/utils/import-graph.js +6 -702
  449. package/dist/utils/import-graph.js.map +1 -1
  450. package/package.json +1 -1
  451. package/rules/codesift.md +11 -2
  452. package/rules/codesift.mdc +11 -2
  453. package/rules/codex.md +11 -2
  454. package/rules/gemini.md +11 -2
@@ -1,1135 +1,11 @@
1
- import { readFile, open } from "node:fs/promises";
2
- import { execFileSync } from "node:child_process";
3
- import { join } from "node:path";
4
- import { searchBM25 } from "../search/bm25.js";
5
- import { findReferencesLsp } from "../lsp/lsp-tools.js";
6
- import { loadConfig } from "../config.js";
7
- import { isTestFileStrict as isTestFile } from "../utils/test-file.js";
8
- import { detectFrameworks, isFrameworkEntryPoint } from "../utils/framework-detect.js";
9
- import { getCodeIndex, getBM25Index } from "./index-tools.js";
10
- import { REACT_STDLIB_HOOKS, extractHookNames } from "./react-tools.js";
11
- const MAX_REFERENCES = 100;
12
- const MAX_DEAD_CODE_RESULTS = 100;
13
- const MAX_CONTEXT_LENGTH = 200; // Truncate context lines to prevent huge output from minified files
14
- /** Skip build artifacts and binary files — docs/audits are intentionally kept */
15
- const NOISE_PATH_PREFIXES = [".next/", "dist/", "build/", "coverage/", "node_modules/", "__snapshots__/"];
16
- const NOISE_EXTENSIONS = new Set([".snap", ".lock", ".map", ".svg", ".png", ".jpg", ".ico", ".woff", ".woff2"]);
17
- function isNoisePath(filePath) {
18
- if (NOISE_PATH_PREFIXES.some((p) => filePath.startsWith(p)))
19
- return true;
20
- const dot = filePath.lastIndexOf(".");
21
- if (dot >= 0 && NOISE_EXTENSIONS.has(filePath.slice(dot)))
22
- return true;
23
- return false;
24
- }
25
1
  /**
26
- * Find symbols with names similar to the requested ID. Used to recover from
27
- * hallucinated symbol IDs (telemetry shows 24-26% of get_symbol/get_symbols
28
- * calls return zero results). Extracts a name candidate from the requested
29
- * ID and returns up to `limit` symbols whose name matches case-insensitively.
30
- */
31
- /**
32
- * Resolve a non-canonical symbol reference to a real symbol ID when it is
33
- * UNAMBIGUOUS.
34
- *
35
- * Symbol IDs are `repo:file:name:line` — they embed the declaration line, so no
36
- * caller can construct one by hand. Agents therefore pass a bare name
37
- * ("getOptions") or a half-remembered path, and the lookup returns null; the
38
- * measured miss rate was 24% of get_symbol calls and 26% of get_symbols. The old
39
- * behaviour answered with a suggestion list, which is a second round trip for a
40
- * question we can already answer.
41
- *
42
- * Only an EXACT, SINGLE name match resolves. Two symbols named `handler` must
43
- * stay ambiguous — silently picking one would return the wrong source with no
44
- * signal, which is worse than the miss it replaces.
45
- */
46
- /**
47
- * Best-effort symbol NAME out of whatever the caller passed.
48
- *
49
- * Symbol IDs end in the declaration line (`repo:file:name:LINE`), so naively
50
- * taking the last segment yields "42" and matches nothing — every suggestion for
51
- * a well-formed-but-stale id was silently useless. Skip trailing numeric
52
- * segments, then fall back to the last non-empty one.
53
- */
54
- export function extractSymbolNameGuess(requestedId) {
55
- const parts = requestedId.split(/[:#/.]/).map((p) => p.trim()).filter(Boolean);
56
- for (let i = parts.length - 1; i >= 0; i--) {
57
- const p = parts[i];
58
- if (!/^\d+$/.test(p))
59
- return p;
60
- }
61
- return requestedId.trim();
62
- }
63
- export async function resolveSymbolIdExact(repo, requestedId) {
64
- const nameGuess = extractSymbolNameGuess(requestedId);
65
- if (!nameGuess)
66
- return null;
67
- const index = await getCodeIndex(repo, { skipFreshness: true });
68
- if (!index)
69
- return null;
70
- let match = null;
71
- for (const s of index.symbols) {
72
- if (s.name !== nameGuess)
73
- continue;
74
- if (match)
75
- return null; // ambiguous — let the caller show suggestions
76
- match = s.id;
77
- }
78
- return match;
79
- }
80
- export async function findSimilarSymbols(repo, requestedId, limit = 3) {
81
- const index = await getCodeIndex(repo, { skipFreshness: true });
82
- if (!index)
83
- return [];
84
- const nameGuess = extractSymbolNameGuess(requestedId);
85
- if (!nameGuess)
86
- return [];
87
- const lower = nameGuess.toLowerCase();
88
- const exact = [];
89
- const prefix = [];
90
- const substr = [];
91
- for (const s of index.symbols) {
92
- const sn = s.name.toLowerCase();
93
- if (sn === lower)
94
- exact.push(s);
95
- else if (sn.startsWith(lower) || lower.startsWith(sn))
96
- prefix.push(s);
97
- else if (sn.includes(lower) || lower.includes(sn))
98
- substr.push(s);
99
- if (exact.length >= limit)
100
- break;
101
- }
102
- const ranked = [...exact, ...prefix, ...substr].slice(0, limit);
103
- return ranked.map((s) => ({
104
- id: s.id,
105
- name: s.name,
106
- kind: s.kind,
107
- file: s.file,
108
- start_line: s.start_line,
109
- }));
110
- }
111
- async function requireCodeIndex(repo) {
112
- const index = await getCodeIndex(repo);
113
- if (!index) {
114
- throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
115
- }
116
- return index;
117
- }
118
- async function requireBM25Index(repo) {
119
- const index = await getBM25Index(repo);
120
- if (!index) {
121
- throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
122
- }
123
- return index;
124
- }
125
- function wordBoundaryPattern(name) {
126
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
- return new RegExp(`\\b${escaped}\\b`);
128
- }
129
- /**
130
- * Strip internal/BM25 fields from CodeSymbol for leaner output.
131
- * Removes: repo, tokens, start_col, end_col. Shortens id (strips repo prefix).
132
- */
133
- function stripSymbol(sym) {
134
- const { repo: _repo, tokens: _tokens, start_col: _sc, end_col: _ec, start_byte: _sb, end_byte: _eb, id, ...rest } = sym;
135
- // Strip "local/reponame:" prefix from id
136
- const shortId = id.includes(":") ? id.slice(id.indexOf(":") + 1) : id;
137
- return { ...rest, id: shortId };
138
- }
139
- /**
140
- * Read a source file and extract lines for a symbol (1-based, inclusive).
141
- * Uses byte offsets when available for precise reads without loading full file.
142
- * Returns undefined if the file cannot be read.
143
- */
144
- async function extractSource(repoRoot, file, startLine, endLine, startByte, endByte) {
145
- const filePath = join(repoRoot, file);
146
- // Fast path: use byte offsets to read exact range
147
- if (startByte != null && endByte != null && endByte > startByte) {
148
- try {
149
- const fh = await open(filePath, "r");
150
- try {
151
- const length = endByte - startByte;
152
- const buf = Buffer.alloc(length);
153
- await fh.read(buf, 0, length, startByte);
154
- return buf.toString("utf-8");
155
- }
156
- finally {
157
- await fh.close();
158
- }
159
- }
160
- catch {
161
- // Fall through to line-based extraction
162
- }
163
- }
164
- // Fallback: line-based extraction
165
- try {
166
- const content = await readFile(filePath, "utf-8");
167
- const lines = content.split("\n");
168
- return lines.slice(startLine - 1, endLine).join("\n");
169
- }
170
- catch {
171
- return undefined;
172
- }
173
- }
174
- /**
175
- * Retrieve a single symbol by ID with fresh source from disk.
176
- * When include_related is true (default), auto-prefetches:
177
- * - children (for classes/interfaces) — saves follow-up get_symbols call
178
- * - symbols in the same file that reference this symbol — saves find_references call
179
- */
180
- /**
181
- * Raised when one id maps to several distinct symbols.
2
+ * Stable public facade for symbol-oriented tools.
182
3
  *
183
- * A named type rather than a bare `Error` because two callers must tell this apart from a real
184
- * failure, and matching on message text would break the moment the wording changes. Structural
185
- * check, not `instanceof`: a duplicated module instance across a worker or bundler boundary makes
186
- * `instanceof` false for an object that is in every observable way the right error — the same
187
- * trap `isIndexStorageError` documents in the storage layer.
188
- */
189
- export class AmbiguousSymbolIdError extends Error {
190
- constructor(message) {
191
- super(message);
192
- this.name = "AmbiguousSymbolIdError";
193
- }
194
- }
195
- export function isAmbiguousSymbolIdError(err) {
196
- return (typeof err === "object" &&
197
- err !== null &&
198
- err.name === "AmbiguousSymbolIdError");
199
- }
200
- async function resolveSearchHit(repo, hit) {
201
- try {
202
- const full = await getSymbol(repo, hit.id, { include_related: false });
203
- return full ? { symbol: full.symbol, ambiguity: { status: "unique" } } : null;
204
- }
205
- catch (err) {
206
- if (!isAmbiguousSymbolIdError(err))
207
- throw err;
208
- // Falling back is defensible; falling back SILENTLY is not. The caller is being handed one of
209
- // several symbols and has no way to tell from the result — which is the same silence the
210
- // `lossy_migration` marker was added to remove one layer down.
211
- const index = await requireCodeIndex(repo);
212
- const candidates = index.symbols
213
- .filter((s) => s.id === hit.id)
214
- .map((s) => ({ name: s.name, kind: s.kind, file: s.file, start_line: s.start_line }));
215
- return {
216
- symbol: hit,
217
- ambiguity: { status: "ambiguous", shared_by: candidates.length, candidates: candidates.slice(0, 5) },
218
- };
219
- }
220
- }
221
- export async function getSymbol(repo, symbolId, options) {
222
- const index = await requireCodeIndex(repo);
223
- const includeRelated = options?.include_related ?? true;
224
- // `repo:file:name:line` is NOT unique — a minified bundle puts many symbols on line 1, and
225
- // PHPDoc synthesis emits a field and a method at the same line (73,165 collisions measured
226
- // across 16 indexes). `.find` would hand back whichever happened to load first, with no
227
- // signal: the caller then reads source for a symbol it did not ask for. This file already
228
- // states the rule for names ("two symbols named `handler` must stay ambiguous — silently
229
- // picking one is worse than the miss it replaces"); it was never applied to ids because they
230
- // were assumed unique.
231
- const matches = index.symbols.filter((s) => s.id === symbolId);
232
- if (matches.length === 0)
233
- return null;
234
- if (matches.length > 1) {
235
- const where = matches
236
- .map((s) => `${s.kind} ${s.name} @ ${s.file}:${s.start_line}`)
237
- .slice(0, 5)
238
- .join("; ");
239
- throw new AmbiguousSymbolIdError(`Symbol id "${symbolId}" is ambiguous — ${matches.length} distinct symbols share it: ${where}. ` +
240
- `Ids embed only file, name and line, which collide in generated or synthesised code. ` +
241
- `Use search_symbols to pick the one you mean.`);
242
- }
243
- const symbol = matches[0];
244
- const source = await extractSource(index.root, symbol.file, symbol.start_line, symbol.end_line, symbol.start_byte, symbol.end_byte);
245
- const result = { ...symbol };
246
- if (source !== undefined) {
247
- result.source = source;
248
- }
249
- const stripped = stripSymbol(result);
250
- if (!includeRelated) {
251
- return { symbol: stripped };
252
- }
253
- // Prefetch children for classes/interfaces
254
- const related = [];
255
- if (symbol.kind === "class" || symbol.kind === "interface") {
256
- const children = index.symbols.filter((s) => s.parent === symbol.id);
257
- for (const child of children.slice(0, 20)) {
258
- related.push(stripSymbol(child));
259
- }
260
- }
261
- const out = { symbol: stripped };
262
- if (related.length > 0)
263
- out.related = related;
264
- return out;
265
- }
266
- /**
267
- * Retrieve multiple symbols by ID with fresh source from disk.
268
- * Groups reads by file to minimize disk I/O.
269
- */
270
- export async function getSymbols(repo, symbolIds) {
271
- const index = await requireCodeIndex(repo);
272
- // Build lookup map for requested symbols.
273
- //
274
- // A colliding id must be refused here for the same reason `getSymbol` refuses it: ids are
275
- // `repo:file:name:line` and that is not unique, so `.set()` on a repeated id was last-write-wins
276
- // — the caller asked for one symbol and silently received whichever happened to come later in
277
- // the array. Leaving the batch path permissive would have meant two entry points in this file
278
- // disagreeing about the same input, one throwing and one substituting, which is worse than
279
- // either rule applied consistently.
280
- const requestedIds = new Set(symbolIds);
281
- const symbolMap = new Map();
282
- const collisions = new Map();
283
- for (const sym of index.symbols) {
284
- if (!requestedIds.has(sym.id))
285
- continue;
286
- const seen = symbolMap.get(sym.id);
287
- if (seen === undefined) {
288
- symbolMap.set(sym.id, sym);
289
- continue;
290
- }
291
- const group = collisions.get(sym.id) ?? [seen];
292
- group.push(sym);
293
- collisions.set(sym.id, group);
294
- }
295
- if (collisions.size > 0) {
296
- const detail = [...collisions.entries()]
297
- .slice(0, 3)
298
- .map(([id, group]) => {
299
- const where = group
300
- .slice(0, 5)
301
- .map((s) => `${s.kind} ${s.name} @ ${s.file}:${s.start_line}`)
302
- .join("; ");
303
- return `"${id}" -> ${group.length} symbols: ${where}`;
304
- })
305
- .join(" | ");
306
- throw new AmbiguousSymbolIdError(`${collisions.size} of the requested ids are ambiguous — ${detail}. ` +
307
- `Ids embed only file, name and line, which collide in generated or synthesised code. ` +
308
- `Use search_symbols to pick the ones you mean.`);
309
- }
310
- // Group symbols by file to read each file only once
311
- const byFile = new Map();
312
- for (const id of symbolIds) {
313
- const sym = symbolMap.get(id);
314
- if (!sym)
315
- continue;
316
- let group = byFile.get(sym.file);
317
- if (!group) {
318
- group = [];
319
- byFile.set(sym.file, group);
320
- }
321
- group.push(sym);
322
- }
323
- // Read all files in parallel, extract source for all symbols in each file
324
- const results = new Map();
325
- const fileEntries = [...byFile.entries()];
326
- const fileContents = await Promise.all(fileEntries.map(([file]) => readFile(join(index.root, file), "utf-8").catch(() => undefined)));
327
- for (let i = 0; i < fileEntries.length; i++) {
328
- const [, symbols] = fileEntries[i];
329
- const lines = fileContents[i]?.split("\n");
330
- for (const sym of symbols) {
331
- const result = { ...sym };
332
- if (lines) {
333
- result.source = lines.slice(sym.start_line - 1, sym.end_line).join("\n");
334
- }
335
- results.set(sym.id, result);
336
- }
337
- }
338
- // Return in the same order as requested, skipping missing IDs
339
- const ordered = [];
340
- for (const id of symbolIds) {
341
- const sym = results.get(id);
342
- if (sym)
343
- ordered.push(stripSymbol(sym));
344
- }
345
- return ordered;
346
- }
347
- export async function findReferencesBatch(repo, symbolNames, filePattern, sink) {
348
- const index = await requireCodeIndex(repo);
349
- const patterns = symbolNames.map((name) => ({
350
- name,
351
- regex: wordBoundaryPattern(name),
352
- }));
353
- const fileFilter = filePattern
354
- ? new RegExp(filePattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*"))
355
- : null;
356
- const result = {};
357
- for (const name of symbolNames)
358
- result[name] = [];
359
- let filesScanned = 0;
360
- let filesSkippedNoise = 0;
361
- let filesUnreadable = 0;
362
- for (const fileEntry of index.files) {
363
- if (fileFilter && !fileFilter.test(fileEntry.path))
364
- continue;
365
- if (!filePattern && isNoisePath(fileEntry.path)) {
366
- // Sensible default — but it means "no references" really means "none outside generated
367
- // and vendored code", and the caller could not tell that scoping had happened.
368
- filesSkippedNoise++;
369
- continue;
370
- }
371
- let content;
372
- try {
373
- content = await readFile(join(index.root, fileEntry.path), "utf-8");
374
- filesScanned++;
375
- }
376
- catch {
377
- // A reference living only in a file we could not read is invisible here, and the symbol
378
- // then looks unused.
379
- filesUnreadable++;
380
- continue;
381
- }
382
- const lines = content.split("\n");
383
- for (let i = 0; i < lines.length; i++) {
384
- const line = lines[i];
385
- if (line === undefined)
386
- continue;
387
- for (const { name, regex } of patterns) {
388
- const refs = result[name];
389
- if (refs.length >= MAX_REFERENCES)
390
- continue;
391
- const match = regex.exec(line);
392
- if (match) {
393
- const rawContext = line.trimEnd();
394
- refs.push({
395
- file: fileEntry.path,
396
- line: i + 1,
397
- col: match.index + 1,
398
- context: rawContext.length > MAX_CONTEXT_LENGTH
399
- ? rawContext.slice(0, MAX_CONTEXT_LENGTH) + "..."
400
- : rawContext,
401
- });
402
- }
403
- }
404
- }
405
- }
406
- if (sink) {
407
- const capped = symbolNames.filter((n) => (result[n]?.length ?? 0) >= MAX_REFERENCES);
408
- const complete = filesSkippedNoise === 0 && filesUnreadable === 0 && capped.length === 0;
409
- const reasons = [];
410
- if (filesSkippedNoise > 0) {
411
- reasons.push(`${filesSkippedNoise} generated/vendored files skipped (pass file_pattern to include them)`);
412
- }
413
- if (filesUnreadable > 0)
414
- reasons.push(`${filesUnreadable} files could not be read`);
415
- if (capped.length > 0)
416
- reasons.push(`${capped.length} symbol(s) hit the ${MAX_REFERENCES}-reference cap`);
417
- sink.coverage = {
418
- status: complete ? "complete" : "partial",
419
- files_indexed: index.files.length,
420
- files_scanned: filesScanned,
421
- ...(filesSkippedNoise > 0 ? { files_skipped_noise: filesSkippedNoise } : {}),
422
- ...(filesUnreadable > 0 ? { files_unreadable: filesUnreadable } : {}),
423
- ...(capped.length > 0 ? { capped_symbols: capped } : {}),
424
- ...(complete
425
- ? {}
426
- : {
427
- detail: `${reasons.join("; ")} — an empty or short result is about what was scanned, ` +
428
- `not proof the symbol is unused`,
429
- }),
430
- };
431
- }
432
- return result;
433
- }
434
- const SEARCH_TIMEOUT_MS = 30_000;
435
- /** Directories to exclude from ripgrep reference search */
436
- const RG_EXCLUDE_DIRS = [
437
- "node_modules", ".git", ".next", "dist", ".codesift", "coverage",
438
- ".playwright-mcp", "__pycache__", "__snapshots__",
439
- ];
440
- /** Detect whether `rg` (ripgrep) is available. Cached at module level. */
441
- let rgAvailable = null;
442
- function hasRipgrep() {
443
- if (rgAvailable !== null)
444
- return rgAvailable;
445
- try {
446
- execFileSync("rg", ["--version"], { stdio: "pipe", timeout: 2000 });
447
- rgAvailable = true;
448
- }
449
- catch {
450
- rgAvailable = false;
451
- }
452
- return rgAvailable;
453
- }
454
- /**
455
- * Find references using ripgrep with word-boundary matching.
456
- * Returns compact `file:line: context` string when results ≤ threshold.
457
- */
458
- function findReferencesWithRipgrep(root, symbolName, maxResults, filePattern) {
459
- const args = [
460
- "-n", "--no-heading", "-w",
461
- "--max-columns", String(MAX_CONTEXT_LENGTH),
462
- "--max-columns-preview",
463
- "--max-count", String(Math.min(maxResults * 2, 5000)),
464
- ];
465
- // Exclude noise dirs
466
- for (const dir of RG_EXCLUDE_DIRS) {
467
- args.push("--glob", `!${dir}`);
468
- }
469
- // Exclude noise extensions
470
- for (const ext of [".snap", ".lock", ".map", ".svg", ".png", ".jpg", ".ico", ".woff", ".woff2", ".md", ".json", ".yaml", ".yml", ".toml", ".css", ".scss", ".html"]) {
471
- args.push("--glob", `!*${ext}`);
472
- }
473
- if (filePattern) {
474
- args.push("--glob", filePattern);
475
- }
476
- else {
477
- // Default to code files only (matches what agent would grep for)
478
- args.push("--type-add", "code:*.{ts,tsx,js,jsx,py,go,rs,java,kt,kts,rb,php,vue,svelte}");
479
- args.push("--type", "code");
480
- }
481
- args.push("--", symbolName, root);
482
- let stdout;
483
- try {
484
- stdout = execFileSync("rg", args, {
485
- encoding: "utf-8",
486
- maxBuffer: 20 * 1024 * 1024,
487
- timeout: SEARCH_TIMEOUT_MS,
488
- });
489
- }
490
- catch (err) {
491
- if (err && typeof err === "object" && "status" in err) {
492
- if (err.status === 1)
493
- return []; // no matches
494
- if ("stdout" in err && typeof err.stdout === "string") {
495
- stdout = err.stdout;
496
- if (!stdout)
497
- return [];
498
- }
499
- else {
500
- return [];
501
- }
502
- }
503
- else {
504
- return [];
505
- }
506
- }
507
- const rootPrefix = root.endsWith("/") ? root : root + "/";
508
- const lines = stdout.split("\n").filter(Boolean);
509
- const refs = [];
510
- for (const rawLine of lines) {
511
- if (refs.length >= maxResults)
512
- break;
513
- const match = rawLine.match(/^(.+?):(\d+):(.*)/);
514
- if (!match || !match[1] || !match[2] || match[3] === undefined)
515
- continue;
516
- const absPath = match[1];
517
- const relPath = absPath.startsWith(rootPrefix) ? absPath.slice(rootPrefix.length) : absPath;
518
- if (isNoisePath(relPath))
519
- continue;
520
- refs.push({
521
- file: relPath,
522
- line: parseInt(match[2], 10),
523
- context: match[3].length > MAX_CONTEXT_LENGTH ? match[3].slice(0, MAX_CONTEXT_LENGTH) + "..." : match[3],
524
- });
525
- }
526
- return refs;
527
- }
528
- export async function findReferences(repo, symbolName, filePattern) {
529
- // Try LSP first (type-safe, no false positives)
530
- const lspRefs = await findReferencesLsp(repo, symbolName);
531
- if (lspRefs !== null)
532
- return lspRefs;
533
- // Use ripgrep when available (10x+ faster than Node.js file walk)
534
- if (hasRipgrep()) {
535
- const index = await requireCodeIndex(repo);
536
- const result = findReferencesWithRipgrep(index.root, symbolName, MAX_REFERENCES, filePattern);
537
- // ripgrep helper may return compact string; convert back to Reference[]
538
- if (typeof result === "string") {
539
- return result.split("\n").filter(Boolean).map((line) => {
540
- const m = line.match(/^(.+?):(\d+): (.*)/);
541
- return m ? { file: m[1], line: parseInt(m[2], 10), context: m[3] } : { file: "", line: 0, context: line };
542
- });
543
- }
544
- return result;
545
- }
546
- // Node.js fallback
547
- const index = await requireCodeIndex(repo);
548
- const pattern = wordBoundaryPattern(symbolName);
549
- const searchStart = Date.now();
550
- const fileFilter = filePattern
551
- ? new RegExp(filePattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*"))
552
- : null;
553
- const refs = [];
554
- for (const fileEntry of index.files) {
555
- if (refs.length >= MAX_REFERENCES)
556
- break;
557
- if (Date.now() - searchStart > SEARCH_TIMEOUT_MS)
558
- break;
559
- if (fileFilter && !fileFilter.test(fileEntry.path))
560
- continue;
561
- if (!filePattern && isNoisePath(fileEntry.path))
562
- continue;
563
- let content;
564
- try {
565
- content = await readFile(join(index.root, fileEntry.path), "utf-8");
566
- }
567
- catch {
568
- continue;
569
- }
570
- const lines = content.split("\n");
571
- for (let i = 0; i < lines.length; i++) {
572
- if (refs.length >= MAX_REFERENCES)
573
- break;
574
- const line = lines[i];
575
- if (line === undefined)
576
- continue;
577
- const match = pattern.exec(line);
578
- if (match) {
579
- const rawContext = line.trimEnd();
580
- refs.push({
581
- file: fileEntry.path,
582
- line: i + 1,
583
- context: rawContext.length > MAX_CONTEXT_LENGTH
584
- ? rawContext.slice(0, MAX_CONTEXT_LENGTH) + "..."
585
- : rawContext,
586
- });
587
- }
588
- }
589
- }
590
- return refs;
591
- }
592
- /** Format references as compact string for MCP output. Groups by file to avoid repeating paths. */
593
- export function formatRefsCompact(refs) {
594
- if (refs.length === 0)
595
- return "";
596
- // Group by file
597
- const groups = new Map();
598
- for (const r of refs) {
599
- let g = groups.get(r.file);
600
- if (!g) {
601
- g = [];
602
- groups.set(r.file, g);
603
- }
604
- g.push(` ${r.line}: ${r.context}`);
605
- }
606
- if (groups.size === refs.length) {
607
- // Each file has 1 ref — flat is fine
608
- return refs.map((r) => `${r.file}:${r.line}: ${r.context}`).join("\n");
609
- }
610
- const parts = [];
611
- for (const [file, lines] of groups) {
612
- parts.push(`${file}\n${lines.join("\n")}`);
613
- }
614
- return parts.join("\n");
615
- }
616
- /** Format a CodeSymbol as compact text: header line + source. ~70% less tokens than JSON. */
617
- export function formatSymbolCompact(sym) {
618
- const loc = `${sym.file}:${sym.start_line}-${sym.end_line}`;
619
- const sig = sym.signature ? ` ${sym.signature}` : "";
620
- const header = `${loc} ${sym.kind} ${sym.name}${sig}`;
621
- if (!sym.source)
622
- return header;
623
- return `${header}\n${sym.source}`;
624
- }
625
- /** Format multiple CodeSymbols as compact text, separated by blank lines. */
626
- export function formatSymbolsCompact(syms) {
627
- return syms.map(formatSymbolCompact).join("\n\n");
628
- }
629
- /** Format ContextBundle as compact text. */
630
- export function formatBundleCompact(bundle) {
631
- const parts = [];
632
- parts.push(formatSymbolCompact(bundle.symbol));
633
- if (bundle.imports.length > 0) {
634
- parts.push(`\n--- imports ---\n${bundle.imports.join("\n")}`);
635
- }
636
- if (bundle.siblings.length > 0) {
637
- const sibLines = bundle.siblings.map((s) => ` ${s.kind} ${s.name} :${s.start_line}-${s.end_line}`);
638
- parts.push(`\n--- siblings ---\n${sibLines.join("\n")}`);
639
- }
640
- if (bundle.types_used.length > 0) {
641
- parts.push(`\n--- types used ---\n${bundle.types_used.join(", ")}`);
642
- }
643
- return parts.join("");
644
- }
645
- /**
646
- * Search for a symbol by query and return it with full source.
647
- * Optionally includes references across the codebase.
648
- */
649
- export async function findAndShow(repo, query, includeRefs) {
650
- const bm25Index = await requireBM25Index(repo);
651
- const config = loadConfig();
652
- const results = searchBM25(bm25Index, query, 1, config.bm25FieldWeights);
653
- const topResult = results[0];
654
- if (!topResult)
655
- return null;
656
- const resolved = await resolveSearchHit(repo, topResult.symbol);
657
- if (!resolved)
658
- return null;
659
- const { symbol: fullSymbol, ambiguity } = resolved;
660
- if (includeRefs) {
661
- const references = await findReferences(repo, fullSymbol.name);
662
- return { symbol: fullSymbol, references, id_ambiguity: ambiguity };
663
- }
664
- return { symbol: fullSymbol, id_ambiguity: ambiguity };
665
- }
666
- /**
667
- * Extract full import lines from file source.
668
- */
669
- function extractImportLines(source) {
670
- const lines = source.split("\n");
671
- return lines.filter((line) => {
672
- const trimmed = line.trim();
673
- return trimmed.startsWith("import ") || (trimmed.startsWith("const ") && trimmed.includes("require("));
674
- });
675
- }
676
- /**
677
- * Get a symbol with its file's imports and sibling symbols in one call.
678
- * Saves 2-3 round-trips vs get_symbol + search_text(imports) + get_file_outline.
679
- */
680
- export async function getContextBundle(repo, symbolName) {
681
- const bm25Index = await requireBM25Index(repo);
682
- const config = loadConfig();
683
- const results = searchBM25(bm25Index, symbolName, 1, config.bm25FieldWeights);
684
- const topResult = results[0];
685
- if (!topResult)
686
- return null;
687
- const index = await requireCodeIndex(repo);
688
- // Get full symbol with source
689
- const resolved = await resolveSearchHit(repo, topResult.symbol);
690
- if (!resolved)
691
- return null;
692
- const { symbol: fullSymbol, ambiguity } = resolved;
693
- // Read the file to extract imports
694
- let fileSource;
695
- try {
696
- fileSource = await readFile(join(index.root, fullSymbol.file), "utf-8");
697
- }
698
- catch {
699
- return {
700
- symbol: fullSymbol,
701
- imports: [],
702
- siblings: [],
703
- types_used: [],
704
- id_ambiguity: ambiguity,
705
- };
706
- }
707
- const imports = extractImportLines(fileSource);
708
- // Get sibling symbols (other symbols in the same file)
709
- const siblings = index.symbols
710
- .filter((s) => s.file === fullSymbol.file && s.id !== fullSymbol.id)
711
- .map((s) => ({
712
- name: s.name,
713
- kind: s.kind,
714
- start_line: s.start_line,
715
- end_line: s.end_line,
716
- }));
717
- // Extract type names used in the symbol's source
718
- const typesUsed = extractTypesUsed(fullSymbol.source ?? "", index.symbols);
719
- // React-specific enrichment for components
720
- const bundle = {
721
- symbol: fullSymbol,
722
- imports,
723
- siblings,
724
- types_used: typesUsed,
725
- id_ambiguity: ambiguity,
726
- };
727
- if (fullSymbol.kind === "component") {
728
- bundle.react_context = buildReactContext(fullSymbol, index.symbols);
729
- }
730
- return bundle;
731
- }
732
- /**
733
- * Build React-specific context for a component symbol:
734
- * hooks used, child/parent components via JSX, wrapper pattern.
735
- *
736
- * Uses REACT_STDLIB_HOOKS imported from react-tools.js as the single source
737
- * of truth for stdlib hook detection (CQ14 — no duplication).
738
- */
739
- function buildReactContext(component, allSymbols) {
740
- const source = component.source ?? "";
741
- // Extract hooks used (uses shared extractHookNames from react-tools.ts — CQ14)
742
- const hooks_used = [...extractHookNames(source)].map((name) => ({
743
- name,
744
- is_stdlib: REACT_STDLIB_HOOKS.has(name),
745
- }));
746
- // Extract child components from JSX (<PascalCase>)
747
- const childSet = new Set();
748
- const jsxPattern = /<([A-Z][a-zA-Z0-9_$]*)\b/g;
749
- let m;
750
- while ((m = jsxPattern.exec(source)) !== null) {
751
- const name = m[1];
752
- if (name !== component.name)
753
- childSet.add(name);
754
- }
755
- const child_components = [...childSet].sort();
756
- // Extract parent components: find other components whose source uses <ThisComponent>
757
- const ownPattern = new RegExp(`<${component.name}\\b`);
758
- const parent_components = allSymbols
759
- .filter((s) => s.kind === "component" &&
760
- s.id !== component.id &&
761
- s.name !== component.name &&
762
- s.source &&
763
- ownPattern.test(s.source))
764
- .map((s) => s.name);
765
- // Detect wrapper pattern from source — supports TypeScript generics:
766
- // forwardRef<HTMLDivElement, Props>(...), memo<Props>(...) (Item 9)
767
- let wrapper = null;
768
- if (/\b(?:React\.)?memo\s*(?:<[^>]+>)?\s*\(/.test(source))
769
- wrapper = "memo";
770
- else if (/\b(?:React\.)?forwardRef\s*(?:<[^>]+>)?\s*\(/.test(source))
771
- wrapper = "forwardRef";
772
- else if (/\b(?:React\.)?lazy\s*(?:<[^>]+>)?\s*\(/.test(source))
773
- wrapper = "lazy";
774
- // Extract props type from signature: (props: MyProps) or ({ a, b }: Props)
775
- let props_type = null;
776
- const sig = component.signature ?? "";
777
- // Pattern: (props: TypeName) or (arg: TypeName) or ({ ... }: TypeName)
778
- const propsMatch = sig.match(/\(\s*(?:\{[^}]*\}|\w+)\s*:\s*([A-Z]\w*)/);
779
- if (propsMatch) {
780
- props_type = propsMatch[1];
781
- }
782
- // Resolve props interface body when type name found in the index (Tier 4 — Item 16).
783
- // Look for an interface or type alias with the same name as props_type.
784
- let props_interface_source = null;
785
- if (props_type) {
786
- const decl = allSymbols.find((s) => (s.kind === "interface" || s.kind === "type") && s.name === props_type);
787
- if (decl?.source) {
788
- // Cap to 800 chars to keep bundle compact
789
- props_interface_source = decl.source.length > 800
790
- ? decl.source.slice(0, 800) + "..."
791
- : decl.source;
792
- }
793
- }
794
- return { props_type, props_interface_source, hooks_used, child_components, parent_components, wrapper };
795
- }
796
- /**
797
- * Extract type/interface names referenced in source by matching against known symbols.
798
- */
799
- function extractTypesUsed(source, allSymbols) {
800
- const typeNames = allSymbols
801
- .filter((s) => (s.kind === "interface" || s.kind === "type" || s.kind === "enum") && s.name.length >= 3)
802
- .map((s) => s.name);
803
- if (typeNames.length === 0)
804
- return [];
805
- // Single combined regex instead of N separate tests (O(n) vs O(n*m))
806
- const escaped = typeNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
807
- const combined = new RegExp(`\\b(${escaped.join("|")})\\b`, "g");
808
- const used = new Set();
809
- let m;
810
- while ((m = combined.exec(source)) !== null) {
811
- used.add(m[1]);
812
- }
813
- return [...used].sort();
814
- }
815
- // Kinds that are typically exported and should have external references
816
- const EXPORTABLE_KINDS = new Set([
817
- "function", "class", "interface", "type", "variable", "constant", "enum",
818
- "component", "hook",
819
- ]);
820
- /**
821
- * Collect top-level symbols of exportable kinds, filtered by test/pattern options.
822
- */
823
- function collectExportedSymbols(symbols, options) {
824
- return symbols.filter((s) => {
825
- if (!EXPORTABLE_KINDS.has(s.kind))
826
- return false;
827
- if (s.parent)
828
- return false;
829
- if (!options.includeTests && isTestFile(s.file))
830
- return false;
831
- if (options.filePattern && !s.file.includes(options.filePattern))
832
- return false;
833
- if (s.name.length < 3)
834
- return false;
835
- if (s.kind === "variable" && s.name === "default")
836
- return false;
837
- return true;
838
- });
839
- }
840
- // Bumped from 2000 → 5000 (F14: prior cap silently dropped references in
841
- // medium-large repos, producing false-positive dead-code candidates whose
842
- // real callers lived in unscanned files). Memory cost: roughly one file
843
- // content string per entry — at 5K average-sized source files this is on
844
- // the order of 50–200 MB peak, well within limits for analysis flows.
845
- const MAX_SCAN_FILES = 5000;
846
- /**
847
- * Resolve a relative import path against a source file's directory. Handles
848
- * the standard TS/Node extensions plus barrel-style `index` resolution.
849
- * Returns the candidate file path that exists in `allFiles`, or null.
850
- *
851
- * Intentionally narrow: only resolves `./` and `../` paths. Aliased imports
852
- * (tsconfig paths, package-json `imports`) are out of scope here — they show
853
- * up textually in scanned content anyway, so they don't drive false positives.
854
- */
855
- function resolveRelativeImport(fromFile, importPath, allFiles) {
856
- if (!importPath.startsWith("."))
857
- return null;
858
- const lastSlash = fromFile.lastIndexOf("/");
859
- const fromDir = lastSlash >= 0 ? fromFile.slice(0, lastSlash) : "";
860
- const segments = (fromDir + "/" + importPath).split("/");
861
- const stack = [];
862
- for (const seg of segments) {
863
- if (!seg || seg === ".")
864
- continue;
865
- if (seg === "..")
866
- stack.pop();
867
- else
868
- stack.push(seg);
869
- }
870
- const base = stack.join("/");
871
- if (allFiles.has(base))
872
- return base;
873
- // TS allows `import './x.js'` to resolve to `x.ts` — strip then re-extend.
874
- const stripped = base.replace(/\.(m?js|jsx)$/, "");
875
- const candidates = [
876
- stripped + ".ts",
877
- stripped + ".tsx",
878
- stripped + ".mjs",
879
- stripped + ".js",
880
- stripped + ".jsx",
881
- stripped + "/index.ts",
882
- stripped + "/index.tsx",
883
- stripped + "/index.js",
884
- stripped + "/index.jsx",
885
- ];
886
- for (const c of candidates) {
887
- if (allFiles.has(c))
888
- return c;
889
- }
890
- return null;
891
- }
892
- /**
893
- * Pre-scan content for re-export edges. A symbol re-exported from another
894
- * file isn't textually referenced in the barrel — without this pass barrel
895
- * patterns like `export * from './foo'` cause every symbol in `./foo` to be
896
- * misclassified as dead.
897
- *
898
- * Returns a set of file paths that are reached via at least one re-export
899
- * (named or star). Callers treat any candidate whose defining file lands in
900
- * this set as live.
901
- */
902
- function collectReExportedFiles(fileContents, allFiles) {
903
- const reExported = new Set();
904
- // Matches: export * from "./x"; export { A, B } from "./x"; export type { T } from "./x";
905
- // Anchor dropped (`^\s*` removed) so block-comment-prefixed exports
906
- // (`/** doc */ export { Y } from "./x"`) and continuation-line exports
907
- // (`...; export * from "./x"`) are detected too.
908
- const RE = /\bexport\s+(?:\*|type\s+\*|\{[^}]*\}|type\s+\{[^}]*\})\s+from\s+['"]([^'"]+)['"]/gm;
909
- for (const [filePath, content] of fileContents) {
910
- RE.lastIndex = 0;
911
- let m;
912
- while ((m = RE.exec(content)) !== null) {
913
- const target = resolveRelativeImport(filePath, m[1], allFiles);
914
- if (target)
915
- reExported.add(target);
916
- }
917
- }
918
- return reExported;
919
- }
920
- /**
921
- * Find potentially dead code: exported symbols with 0 references outside their own file.
922
- * Scans all indexed files for word-boundary matches of each exported symbol name.
923
- *
924
- * F14 fixes (2026-05-05):
925
- * - Test files are now ALWAYS scanned for references regardless of
926
- * `include_tests`. The flag now gates only candidate selection (whether
927
- * test-internal exports can themselves be flagged dead). Symbols used
928
- * only in tests are no longer false-positive dead.
929
- * - Re-exports via `export * from './x'` and `export { Y } from './x'`
930
- * now mark `./x` as live; previously the lack of textual mention in the
931
- * barrel made every barrel-forwarded symbol look dead.
932
- * - MAX_SCAN_FILES raised 2000 → 5000; truncation now surfaces in the
933
- * `truncated` field so callers can react.
934
- */
935
- export async function findDeadCode(repo, options) {
936
- const index = await requireCodeIndex(repo);
937
- const includeTests = options?.include_tests ?? false;
938
- const filePattern = options?.file_pattern;
939
- const exportedSymbols = collectExportedSymbols(index.symbols, { includeTests, filePattern });
940
- const frameworks = detectFrameworks(index);
941
- // Read EVERY indexed file (incl. tests) for reference scanning. The previous
942
- // version honored `includeTests` here, which meant a symbol referenced only
943
- // from tests was misclassified as dead. Candidate selection still uses
944
- // `includeTests` (above) so test-only helpers don't appear in the result list.
945
- const fileContents = new Map();
946
- let scanTruncated = false;
947
- let filesSkippedByCap = 0;
948
- let filesUnreadable = 0;
949
- for (const file of index.files) {
950
- if (fileContents.size >= MAX_SCAN_FILES) {
951
- scanTruncated = true;
952
- filesSkippedByCap = index.files.length - fileContents.size - filesUnreadable;
953
- break;
954
- }
955
- try {
956
- fileContents.set(file.path, await readFile(join(index.root, file.path), "utf-8"));
957
- }
958
- catch {
959
- // Deleted, unreadable, or raced with a write. Counted rather than merely skipped: a
960
- // reference living only in a file we could not read is invisible to the scan below, and
961
- // the symbol it points at then looks dead.
962
- filesUnreadable++;
963
- }
964
- }
965
- // Build set of files that are forwarded via re-exports (barrel chains).
966
- // Symbols defined in such files are reachable even without textual mention.
967
- const allFilePaths = new Set(fileContents.keys());
968
- const reExportedFiles = collectReExportedFiles(fileContents, allFilePaths);
969
- // Reference index, built in ONE pass over file contents. Previously this scan
970
- // was O(exportedSymbols x files): a `\b<name>\b` regex over every file's full
971
- // text for every exported symbol. On a healthy repo (few dead symbols) the
972
- // early-exit never fires, so the full product ran — telemetry (2026-07-20)
973
- // measured a 1,066,401 ms (~17.8 min) p95 for find_dead_code.
974
- //
975
- // Tokenising on [^A-Za-z0-9_]+ mirrors the \b\w boundary semantics of the old
976
- // regex. Per token we only need "is it mentioned outside its defining file",
977
- // so we keep the first file that mentioned it plus a multi-file flag — O(1)
978
- // memory per unique token instead of a file set, and O(1) lookup per symbol.
979
- const tokenIndex = new Map();
980
- for (const [filePath, content] of fileContents) {
981
- for (const token of new Set(content.split(/[^A-Za-z0-9_]+/))) {
982
- if (!token)
983
- continue;
984
- const entry = tokenIndex.get(token);
985
- if (!entry)
986
- tokenIndex.set(token, { first: filePath, multi: false });
987
- else if (!entry.multi && entry.first !== filePath)
988
- entry.multi = true;
989
- }
990
- }
991
- const candidates = [];
992
- for (const sym of exportedSymbols) {
993
- if (candidates.length >= MAX_DEAD_CODE_RESULTS)
994
- break;
995
- if (isFrameworkEntryPoint(sym, frameworks))
996
- continue;
997
- // Re-export reachability — barrel forwards skip the textual-mention check.
998
- if (reExportedFiles.has(sym.file))
999
- continue;
1000
- // Mentioned in any file other than the one defining it => not dead.
1001
- const seen = tokenIndex.get(sym.name);
1002
- const hasExternalRef = !!seen && (seen.multi || seen.first !== sym.file);
1003
- if (!hasExternalRef) {
1004
- candidates.push({
1005
- name: sym.name,
1006
- kind: sym.kind,
1007
- file: sym.file,
1008
- start_line: sym.start_line,
1009
- end_line: sym.end_line,
1010
- reason: "exported but no references found outside defining file",
1011
- });
1012
- }
1013
- }
1014
- const scanComplete = !scanTruncated && filesUnreadable === 0;
1015
- const coverage = {
1016
- status: scanComplete ? "complete" : "partial",
1017
- files_indexed: index.files.length,
1018
- files_read: fileContents.size,
1019
- ...(filesSkippedByCap > 0 ? { files_skipped_by_cap: filesSkippedByCap } : {}),
1020
- ...(filesUnreadable > 0 ? { files_unreadable: filesUnreadable } : {}),
1021
- ...(scanComplete
1022
- ? {}
1023
- : {
1024
- detail: `reference scan read ${fileContents.size} of ${index.files.length} indexed files` +
1025
- (filesSkippedByCap > 0 ? ` (${filesSkippedByCap} beyond the ${MAX_SCAN_FILES}-file cap)` : "") +
1026
- (filesUnreadable > 0 ? ` (${filesUnreadable} unreadable)` : "") +
1027
- " — a symbol referenced only from an unread file is indistinguishable from a dead one," +
1028
- " so do NOT delete on this result alone; narrow with file_pattern to get a complete scan",
1029
- }),
1030
- };
1031
- return {
1032
- candidates,
1033
- scanned_symbols: exportedSymbols.length,
1034
- scanned_files: fileContents.size,
1035
- // Deliberately ONLY about list length now. Scan completeness lives in `coverage`, because
1036
- // the two have opposite implications: a cut-off list means "there are more", a short scan
1037
- // means "these may be wrong".
1038
- ...(candidates.length >= MAX_DEAD_CODE_RESULTS ? { truncated: true } : {}),
1039
- coverage,
1040
- };
1041
- }
1042
- // ---------------------------------------------------------------------------
1043
- // Unused import detection
1044
- // ---------------------------------------------------------------------------
1045
- const MAX_UNUSED_IMPORTS = 200;
1046
- /**
1047
- * Find imports whose imported names are never referenced in the file body.
1048
- * Supports ES module named imports: import { A, B } from '...'
4
+ * Deferred tool loaders and external consumers import this exact module path.
5
+ * Keep exports explicit so internal split helpers cannot accidentally become public.
1049
6
  */
1050
- export async function findUnusedImports(repo, options) {
1051
- const index = await requireCodeIndex(repo);
1052
- const includeTests = options?.include_tests ?? false;
1053
- const unused = [];
1054
- let scannedFiles = 0;
1055
- for (const file of index.files) {
1056
- if (unused.length >= MAX_UNUSED_IMPORTS)
1057
- break;
1058
- if (!includeTests && isTestFile(file.path))
1059
- continue;
1060
- if (options?.file_pattern && !file.path.includes(options.file_pattern))
1061
- continue;
1062
- // Only analyze JS/TS/Kotlin files
1063
- if (!/\.(ts|tsx|js|jsx|mjs|kt|kts)$/.test(file.path))
1064
- continue;
1065
- let source;
1066
- try {
1067
- source = await readFile(join(index.root, file.path), "utf-8");
1068
- }
1069
- catch {
1070
- continue;
1071
- }
1072
- scannedFiles++;
1073
- const lines = source.split("\n");
1074
- // Find named import lines: import { A, B, C } from '...'
1075
- // Also: import A from '...' and import * as A from '...'
1076
- const importRegex = /^import\s+(?:type\s+)?(?:\{([^}]+)\}|(\*\s+as\s+\w+)|(\w+)).*from\s+['"][^'"]+['"]/;
1077
- for (let i = 0; i < lines.length; i++) {
1078
- const line = lines[i].trim();
1079
- if (!line.startsWith("import "))
1080
- continue;
1081
- // Stop scanning imports when we hit non-import code
1082
- if (i > 0 && !line.startsWith("import") && !line.startsWith("//") && !line.startsWith("/*") && line.length > 0 && !lines[i].trim().startsWith("*") && !lines[i].trim().startsWith("}")) {
1083
- // Could be multi-line import continuation, keep going
1084
- }
1085
- const match = importRegex.exec(line);
1086
- if (!match)
1087
- continue;
1088
- const names = [];
1089
- if (match[1]) {
1090
- // Named imports: { A, B as C, type D }
1091
- for (const part of match[1].split(",")) {
1092
- const trimmed = part.trim().replace(/^type\s+/, "");
1093
- if (!trimmed)
1094
- continue;
1095
- // Handle "A as B" — the local name is B
1096
- const asMatch = /(\w+)\s+as\s+(\w+)/.exec(trimmed);
1097
- names.push(asMatch ? asMatch[2] : trimmed);
1098
- }
1099
- }
1100
- else if (match[2]) {
1101
- // Namespace import: * as A
1102
- const nsMatch = /\*\s+as\s+(\w+)/.exec(match[2]);
1103
- if (nsMatch)
1104
- names.push(nsMatch[1]);
1105
- }
1106
- else if (match[3]) {
1107
- // Default import: import A
1108
- names.push(match[3]);
1109
- }
1110
- // Check each imported name against rest of file
1111
- const bodyAfterImports = lines.slice(i + 1).join("\n");
1112
- for (const name of names) {
1113
- if (name.length < 2)
1114
- continue;
1115
- const nameRegex = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
1116
- if (!nameRegex.test(bodyAfterImports)) {
1117
- unused.push({
1118
- file: file.path,
1119
- line: i + 1,
1120
- import_text: line,
1121
- imported_name: name,
1122
- });
1123
- if (unused.length >= MAX_UNUSED_IMPORTS)
1124
- break;
1125
- }
1126
- }
1127
- }
1128
- }
1129
- return {
1130
- unused,
1131
- scanned_files: scannedFiles,
1132
- ...(unused.length >= MAX_UNUSED_IMPORTS ? { truncated: true } : {}),
1133
- };
1134
- }
7
+ export { AmbiguousSymbolIdError, extractSymbolNameGuess, findSimilarSymbols, getSymbol, getSymbols, isAmbiguousSymbolIdError, resolveSymbolIdExact, } from "./symbol-lookup-tools.js";
8
+ export { findReferences, findReferencesBatch, } from "./symbol-reference-tools.js";
9
+ export { findAndShow, formatBundleCompact, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, getContextBundle, } from "./symbol-context-tools.js";
10
+ export { findDeadCode, findUnusedImports, } from "./symbol-analysis-tools.js";
1135
11
  //# sourceMappingURL=symbol-tools.js.map