onbuzz 4.9.13 → 4.10.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 (451) hide show
  1. package/node_modules/glob/README.md +31 -5
  2. package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
  3. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
  4. package/node_modules/glob/dist/commonjs/glob.js +2 -1
  5. package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
  6. package/node_modules/glob/dist/commonjs/index.min.js +3 -3
  7. package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
  8. package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
  9. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
  10. package/node_modules/glob/dist/commonjs/pattern.js +4 -0
  11. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
  12. package/node_modules/glob/dist/esm/glob.d.ts +8 -0
  13. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
  14. package/node_modules/glob/dist/esm/glob.js +2 -1
  15. package/node_modules/glob/dist/esm/glob.js.map +1 -1
  16. package/node_modules/glob/dist/esm/index.min.js +3 -3
  17. package/node_modules/glob/dist/esm/index.min.js.map +4 -4
  18. package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
  19. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
  20. package/node_modules/glob/dist/esm/pattern.js +4 -0
  21. package/node_modules/glob/dist/esm/pattern.js.map +1 -1
  22. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
  23. package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
  24. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
  25. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
  26. package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
  27. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
  28. package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
  29. package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
  30. package/node_modules/glob/node_modules/minimatch/README.md +76 -1
  31. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
  32. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
  33. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
  34. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
  35. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
  36. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
  37. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
  38. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
  39. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
  40. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
  41. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
  42. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
  43. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
  44. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
  45. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
  46. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
  47. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
  48. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
  49. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
  50. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
  51. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
  52. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
  53. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
  54. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
  55. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
  56. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
  57. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
  58. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
  59. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
  60. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
  61. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
  62. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
  63. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
  64. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
  65. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
  66. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
  67. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
  68. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
  69. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
  70. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
  71. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
  72. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
  73. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
  74. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
  75. package/node_modules/glob/node_modules/minimatch/package.json +17 -11
  76. package/node_modules/glob/package.json +10 -13
  77. package/node_modules/minipass/LICENSE.md +55 -0
  78. package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
  79. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
  80. package/node_modules/minipass/dist/commonjs/index.js +13 -3
  81. package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
  82. package/node_modules/minipass/dist/esm/index.d.ts +12 -16
  83. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
  84. package/node_modules/minipass/dist/esm/index.js +3 -1
  85. package/node_modules/minipass/dist/esm/index.js.map +1 -1
  86. package/node_modules/minipass/package.json +9 -14
  87. package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
  88. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
  89. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
  90. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
  91. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  92. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
  93. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
  94. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  95. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
  96. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  97. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
  98. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
  99. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
  100. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  101. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
  102. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
  103. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  104. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
  105. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  106. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
  107. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  108. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
  109. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  110. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  111. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
  112. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
  113. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
  114. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
  115. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  116. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
  117. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
  118. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  119. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
  120. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  121. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
  122. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
  123. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
  124. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  125. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
  126. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
  127. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
  128. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  129. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
  130. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
  131. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
  132. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
  133. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  134. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
  135. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
  136. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  137. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
  138. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  139. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
  140. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
  141. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
  142. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  143. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
  144. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  145. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
  146. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
  147. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  148. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
  149. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  150. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
  151. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  152. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  153. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
  154. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
  155. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
  156. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
  157. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  158. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
  173. package/node_modules/path-scurry/package.json +8 -24
  174. package/package.json +1 -1
  175. package/scripts/debug-balance-probe.mjs +35 -35
  176. package/scripts/push-image.sh +43 -43
  177. package/scripts/setup-acr.sh +65 -65
  178. package/scripts/verify-optional-deps.js +96 -1
  179. package/src/__tests__/composioCliFlags.test.js +239 -239
  180. package/src/analyzers/CSSAnalyzer.js +298 -297
  181. package/src/analyzers/ConfigValidator.js +691 -690
  182. package/src/analyzers/ESLintAnalyzer.js +320 -320
  183. package/src/analyzers/JavaScriptAnalyzer.js +260 -261
  184. package/src/analyzers/PrettierFormatter.js +246 -247
  185. package/src/analyzers/PythonAnalyzer.js +283 -283
  186. package/src/analyzers/SecurityAnalyzer.js +729 -729
  187. package/src/analyzers/SparrowAnalyzer.js +341 -341
  188. package/src/analyzers/TypeScriptAnalyzer.js +247 -247
  189. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
  190. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
  191. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
  192. package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
  193. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
  194. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
  195. package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
  196. package/src/analyzers/codeCloneDetector/detector.js +250 -250
  197. package/src/analyzers/codeCloneDetector/index.js +194 -192
  198. package/src/analyzers/codeCloneDetector/parser.js +199 -199
  199. package/src/core/__tests__/agentPool.test.js +866 -866
  200. package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
  201. package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
  202. package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
  203. package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
  204. package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
  205. package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
  206. package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
  207. package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
  208. package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
  209. package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
  210. package/src/core/__tests__/flowEndToEnd.test.js +565 -565
  211. package/src/core/__tests__/flowFieldMapping.test.js +188 -189
  212. package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
  213. package/src/core/__tests__/flowSavePayload.test.js +170 -169
  214. package/src/core/__tests__/flowTemplates.test.js +311 -311
  215. package/src/core/__tests__/flowVersionStore.test.js +123 -123
  216. package/src/core/__tests__/messageProcessor.test.js +669 -669
  217. package/src/core/__tests__/stateManager.test.js +0 -1
  218. package/src/core/agentPool.js +2474 -2475
  219. package/src/core/agentScheduler.js +1 -4
  220. package/src/core/contextManager.js +708 -708
  221. package/src/core/flowExecutor.js +1510 -1510
  222. package/src/core/flowFieldMapping.js +136 -138
  223. package/src/core/messageProcessor.js +953 -954
  224. package/src/core/orchestrator.js +593 -595
  225. package/src/core/stateManager.js +1765 -1752
  226. package/src/index.js +1221 -1221
  227. package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
  228. package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
  229. package/src/interfaces/__tests__/imageServing.test.js +228 -228
  230. package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
  231. package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
  232. package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
  233. package/src/interfaces/schedulerRoutes.js +50 -50
  234. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
  235. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
  236. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
  237. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
  238. package/src/interfaces/terminal/api/session.js +265 -266
  239. package/src/interfaces/terminal/api/websocket.js +496 -497
  240. package/src/interfaces/terminal/components/AgentCreator.js +691 -705
  241. package/src/interfaces/terminal/components/AgentEditor.js +676 -678
  242. package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
  243. package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
  244. package/src/interfaces/terminal/components/Header.js +28 -28
  245. package/src/interfaces/terminal/components/Layout.js +598 -603
  246. package/src/interfaces/terminal/components/MessageList.js +280 -281
  247. package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
  248. package/src/interfaces/terminal/components/StatusBar.js +2 -0
  249. package/src/interfaces/terminal/index.js +168 -168
  250. package/src/interfaces/terminal/state/useAgentControl.js +496 -496
  251. package/src/interfaces/terminal/state/useAgents.js +537 -537
  252. package/src/interfaces/terminal/state/useMessages.js +629 -630
  253. package/src/interfaces/terminal/state/useTools.js +554 -554
  254. package/src/interfaces/terminal/utils/debugLogger.js +44 -44
  255. package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
  256. package/src/interfaces/webServer.js +7578 -7579
  257. package/src/interfaces/webServer.js.bak +7046 -7046
  258. package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
  259. package/src/modules/fileExplorer/controller.js +470 -469
  260. package/src/modules/fileExplorer/routes.js +285 -286
  261. package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
  262. package/src/modules/widget/__tests__/routes.test.js +677 -678
  263. package/src/modules/widget/__tests__/runtime.test.js +401 -401
  264. package/src/modules/widget/__tests__/versioning.test.js +309 -309
  265. package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
  266. package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
  267. package/src/modules/widget/routes.js +435 -435
  268. package/src/modules/widget/runtime/bundle.js +640 -640
  269. package/src/modules/widget/runtime/webComponentBundle.js +470 -470
  270. package/src/modules/widget/schema.js +182 -181
  271. package/src/modules/widget/widgetTool.js +1389 -1389
  272. package/src/services/__tests__/agentActivityService.test.js +401 -402
  273. package/src/services/__tests__/benchmarkService.test.js +184 -184
  274. package/src/services/__tests__/contextInjectionService.test.js +246 -246
  275. package/src/services/__tests__/conversationQuery.test.js +721 -723
  276. package/src/services/__tests__/credentialVault.test.js +469 -469
  277. package/src/services/__tests__/discordService.integration.test.js +638 -639
  278. package/src/services/__tests__/flowContextService.test.js +590 -590
  279. package/src/services/__tests__/memoryService.test.js +1 -1
  280. package/src/services/__tests__/messageSource.test.js +380 -380
  281. package/src/services/__tests__/modelRouterNaming.test.js +111 -111
  282. package/src/services/__tests__/projectDetector.test.js +34 -34
  283. package/src/services/__tests__/promptService.test.js +242 -242
  284. package/src/services/__tests__/telegramService.test.js +941 -941
  285. package/src/services/__tests__/tokenCountingService.test.js +48 -48
  286. package/src/services/agentActivityService.js +419 -420
  287. package/src/services/aiService.js +2997 -3001
  288. package/src/services/apiKeyManager.js +359 -359
  289. package/src/services/benchmarkService.js +196 -196
  290. package/src/services/codebaseKnowledgeService.js +2 -2
  291. package/src/services/composioService.js +738 -738
  292. package/src/services/conversationCompactionService.js +1258 -1257
  293. package/src/services/credentialVault.js +685 -685
  294. package/src/services/discordService.js +792 -793
  295. package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
  296. package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
  297. package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
  298. package/src/services/embeddings/autoRecall.js +218 -219
  299. package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
  300. package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
  301. package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
  302. package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
  303. package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
  304. package/src/services/embeddings/indexers/composioIndexer.js +279 -279
  305. package/src/services/embeddings/providerInterface.js +206 -206
  306. package/src/services/embeddings/providers/localProvider.js +11 -7
  307. package/src/services/embeddings/providers/openaiProvider.js +101 -101
  308. package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
  309. package/src/services/errorHandler.js +809 -809
  310. package/src/services/flowContextService.js +586 -586
  311. package/src/services/grounding/MockAdapter.js +125 -125
  312. package/src/services/modelRouterService.js +26 -31
  313. package/src/services/modelsService.js +322 -322
  314. package/src/services/ollamaService.js +452 -452
  315. package/src/services/projectDetector.js +403 -404
  316. package/src/services/promptService.js +418 -418
  317. package/src/services/qualityInspector.js +795 -795
  318. package/src/services/scheduleService.js +726 -726
  319. package/src/services/serviceRegistry.js +386 -386
  320. package/src/services/telegrafBot.js +174 -174
  321. package/src/services/telegramService.js +1972 -1972
  322. package/src/services/visualEditorBridge.js +1033 -1033
  323. package/src/services/visualEditorServer.js +1769 -1774
  324. package/src/services/whatsappService.js +667 -668
  325. package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
  326. package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
  327. package/src/tools/__tests__/agentDelayTool.test.js +342 -342
  328. package/src/tools/__tests__/baseTool.test.js +3 -3
  329. package/src/tools/__tests__/codeMapTool.test.js +915 -915
  330. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
  331. package/src/tools/__tests__/fileTreeTool.test.js +274 -274
  332. package/src/tools/__tests__/filesystemTool.test.js +815 -815
  333. package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
  334. package/src/tools/__tests__/imageTool.validator.test.js +194 -194
  335. package/src/tools/__tests__/jobDoneTool.test.js +580 -581
  336. package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
  337. package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
  338. package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
  339. package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
  340. package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
  341. package/src/tools/__tests__/memoryTool.test.js +1 -1
  342. package/src/tools/__tests__/seekTool.test.js +282 -282
  343. package/src/tools/__tests__/skillsTool.search.test.js +164 -164
  344. package/src/tools/__tests__/skillsTool.test.js +226 -226
  345. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
  346. package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
  347. package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
  348. package/src/tools/__tests__/taskManagerTool.test.js +866 -866
  349. package/src/tools/__tests__/terminalTool.test.js +448 -448
  350. package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
  351. package/src/tools/__tests__/userPromptTool.test.js +297 -297
  352. package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
  353. package/src/tools/__tests__/webTool.e2e.test.js +609 -603
  354. package/src/tools/__tests__/webTool.unit.test.js +195 -195
  355. package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
  356. package/src/tools/agentCommunicationTool.js +8 -10
  357. package/src/tools/agentDelayTool.js +496 -497
  358. package/src/tools/asyncToolManager.js +602 -603
  359. package/src/tools/baseTool.js +12 -11
  360. package/src/tools/cloneDetectionTool.js +576 -581
  361. package/src/tools/codeMapTool.js +0 -6
  362. package/src/tools/composioTool.js +617 -617
  363. package/src/tools/dependencyResolverTool.js +1211 -1212
  364. package/src/tools/desktop/DesktopTool.js +629 -638
  365. package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
  366. package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
  367. package/src/tools/desktop/__tests__/osController.test.js +364 -364
  368. package/src/tools/desktop/osController.js +491 -491
  369. package/src/tools/docxTool.js +623 -623
  370. package/src/tools/excelTool.js +636 -636
  371. package/src/tools/fileContentReplaceTool.js +5 -7
  372. package/src/tools/fileSystemTool.js +12 -19
  373. package/src/tools/fileTreeTool.js +840 -840
  374. package/src/tools/foundryWebSearchTool.js +273 -273
  375. package/src/tools/helpTool.js +198 -198
  376. package/src/tools/imageTool.js +1397 -1397
  377. package/src/tools/importAnalyzerTool.js +1056 -1056
  378. package/src/tools/jobDoneTool.js +495 -495
  379. package/src/tools/memoryTool.js +1 -1
  380. package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
  381. package/src/tools/office/pres/archetypes/agenda.js +61 -61
  382. package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
  383. package/src/tools/office/pres/archetypes/bigStat.js +140 -142
  384. package/src/tools/office/pres/archetypes/closing.js +70 -70
  385. package/src/tools/office/pres/archetypes/hero.js +70 -70
  386. package/src/tools/office/pres/archetypes/productHero.js +93 -94
  387. package/src/tools/office/pres/archetypes/table.js +73 -74
  388. package/src/tools/office/pres/backgrounds/orb.js +66 -66
  389. package/src/tools/office/pres/components.js +422 -423
  390. package/src/tools/officeTool.js +441 -441
  391. package/src/tools/pdfTool.js +625 -627
  392. package/src/tools/platformControlTool.js +1081 -1081
  393. package/src/tools/seekTool.js +917 -918
  394. package/src/tools/skillsTool.js +1 -1
  395. package/src/tools/staticAnalysisTool.js +2143 -2146
  396. package/src/tools/taskManagerTool.js +3324 -3324
  397. package/src/tools/terminalTool.js +2615 -2618
  398. package/src/tools/videoTool.js +1303 -1303
  399. package/src/tools/visionTool.js +508 -508
  400. package/src/tools/visualEditorTool.js +1289 -1290
  401. package/src/tools/webTool.js +3368 -3368
  402. package/src/tools/whatsappTool.js +464 -464
  403. package/src/types/__tests__/agent.test.js +499 -499
  404. package/src/types/__tests__/contextReference.test.js +606 -606
  405. package/src/types/__tests__/conversation.test.js +555 -555
  406. package/src/types/__tests__/toolCommand.test.js +584 -584
  407. package/src/types/contextReference.js +974 -971
  408. package/src/types/conversation.js +729 -729
  409. package/src/types/toolCommand.js +746 -746
  410. package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
  411. package/src/utilities/__tests__/auditReport.test.js +328 -328
  412. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
  413. package/src/utilities/__tests__/jsonRepair.test.js +103 -104
  414. package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
  415. package/src/utilities/__tests__/platformUtils.test.js +80 -87
  416. package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
  417. package/src/utilities/__tests__/toolConstants.test.js +92 -94
  418. package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
  419. package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
  420. package/src/utilities/attachmentValidator.js +284 -288
  421. package/src/utilities/authCache.js.backup-1779570472481 +121 -121
  422. package/src/utilities/browserStealth.js +631 -630
  423. package/src/utilities/configManager.js +616 -617
  424. package/src/utilities/directoryAccessManager.js +564 -565
  425. package/src/utilities/fileProcessor.js +308 -307
  426. package/src/utilities/humanBehavior.js +454 -453
  427. package/src/utilities/logger.js +479 -479
  428. package/src/utilities/structuredFileValidator.js +696 -699
  429. package/src/utilities/tagParser.js +5 -10
  430. package/src/utilities/userDataDir.js +308 -308
  431. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
  432. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
  433. package/node_modules/minipass/LICENSE +0 -15
  434. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
  435. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
  436. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
  437. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
  438. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
  439. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
  440. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
  441. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
  442. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
  443. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
  444. /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
  445. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
  446. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
  447. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
  448. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
  449. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
  450. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
  451. /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
@@ -1,1056 +1,1056 @@
1
- /**
2
- * @file tools/importAnalyzerTool.js
3
- * @description Modern tool for analyzing and detecting broken imports/exports in Node.js projects
4
- */
5
-
6
- import { promises as fs } from 'fs';
7
- import path from 'path';
8
- import { BaseTool } from './baseTool.js';
9
- import TagParser from '../utilities/tagParser.js';
10
-
11
- /**
12
- * Configuration constants for the import analyzer
13
- */
14
- const ANALYZER_CONFIG = {
15
- DEFAULT_MODE: 'full',
16
- VALID_MODES: ['full', 'quick', 'fix'],
17
- DEFAULT_OUTPUT: 'summary',
18
- VALID_OUTPUTS: ['summary', 'detailed', 'json'],
19
- DEFAULT_IGNORE_FILE: '.gitignore',
20
- MAX_FILES: 10000, // Safety limit for file count
21
- SUPPORTED_EXTENSIONS: ['.js', '.mjs', '.ts', '.jsx', '.tsx'],
22
- DEFAULT_IGNORE_PATTERNS: ['node_modules', '.git', 'dist', 'build', 'coverage'],
23
- FILE_READ_TIMEOUT: 5000 // Timeout for reading large files
24
- };
25
-
26
- /**
27
- * ImportAnalyzerTool - Modern implementation
28
- * Analyzes JavaScript/TypeScript projects to detect broken imports, missing exports, and dependency issues
29
- */
30
- export class ImportAnalyzerTool extends BaseTool {
31
- constructor(config = {}, logger = null) {
32
- super(config, logger);
33
-
34
- // Override tool ID to match documentation (with hyphen)
35
- this.id = 'import-analyzer';
36
- }
37
-
38
- /**
39
- * Get tool description for agent system prompt
40
- * @returns {string} Formatted tool description
41
- */
42
- getDescription() {
43
- return `Tool: Import Analyzer - Analyze JavaScript/TypeScript imports and exports
44
-
45
- **Purpose:** Analyzes JavaScript/TypeScript projects to detect broken imports, missing exports, circular dependencies, and unused exports.
46
-
47
- **USAGE:**
48
- \`\`\`json
49
- {
50
- "toolId": "import-analyzer",
51
- "parameters": {
52
- "path": "./src",
53
- "mode": "full",
54
- "output": "summary",
55
- "ignoreFile": ".gitignore"
56
- }
57
- }
58
- \`\`\`
59
-
60
- **Parameters:**
61
- - **path** (string, optional): Path to directory to analyze. Default: "."
62
- - **mode** (string, optional): Analysis mode. Options:
63
- - "full" - Complete analysis (default)
64
- - "quick" - Fast scan for missing files only
65
- - "fix" - Includes fix suggestions
66
- - **output** (string, optional): Output format. Options:
67
- - "summary" - Concise summary (default)
68
- - "detailed" - Full report with fixes
69
- - "json" - Machine-readable format
70
- - **ignoreFile** (string, optional): Ignore file name. Default: ".gitignore"
71
-
72
- **What It Detects:**
73
- - Missing files (imports pointing to non-existent files)
74
- - Missing exports (symbols not exported from target files)
75
- - Circular dependencies (files that depend on each other in a loop)
76
- - Unused exports (exports never imported anywhere)
77
-
78
- **Examples:**
79
-
80
- 1. Quick project scan:
81
- \`\`\`json
82
- {
83
- "toolId": "import-analyzer",
84
- "parameters": { "mode": "quick" }
85
- }
86
- \`\`\`
87
-
88
- 2. Full analysis with detailed report:
89
- \`\`\`json
90
- {
91
- "toolId": "import-analyzer",
92
- "parameters": { "mode": "full", "output": "detailed" }
93
- }
94
- \`\`\`
95
-
96
- 3. Analyze specific directory:
97
- \`\`\`json
98
- {
99
- "toolId": "import-analyzer",
100
- "parameters": { "path": "./src/components", "mode": "full" }
101
- }
102
- \`\`\`
103
-
104
- **Notes:**
105
- - Supports ES6 modules (import/export) and CommonJS (require/module.exports)
106
- - Respects .gitignore patterns
107
- - Correctly handles commented imports and multi-line statements
108
- - Works with .js, .mjs, .ts, .jsx, .tsx files`;
109
- }
110
-
111
- /**
112
- * Parse tool parameters from raw content (XML or JSON)
113
- * @param {string|Object} content - Raw tool content or parsed object
114
- * @returns {Object} Parsed parameters
115
- */
116
- parseParameters(content) {
117
- // If already an object, validate and return
118
- if (typeof content === 'object' && content !== null) {
119
- return {
120
- path: content.path || '.',
121
- mode: content.mode || ANALYZER_CONFIG.DEFAULT_MODE,
122
- output: content.output || ANALYZER_CONFIG.DEFAULT_OUTPUT,
123
- ignoreFile: content.ignoreFile || ANALYZER_CONFIG.DEFAULT_IGNORE_FILE
124
- };
125
- }
126
-
127
- // Parse XML content
128
- if (typeof content === 'string') {
129
- // Try modern XML format first: <import-analyzer>...</import-analyzer>
130
- const modernPattern = /<import-analyzer([^>]*)>([\s\S]*?)<\/import-analyzer>/i;
131
- const modernMatch = modernPattern.exec(content);
132
-
133
- if (modernMatch) {
134
- const attributesStr = modernMatch[1];
135
- const innerContent = modernMatch[2];
136
-
137
- // Parse attributes from opening tag
138
- const pathAttr = /path=["']([^"']*)["']/i.exec(attributesStr);
139
- const modeAttr = /mode=["']([^"']*)["']/i.exec(attributesStr);
140
- const outputAttr = /output=["']([^"']*)["']/i.exec(attributesStr);
141
- const ignoreFileAttr = /ignore-file=["']([^"']*)["']/i.exec(attributesStr);
142
-
143
- // Extract from inner content
144
- const pathPattern = /<path>(.*?)<\/path>/i;
145
- const pathMatch = pathPattern.exec(innerContent);
146
-
147
- const modePattern = /<mode>(.*?)<\/mode>/i;
148
- const modeMatch = modePattern.exec(innerContent);
149
-
150
- const outputPattern = /<output>(.*?)<\/output>/i;
151
- const outputMatch = outputPattern.exec(innerContent);
152
-
153
- const ignoreFilePattern = /<ignore-file>(.*?)<\/ignore-file>/i;
154
- const ignoreFileMatch = ignoreFilePattern.exec(innerContent);
155
-
156
- // Content takes precedence over attributes
157
- const extractedPath = (pathMatch ? pathMatch[1].trim() : null) || (pathAttr ? pathAttr[1] : '.');
158
- const extractedMode = (modeMatch ? modeMatch[1].trim() : null) || (modeAttr ? modeAttr[1] : ANALYZER_CONFIG.DEFAULT_MODE);
159
- const extractedOutput = (outputMatch ? outputMatch[1].trim() : null) || (outputAttr ? outputAttr[1] : ANALYZER_CONFIG.DEFAULT_OUTPUT);
160
- const extractedIgnoreFile = (ignoreFileMatch ? ignoreFileMatch[1].trim() : null) || (ignoreFileAttr ? ignoreFileAttr[1] : ANALYZER_CONFIG.DEFAULT_IGNORE_FILE);
161
-
162
- return {
163
- path: extractedPath,
164
- mode: extractedMode,
165
- output: extractedOutput,
166
- ignoreFile: extractedIgnoreFile
167
- };
168
- }
169
-
170
- // Try legacy format with TagParser
171
- try {
172
- const parsed = TagParser.parseTags(content, 'analyze');
173
-
174
- if (parsed && parsed.length > 0) {
175
- const analyzeCommand = parsed[0];
176
-
177
- return {
178
- path: analyzeCommand.attributes.path || '.',
179
- mode: analyzeCommand.attributes.mode || ANALYZER_CONFIG.DEFAULT_MODE,
180
- output: analyzeCommand.attributes.output || ANALYZER_CONFIG.DEFAULT_OUTPUT,
181
- ignoreFile: analyzeCommand.attributes['ignore-file'] || ANALYZER_CONFIG.DEFAULT_IGNORE_FILE
182
- };
183
- }
184
- } catch (error) {
185
- // Fall through to error
186
- }
187
-
188
- throw new Error('Invalid import-analyzer format. Use <import-analyzer> tags or JSON format.');
189
- }
190
-
191
- throw new Error('Invalid parameter format. Expected string (XML) or object (JSON).');
192
- }
193
-
194
- /**
195
- * Validate parameters
196
- * @param {Object} params - Parameters to validate
197
- * @throws {Error} If validation fails
198
- * @private
199
- */
200
- _validateParameters(params) {
201
- if (!params || typeof params !== 'object') {
202
- throw new Error('Parameters must be an object');
203
- }
204
-
205
- if (params.path && typeof params.path !== 'string') {
206
- throw new Error('path must be a string');
207
- }
208
-
209
- if (params.mode && !ANALYZER_CONFIG.VALID_MODES.includes(params.mode)) {
210
- throw new Error(`Invalid mode: ${params.mode}. Must be one of: ${ANALYZER_CONFIG.VALID_MODES.join(', ')}`);
211
- }
212
-
213
- if (params.output && !ANALYZER_CONFIG.VALID_OUTPUTS.includes(params.output)) {
214
- throw new Error(`Invalid output: ${params.output}. Must be one of: ${ANALYZER_CONFIG.VALID_OUTPUTS.join(', ')}`);
215
- }
216
-
217
- if (params.ignoreFile && typeof params.ignoreFile !== 'string') {
218
- throw new Error('ignoreFile must be a string');
219
- }
220
- }
221
-
222
- /**
223
- * Validate and resolve file path
224
- * @param {string} targetPath - Target path from parameters
225
- * @param {Object} context - Execution context
226
- * @returns {string} Resolved absolute path
227
- * @throws {Error} If path is invalid or inaccessible
228
- * @private
229
- */
230
- _resolveAndValidatePath(targetPath, context) {
231
- const { projectDir, directoryAccess } = context;
232
-
233
- // Determine working directory
234
- let workingDirectory = projectDir || process.cwd();
235
-
236
- if (directoryAccess && directoryAccess.workingDirectory) {
237
- workingDirectory = directoryAccess.workingDirectory;
238
- }
239
-
240
- // Resolve the target path
241
- const resolvedPath = path.isAbsolute(targetPath)
242
- ? path.normalize(targetPath)
243
- : path.normalize(path.join(workingDirectory, targetPath));
244
-
245
- // Security: Check for path traversal
246
- const realWorkingDir = path.normalize(workingDirectory);
247
- if (!resolvedPath.startsWith(realWorkingDir)) {
248
- throw new Error(`Path traversal detected: ${targetPath} resolves outside working directory`);
249
- }
250
-
251
- return resolvedPath;
252
- }
253
-
254
- /**
255
- * Execute tool with parsed parameters
256
- * @param {Object} params - Parsed parameters
257
- * @param {Object} context - Execution context
258
- * @returns {Promise<Object>} Execution result
259
- */
260
- async execute(params, context = {}) {
261
- try {
262
- // Validate parameters
263
- this._validateParameters(params);
264
-
265
- const { path: targetPath, mode, output, ignoreFile } = params;
266
- const { projectDir, agentId, directoryAccess } = context;
267
-
268
- // Resolve and validate path
269
- const resolvedPath = this._resolveAndValidatePath(targetPath, context);
270
-
271
- this.logger?.info('Import analyzer executing', {
272
- mode,
273
- resolvedPath,
274
- output,
275
- agentId
276
- });
277
-
278
- const outputLines = [];
279
- outputLines.push(`🔍 Analyzing imports in: ${resolvedPath}`);
280
- outputLines.push(`Mode: ${mode}`);
281
- outputLines.push(`Output: ${output}\n`);
282
-
283
- // Check if path exists
284
- try {
285
- await fs.access(resolvedPath);
286
- } catch {
287
- return {
288
- success: false,
289
- error: `Path does not exist: ${resolvedPath}`,
290
- output: outputLines.join('\n')
291
- };
292
- }
293
-
294
- // Run analysis
295
- const analyzer = new ImportExportAnalyzer(resolvedPath, ignoreFile, this.logger);
296
- const results = await analyzer.analyze(mode);
297
-
298
- // Format output based on requested format
299
- let formattedOutput;
300
- switch (output) {
301
- case 'json':
302
- // For JSON output, don't include header lines
303
- formattedOutput = JSON.stringify(results, null, 2);
304
- break;
305
- case 'detailed':
306
- formattedOutput = this._formatDetailedOutput(results);
307
- outputLines.push(formattedOutput);
308
- break;
309
- case 'summary':
310
- default:
311
- formattedOutput = this._formatSummaryOutput(results);
312
- outputLines.push(formattedOutput);
313
- break;
314
- }
315
-
316
- return {
317
- success: true,
318
- mode,
319
- message: 'Import analysis completed',
320
- statistics: {
321
- totalFiles: results.summary.totalFiles,
322
- totalImports: results.summary.totalImports,
323
- totalExports: results.summary.totalExports,
324
- issuesFound: results.fileNotFoundImports.length + Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0)
325
- },
326
- output: output === 'json' ? formattedOutput : outputLines.join('\n'),
327
- results
328
- };
329
-
330
- } catch (error) {
331
- this.logger?.error('Import analyzer error:', error);
332
-
333
- return {
334
- success: false,
335
- error: error.message,
336
- output: error.message
337
- };
338
- }
339
- }
340
-
341
- /**
342
- * Format summary output
343
- * @param {Object} results - Analysis results
344
- * @returns {string} Formatted output
345
- * @private
346
- */
347
- _formatSummaryOutput(results) {
348
- const lines = [];
349
-
350
- lines.push('📊 Import/Export Analysis Summary');
351
- lines.push('================================\n');
352
-
353
- lines.push(`📁 Files analyzed: ${results.summary.totalFiles}`);
354
- lines.push(`📥 Total imports: ${results.summary.totalImports}`);
355
- lines.push(`📤 Total exports: ${results.summary.totalExports}\n`);
356
-
357
- // Critical issues
358
- const missingFilesCount = results.fileNotFoundImports.length;
359
- const missingExportsCount = Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0);
360
- const circularDepsCount = results.circularDependencies ? results.circularDependencies.length : 0;
361
-
362
- if (missingFilesCount > 0) {
363
- lines.push(`❌ Missing files: ${missingFilesCount} imports pointing to non-existent files`);
364
- }
365
-
366
- if (missingExportsCount > 0) {
367
- lines.push(`⚠️ Missing exports: ${missingExportsCount} symbols not exported from their sources`);
368
- }
369
-
370
- if (circularDepsCount > 0) {
371
- lines.push(`🔄 Circular dependencies: ${circularDepsCount} circular dependency chains detected`);
372
- }
373
-
374
- if (missingFilesCount === 0 && missingExportsCount === 0 && circularDepsCount === 0) {
375
- lines.push('✅ No import/export issues detected!');
376
- } else {
377
- lines.push('\n🔍 Top Issues to Fix:');
378
-
379
- // Show top 5 files with issues
380
- if (Object.keys(results.missingFiles || {}).length > 0) {
381
- lines.push('\n Missing Files:');
382
- Object.entries(results.missingFiles).slice(0, 3).forEach(([file, issues]) => {
383
- lines.push(` • ${file} has ${issues.length} broken import(s)`);
384
- });
385
- }
386
-
387
- if (Object.keys(results.missingExports).length > 0) {
388
- lines.push('\n Missing Exports:');
389
- Object.entries(results.missingExports).slice(0, 3).forEach(([file, issues]) => {
390
- lines.push(` • ${file} imports ${issues.length} non-existent symbol(s)`);
391
- });
392
- }
393
-
394
- lines.push('\n💡 Run with output="detailed" for complete analysis and fix suggestions');
395
- }
396
-
397
- return lines.join('\n');
398
- }
399
-
400
- /**
401
- * Format detailed output
402
- * @param {Object} results - Analysis results
403
- * @returns {string} Formatted output
404
- * @private
405
- */
406
- _formatDetailedOutput(results) {
407
- const lines = [];
408
-
409
- lines.push('📊 Detailed Import/Export Analysis Report');
410
- lines.push('=========================================\n');
411
-
412
- lines.push('📈 Statistics:');
413
- lines.push(` • Files analyzed: ${results.summary.totalFiles}`);
414
- lines.push(` • Total imports: ${results.summary.totalImports}`);
415
- lines.push(` • Total exports: ${results.summary.totalExports}\n`);
416
-
417
- // Missing files section
418
- if (results.fileNotFoundImports.length > 0) {
419
- lines.push('❌ MISSING FILES');
420
- lines.push('─────────────────');
421
-
422
- const fileGroups = {};
423
- results.fileNotFoundImports.forEach(item => {
424
- if (!fileGroups[item.importingFile]) {
425
- fileGroups[item.importingFile] = [];
426
- }
427
- fileGroups[item.importingFile].push(item);
428
- });
429
-
430
- Object.entries(fileGroups).forEach(([file, imports]) => {
431
- lines.push(`\n📄 ${file}:`);
432
- imports.forEach(imp => {
433
- const importType = imp.isDefault ? 'default' : imp.isNamespace ? 'namespace' : 'named';
434
- lines.push(` ⚠️ Cannot find file: ${imp.importedFromFile}`);
435
- lines.push(` Trying to import: ${imp.importedSymbol} (${importType})`);
436
- lines.push(` 💡 Fix: Check if file exists or correct the import path`);
437
- });
438
- });
439
- }
440
-
441
- // Missing exports section
442
- if (Object.keys(results.missingExports).length > 0) {
443
- lines.push('\n⚠️ MISSING EXPORTS');
444
- lines.push('──────────────────');
445
-
446
- Object.entries(results.missingExports).forEach(([file, issues]) => {
447
- lines.push(`\n📄 ${file}:`);
448
- issues.forEach(issue => {
449
- const importType = issue.isDefault ? 'default' : issue.isNamespace ? 'namespace' : 'named';
450
- lines.push(` ❌ Symbol not exported: "${issue.importedSymbol}" (${importType})`);
451
- lines.push(` From file: ${issue.importedFromFile}`);
452
-
453
- if (issue.availableExports.length > 0) {
454
- lines.push(` 📤 Available exports: ${issue.availableExports.join(', ')}`);
455
-
456
- // Suggest potential fixes
457
- if (issue.isDefault && issue.availableExports.includes('default')) {
458
- lines.push(` 💡 Fix: Default export exists, check import syntax`);
459
- } else if (issue.isDefault && !issue.availableExports.includes('default')) {
460
- lines.push(` 💡 Fix: No default export. Use named import: { ${issue.availableExports[0] || 'symbolName'} }`);
461
- } else {
462
- // Check for similar names
463
- const similar = issue.availableExports.find(exp =>
464
- exp.toLowerCase() === issue.importedSymbol.toLowerCase()
465
- );
466
- if (similar) {
467
- lines.push(` 💡 Fix: Did you mean "${similar}"? (case mismatch)`);
468
- } else {
469
- lines.push(` 💡 Fix: Add export for "${issue.importedSymbol}" or use one of the available exports`);
470
- }
471
- }
472
- } else {
473
- lines.push(` 📤 No exports found in target file`);
474
- lines.push(` 💡 Fix: Add exports to ${issue.importedFromFile} or check if it's the correct file`);
475
- }
476
- });
477
- });
478
- }
479
-
480
- // Circular dependencies
481
- if (results.circularDependencies && results.circularDependencies.length > 0) {
482
- lines.push('\n🔄 CIRCULAR DEPENDENCIES');
483
- lines.push('────────────────────────');
484
-
485
- results.circularDependencies.forEach((cycle, index) => {
486
- lines.push(`\n Cycle ${index + 1}:`);
487
- cycle.forEach((file, i) => {
488
- if (i < cycle.length - 1) {
489
- lines.push(` ${file} → ${cycle[i + 1]}`);
490
- }
491
- });
492
- lines.push(` 💡 Fix: Refactor to break the circular dependency`);
493
- });
494
- }
495
-
496
- // Unused exports (if available)
497
- if (results.unusedExports && Object.keys(results.unusedExports).length > 0) {
498
- lines.push('\n🗑️ POTENTIALLY UNUSED EXPORTS');
499
- lines.push('──────────────────────────────');
500
-
501
- Object.entries(results.unusedExports).slice(0, 10).forEach(([file, exports]) => {
502
- lines.push(`\n📄 ${file}:`);
503
- lines.push(` Unused: ${exports.join(', ')}`);
504
- });
505
-
506
- lines.push('\n 💡 Note: These exports are not imported within this project');
507
- lines.push(' They might be used by external packages or could be removed');
508
- }
509
-
510
- // Summary and recommendations
511
- lines.push('\n📋 RECOMMENDATIONS');
512
- lines.push('──────────────────');
513
-
514
- const totalIssues = results.fileNotFoundImports.length +
515
- Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0);
516
-
517
- if (totalIssues === 0) {
518
- lines.push('✅ Your import/export structure looks good!');
519
- } else {
520
- lines.push(`Found ${totalIssues} issue(s) that need attention:`);
521
-
522
- if (results.fileNotFoundImports.length > 0) {
523
- lines.push(` 1. Fix ${results.fileNotFoundImports.length} missing file reference(s)`);
524
- }
525
-
526
- if (Object.keys(results.missingExports).length > 0) {
527
- lines.push(` 2. Resolve ${Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0)} missing export(s)`);
528
- }
529
-
530
- if (results.circularDependencies && results.circularDependencies.length > 0) {
531
- lines.push(` 3. Refactor ${results.circularDependencies.length} circular dependency chain(s)`);
532
- }
533
- }
534
-
535
- return lines.join('\n');
536
- }
537
- }
538
-
539
- /**
540
- * Internal analyzer class
541
- */
542
- class ImportExportAnalyzer {
543
- constructor(rootDir, ignoreFile = '.gitignore', logger = null) {
544
- this.rootDir = path.resolve(rootDir);
545
- this.ignoreFile = ignoreFile;
546
- this.logger = logger;
547
- this.ignorePatterns = [...ANALYZER_CONFIG.DEFAULT_IGNORE_PATTERNS];
548
- this.imports = [];
549
- this.exports = new Map();
550
- this.dependencies = new Map(); // For circular dependency detection
551
- }
552
-
553
- async loadIgnoreFile() {
554
- try {
555
- const ignoreFilePath = path.join(this.rootDir, this.ignoreFile);
556
- const content = await fs.readFile(ignoreFilePath, 'utf-8');
557
- const patterns = content
558
- .split('\n')
559
- .map(line => line.trim())
560
- .filter(line => line && !line.startsWith('#'));
561
-
562
- this.ignorePatterns.push(...patterns);
563
- this.logger?.debug('Loaded ignore patterns', { count: patterns.length });
564
- } catch {
565
- // Ignore file doesn't exist, use defaults
566
- this.logger?.debug('No ignore file found, using defaults');
567
- }
568
- }
569
-
570
- shouldIgnoreFile(filePath) {
571
- const relativePath = path.relative(this.rootDir, filePath);
572
- return this.ignorePatterns.some(pattern => {
573
- if (pattern.includes('*')) {
574
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
575
- return regex.test(relativePath) || regex.test(path.basename(filePath));
576
- }
577
- return relativePath.includes(pattern) || path.basename(filePath) === pattern;
578
- });
579
- }
580
-
581
- async getAllFiles(dir) {
582
- const files = [];
583
-
584
- const traverse = async (currentDir) => {
585
- try {
586
- const entries = await fs.readdir(currentDir, { withFileTypes: true });
587
-
588
- for (const entry of entries) {
589
- const fullPath = path.join(currentDir, entry.name);
590
-
591
- if (this.shouldIgnoreFile(fullPath)) {
592
- continue;
593
- }
594
-
595
- if (entry.isDirectory()) {
596
- await traverse(fullPath);
597
- } else if (entry.isFile()) {
598
- const ext = path.extname(entry.name);
599
- if (ANALYZER_CONFIG.SUPPORTED_EXTENSIONS.includes(ext)) {
600
- files.push(fullPath);
601
- }
602
- }
603
- }
604
- } catch (error) {
605
- // Skip directories we can't read
606
- this.logger?.warn('Cannot read directory', { dir: currentDir, error: error.message });
607
- }
608
- };
609
-
610
- await traverse(dir);
611
-
612
- // Safety check
613
- if (files.length > ANALYZER_CONFIG.MAX_FILES) {
614
- this.logger?.warn(`File count exceeds limit: ${files.length} > ${ANALYZER_CONFIG.MAX_FILES}`);
615
- throw new Error(`Too many files to analyze: ${files.length} (max: ${ANALYZER_CONFIG.MAX_FILES})`);
616
- }
617
-
618
- return files;
619
- }
620
-
621
- async parseImports(content, filePath) {
622
- const imports = [];
623
- const lines = content.split('\n');
624
- const relativePath = this.getRelativePath(filePath);
625
-
626
- // Track dependencies for circular detection
627
- if (!this.dependencies.has(relativePath)) {
628
- this.dependencies.set(relativePath, new Set());
629
- }
630
-
631
- for (let i = 0; i < lines.length; i++) {
632
- const line = lines[i].trim();
633
-
634
- if (line.startsWith('//') || line.startsWith('/*')) continue;
635
-
636
- // Build multi-line statements
637
- let fullStatement = line;
638
- let j = i;
639
- while (!fullStatement.includes(';') && !fullStatement.match(/from\s+['"`][^'"`]+['"`]/) && j < lines.length - 1) {
640
- j++;
641
- const nextLine = lines[j].trim();
642
- // Skip commented lines when building multi-line statements
643
- if (nextLine.startsWith('//') || nextLine.startsWith('/*')) {
644
- continue;
645
- }
646
- fullStatement += ' ' + nextLine;
647
- }
648
-
649
- // ES6 imports
650
- const importRegex = /import\s+(?:(?:\{([^}]+)\})|(?:([^,\s]+)(?:\s*,\s*\{([^}]+)\})?)|(?:\*\s+as\s+([^,\s]+)))\s+from\s+['"`]([^'"`]+)['"`]/g;
651
- let match;
652
-
653
- while ((match = importRegex.exec(fullStatement)) !== null) {
654
- const [, namedImports, defaultImport, additionalNamed, namespaceImport, source] = match;
655
- const resolvedSource = await this.resolveImportPath(source, filePath);
656
-
657
- // Track dependency
658
- if (!resolvedSource.isExternal && resolvedSource.exists) {
659
- this.dependencies.get(relativePath).add(resolvedSource.path);
660
- }
661
-
662
- if (defaultImport) {
663
- imports.push({
664
- importingFile: relativePath,
665
- importedSymbol: defaultImport.trim(),
666
- importedFromFile: resolvedSource.path,
667
- fileExists: resolvedSource.exists,
668
- isExternal: resolvedSource.isExternal || false,
669
- isDefault: true
670
- });
671
- }
672
-
673
- if (namespaceImport) {
674
- imports.push({
675
- importingFile: relativePath,
676
- importedSymbol: namespaceImport.trim(),
677
- importedFromFile: resolvedSource.path,
678
- fileExists: resolvedSource.exists,
679
- isExternal: resolvedSource.isExternal || false,
680
- isNamespace: true
681
- });
682
- }
683
-
684
- const allNamedImports = [namedImports, additionalNamed].filter(Boolean).join(',');
685
- if (allNamedImports) {
686
- const symbols = allNamedImports.split(',').map(s => {
687
- const parts = s.trim().split(/\s+as\s+/);
688
- return parts[0].trim();
689
- });
690
-
691
- symbols.forEach(symbol => {
692
- if (symbol) {
693
- imports.push({
694
- importingFile: relativePath,
695
- importedSymbol: symbol,
696
- importedFromFile: resolvedSource.path,
697
- fileExists: resolvedSource.exists,
698
- isExternal: resolvedSource.isExternal || false,
699
- isDefault: false
700
- });
701
- }
702
- });
703
- }
704
- }
705
-
706
- // CommonJS requires
707
- const requireRegex = /(?:const|let|var)\s+(?:\{([^}]+)\}|([^=\s]+))\s*=\s*require\(['"`]([^'"`]+)['"`]\)/g;
708
- while ((match = requireRegex.exec(fullStatement)) !== null) {
709
- const [, destructured, variable, source] = match;
710
- const resolvedSource = await this.resolveImportPath(source, filePath);
711
-
712
- // Track dependency
713
- if (!resolvedSource.isExternal && resolvedSource.exists) {
714
- this.dependencies.get(relativePath).add(resolvedSource.path);
715
- }
716
-
717
- if (destructured) {
718
- const symbols = destructured.split(',').map(s => s.trim().split(':')[0].trim());
719
- symbols.forEach(symbol => {
720
- if (symbol) {
721
- imports.push({
722
- importingFile: relativePath,
723
- importedSymbol: symbol,
724
- importedFromFile: resolvedSource.path,
725
- fileExists: resolvedSource.exists,
726
- isExternal: resolvedSource.isExternal || false,
727
- isDefault: false
728
- });
729
- }
730
- });
731
- } else if (variable) {
732
- imports.push({
733
- importingFile: relativePath,
734
- importedSymbol: variable.trim(),
735
- importedFromFile: resolvedSource.path,
736
- fileExists: resolvedSource.exists,
737
- isExternal: resolvedSource.isExternal || false,
738
- isDefault: true
739
- });
740
- }
741
- }
742
-
743
- // Skip lines that were already processed as part of multi-line statement
744
- i = j;
745
- }
746
-
747
- return imports;
748
- }
749
-
750
- parseExports(content) {
751
- const exports = new Set();
752
- const lines = content.split('\n');
753
-
754
- for (const line of lines) {
755
- const trimmedLine = line.trim();
756
-
757
- if (trimmedLine.startsWith('//') || trimmedLine.startsWith('/*')) continue;
758
-
759
- // Export default
760
- if (/export\s+default\s+/.test(trimmedLine)) {
761
- exports.add('default');
762
- }
763
-
764
- // Named exports
765
- const namedExportMatch = trimmedLine.match(/export\s+\{([^}]+)\}/);
766
- if (namedExportMatch) {
767
- const symbols = namedExportMatch[1].split(',').map(s => {
768
- const parts = s.trim().split(/\s+as\s+/);
769
- return parts[parts.length - 1].trim();
770
- });
771
- symbols.forEach(symbol => exports.add(symbol));
772
- }
773
-
774
- // Direct exports
775
- const directExportMatch = trimmedLine.match(/export\s+(?:const|let|var|function|class|async\s+function)\s+([^=\s(]+)/);
776
- if (directExportMatch) {
777
- exports.add(directExportMatch[1]);
778
- }
779
-
780
- // Export from
781
- const exportFromMatch = trimmedLine.match(/export\s+\{([^}]+)\}\s+from/);
782
- if (exportFromMatch) {
783
- const symbols = exportFromMatch[1].split(',').map(s => {
784
- const parts = s.trim().split(/\s+as\s+/);
785
- return parts[parts.length - 1].trim();
786
- });
787
- symbols.forEach(symbol => exports.add(symbol));
788
- }
789
-
790
- // Export all
791
- if (/export\s+\*\s+from/.test(trimmedLine)) {
792
- exports.add('*');
793
- }
794
-
795
- // CommonJS exports
796
- const moduleExportsMatch = trimmedLine.match(/module\.exports\s*=\s*\{([^}]+)\}/);
797
- if (moduleExportsMatch) {
798
- const symbols = moduleExportsMatch[1].split(',').map(s => {
799
- const parts = s.trim().split(':');
800
- return parts[0].trim();
801
- });
802
- symbols.forEach(symbol => exports.add(symbol));
803
- }
804
-
805
- if (/module\.exports\s*=\s*[^{]/.test(trimmedLine)) {
806
- exports.add('default');
807
- }
808
-
809
- const moduleExportsPropMatch = trimmedLine.match(/module\.exports\.([^=\s]+)\s*=/);
810
- if (moduleExportsPropMatch) {
811
- exports.add(moduleExportsPropMatch[1]);
812
- }
813
-
814
- const exportsPropMatch = trimmedLine.match(/exports\.([^=\s]+)\s*=/);
815
- if (exportsPropMatch) {
816
- exports.add(exportsPropMatch[1]);
817
- }
818
- }
819
-
820
- return exports;
821
- }
822
-
823
- async resolveImportPath(importPath, currentFile) {
824
- if (importPath.startsWith('.')) {
825
- const currentDir = path.dirname(currentFile);
826
- const resolved = path.resolve(currentDir, importPath);
827
-
828
- // Try with extension first if provided
829
- if (path.extname(importPath)) {
830
- try {
831
- const stat = await fs.stat(resolved);
832
- if (stat.isFile()) {
833
- return {
834
- path: this.getRelativePath(resolved),
835
- exists: true
836
- };
837
- }
838
- } catch {
839
- // File doesn't exist
840
- }
841
- }
842
-
843
- // Try different extensions
844
- const extensions = ['', '.js', '.mjs', '.ts', '.jsx', '.tsx', '/index.js', '/index.ts', '/index.jsx', '/index.tsx'];
845
- for (const ext of extensions) {
846
- const withExt = resolved + ext;
847
- try {
848
- const stat = await fs.stat(withExt);
849
- if (stat.isFile()) {
850
- return {
851
- path: this.getRelativePath(withExt),
852
- exists: true
853
- };
854
- }
855
- } catch {
856
- // Try next
857
- }
858
- }
859
-
860
- return {
861
- path: this.getRelativePath(resolved),
862
- exists: false
863
- };
864
- }
865
-
866
- // Node modules or absolute imports
867
- return {
868
- path: importPath,
869
- exists: true,
870
- isExternal: true
871
- };
872
- }
873
-
874
- getRelativePath(filePath) {
875
- return path.relative(this.rootDir, filePath).replace(/\\/g, '/');
876
- }
877
-
878
- findCircularDependencies() {
879
- const cycles = [];
880
- const visited = new Set();
881
- const recursionStack = new Set();
882
-
883
- const dfs = (node, path = []) => {
884
- if (recursionStack.has(node)) {
885
- const cycleStart = path.indexOf(node);
886
- if (cycleStart !== -1) {
887
- cycles.push([...path.slice(cycleStart), node]);
888
- }
889
- return;
890
- }
891
-
892
- if (visited.has(node)) {
893
- return;
894
- }
895
-
896
- visited.add(node);
897
- recursionStack.add(node);
898
- path.push(node);
899
-
900
- const deps = this.dependencies.get(node) || new Set();
901
- for (const dep of deps) {
902
- dfs(dep, [...path]);
903
- }
904
-
905
- recursionStack.delete(node);
906
- };
907
-
908
- for (const node of this.dependencies.keys()) {
909
- if (!visited.has(node)) {
910
- dfs(node);
911
- }
912
- }
913
-
914
- return cycles;
915
- }
916
-
917
- findUnusedExports() {
918
- const usedExports = new Map();
919
-
920
- // Track which exports are actually imported
921
- for (const imp of this.imports) {
922
- if (!imp.isExternal) {
923
- if (!usedExports.has(imp.importedFromFile)) {
924
- usedExports.set(imp.importedFromFile, new Set());
925
- }
926
- usedExports.get(imp.importedFromFile).add(imp.importedSymbol);
927
- }
928
- }
929
-
930
- // Find exports that are never imported
931
- const unusedExports = {};
932
- for (const [file, exports] of this.exports.entries()) {
933
- const used = usedExports.get(file) || new Set();
934
- const unused = Array.from(exports).filter(exp => !used.has(exp) && exp !== '*');
935
-
936
- if (unused.length > 0) {
937
- unusedExports[file] = unused;
938
- }
939
- }
940
-
941
- return unusedExports;
942
- }
943
-
944
- async analyze(mode = 'full') {
945
- this.logger?.info('Starting import analysis', { mode, rootDir: this.rootDir });
946
-
947
- await this.loadIgnoreFile();
948
-
949
- const files = await this.getAllFiles(this.rootDir);
950
- this.logger?.info('Found files', { count: files.length });
951
-
952
- // Parse all files
953
- for (const file of files) {
954
- try {
955
- const content = await fs.readFile(file, 'utf-8');
956
- const relativeFile = this.getRelativePath(file);
957
-
958
- const fileImports = await this.parseImports(content, file);
959
- this.imports.push(...fileImports);
960
-
961
- const fileExports = this.parseExports(content);
962
- this.exports.set(relativeFile, fileExports);
963
- } catch (error) {
964
- // Skip files with errors
965
- this.logger?.warn('Error parsing file', { file, error: error.message });
966
- }
967
- }
968
-
969
- this.logger?.info('Parsed all files', { imports: this.imports.length, exports: this.exports.size });
970
-
971
- // Analyze issues
972
- const results = {
973
- summary: {
974
- totalFiles: files.length,
975
- totalImports: this.imports.length,
976
- totalExports: Array.from(this.exports.values()).reduce((sum, exports) => sum + exports.size, 0)
977
- },
978
- missingExports: {},
979
- missingFiles: {},
980
- fileNotFoundImports: []
981
- };
982
-
983
- // Check imports
984
- for (const importEntry of this.imports) {
985
- const { importingFile, importedSymbol, importedFromFile, isDefault, isNamespace, fileExists, isExternal } = importEntry;
986
-
987
- if (!fileExists && !isExternal) {
988
- results.fileNotFoundImports.push({
989
- importingFile,
990
- importedSymbol,
991
- importedFromFile,
992
- isDefault: isDefault || false,
993
- isNamespace: isNamespace || false
994
- });
995
-
996
- if (!results.missingFiles[importingFile]) {
997
- results.missingFiles[importingFile] = [];
998
- }
999
-
1000
- results.missingFiles[importingFile].push({
1001
- missingFile: importedFromFile,
1002
- importedSymbol,
1003
- isDefault: isDefault || false,
1004
- isNamespace: isNamespace || false
1005
- });
1006
-
1007
- continue;
1008
- }
1009
-
1010
- if (isExternal) continue;
1011
-
1012
- const exportingFileExports = this.exports.get(importedFromFile);
1013
- let exists = false;
1014
-
1015
- if (exportingFileExports) {
1016
- if (isNamespace) {
1017
- exists = exportingFileExports.size > 0;
1018
- } else if (isDefault) {
1019
- exists = exportingFileExports.has('default');
1020
- } else {
1021
- exists = exportingFileExports.has(importedSymbol) || exportingFileExports.has('*');
1022
- }
1023
- }
1024
-
1025
- if (!exists) {
1026
- if (!results.missingExports[importingFile]) {
1027
- results.missingExports[importingFile] = [];
1028
- }
1029
-
1030
- results.missingExports[importingFile].push({
1031
- importedSymbol,
1032
- importedFromFile,
1033
- availableExports: exportingFileExports ? Array.from(exportingFileExports) : [],
1034
- isDefault: isDefault || false,
1035
- isNamespace: isNamespace || false
1036
- });
1037
- }
1038
- }
1039
-
1040
- // Additional analysis for full mode
1041
- if (mode === 'full' || mode === 'fix') {
1042
- this.logger?.info('Running full analysis');
1043
- results.circularDependencies = this.findCircularDependencies();
1044
- results.unusedExports = this.findUnusedExports();
1045
- }
1046
-
1047
- this.logger?.info('Analysis complete', {
1048
- missingFiles: results.fileNotFoundImports.length,
1049
- missingExports: Object.keys(results.missingExports).length
1050
- });
1051
-
1052
- return results;
1053
- }
1054
- }
1055
-
1056
- export default ImportAnalyzerTool;
1
+ /**
2
+ * @file tools/importAnalyzerTool.js
3
+ * @description Modern tool for analyzing and detecting broken imports/exports in Node.js projects
4
+ */
5
+
6
+ import { promises as fs } from 'fs';
7
+ import path from 'path';
8
+ import { BaseTool } from './baseTool.js';
9
+ import TagParser from '../utilities/tagParser.js';
10
+
11
+ /**
12
+ * Configuration constants for the import analyzer
13
+ */
14
+ const ANALYZER_CONFIG = {
15
+ DEFAULT_MODE: 'full',
16
+ VALID_MODES: ['full', 'quick', 'fix'],
17
+ DEFAULT_OUTPUT: 'summary',
18
+ VALID_OUTPUTS: ['summary', 'detailed', 'json'],
19
+ DEFAULT_IGNORE_FILE: '.gitignore',
20
+ MAX_FILES: 10000, // Safety limit for file count
21
+ SUPPORTED_EXTENSIONS: ['.js', '.mjs', '.ts', '.jsx', '.tsx'],
22
+ DEFAULT_IGNORE_PATTERNS: ['node_modules', '.git', 'dist', 'build', 'coverage'],
23
+ FILE_READ_TIMEOUT: 5000 // Timeout for reading large files
24
+ };
25
+
26
+ /**
27
+ * ImportAnalyzerTool - Modern implementation
28
+ * Analyzes JavaScript/TypeScript projects to detect broken imports, missing exports, and dependency issues
29
+ */
30
+ export class ImportAnalyzerTool extends BaseTool {
31
+ constructor(config = {}, logger = null) {
32
+ super(config, logger);
33
+
34
+ // Override tool ID to match documentation (with hyphen)
35
+ this.id = 'import-analyzer';
36
+ }
37
+
38
+ /**
39
+ * Get tool description for agent system prompt
40
+ * @returns {string} Formatted tool description
41
+ */
42
+ getDescription() {
43
+ return `Tool: Import Analyzer - Analyze JavaScript/TypeScript imports and exports
44
+
45
+ **Purpose:** Analyzes JavaScript/TypeScript projects to detect broken imports, missing exports, circular dependencies, and unused exports.
46
+
47
+ **USAGE:**
48
+ \`\`\`json
49
+ {
50
+ "toolId": "import-analyzer",
51
+ "parameters": {
52
+ "path": "./src",
53
+ "mode": "full",
54
+ "output": "summary",
55
+ "ignoreFile": ".gitignore"
56
+ }
57
+ }
58
+ \`\`\`
59
+
60
+ **Parameters:**
61
+ - **path** (string, optional): Path to directory to analyze. Default: "."
62
+ - **mode** (string, optional): Analysis mode. Options:
63
+ - "full" - Complete analysis (default)
64
+ - "quick" - Fast scan for missing files only
65
+ - "fix" - Includes fix suggestions
66
+ - **output** (string, optional): Output format. Options:
67
+ - "summary" - Concise summary (default)
68
+ - "detailed" - Full report with fixes
69
+ - "json" - Machine-readable format
70
+ - **ignoreFile** (string, optional): Ignore file name. Default: ".gitignore"
71
+
72
+ **What It Detects:**
73
+ - Missing files (imports pointing to non-existent files)
74
+ - Missing exports (symbols not exported from target files)
75
+ - Circular dependencies (files that depend on each other in a loop)
76
+ - Unused exports (exports never imported anywhere)
77
+
78
+ **Examples:**
79
+
80
+ 1. Quick project scan:
81
+ \`\`\`json
82
+ {
83
+ "toolId": "import-analyzer",
84
+ "parameters": { "mode": "quick" }
85
+ }
86
+ \`\`\`
87
+
88
+ 2. Full analysis with detailed report:
89
+ \`\`\`json
90
+ {
91
+ "toolId": "import-analyzer",
92
+ "parameters": { "mode": "full", "output": "detailed" }
93
+ }
94
+ \`\`\`
95
+
96
+ 3. Analyze specific directory:
97
+ \`\`\`json
98
+ {
99
+ "toolId": "import-analyzer",
100
+ "parameters": { "path": "./src/components", "mode": "full" }
101
+ }
102
+ \`\`\`
103
+
104
+ **Notes:**
105
+ - Supports ES6 modules (import/export) and CommonJS (require/module.exports)
106
+ - Respects .gitignore patterns
107
+ - Correctly handles commented imports and multi-line statements
108
+ - Works with .js, .mjs, .ts, .jsx, .tsx files`;
109
+ }
110
+
111
+ /**
112
+ * Parse tool parameters from raw content (XML or JSON)
113
+ * @param {string|Object} content - Raw tool content or parsed object
114
+ * @returns {Object} Parsed parameters
115
+ */
116
+ parseParameters(content) {
117
+ // If already an object, validate and return
118
+ if (typeof content === 'object' && content !== null) {
119
+ return {
120
+ path: content.path || '.',
121
+ mode: content.mode || ANALYZER_CONFIG.DEFAULT_MODE,
122
+ output: content.output || ANALYZER_CONFIG.DEFAULT_OUTPUT,
123
+ ignoreFile: content.ignoreFile || ANALYZER_CONFIG.DEFAULT_IGNORE_FILE
124
+ };
125
+ }
126
+
127
+ // Parse XML content
128
+ if (typeof content === 'string') {
129
+ // Try modern XML format first: <import-analyzer>...</import-analyzer>
130
+ const modernPattern = /<import-analyzer([^>]*)>([\s\S]*?)<\/import-analyzer>/i;
131
+ const modernMatch = modernPattern.exec(content);
132
+
133
+ if (modernMatch) {
134
+ const attributesStr = modernMatch[1];
135
+ const innerContent = modernMatch[2];
136
+
137
+ // Parse attributes from opening tag
138
+ const pathAttr = /path=["']([^"']*)["']/i.exec(attributesStr);
139
+ const modeAttr = /mode=["']([^"']*)["']/i.exec(attributesStr);
140
+ const outputAttr = /output=["']([^"']*)["']/i.exec(attributesStr);
141
+ const ignoreFileAttr = /ignore-file=["']([^"']*)["']/i.exec(attributesStr);
142
+
143
+ // Extract from inner content
144
+ const pathPattern = /<path>(.*?)<\/path>/i;
145
+ const pathMatch = pathPattern.exec(innerContent);
146
+
147
+ const modePattern = /<mode>(.*?)<\/mode>/i;
148
+ const modeMatch = modePattern.exec(innerContent);
149
+
150
+ const outputPattern = /<output>(.*?)<\/output>/i;
151
+ const outputMatch = outputPattern.exec(innerContent);
152
+
153
+ const ignoreFilePattern = /<ignore-file>(.*?)<\/ignore-file>/i;
154
+ const ignoreFileMatch = ignoreFilePattern.exec(innerContent);
155
+
156
+ // Content takes precedence over attributes
157
+ const extractedPath = (pathMatch ? pathMatch[1].trim() : null) || (pathAttr ? pathAttr[1] : '.');
158
+ const extractedMode = (modeMatch ? modeMatch[1].trim() : null) || (modeAttr ? modeAttr[1] : ANALYZER_CONFIG.DEFAULT_MODE);
159
+ const extractedOutput = (outputMatch ? outputMatch[1].trim() : null) || (outputAttr ? outputAttr[1] : ANALYZER_CONFIG.DEFAULT_OUTPUT);
160
+ const extractedIgnoreFile = (ignoreFileMatch ? ignoreFileMatch[1].trim() : null) || (ignoreFileAttr ? ignoreFileAttr[1] : ANALYZER_CONFIG.DEFAULT_IGNORE_FILE);
161
+
162
+ return {
163
+ path: extractedPath,
164
+ mode: extractedMode,
165
+ output: extractedOutput,
166
+ ignoreFile: extractedIgnoreFile
167
+ };
168
+ }
169
+
170
+ // Try legacy format with TagParser
171
+ try {
172
+ const parsed = TagParser.parseTags(content, 'analyze');
173
+
174
+ if (parsed && parsed.length > 0) {
175
+ const analyzeCommand = parsed[0];
176
+
177
+ return {
178
+ path: analyzeCommand.attributes.path || '.',
179
+ mode: analyzeCommand.attributes.mode || ANALYZER_CONFIG.DEFAULT_MODE,
180
+ output: analyzeCommand.attributes.output || ANALYZER_CONFIG.DEFAULT_OUTPUT,
181
+ ignoreFile: analyzeCommand.attributes['ignore-file'] || ANALYZER_CONFIG.DEFAULT_IGNORE_FILE
182
+ };
183
+ }
184
+ } catch {
185
+ // Fall through to error
186
+ }
187
+
188
+ throw new Error('Invalid import-analyzer format. Use <import-analyzer> tags or JSON format.');
189
+ }
190
+
191
+ throw new Error('Invalid parameter format. Expected string (XML) or object (JSON).');
192
+ }
193
+
194
+ /**
195
+ * Validate parameters
196
+ * @param {Object} params - Parameters to validate
197
+ * @throws {Error} If validation fails
198
+ * @private
199
+ */
200
+ _validateParameters(params) {
201
+ if (!params || typeof params !== 'object') {
202
+ throw new Error('Parameters must be an object');
203
+ }
204
+
205
+ if (params.path && typeof params.path !== 'string') {
206
+ throw new Error('path must be a string');
207
+ }
208
+
209
+ if (params.mode && !ANALYZER_CONFIG.VALID_MODES.includes(params.mode)) {
210
+ throw new Error(`Invalid mode: ${params.mode}. Must be one of: ${ANALYZER_CONFIG.VALID_MODES.join(', ')}`);
211
+ }
212
+
213
+ if (params.output && !ANALYZER_CONFIG.VALID_OUTPUTS.includes(params.output)) {
214
+ throw new Error(`Invalid output: ${params.output}. Must be one of: ${ANALYZER_CONFIG.VALID_OUTPUTS.join(', ')}`);
215
+ }
216
+
217
+ if (params.ignoreFile && typeof params.ignoreFile !== 'string') {
218
+ throw new Error('ignoreFile must be a string');
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Validate and resolve file path
224
+ * @param {string} targetPath - Target path from parameters
225
+ * @param {Object} context - Execution context
226
+ * @returns {string} Resolved absolute path
227
+ * @throws {Error} If path is invalid or inaccessible
228
+ * @private
229
+ */
230
+ _resolveAndValidatePath(targetPath, context) {
231
+ const { projectDir, directoryAccess } = context;
232
+
233
+ // Determine working directory
234
+ let workingDirectory = projectDir || process.cwd();
235
+
236
+ if (directoryAccess && directoryAccess.workingDirectory) {
237
+ workingDirectory = directoryAccess.workingDirectory;
238
+ }
239
+
240
+ // Resolve the target path
241
+ const resolvedPath = path.isAbsolute(targetPath)
242
+ ? path.normalize(targetPath)
243
+ : path.normalize(path.join(workingDirectory, targetPath));
244
+
245
+ // Security: Check for path traversal
246
+ const realWorkingDir = path.normalize(workingDirectory);
247
+ if (!resolvedPath.startsWith(realWorkingDir)) {
248
+ throw new Error(`Path traversal detected: ${targetPath} resolves outside working directory`);
249
+ }
250
+
251
+ return resolvedPath;
252
+ }
253
+
254
+ /**
255
+ * Execute tool with parsed parameters
256
+ * @param {Object} params - Parsed parameters
257
+ * @param {Object} context - Execution context
258
+ * @returns {Promise<Object>} Execution result
259
+ */
260
+ async execute(params, context = {}) {
261
+ try {
262
+ // Validate parameters
263
+ this._validateParameters(params);
264
+
265
+ const { path: targetPath, mode, output, ignoreFile } = params;
266
+ const { agentId } = context;
267
+
268
+ // Resolve and validate path
269
+ const resolvedPath = this._resolveAndValidatePath(targetPath, context);
270
+
271
+ this.logger?.info('Import analyzer executing', {
272
+ mode,
273
+ resolvedPath,
274
+ output,
275
+ agentId
276
+ });
277
+
278
+ const outputLines = [];
279
+ outputLines.push(`🔍 Analyzing imports in: ${resolvedPath}`);
280
+ outputLines.push(`Mode: ${mode}`);
281
+ outputLines.push(`Output: ${output}\n`);
282
+
283
+ // Check if path exists
284
+ try {
285
+ await fs.access(resolvedPath);
286
+ } catch {
287
+ return {
288
+ success: false,
289
+ error: `Path does not exist: ${resolvedPath}`,
290
+ output: outputLines.join('\n')
291
+ };
292
+ }
293
+
294
+ // Run analysis
295
+ const analyzer = new ImportExportAnalyzer(resolvedPath, ignoreFile, this.logger);
296
+ const results = await analyzer.analyze(mode);
297
+
298
+ // Format output based on requested format
299
+ let formattedOutput;
300
+ switch (output) {
301
+ case 'json':
302
+ // For JSON output, don't include header lines
303
+ formattedOutput = JSON.stringify(results, null, 2);
304
+ break;
305
+ case 'detailed':
306
+ formattedOutput = this._formatDetailedOutput(results);
307
+ outputLines.push(formattedOutput);
308
+ break;
309
+ case 'summary':
310
+ default:
311
+ formattedOutput = this._formatSummaryOutput(results);
312
+ outputLines.push(formattedOutput);
313
+ break;
314
+ }
315
+
316
+ return {
317
+ success: true,
318
+ mode,
319
+ message: 'Import analysis completed',
320
+ statistics: {
321
+ totalFiles: results.summary.totalFiles,
322
+ totalImports: results.summary.totalImports,
323
+ totalExports: results.summary.totalExports,
324
+ issuesFound: results.fileNotFoundImports.length + Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0)
325
+ },
326
+ output: output === 'json' ? formattedOutput : outputLines.join('\n'),
327
+ results
328
+ };
329
+
330
+ } catch (error) {
331
+ this.logger?.error('Import analyzer error:', error);
332
+
333
+ return {
334
+ success: false,
335
+ error: error.message,
336
+ output: error.message
337
+ };
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Format summary output
343
+ * @param {Object} results - Analysis results
344
+ * @returns {string} Formatted output
345
+ * @private
346
+ */
347
+ _formatSummaryOutput(results) {
348
+ const lines = [];
349
+
350
+ lines.push('📊 Import/Export Analysis Summary');
351
+ lines.push('================================\n');
352
+
353
+ lines.push(`📁 Files analyzed: ${results.summary.totalFiles}`);
354
+ lines.push(`📥 Total imports: ${results.summary.totalImports}`);
355
+ lines.push(`📤 Total exports: ${results.summary.totalExports}\n`);
356
+
357
+ // Critical issues
358
+ const missingFilesCount = results.fileNotFoundImports.length;
359
+ const missingExportsCount = Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0);
360
+ const circularDepsCount = results.circularDependencies ? results.circularDependencies.length : 0;
361
+
362
+ if (missingFilesCount > 0) {
363
+ lines.push(`❌ Missing files: ${missingFilesCount} imports pointing to non-existent files`);
364
+ }
365
+
366
+ if (missingExportsCount > 0) {
367
+ lines.push(`⚠️ Missing exports: ${missingExportsCount} symbols not exported from their sources`);
368
+ }
369
+
370
+ if (circularDepsCount > 0) {
371
+ lines.push(`🔄 Circular dependencies: ${circularDepsCount} circular dependency chains detected`);
372
+ }
373
+
374
+ if (missingFilesCount === 0 && missingExportsCount === 0 && circularDepsCount === 0) {
375
+ lines.push('✅ No import/export issues detected!');
376
+ } else {
377
+ lines.push('\n🔍 Top Issues to Fix:');
378
+
379
+ // Show top 5 files with issues
380
+ if (Object.keys(results.missingFiles || {}).length > 0) {
381
+ lines.push('\n Missing Files:');
382
+ Object.entries(results.missingFiles).slice(0, 3).forEach(([file, issues]) => {
383
+ lines.push(` • ${file} has ${issues.length} broken import(s)`);
384
+ });
385
+ }
386
+
387
+ if (Object.keys(results.missingExports).length > 0) {
388
+ lines.push('\n Missing Exports:');
389
+ Object.entries(results.missingExports).slice(0, 3).forEach(([file, issues]) => {
390
+ lines.push(` • ${file} imports ${issues.length} non-existent symbol(s)`);
391
+ });
392
+ }
393
+
394
+ lines.push('\n💡 Run with output="detailed" for complete analysis and fix suggestions');
395
+ }
396
+
397
+ return lines.join('\n');
398
+ }
399
+
400
+ /**
401
+ * Format detailed output
402
+ * @param {Object} results - Analysis results
403
+ * @returns {string} Formatted output
404
+ * @private
405
+ */
406
+ _formatDetailedOutput(results) {
407
+ const lines = [];
408
+
409
+ lines.push('📊 Detailed Import/Export Analysis Report');
410
+ lines.push('=========================================\n');
411
+
412
+ lines.push('📈 Statistics:');
413
+ lines.push(` • Files analyzed: ${results.summary.totalFiles}`);
414
+ lines.push(` • Total imports: ${results.summary.totalImports}`);
415
+ lines.push(` • Total exports: ${results.summary.totalExports}\n`);
416
+
417
+ // Missing files section
418
+ if (results.fileNotFoundImports.length > 0) {
419
+ lines.push('❌ MISSING FILES');
420
+ lines.push('─────────────────');
421
+
422
+ const fileGroups = {};
423
+ results.fileNotFoundImports.forEach(item => {
424
+ if (!fileGroups[item.importingFile]) {
425
+ fileGroups[item.importingFile] = [];
426
+ }
427
+ fileGroups[item.importingFile].push(item);
428
+ });
429
+
430
+ Object.entries(fileGroups).forEach(([file, imports]) => {
431
+ lines.push(`\n📄 ${file}:`);
432
+ imports.forEach(imp => {
433
+ const importType = imp.isDefault ? 'default' : imp.isNamespace ? 'namespace' : 'named';
434
+ lines.push(` ⚠️ Cannot find file: ${imp.importedFromFile}`);
435
+ lines.push(` Trying to import: ${imp.importedSymbol} (${importType})`);
436
+ lines.push(` 💡 Fix: Check if file exists or correct the import path`);
437
+ });
438
+ });
439
+ }
440
+
441
+ // Missing exports section
442
+ if (Object.keys(results.missingExports).length > 0) {
443
+ lines.push('\n⚠️ MISSING EXPORTS');
444
+ lines.push('──────────────────');
445
+
446
+ Object.entries(results.missingExports).forEach(([file, issues]) => {
447
+ lines.push(`\n📄 ${file}:`);
448
+ issues.forEach(issue => {
449
+ const importType = issue.isDefault ? 'default' : issue.isNamespace ? 'namespace' : 'named';
450
+ lines.push(` ❌ Symbol not exported: "${issue.importedSymbol}" (${importType})`);
451
+ lines.push(` From file: ${issue.importedFromFile}`);
452
+
453
+ if (issue.availableExports.length > 0) {
454
+ lines.push(` 📤 Available exports: ${issue.availableExports.join(', ')}`);
455
+
456
+ // Suggest potential fixes
457
+ if (issue.isDefault && issue.availableExports.includes('default')) {
458
+ lines.push(` 💡 Fix: Default export exists, check import syntax`);
459
+ } else if (issue.isDefault && !issue.availableExports.includes('default')) {
460
+ lines.push(` 💡 Fix: No default export. Use named import: { ${issue.availableExports[0] || 'symbolName'} }`);
461
+ } else {
462
+ // Check for similar names
463
+ const similar = issue.availableExports.find(exp =>
464
+ exp.toLowerCase() === issue.importedSymbol.toLowerCase()
465
+ );
466
+ if (similar) {
467
+ lines.push(` 💡 Fix: Did you mean "${similar}"? (case mismatch)`);
468
+ } else {
469
+ lines.push(` 💡 Fix: Add export for "${issue.importedSymbol}" or use one of the available exports`);
470
+ }
471
+ }
472
+ } else {
473
+ lines.push(` 📤 No exports found in target file`);
474
+ lines.push(` 💡 Fix: Add exports to ${issue.importedFromFile} or check if it's the correct file`);
475
+ }
476
+ });
477
+ });
478
+ }
479
+
480
+ // Circular dependencies
481
+ if (results.circularDependencies && results.circularDependencies.length > 0) {
482
+ lines.push('\n🔄 CIRCULAR DEPENDENCIES');
483
+ lines.push('────────────────────────');
484
+
485
+ results.circularDependencies.forEach((cycle, index) => {
486
+ lines.push(`\n Cycle ${index + 1}:`);
487
+ cycle.forEach((file, i) => {
488
+ if (i < cycle.length - 1) {
489
+ lines.push(` ${file} → ${cycle[i + 1]}`);
490
+ }
491
+ });
492
+ lines.push(` 💡 Fix: Refactor to break the circular dependency`);
493
+ });
494
+ }
495
+
496
+ // Unused exports (if available)
497
+ if (results.unusedExports && Object.keys(results.unusedExports).length > 0) {
498
+ lines.push('\n🗑️ POTENTIALLY UNUSED EXPORTS');
499
+ lines.push('──────────────────────────────');
500
+
501
+ Object.entries(results.unusedExports).slice(0, 10).forEach(([file, exports]) => {
502
+ lines.push(`\n📄 ${file}:`);
503
+ lines.push(` Unused: ${exports.join(', ')}`);
504
+ });
505
+
506
+ lines.push('\n 💡 Note: These exports are not imported within this project');
507
+ lines.push(' They might be used by external packages or could be removed');
508
+ }
509
+
510
+ // Summary and recommendations
511
+ lines.push('\n📋 RECOMMENDATIONS');
512
+ lines.push('──────────────────');
513
+
514
+ const totalIssues = results.fileNotFoundImports.length +
515
+ Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0);
516
+
517
+ if (totalIssues === 0) {
518
+ lines.push('✅ Your import/export structure looks good!');
519
+ } else {
520
+ lines.push(`Found ${totalIssues} issue(s) that need attention:`);
521
+
522
+ if (results.fileNotFoundImports.length > 0) {
523
+ lines.push(` 1. Fix ${results.fileNotFoundImports.length} missing file reference(s)`);
524
+ }
525
+
526
+ if (Object.keys(results.missingExports).length > 0) {
527
+ lines.push(` 2. Resolve ${Object.values(results.missingExports).reduce((sum, arr) => sum + arr.length, 0)} missing export(s)`);
528
+ }
529
+
530
+ if (results.circularDependencies && results.circularDependencies.length > 0) {
531
+ lines.push(` 3. Refactor ${results.circularDependencies.length} circular dependency chain(s)`);
532
+ }
533
+ }
534
+
535
+ return lines.join('\n');
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Internal analyzer class
541
+ */
542
+ class ImportExportAnalyzer {
543
+ constructor(rootDir, ignoreFile = '.gitignore', logger = null) {
544
+ this.rootDir = path.resolve(rootDir);
545
+ this.ignoreFile = ignoreFile;
546
+ this.logger = logger;
547
+ this.ignorePatterns = [...ANALYZER_CONFIG.DEFAULT_IGNORE_PATTERNS];
548
+ this.imports = [];
549
+ this.exports = new Map();
550
+ this.dependencies = new Map(); // For circular dependency detection
551
+ }
552
+
553
+ async loadIgnoreFile() {
554
+ try {
555
+ const ignoreFilePath = path.join(this.rootDir, this.ignoreFile);
556
+ const content = await fs.readFile(ignoreFilePath, 'utf-8');
557
+ const patterns = content
558
+ .split('\n')
559
+ .map(line => line.trim())
560
+ .filter(line => line && !line.startsWith('#'));
561
+
562
+ this.ignorePatterns.push(...patterns);
563
+ this.logger?.debug('Loaded ignore patterns', { count: patterns.length });
564
+ } catch {
565
+ // Ignore file doesn't exist, use defaults
566
+ this.logger?.debug('No ignore file found, using defaults');
567
+ }
568
+ }
569
+
570
+ shouldIgnoreFile(filePath) {
571
+ const relativePath = path.relative(this.rootDir, filePath);
572
+ return this.ignorePatterns.some(pattern => {
573
+ if (pattern.includes('*')) {
574
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
575
+ return regex.test(relativePath) || regex.test(path.basename(filePath));
576
+ }
577
+ return relativePath.includes(pattern) || path.basename(filePath) === pattern;
578
+ });
579
+ }
580
+
581
+ async getAllFiles(dir) {
582
+ const files = [];
583
+
584
+ const traverse = async (currentDir) => {
585
+ try {
586
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
587
+
588
+ for (const entry of entries) {
589
+ const fullPath = path.join(currentDir, entry.name);
590
+
591
+ if (this.shouldIgnoreFile(fullPath)) {
592
+ continue;
593
+ }
594
+
595
+ if (entry.isDirectory()) {
596
+ await traverse(fullPath);
597
+ } else if (entry.isFile()) {
598
+ const ext = path.extname(entry.name);
599
+ if (ANALYZER_CONFIG.SUPPORTED_EXTENSIONS.includes(ext)) {
600
+ files.push(fullPath);
601
+ }
602
+ }
603
+ }
604
+ } catch (error) {
605
+ // Skip directories we can't read
606
+ this.logger?.warn('Cannot read directory', { dir: currentDir, error: error.message });
607
+ }
608
+ };
609
+
610
+ await traverse(dir);
611
+
612
+ // Safety check
613
+ if (files.length > ANALYZER_CONFIG.MAX_FILES) {
614
+ this.logger?.warn(`File count exceeds limit: ${files.length} > ${ANALYZER_CONFIG.MAX_FILES}`);
615
+ throw new Error(`Too many files to analyze: ${files.length} (max: ${ANALYZER_CONFIG.MAX_FILES})`);
616
+ }
617
+
618
+ return files;
619
+ }
620
+
621
+ async parseImports(content, filePath) {
622
+ const imports = [];
623
+ const lines = content.split('\n');
624
+ const relativePath = this.getRelativePath(filePath);
625
+
626
+ // Track dependencies for circular detection
627
+ if (!this.dependencies.has(relativePath)) {
628
+ this.dependencies.set(relativePath, new Set());
629
+ }
630
+
631
+ for (let i = 0; i < lines.length; i++) {
632
+ const line = lines[i].trim();
633
+
634
+ if (line.startsWith('//') || line.startsWith('/*')) continue;
635
+
636
+ // Build multi-line statements
637
+ let fullStatement = line;
638
+ let j = i;
639
+ while (!fullStatement.includes(';') && !fullStatement.match(/from\s+['"`][^'"`]+['"`]/) && j < lines.length - 1) {
640
+ j++;
641
+ const nextLine = lines[j].trim();
642
+ // Skip commented lines when building multi-line statements
643
+ if (nextLine.startsWith('//') || nextLine.startsWith('/*')) {
644
+ continue;
645
+ }
646
+ fullStatement += ' ' + nextLine;
647
+ }
648
+
649
+ // ES6 imports
650
+ const importRegex = /import\s+(?:(?:\{([^}]+)\})|(?:([^,\s]+)(?:\s*,\s*\{([^}]+)\})?)|(?:\*\s+as\s+([^,\s]+)))\s+from\s+['"`]([^'"`]+)['"`]/g;
651
+ let match;
652
+
653
+ while ((match = importRegex.exec(fullStatement)) !== null) {
654
+ const [, namedImports, defaultImport, additionalNamed, namespaceImport, source] = match;
655
+ const resolvedSource = await this.resolveImportPath(source, filePath);
656
+
657
+ // Track dependency
658
+ if (!resolvedSource.isExternal && resolvedSource.exists) {
659
+ this.dependencies.get(relativePath).add(resolvedSource.path);
660
+ }
661
+
662
+ if (defaultImport) {
663
+ imports.push({
664
+ importingFile: relativePath,
665
+ importedSymbol: defaultImport.trim(),
666
+ importedFromFile: resolvedSource.path,
667
+ fileExists: resolvedSource.exists,
668
+ isExternal: resolvedSource.isExternal || false,
669
+ isDefault: true
670
+ });
671
+ }
672
+
673
+ if (namespaceImport) {
674
+ imports.push({
675
+ importingFile: relativePath,
676
+ importedSymbol: namespaceImport.trim(),
677
+ importedFromFile: resolvedSource.path,
678
+ fileExists: resolvedSource.exists,
679
+ isExternal: resolvedSource.isExternal || false,
680
+ isNamespace: true
681
+ });
682
+ }
683
+
684
+ const allNamedImports = [namedImports, additionalNamed].filter(Boolean).join(',');
685
+ if (allNamedImports) {
686
+ const symbols = allNamedImports.split(',').map(s => {
687
+ const parts = s.trim().split(/\s+as\s+/);
688
+ return parts[0].trim();
689
+ });
690
+
691
+ symbols.forEach(symbol => {
692
+ if (symbol) {
693
+ imports.push({
694
+ importingFile: relativePath,
695
+ importedSymbol: symbol,
696
+ importedFromFile: resolvedSource.path,
697
+ fileExists: resolvedSource.exists,
698
+ isExternal: resolvedSource.isExternal || false,
699
+ isDefault: false
700
+ });
701
+ }
702
+ });
703
+ }
704
+ }
705
+
706
+ // CommonJS requires
707
+ const requireRegex = /(?:const|let|var)\s+(?:\{([^}]+)\}|([^=\s]+))\s*=\s*require\(['"`]([^'"`]+)['"`]\)/g;
708
+ while ((match = requireRegex.exec(fullStatement)) !== null) {
709
+ const [, destructured, variable, source] = match;
710
+ const resolvedSource = await this.resolveImportPath(source, filePath);
711
+
712
+ // Track dependency
713
+ if (!resolvedSource.isExternal && resolvedSource.exists) {
714
+ this.dependencies.get(relativePath).add(resolvedSource.path);
715
+ }
716
+
717
+ if (destructured) {
718
+ const symbols = destructured.split(',').map(s => s.trim().split(':')[0].trim());
719
+ symbols.forEach(symbol => {
720
+ if (symbol) {
721
+ imports.push({
722
+ importingFile: relativePath,
723
+ importedSymbol: symbol,
724
+ importedFromFile: resolvedSource.path,
725
+ fileExists: resolvedSource.exists,
726
+ isExternal: resolvedSource.isExternal || false,
727
+ isDefault: false
728
+ });
729
+ }
730
+ });
731
+ } else if (variable) {
732
+ imports.push({
733
+ importingFile: relativePath,
734
+ importedSymbol: variable.trim(),
735
+ importedFromFile: resolvedSource.path,
736
+ fileExists: resolvedSource.exists,
737
+ isExternal: resolvedSource.isExternal || false,
738
+ isDefault: true
739
+ });
740
+ }
741
+ }
742
+
743
+ // Skip lines that were already processed as part of multi-line statement
744
+ i = j;
745
+ }
746
+
747
+ return imports;
748
+ }
749
+
750
+ parseExports(content) {
751
+ const exports = new Set();
752
+ const lines = content.split('\n');
753
+
754
+ for (const line of lines) {
755
+ const trimmedLine = line.trim();
756
+
757
+ if (trimmedLine.startsWith('//') || trimmedLine.startsWith('/*')) continue;
758
+
759
+ // Export default
760
+ if (/export\s+default\s+/.test(trimmedLine)) {
761
+ exports.add('default');
762
+ }
763
+
764
+ // Named exports
765
+ const namedExportMatch = trimmedLine.match(/export\s+\{([^}]+)\}/);
766
+ if (namedExportMatch) {
767
+ const symbols = namedExportMatch[1].split(',').map(s => {
768
+ const parts = s.trim().split(/\s+as\s+/);
769
+ return parts[parts.length - 1].trim();
770
+ });
771
+ symbols.forEach(symbol => exports.add(symbol));
772
+ }
773
+
774
+ // Direct exports
775
+ const directExportMatch = trimmedLine.match(/export\s+(?:const|let|var|function|class|async\s+function)\s+([^=\s(]+)/);
776
+ if (directExportMatch) {
777
+ exports.add(directExportMatch[1]);
778
+ }
779
+
780
+ // Export from
781
+ const exportFromMatch = trimmedLine.match(/export\s+\{([^}]+)\}\s+from/);
782
+ if (exportFromMatch) {
783
+ const symbols = exportFromMatch[1].split(',').map(s => {
784
+ const parts = s.trim().split(/\s+as\s+/);
785
+ return parts[parts.length - 1].trim();
786
+ });
787
+ symbols.forEach(symbol => exports.add(symbol));
788
+ }
789
+
790
+ // Export all
791
+ if (/export\s+\*\s+from/.test(trimmedLine)) {
792
+ exports.add('*');
793
+ }
794
+
795
+ // CommonJS exports
796
+ const moduleExportsMatch = trimmedLine.match(/module\.exports\s*=\s*\{([^}]+)\}/);
797
+ if (moduleExportsMatch) {
798
+ const symbols = moduleExportsMatch[1].split(',').map(s => {
799
+ const parts = s.trim().split(':');
800
+ return parts[0].trim();
801
+ });
802
+ symbols.forEach(symbol => exports.add(symbol));
803
+ }
804
+
805
+ if (/module\.exports\s*=\s*[^{]/.test(trimmedLine)) {
806
+ exports.add('default');
807
+ }
808
+
809
+ const moduleExportsPropMatch = trimmedLine.match(/module\.exports\.([^=\s]+)\s*=/);
810
+ if (moduleExportsPropMatch) {
811
+ exports.add(moduleExportsPropMatch[1]);
812
+ }
813
+
814
+ const exportsPropMatch = trimmedLine.match(/exports\.([^=\s]+)\s*=/);
815
+ if (exportsPropMatch) {
816
+ exports.add(exportsPropMatch[1]);
817
+ }
818
+ }
819
+
820
+ return exports;
821
+ }
822
+
823
+ async resolveImportPath(importPath, currentFile) {
824
+ if (importPath.startsWith('.')) {
825
+ const currentDir = path.dirname(currentFile);
826
+ const resolved = path.resolve(currentDir, importPath);
827
+
828
+ // Try with extension first if provided
829
+ if (path.extname(importPath)) {
830
+ try {
831
+ const stat = await fs.stat(resolved);
832
+ if (stat.isFile()) {
833
+ return {
834
+ path: this.getRelativePath(resolved),
835
+ exists: true
836
+ };
837
+ }
838
+ } catch {
839
+ // File doesn't exist
840
+ }
841
+ }
842
+
843
+ // Try different extensions
844
+ const extensions = ['', '.js', '.mjs', '.ts', '.jsx', '.tsx', '/index.js', '/index.ts', '/index.jsx', '/index.tsx'];
845
+ for (const ext of extensions) {
846
+ const withExt = resolved + ext;
847
+ try {
848
+ const stat = await fs.stat(withExt);
849
+ if (stat.isFile()) {
850
+ return {
851
+ path: this.getRelativePath(withExt),
852
+ exists: true
853
+ };
854
+ }
855
+ } catch {
856
+ // Try next
857
+ }
858
+ }
859
+
860
+ return {
861
+ path: this.getRelativePath(resolved),
862
+ exists: false
863
+ };
864
+ }
865
+
866
+ // Node modules or absolute imports
867
+ return {
868
+ path: importPath,
869
+ exists: true,
870
+ isExternal: true
871
+ };
872
+ }
873
+
874
+ getRelativePath(filePath) {
875
+ return path.relative(this.rootDir, filePath).replace(/\\/g, '/');
876
+ }
877
+
878
+ findCircularDependencies() {
879
+ const cycles = [];
880
+ const visited = new Set();
881
+ const recursionStack = new Set();
882
+
883
+ const dfs = (node, path = []) => {
884
+ if (recursionStack.has(node)) {
885
+ const cycleStart = path.indexOf(node);
886
+ if (cycleStart !== -1) {
887
+ cycles.push([...path.slice(cycleStart), node]);
888
+ }
889
+ return;
890
+ }
891
+
892
+ if (visited.has(node)) {
893
+ return;
894
+ }
895
+
896
+ visited.add(node);
897
+ recursionStack.add(node);
898
+ path.push(node);
899
+
900
+ const deps = this.dependencies.get(node) || new Set();
901
+ for (const dep of deps) {
902
+ dfs(dep, [...path]);
903
+ }
904
+
905
+ recursionStack.delete(node);
906
+ };
907
+
908
+ for (const node of this.dependencies.keys()) {
909
+ if (!visited.has(node)) {
910
+ dfs(node);
911
+ }
912
+ }
913
+
914
+ return cycles;
915
+ }
916
+
917
+ findUnusedExports() {
918
+ const usedExports = new Map();
919
+
920
+ // Track which exports are actually imported
921
+ for (const imp of this.imports) {
922
+ if (!imp.isExternal) {
923
+ if (!usedExports.has(imp.importedFromFile)) {
924
+ usedExports.set(imp.importedFromFile, new Set());
925
+ }
926
+ usedExports.get(imp.importedFromFile).add(imp.importedSymbol);
927
+ }
928
+ }
929
+
930
+ // Find exports that are never imported
931
+ const unusedExports = {};
932
+ for (const [file, exports] of this.exports.entries()) {
933
+ const used = usedExports.get(file) || new Set();
934
+ const unused = Array.from(exports).filter(exp => !used.has(exp) && exp !== '*');
935
+
936
+ if (unused.length > 0) {
937
+ unusedExports[file] = unused;
938
+ }
939
+ }
940
+
941
+ return unusedExports;
942
+ }
943
+
944
+ async analyze(mode = 'full') {
945
+ this.logger?.info('Starting import analysis', { mode, rootDir: this.rootDir });
946
+
947
+ await this.loadIgnoreFile();
948
+
949
+ const files = await this.getAllFiles(this.rootDir);
950
+ this.logger?.info('Found files', { count: files.length });
951
+
952
+ // Parse all files
953
+ for (const file of files) {
954
+ try {
955
+ const content = await fs.readFile(file, 'utf-8');
956
+ const relativeFile = this.getRelativePath(file);
957
+
958
+ const fileImports = await this.parseImports(content, file);
959
+ this.imports.push(...fileImports);
960
+
961
+ const fileExports = this.parseExports(content);
962
+ this.exports.set(relativeFile, fileExports);
963
+ } catch (error) {
964
+ // Skip files with errors
965
+ this.logger?.warn('Error parsing file', { file, error: error.message });
966
+ }
967
+ }
968
+
969
+ this.logger?.info('Parsed all files', { imports: this.imports.length, exports: this.exports.size });
970
+
971
+ // Analyze issues
972
+ const results = {
973
+ summary: {
974
+ totalFiles: files.length,
975
+ totalImports: this.imports.length,
976
+ totalExports: Array.from(this.exports.values()).reduce((sum, exports) => sum + exports.size, 0)
977
+ },
978
+ missingExports: {},
979
+ missingFiles: {},
980
+ fileNotFoundImports: []
981
+ };
982
+
983
+ // Check imports
984
+ for (const importEntry of this.imports) {
985
+ const { importingFile, importedSymbol, importedFromFile, isDefault, isNamespace, fileExists, isExternal } = importEntry;
986
+
987
+ if (!fileExists && !isExternal) {
988
+ results.fileNotFoundImports.push({
989
+ importingFile,
990
+ importedSymbol,
991
+ importedFromFile,
992
+ isDefault: isDefault || false,
993
+ isNamespace: isNamespace || false
994
+ });
995
+
996
+ if (!results.missingFiles[importingFile]) {
997
+ results.missingFiles[importingFile] = [];
998
+ }
999
+
1000
+ results.missingFiles[importingFile].push({
1001
+ missingFile: importedFromFile,
1002
+ importedSymbol,
1003
+ isDefault: isDefault || false,
1004
+ isNamespace: isNamespace || false
1005
+ });
1006
+
1007
+ continue;
1008
+ }
1009
+
1010
+ if (isExternal) continue;
1011
+
1012
+ const exportingFileExports = this.exports.get(importedFromFile);
1013
+ let exists = false;
1014
+
1015
+ if (exportingFileExports) {
1016
+ if (isNamespace) {
1017
+ exists = exportingFileExports.size > 0;
1018
+ } else if (isDefault) {
1019
+ exists = exportingFileExports.has('default');
1020
+ } else {
1021
+ exists = exportingFileExports.has(importedSymbol) || exportingFileExports.has('*');
1022
+ }
1023
+ }
1024
+
1025
+ if (!exists) {
1026
+ if (!results.missingExports[importingFile]) {
1027
+ results.missingExports[importingFile] = [];
1028
+ }
1029
+
1030
+ results.missingExports[importingFile].push({
1031
+ importedSymbol,
1032
+ importedFromFile,
1033
+ availableExports: exportingFileExports ? Array.from(exportingFileExports) : [],
1034
+ isDefault: isDefault || false,
1035
+ isNamespace: isNamespace || false
1036
+ });
1037
+ }
1038
+ }
1039
+
1040
+ // Additional analysis for full mode
1041
+ if (mode === 'full' || mode === 'fix') {
1042
+ this.logger?.info('Running full analysis');
1043
+ results.circularDependencies = this.findCircularDependencies();
1044
+ results.unusedExports = this.findUnusedExports();
1045
+ }
1046
+
1047
+ this.logger?.info('Analysis complete', {
1048
+ missingFiles: results.fileNotFoundImports.length,
1049
+ missingExports: Object.keys(results.missingExports).length
1050
+ });
1051
+
1052
+ return results;
1053
+ }
1054
+ }
1055
+
1056
+ export default ImportAnalyzerTool;