@sun-asterisk/sungen 1.0.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/LICENSE +21 -0
  2. package/README.md +490 -0
  3. package/bin/sungen.js +12 -0
  4. package/dist/cli/commands/auto-tag-command.d.ts +8 -0
  5. package/dist/cli/commands/auto-tag-command.d.ts.map +1 -0
  6. package/dist/cli/commands/auto-tag-command.js +104 -0
  7. package/dist/cli/commands/auto-tag-command.js.map +1 -0
  8. package/dist/cli/index.d.ts +7 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +196 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/config/ai-providers.yaml +56 -0
  13. package/dist/config/config-loader.d.ts +51 -0
  14. package/dist/config/config-loader.d.ts.map +1 -0
  15. package/dist/config/config-loader.js +216 -0
  16. package/dist/config/config-loader.js.map +1 -0
  17. package/dist/config/config-schema.d.ts +121 -0
  18. package/dist/config/config-schema.d.ts.map +1 -0
  19. package/dist/config/config-schema.js +7 -0
  20. package/dist/config/config-schema.js.map +1 -0
  21. package/dist/config/default.config.yaml +101 -0
  22. package/dist/config/framework.config.yaml +52 -0
  23. package/dist/config/routes.yaml +31 -0
  24. package/dist/core/selector-base/annotation-handler.d.ts +45 -0
  25. package/dist/core/selector-base/annotation-handler.d.ts.map +1 -0
  26. package/dist/core/selector-base/annotation-handler.js +102 -0
  27. package/dist/core/selector-base/annotation-handler.js.map +1 -0
  28. package/dist/core/selector-base/base-generator.d.ts +49 -0
  29. package/dist/core/selector-base/base-generator.d.ts.map +1 -0
  30. package/dist/core/selector-base/base-generator.js +214 -0
  31. package/dist/core/selector-base/base-generator.js.map +1 -0
  32. package/dist/core/selector-base/gherkin-parser.d.ts +24 -0
  33. package/dist/core/selector-base/gherkin-parser.d.ts.map +1 -0
  34. package/dist/core/selector-base/gherkin-parser.js +42 -0
  35. package/dist/core/selector-base/gherkin-parser.js.map +1 -0
  36. package/dist/core/selector-mapper/priority-mapper.d.ts +74 -0
  37. package/dist/core/selector-mapper/priority-mapper.d.ts.map +1 -0
  38. package/dist/core/selector-mapper/priority-mapper.js +477 -0
  39. package/dist/core/selector-mapper/priority-mapper.js.map +1 -0
  40. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts +91 -0
  41. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts.map +1 -0
  42. package/dist/core/ui-scanner/heuristics/base-heuristic.js +175 -0
  43. package/dist/core/ui-scanner/heuristics/base-heuristic.js.map +1 -0
  44. package/dist/core/ui-scanner/react-scanner.d.ts +32 -0
  45. package/dist/core/ui-scanner/react-scanner.d.ts.map +1 -0
  46. package/dist/core/ui-scanner/react-scanner.js +163 -0
  47. package/dist/core/ui-scanner/react-scanner.js.map +1 -0
  48. package/dist/core/ui-scanner/scanner-interface.d.ts +94 -0
  49. package/dist/core/ui-scanner/scanner-interface.d.ts.map +1 -0
  50. package/dist/core/ui-scanner/scanner-interface.js +33 -0
  51. package/dist/core/ui-scanner/scanner-interface.js.map +1 -0
  52. package/dist/core/ui-scanner/strict-scanner.d.ts +81 -0
  53. package/dist/core/ui-scanner/strict-scanner.d.ts.map +1 -0
  54. package/dist/core/ui-scanner/strict-scanner.js +511 -0
  55. package/dist/core/ui-scanner/strict-scanner.js.map +1 -0
  56. package/dist/executor/playwright/playwright-generator.d.ts +33 -0
  57. package/dist/executor/playwright/playwright-generator.d.ts.map +1 -0
  58. package/dist/executor/playwright/playwright-generator.js +136 -0
  59. package/dist/executor/playwright/playwright-generator.js.map +1 -0
  60. package/dist/executor/test-generator.d.ts +63 -0
  61. package/dist/executor/test-generator.d.ts.map +1 -0
  62. package/dist/executor/test-generator.js +30 -0
  63. package/dist/executor/test-generator.js.map +1 -0
  64. package/dist/external/ai-provider.d.ts +60 -0
  65. package/dist/external/ai-provider.d.ts.map +1 -0
  66. package/dist/external/ai-provider.js +30 -0
  67. package/dist/external/ai-provider.js.map +1 -0
  68. package/dist/external/anthropic-provider.d.ts +29 -0
  69. package/dist/external/anthropic-provider.d.ts.map +1 -0
  70. package/dist/external/anthropic-provider.js +85 -0
  71. package/dist/external/anthropic-provider.js.map +1 -0
  72. package/dist/generators/cache/cache-manager.d.ts +66 -0
  73. package/dist/generators/cache/cache-manager.d.ts.map +1 -0
  74. package/dist/generators/cache/cache-manager.js +286 -0
  75. package/dist/generators/cache/cache-manager.js.map +1 -0
  76. package/dist/generators/cli.d.ts +7 -0
  77. package/dist/generators/cli.d.ts.map +1 -0
  78. package/dist/generators/cli.js +570 -0
  79. package/dist/generators/cli.js.map +1 -0
  80. package/dist/generators/dsl-writer/index.d.ts +33 -0
  81. package/dist/generators/dsl-writer/index.d.ts.map +1 -0
  82. package/dist/generators/dsl-writer/index.js +226 -0
  83. package/dist/generators/dsl-writer/index.js.map +1 -0
  84. package/dist/generators/gherkin-parser/index.d.ts +47 -0
  85. package/dist/generators/gherkin-parser/index.d.ts.map +1 -0
  86. package/dist/generators/gherkin-parser/index.js +149 -0
  87. package/dist/generators/gherkin-parser/index.js.map +1 -0
  88. package/dist/generators/gherkin-parser/selector-extractor.d.ts +37 -0
  89. package/dist/generators/gherkin-parser/selector-extractor.d.ts.map +1 -0
  90. package/dist/generators/gherkin-parser/selector-extractor.js +108 -0
  91. package/dist/generators/gherkin-parser/selector-extractor.js.map +1 -0
  92. package/dist/generators/scaffold-generator/index.d.ts +111 -0
  93. package/dist/generators/scaffold-generator/index.d.ts.map +1 -0
  94. package/dist/generators/scaffold-generator/index.js +408 -0
  95. package/dist/generators/scaffold-generator/index.js.map +1 -0
  96. package/dist/generators/selector-mapper/ai-mapper.d.ts +56 -0
  97. package/dist/generators/selector-mapper/ai-mapper.d.ts.map +1 -0
  98. package/dist/generators/selector-mapper/ai-mapper.js +457 -0
  99. package/dist/generators/selector-mapper/ai-mapper.js.map +1 -0
  100. package/dist/generators/selector-mapper/hybrid-mapper.d.ts +67 -0
  101. package/dist/generators/selector-mapper/hybrid-mapper.d.ts.map +1 -0
  102. package/dist/generators/selector-mapper/hybrid-mapper.js +349 -0
  103. package/dist/generators/selector-mapper/hybrid-mapper.js.map +1 -0
  104. package/dist/generators/selector-mapper/index.d.ts +8 -0
  105. package/dist/generators/selector-mapper/index.d.ts.map +1 -0
  106. package/dist/generators/selector-mapper/index.js +12 -0
  107. package/dist/generators/selector-mapper/index.js.map +1 -0
  108. package/dist/generators/selector-mapper/intelligent-mapper.d.ts +125 -0
  109. package/dist/generators/selector-mapper/intelligent-mapper.d.ts.map +1 -0
  110. package/dist/generators/selector-mapper/intelligent-mapper.js +391 -0
  111. package/dist/generators/selector-mapper/intelligent-mapper.js.map +1 -0
  112. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +49 -0
  113. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -0
  114. package/dist/generators/test-generator/adapters/adapter-interface.js +7 -0
  115. package/dist/generators/test-generator/adapters/adapter-interface.js.map +1 -0
  116. package/dist/generators/test-generator/adapters/adapter-registry.d.ts +29 -0
  117. package/dist/generators/test-generator/adapters/adapter-registry.d.ts.map +1 -0
  118. package/dist/generators/test-generator/adapters/adapter-registry.js +50 -0
  119. package/dist/generators/test-generator/adapters/adapter-registry.js.map +1 -0
  120. package/dist/generators/test-generator/adapters/index.d.ts +4 -0
  121. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -0
  122. package/dist/generators/test-generator/adapters/index.js +13 -0
  123. package/dist/generators/test-generator/adapters/index.js.map +1 -0
  124. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +23 -0
  125. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -0
  126. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +38 -0
  127. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -0
  128. package/dist/generators/test-generator/adapters/playwright/templates/before-each.hbs +8 -0
  129. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +5 -0
  130. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +8 -0
  131. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +1 -0
  132. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/clear-action.hbs +1 -0
  133. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-action.hbs +1 -0
  134. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/double-click-action.hbs +1 -0
  135. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -0
  136. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/hover-action.hbs +1 -0
  137. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/press-action.hbs +1 -0
  138. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -0
  139. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +1 -0
  140. package/dist/generators/test-generator/adapters/playwright/templates/steps/active-state-assertion.hbs +2 -0
  141. package/dist/generators/test-generator/adapters/playwright/templates/steps/ai-response-assertion-selector.hbs +5 -0
  142. package/dist/generators/test-generator/adapters/playwright/templates/steps/ai-response-assertion-simple.hbs +1 -0
  143. package/dist/generators/test-generator/adapters/playwright/templates/steps/application-running.hbs +1 -0
  144. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +1 -0
  145. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -0
  146. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +1 -0
  147. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +2 -0
  148. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +2 -0
  149. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +2 -0
  150. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +1 -0
  151. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -0
  152. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  153. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-visible-assertion.hbs +1 -0
  154. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +1 -0
  155. package/dist/generators/test-generator/adapters/playwright/templates/steps/check-action.hbs +1 -0
  156. package/dist/generators/test-generator/adapters/playwright/templates/steps/checkbox.hbs +2 -0
  157. package/dist/generators/test-generator/adapters/playwright/templates/steps/checked-assertion.hbs +1 -0
  158. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-action.hbs +1 -0
  159. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-auth.hbs +6 -0
  160. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-browser-state.hbs +6 -0
  161. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-database.hbs +4 -0
  162. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear.hbs +2 -0
  163. package/dist/generators/test-generator/adapters/playwright/templates/steps/click-action.hbs +1 -0
  164. package/dist/generators/test-generator/adapters/playwright/templates/steps/click.hbs +2 -0
  165. package/dist/generators/test-generator/adapters/playwright/templates/steps/contain-text-assertion.hbs +1 -0
  166. package/dist/generators/test-generator/adapters/playwright/templates/steps/contains-text-assertion.hbs +2 -0
  167. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-assertion.hbs +1 -0
  168. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-greater-than.hbs +3 -0
  169. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-less-than.hbs +3 -0
  170. package/dist/generators/test-generator/adapters/playwright/templates/steps/disabled-assertion.hbs +2 -0
  171. package/dist/generators/test-generator/adapters/playwright/templates/steps/displayed-containing-text.hbs +3 -0
  172. package/dist/generators/test-generator/adapters/playwright/templates/steps/displayed-with-text.hbs +3 -0
  173. package/dist/generators/test-generator/adapters/playwright/templates/steps/double-click-action.hbs +1 -0
  174. package/dist/generators/test-generator/adapters/playwright/templates/steps/empty-assertion-advanced.hbs +3 -0
  175. package/dist/generators/test-generator/adapters/playwright/templates/steps/empty-assertion.hbs +2 -0
  176. package/dist/generators/test-generator/adapters/playwright/templates/steps/enabled-assertion.hbs +2 -0
  177. package/dist/generators/test-generator/adapters/playwright/templates/steps/error-message-assertion.hbs +3 -0
  178. package/dist/generators/test-generator/adapters/playwright/templates/steps/fill-action.hbs +1 -0
  179. package/dist/generators/test-generator/adapters/playwright/templates/steps/fill.hbs +2 -0
  180. package/dist/generators/test-generator/adapters/playwright/templates/steps/focused-assertion.hbs +1 -0
  181. package/dist/generators/test-generator/adapters/playwright/templates/steps/generic-message-assertion.hbs +3 -0
  182. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-attribute.hbs +2 -0
  183. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-class.hbs +2 -0
  184. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-count.hbs +2 -0
  185. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-image-src.hbs +3 -0
  186. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-link.hbs +3 -0
  187. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-placeholder.hbs +3 -0
  188. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-value.hbs +2 -0
  189. package/dist/generators/test-generator/adapters/playwright/templates/steps/have-text-assertion.hbs +1 -0
  190. package/dist/generators/test-generator/adapters/playwright/templates/steps/hover-action.hbs +1 -0
  191. package/dist/generators/test-generator/adapters/playwright/templates/steps/html5-validation-check.hbs +4 -0
  192. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-checked.hbs +2 -0
  193. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-editable.hbs +2 -0
  194. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-focused.hbs +2 -0
  195. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-hidden.hbs +2 -0
  196. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-unchecked.hbs +2 -0
  197. package/dist/generators/test-generator/adapters/playwright/templates/steps/locator.hbs +1 -0
  198. package/dist/generators/test-generator/adapters/playwright/templates/steps/login.hbs +8 -0
  199. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-assertion-body.hbs +1 -0
  200. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-assertion-selector.hbs +2 -0
  201. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-count-assertion.hbs +3 -0
  202. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -0
  203. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +2 -0
  204. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -0
  205. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -0
  206. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation.hbs +1 -0
  207. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-checked-assertion.hbs +1 -0
  208. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-visible-assertion.hbs +1 -0
  209. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-visible.hbs +2 -0
  210. package/dist/generators/test-generator/adapters/playwright/templates/steps/notification-assertion.hbs +4 -0
  211. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -0
  212. package/dist/generators/test-generator/adapters/playwright/templates/steps/press-action.hbs +1 -0
  213. package/dist/generators/test-generator/adapters/playwright/templates/steps/press-enter.hbs +2 -0
  214. package/dist/generators/test-generator/adapters/playwright/templates/steps/redirect-assertion.hbs +3 -0
  215. package/dist/generators/test-generator/adapters/playwright/templates/steps/route-assertion.hbs +2 -0
  216. package/dist/generators/test-generator/adapters/playwright/templates/steps/screen-navigation.hbs +3 -0
  217. package/dist/generators/test-generator/adapters/playwright/templates/steps/scroll-bottom-assertion.hbs +5 -0
  218. package/dist/generators/test-generator/adapters/playwright/templates/steps/select-action.hbs +1 -0
  219. package/dist/generators/test-generator/adapters/playwright/templates/steps/select.hbs +2 -0
  220. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/application-running.hbs +1 -0
  221. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-auth.hbs +6 -0
  222. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-browser-state.hbs +6 -0
  223. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-database.hbs +4 -0
  224. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/user-login-todo.hbs +6 -0
  225. package/dist/generators/test-generator/adapters/playwright/templates/steps/text-matches-pattern.hbs +3 -0
  226. package/dist/generators/test-generator/adapters/playwright/templates/steps/uncheck-action.hbs +1 -0
  227. package/dist/generators/test-generator/adapters/playwright/templates/steps/user-login-todo.hbs +6 -0
  228. package/dist/generators/test-generator/adapters/playwright/templates/steps/visibility-assertion.hbs +2 -0
  229. package/dist/generators/test-generator/adapters/playwright/templates/steps/visible-assertion.hbs +1 -0
  230. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait-for-element.hbs +1 -0
  231. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait-timeout.hbs +1 -0
  232. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait.hbs +1 -0
  233. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +19 -0
  234. package/dist/generators/test-generator/ai-step-mapper.d.ts +27 -0
  235. package/dist/generators/test-generator/ai-step-mapper.d.ts.map +1 -0
  236. package/dist/generators/test-generator/ai-step-mapper.js +204 -0
  237. package/dist/generators/test-generator/ai-step-mapper.js.map +1 -0
  238. package/dist/generators/test-generator/code-generator.d.ts +52 -0
  239. package/dist/generators/test-generator/code-generator.d.ts.map +1 -0
  240. package/dist/generators/test-generator/code-generator.js +191 -0
  241. package/dist/generators/test-generator/code-generator.js.map +1 -0
  242. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +7 -0
  243. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -0
  244. package/dist/generators/test-generator/patterns/assertion-patterns.js +173 -0
  245. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -0
  246. package/dist/generators/test-generator/patterns/form-patterns.d.ts +8 -0
  247. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +1 -0
  248. package/dist/generators/test-generator/patterns/form-patterns.js +110 -0
  249. package/dist/generators/test-generator/patterns/form-patterns.js.map +1 -0
  250. package/dist/generators/test-generator/patterns/index.d.ts +45 -0
  251. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -0
  252. package/dist/generators/test-generator/patterns/index.js +106 -0
  253. package/dist/generators/test-generator/patterns/index.js.map +1 -0
  254. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +7 -0
  255. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -0
  256. package/dist/generators/test-generator/patterns/interaction-patterns.js +100 -0
  257. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -0
  258. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +8 -0
  259. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -0
  260. package/dist/generators/test-generator/patterns/navigation-patterns.js +92 -0
  261. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -0
  262. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +7 -0
  263. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +1 -0
  264. package/dist/generators/test-generator/patterns/setup-patterns.js +84 -0
  265. package/dist/generators/test-generator/patterns/setup-patterns.js.map +1 -0
  266. package/dist/generators/test-generator/patterns/types.d.ts +38 -0
  267. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -0
  268. package/dist/generators/test-generator/patterns/types.js +3 -0
  269. package/dist/generators/test-generator/patterns/types.js.map +1 -0
  270. package/dist/generators/test-generator/step-mapper-old.d.ts +180 -0
  271. package/dist/generators/test-generator/step-mapper-old.d.ts.map +1 -0
  272. package/dist/generators/test-generator/step-mapper-old.js +752 -0
  273. package/dist/generators/test-generator/step-mapper-old.js.map +1 -0
  274. package/dist/generators/test-generator/step-mapper-refactored.d.ts +47 -0
  275. package/dist/generators/test-generator/step-mapper-refactored.d.ts.map +1 -0
  276. package/dist/generators/test-generator/step-mapper-refactored.js +182 -0
  277. package/dist/generators/test-generator/step-mapper-refactored.js.map +1 -0
  278. package/dist/generators/test-generator/step-mapper.d.ts +66 -0
  279. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -0
  280. package/dist/generators/test-generator/step-mapper.js +248 -0
  281. package/dist/generators/test-generator/step-mapper.js.map +1 -0
  282. package/dist/generators/test-generator/template-engine.d.ts +33 -0
  283. package/dist/generators/test-generator/template-engine.d.ts.map +1 -0
  284. package/dist/generators/test-generator/template-engine.js +129 -0
  285. package/dist/generators/test-generator/template-engine.js.map +1 -0
  286. package/dist/generators/test-generator/utils/data-resolver.d.ts +39 -0
  287. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -0
  288. package/dist/generators/test-generator/utils/data-resolver.js +162 -0
  289. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -0
  290. package/dist/generators/test-generator/utils/path-inference.d.ts +49 -0
  291. package/dist/generators/test-generator/utils/path-inference.d.ts.map +1 -0
  292. package/dist/generators/test-generator/utils/path-inference.js +286 -0
  293. package/dist/generators/test-generator/utils/path-inference.js.map +1 -0
  294. package/dist/generators/test-generator/utils/selector-resolver.d.ts +93 -0
  295. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -0
  296. package/dist/generators/test-generator/utils/selector-resolver.js +408 -0
  297. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -0
  298. package/dist/generators/types.d.ts +118 -0
  299. package/dist/generators/types.d.ts.map +1 -0
  300. package/dist/generators/types.js +48 -0
  301. package/dist/generators/types.js.map +1 -0
  302. package/dist/generators/ui-model-builder/deep-scanner.d.ts +121 -0
  303. package/dist/generators/ui-model-builder/deep-scanner.d.ts.map +1 -0
  304. package/dist/generators/ui-model-builder/deep-scanner.js +1113 -0
  305. package/dist/generators/ui-model-builder/deep-scanner.js.map +1 -0
  306. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts +110 -0
  307. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts.map +1 -0
  308. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js +608 -0
  309. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js.map +1 -0
  310. package/dist/generators/ui-model-builder/react-scanner.d.ts +107 -0
  311. package/dist/generators/ui-model-builder/react-scanner.d.ts.map +1 -0
  312. package/dist/generators/ui-model-builder/react-scanner.js +797 -0
  313. package/dist/generators/ui-model-builder/react-scanner.js.map +1 -0
  314. package/dist/input/cli-adapter.d.ts +63 -0
  315. package/dist/input/cli-adapter.d.ts.map +1 -0
  316. package/dist/input/cli-adapter.js +173 -0
  317. package/dist/input/cli-adapter.js.map +1 -0
  318. package/dist/input/config-adapter.d.ts +25 -0
  319. package/dist/input/config-adapter.d.ts.map +1 -0
  320. package/dist/input/config-adapter.js +70 -0
  321. package/dist/input/config-adapter.js.map +1 -0
  322. package/dist/input/input-adapter.d.ts +28 -0
  323. package/dist/input/input-adapter.d.ts.map +1 -0
  324. package/dist/input/input-adapter.js +17 -0
  325. package/dist/input/input-adapter.js.map +1 -0
  326. package/dist/input/vscode-adapter.d.ts +62 -0
  327. package/dist/input/vscode-adapter.d.ts.map +1 -0
  328. package/dist/input/vscode-adapter.js +64 -0
  329. package/dist/input/vscode-adapter.js.map +1 -0
  330. package/dist/orchestrator/cache-manager.d.ts +37 -0
  331. package/dist/orchestrator/cache-manager.d.ts.map +1 -0
  332. package/dist/orchestrator/cache-manager.js +148 -0
  333. package/dist/orchestrator/cache-manager.js.map +1 -0
  334. package/dist/orchestrator/pipeline.d.ts +73 -0
  335. package/dist/orchestrator/pipeline.d.ts.map +1 -0
  336. package/dist/orchestrator/pipeline.js +607 -0
  337. package/dist/orchestrator/pipeline.js.map +1 -0
  338. package/dist/orchestrator/project-initializer.d.ts +51 -0
  339. package/dist/orchestrator/project-initializer.d.ts.map +1 -0
  340. package/dist/orchestrator/project-initializer.js +326 -0
  341. package/dist/orchestrator/project-initializer.js.map +1 -0
  342. package/dist/orchestrator/reporter.d.ts +15 -0
  343. package/dist/orchestrator/reporter.d.ts.map +1 -0
  344. package/dist/orchestrator/reporter.js +30 -0
  345. package/dist/orchestrator/reporter.js.map +1 -0
  346. package/dist/orchestrator/screen-manager.d.ts +47 -0
  347. package/dist/orchestrator/screen-manager.d.ts.map +1 -0
  348. package/dist/orchestrator/screen-manager.js +271 -0
  349. package/dist/orchestrator/screen-manager.js.map +1 -0
  350. package/dist/tools/auto-tagger.d.ts +107 -0
  351. package/dist/tools/auto-tagger.d.ts.map +1 -0
  352. package/dist/tools/auto-tagger.js +502 -0
  353. package/dist/tools/auto-tagger.js.map +1 -0
  354. package/package.json +73 -0
  355. package/src/cli/commands/auto-tag-command.ts +80 -0
  356. package/src/cli/index.ts +205 -0
  357. package/src/config/ai-providers.yaml +56 -0
  358. package/src/config/config-loader.ts +248 -0
  359. package/src/config/config-schema.ts +148 -0
  360. package/src/config/default.config.yaml +101 -0
  361. package/src/config/framework.config.yaml +52 -0
  362. package/src/config/routes.yaml +31 -0
  363. package/src/core/selector-base/annotation-handler.ts +127 -0
  364. package/src/core/selector-base/base-generator.ts +234 -0
  365. package/src/core/selector-base/gherkin-parser.ts +57 -0
  366. package/src/core/selector-mapper/priority-mapper.ts +607 -0
  367. package/src/core/ui-scanner/heuristics/base-heuristic.ts +216 -0
  368. package/src/core/ui-scanner/react-scanner.ts +156 -0
  369. package/src/core/ui-scanner/scanner-interface.ts +133 -0
  370. package/src/core/ui-scanner/strict-scanner.ts +629 -0
  371. package/src/executor/playwright/playwright-generator.ts +125 -0
  372. package/src/executor/test-generator.ts +90 -0
  373. package/src/external/ai-provider.ts +90 -0
  374. package/src/external/anthropic-provider.ts +114 -0
  375. package/src/generators/README.md +410 -0
  376. package/src/generators/cache/cache-manager.ts +322 -0
  377. package/src/generators/cli.ts +640 -0
  378. package/src/generators/dsl-writer/index.ts +253 -0
  379. package/src/generators/gherkin-parser/index.ts +155 -0
  380. package/src/generators/gherkin-parser/selector-extractor.ts +142 -0
  381. package/src/generators/scaffold-generator/index.ts +524 -0
  382. package/src/generators/selector-mapper/ai-mapper.ts +528 -0
  383. package/src/generators/selector-mapper/hybrid-mapper.ts +427 -0
  384. package/src/generators/selector-mapper/index.ts +10 -0
  385. package/src/generators/selector-mapper/intelligent-mapper.ts +530 -0
  386. package/src/generators/test-generator/adapters/adapter-interface.ts +49 -0
  387. package/src/generators/test-generator/adapters/adapter-registry.ts +56 -0
  388. package/src/generators/test-generator/adapters/index.ts +9 -0
  389. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +40 -0
  390. package/src/generators/test-generator/adapters/playwright/templates/before-each.hbs +8 -0
  391. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +5 -0
  392. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +8 -0
  393. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +1 -0
  394. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/clear-action.hbs +1 -0
  395. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-action.hbs +1 -0
  396. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/double-click-action.hbs +1 -0
  397. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -0
  398. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/hover-action.hbs +1 -0
  399. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/press-action.hbs +1 -0
  400. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -0
  401. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +1 -0
  402. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +1 -0
  403. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -0
  404. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +1 -0
  405. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +2 -0
  406. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +2 -0
  407. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +2 -0
  408. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +1 -0
  409. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -0
  410. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  411. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-visible-assertion.hbs +1 -0
  412. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +1 -0
  413. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -0
  414. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +2 -0
  415. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -0
  416. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -0
  417. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -0
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/application-running.hbs +1 -0
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-auth.hbs +6 -0
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-browser-state.hbs +6 -0
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-database.hbs +4 -0
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/user-login-todo.hbs +6 -0
  423. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +19 -0
  424. package/src/generators/test-generator/ai-step-mapper.ts +224 -0
  425. package/src/generators/test-generator/code-generator.ts +235 -0
  426. package/src/generators/test-generator/patterns/assertion-patterns.ts +183 -0
  427. package/src/generators/test-generator/patterns/form-patterns.ts +124 -0
  428. package/src/generators/test-generator/patterns/index.ts +97 -0
  429. package/src/generators/test-generator/patterns/interaction-patterns.ts +119 -0
  430. package/src/generators/test-generator/patterns/navigation-patterns.ts +110 -0
  431. package/src/generators/test-generator/patterns/setup-patterns.ts +94 -0
  432. package/src/generators/test-generator/patterns/types.ts +41 -0
  433. package/src/generators/test-generator/step-mapper.ts +254 -0
  434. package/src/generators/test-generator/template-engine.ts +160 -0
  435. package/src/generators/test-generator/utils/data-resolver.ts +147 -0
  436. package/src/generators/test-generator/utils/path-inference.ts +344 -0
  437. package/src/generators/test-generator/utils/selector-resolver.ts +480 -0
  438. package/src/generators/types.ts +226 -0
  439. package/src/generators/ui-model-builder/deep-scanner.ts +1244 -0
  440. package/src/generators/ui-model-builder/enhanced-deep-scanner.ts +731 -0
  441. package/src/generators/ui-model-builder/react-scanner.ts +959 -0
  442. package/src/input/cli-adapter.ts +185 -0
  443. package/src/input/config-adapter.ts +71 -0
  444. package/src/input/input-adapter.ts +32 -0
  445. package/src/input/vscode-adapter.ts +90 -0
  446. package/src/orchestrator/cache-manager.ts +138 -0
  447. package/src/orchestrator/pipeline.ts +713 -0
  448. package/src/orchestrator/project-initializer.ts +315 -0
  449. package/src/orchestrator/reporter.ts +36 -0
  450. package/src/orchestrator/screen-manager.ts +268 -0
  451. package/src/tools/auto-tagger.ts +572 -0
@@ -0,0 +1,959 @@
1
+ /**
2
+ * React/Next.js UI Model Builder
3
+ * Scans React/Next.js source code to extract UI elements using heuristic analysis
4
+ */
5
+
6
+ import * as parser from '@babel/parser';
7
+ import traverse, { NodePath } from '@babel/traverse';
8
+ import * as t from '@babel/types';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { glob } from 'glob';
12
+ import { UIModel, UIElement, FrameworkType } from '../types';
13
+
14
+ // ============================================================================
15
+ // Configuration
16
+ // ============================================================================
17
+
18
+ interface ScanConfig {
19
+ sourceRoot: string;
20
+ framework: FrameworkType;
21
+ verbose?: boolean;
22
+ maxDepth?: number; // Maximum depth for component scanning (default: 999 = unlimited)
23
+ }
24
+
25
+ // ============================================================================
26
+ // React Scanner Class
27
+ // ============================================================================
28
+
29
+ interface ComponentInfo {
30
+ name: string; // Component name (e.g., "ChatInput")
31
+ importPath: string; // Import path (e.g., "@/components/ChatInput")
32
+ sourceFile: string; // File where component was used
33
+ }
34
+
35
+ export class ReactScanner {
36
+ private config: ScanConfig;
37
+ private elements: UIElement[] = [];
38
+ private elementCounter = 0;
39
+ private detectedComponents: ComponentInfo[] = [];
40
+ private scannedComponentPaths = new Set<string>(); // Avoid duplicate scans
41
+
42
+ constructor(config: ScanConfig) {
43
+ this.config = config;
44
+ }
45
+
46
+ /**
47
+ * Discover all routes/screens in Next.js app directory
48
+ */
49
+ async discoverScreens(): Promise<string[]> {
50
+ const appDir = this.config.sourceRoot;
51
+
52
+ // Find all page.tsx/page.jsx files (Next.js 13+ app directory)
53
+ const pageFiles = await glob('**/page.{tsx,jsx}', {
54
+ cwd: appDir,
55
+ absolute: false,
56
+ ignore: ['**/node_modules/**', '**/api/**']
57
+ });
58
+
59
+ // Extract screen IDs from paths
60
+ // Example: "login/page.tsx" -> "login"
61
+ // Example: "auth/login/page.tsx" -> "auth-login"
62
+ // Example: "page.tsx" -> "home"
63
+ const screens = pageFiles.map(file => {
64
+ const dir = path.dirname(file);
65
+ return dir === '.' ? 'home' : dir.replace(/\//g, '-');
66
+ });
67
+
68
+ return screens;
69
+ }
70
+
71
+ /**
72
+ * Build UI Model for a specific screen
73
+ */
74
+ async buildUIModel(screenId: string): Promise<UIModel> {
75
+ // Reset state for new screen
76
+ this.elements = [];
77
+ this.elementCounter = 0;
78
+ this.detectedComponents = [];
79
+ this.scannedComponentPaths.clear();
80
+
81
+ // Determine route path from screen ID
82
+ // "home" -> "" (root)
83
+ // "auth-login" -> "auth/login"
84
+ const routePath = screenId === 'home' ? '' : screenId.replace(/-/g, '/');
85
+ const routeDir = path.join(this.config.sourceRoot, routePath);
86
+
87
+ // Collect source files to scan
88
+ const sourceFiles: string[] = [];
89
+
90
+ // 1. App root layout
91
+ const rootLayout = path.join(this.config.sourceRoot, 'layout.tsx');
92
+ if (fs.existsSync(rootLayout)) {
93
+ sourceFiles.push(rootLayout);
94
+ }
95
+
96
+ // 2. Route-specific layout (if exists)
97
+ const routeLayout = path.join(routeDir, 'layout.tsx');
98
+ if (fs.existsSync(routeLayout)) {
99
+ sourceFiles.push(routeLayout);
100
+ }
101
+
102
+ // 3. Page component
103
+ const pageTsx = path.join(routeDir, 'page.tsx');
104
+ const pageJsx = path.join(routeDir, 'page.jsx');
105
+ if (fs.existsSync(pageTsx)) {
106
+ sourceFiles.push(pageTsx);
107
+ } else if (fs.existsSync(pageJsx)) {
108
+ sourceFiles.push(pageJsx);
109
+ } else {
110
+ throw new Error(`Page component not found for screen: ${screenId} at ${routeDir}`);
111
+ }
112
+
113
+ // Phase 1: Scan all source files (page components)
114
+ for (const filePath of sourceFiles) {
115
+ await this.scanFile(filePath);
116
+ }
117
+
118
+ // Phase 2: Scan detected components (unlimited depth by default)
119
+ const maxDepth = this.config.maxDepth !== undefined ? this.config.maxDepth : 999; // Default unlimited
120
+ const depthLabel = maxDepth === 999 ? 'unlimited' : maxDepth.toString();
121
+ if (this.config.verbose && this.detectedComponents.length > 0) {
122
+ console.log(` Detected ${this.detectedComponents.length} component(s), scanning with depth=${depthLabel}...`);
123
+ }
124
+
125
+ await this.scanComponentsRecursive(this.detectedComponents, 1, maxDepth);
126
+
127
+ // Build and return UI Model
128
+ const uiModel: UIModel = {
129
+ screenId,
130
+ routeId: routePath || 'home',
131
+ framework: this.config.framework,
132
+ sourceFiles: sourceFiles.map(f => path.relative(this.config.sourceRoot, f)),
133
+ elements: this.elements,
134
+ metadata: {
135
+ scannedAt: new Date().toISOString(),
136
+ version: '1.0.0',
137
+ hash: this.generateHash(this.elements)
138
+ }
139
+ };
140
+
141
+ return uiModel;
142
+ }
143
+
144
+ /**
145
+ * Scan a single file and extract UI elements
146
+ */
147
+ private async scanFile(filePath: string): Promise<void> {
148
+ const sourceFileName = path.basename(filePath);
149
+ const importMap = new Map<string, string>(); // componentName → importPath
150
+
151
+ if (this.config.verbose) {
152
+ console.log(` Scanning: ${filePath}`);
153
+ }
154
+
155
+ const code = fs.readFileSync(filePath, 'utf-8');
156
+
157
+ // Parse with Babel
158
+ const ast = parser.parse(code, {
159
+ sourceType: 'module',
160
+ plugins: ['typescript', 'jsx']
161
+ });
162
+
163
+ // Traverse AST to extract imports and JSX elements
164
+ traverse(ast, {
165
+ // Detect imports
166
+ ImportDeclaration: (path: NodePath<t.ImportDeclaration>) => {
167
+ const importPath = path.node.source.value;
168
+
169
+ // Extract default import: import ChatInput from '...'
170
+ const defaultSpecifier = path.node.specifiers.find(s => t.isImportDefaultSpecifier(s));
171
+ if (defaultSpecifier && t.isImportDefaultSpecifier(defaultSpecifier)) {
172
+ const componentName = defaultSpecifier.local.name;
173
+ importMap.set(componentName, importPath);
174
+ }
175
+
176
+ // Extract named imports: import { Button } from '...'
177
+ path.node.specifiers
178
+ .filter(s => t.isImportSpecifier(s))
179
+ .forEach(specifier => {
180
+ if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) {
181
+ const componentName = specifier.imported.name;
182
+ importMap.set(componentName, importPath);
183
+ }
184
+ });
185
+ },
186
+
187
+ // Extract JSX elements
188
+ JSXElement: (path: NodePath<t.JSXElement>) => {
189
+ this.extractElement(path, sourceFileName, filePath, importMap);
190
+ },
191
+
192
+ // PHASE 1: Detect conditional rendering patterns
193
+ JSXExpressionContainer: (path: NodePath<t.JSXExpressionContainer>) => {
194
+ this.detectConditionalElements(path, sourceFileName, filePath, importMap);
195
+ }
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Smart element filter - determines if element should be scanned
201
+ */
202
+ private shouldScanElement(
203
+ tagName: string,
204
+ attributes: Record<string, string>,
205
+ textContent: string | null,
206
+ path?: NodePath<t.JSXElement>
207
+ ): boolean {
208
+ const lowercaseTag = tagName.toLowerCase();
209
+
210
+ // 1. Always scan form elements
211
+ const formElements = ['input', 'button', 'textarea', 'select', 'form'];
212
+ if (formElements.includes(lowercaseTag)) return true;
213
+
214
+ // 2. Always scan links (a, Link)
215
+ if (lowercaseTag === 'a' || tagName === 'Link') return true;
216
+
217
+ // 3. Scan headings with text content
218
+ if (/^h[1-6]$/.test(lowercaseTag) && textContent) return true;
219
+
220
+ // 4. Scan images with alt text
221
+ if (lowercaseTag === 'img' && attributes['alt']) return true;
222
+
223
+ // 5. Scan elements with identifying attributes
224
+ const hasIdentifier =
225
+ attributes['id'] ||
226
+ attributes['name'] ||
227
+ attributes['data-testid'] ||
228
+ attributes['data-test-id'] ||
229
+ attributes['aria-label'] ||
230
+ attributes['ariaLabel'];
231
+ if (hasIdentifier) return true;
232
+
233
+ // 6. Scan semantic components (ChatInput, SubmitButton, UserProfile, etc.)
234
+ // Pattern: *Input, *Field, *TextArea, *Select, *Button, *Link, *Form
235
+ const semanticPatterns = [
236
+ /Input$/, // ChatInput, MessageInput
237
+ /Field$/, // TextField, EmailField
238
+ /TextArea$/, // MessageTextArea
239
+ /Select$/, // CountrySelect
240
+ /Button$/, // SubmitButton, CancelButton
241
+ /Link$/, // NavLink
242
+ /Form$/, // LoginForm
243
+ /Avatar$/, // UserAvatar
244
+ /Icon$/, // SearchIcon
245
+ /Image$/, // ProfileImage
246
+ /Card$/, // UserCard
247
+ /List$/, // MessageList
248
+ /Item$/ // ListItem
249
+ ];
250
+
251
+ if (semanticPatterns.some(pattern => pattern.test(tagName))) {
252
+ return true;
253
+ }
254
+
255
+ // 7. Scan text containers with content
256
+ const textContainers = ['p', 'span', 'div', 'section', 'article'];
257
+ if (textContainers.includes(lowercaseTag) && textContent && textContent.length > 0) {
258
+ return true;
259
+ }
260
+
261
+ // 7.5. PHASE 1.5: Scan error/success message elements by className pattern (even without text)
262
+ const className = attributes['className'] || '';
263
+ const isErrorOrSuccessMessage =
264
+ className.includes('text-red') ||
265
+ className.includes('text-green') ||
266
+ className.includes('bg-red') ||
267
+ className.includes('bg-green') ||
268
+ className.includes('error') ||
269
+ className.includes('success') ||
270
+ className.includes('alert');
271
+
272
+ if (textContainers.includes(lowercaseTag) && isErrorOrSuccessMessage) {
273
+ return true;
274
+ }
275
+
276
+ // 7.6. PHASE 1.6: Scan timestamp/metadata elements by className pattern
277
+ // Fix for elements with dynamic expressions like {timestamp.toLocaleTimeString()}
278
+ const isTimestampOrMetadata =
279
+ className.includes('timestamp') ||
280
+ className.includes('time') ||
281
+ (className.includes('text-xs') && className.includes('text-gray')) ||
282
+ (className.includes('text-sm') && className.includes('text-gray') && className.includes('mt-'));
283
+
284
+ if (textContainers.includes(lowercaseTag) && isTimestampOrMetadata) {
285
+ return true;
286
+ }
287
+
288
+ // 8. NEW: Smart container detection for structural elements
289
+ if (['div', 'section', 'article', 'main', 'aside', 'nav'].includes(lowercaseTag)) {
290
+ if (path && this.shouldScanContainer(tagName, attributes, path)) {
291
+ return true;
292
+ }
293
+ }
294
+
295
+ return false; // Skip everything else
296
+ }
297
+
298
+ /**
299
+ * Smart container detection - checks if container is structurally important
300
+ * NEW: Phase 1 - Task 1
301
+ */
302
+ private shouldScanContainer(
303
+ tagName: string,
304
+ attributes: Record<string, string>,
305
+ path: NodePath<t.JSXElement>
306
+ ): boolean {
307
+
308
+ // 1. Has important children (form elements, buttons, custom components)?
309
+ const children = path.node.children.filter(child => t.isJSXElement(child)) as t.JSXElement[];
310
+
311
+ for (const child of children) {
312
+ const childTag = this.getTagName(child.openingElement.name);
313
+ const childTagLower = childTag.toLowerCase();
314
+
315
+ // Has form elements
316
+ if (['input', 'button', 'textarea', 'select', 'form'].includes(childTagLower)) {
317
+ return true;
318
+ }
319
+
320
+ // Has custom components (PascalCase)
321
+ if (/^[A-Z]/.test(childTag)) {
322
+ return true;
323
+ }
324
+ }
325
+
326
+ // 2. Semantic className keywords
327
+ const className = attributes['className'] || '';
328
+ const semanticKeywords = [
329
+ 'message', 'messages', 'chat',
330
+ 'list', 'items',
331
+ 'container', 'wrapper', 'content',
332
+ 'scroll', 'overflow',
333
+ 'grid', 'flex'
334
+ ];
335
+
336
+ const hasSemanticClass = semanticKeywords.some(keyword =>
337
+ className.toLowerCase().includes(keyword)
338
+ );
339
+ if (hasSemanticClass) {
340
+ return true;
341
+ }
342
+
343
+ // 3. Has scroll behavior (overflow-auto, overflow-y-auto, etc.)
344
+ if (className.includes('overflow')) {
345
+ return true;
346
+ }
347
+
348
+ // 4. Has ARIA role
349
+ if (attributes['role']) {
350
+ return true;
351
+ }
352
+
353
+ // 5. Has ref (often used for important containers)
354
+ if (attributes['ref']) {
355
+ return true;
356
+ }
357
+
358
+ return false;
359
+ }
360
+
361
+ /**
362
+ * Extract UI element from JSX node
363
+ */
364
+ private extractElement(
365
+ path: NodePath<t.JSXElement>,
366
+ sourceFile: string,
367
+ fullFilePath: string,
368
+ importMap: Map<string, string>
369
+ ): void {
370
+ const openingElement = path.node.openingElement;
371
+ const tagName = this.getTagName(openingElement.name);
372
+
373
+ // Extract attributes and text first (needed for filtering)
374
+ const attributes = this.extractAttributes(openingElement.attributes);
375
+ const textContent = this.extractTextContent(path.node);
376
+
377
+ // Check if this is a custom component (starts with uppercase)
378
+ const isCustomComponent = /^[A-Z]/.test(tagName);
379
+
380
+ // If it's a custom component and we have an import for it, track it for scanning
381
+ // UPDATED: Phase 1 - Task 2 - Track ALL PascalCase components, not just semantic patterns
382
+ if (isCustomComponent && importMap.has(tagName)) {
383
+ const importPath = importMap.get(tagName)!;
384
+
385
+ // Check if not already tracked
386
+ const alreadyTracked = this.detectedComponents.some(c => c.name === tagName && c.importPath === importPath);
387
+ if (!alreadyTracked) {
388
+ this.detectedComponents.push({
389
+ name: tagName,
390
+ importPath,
391
+ sourceFile: fullFilePath
392
+ });
393
+
394
+ if (this.config.verbose) {
395
+ console.log(` Found component: ${tagName} from ${importPath}`);
396
+ }
397
+ }
398
+ }
399
+
400
+ // Use smart filter instead of static whitelist (pass path for container detection)
401
+ if (!this.shouldScanElement(tagName, attributes, textContent, path)) {
402
+ return; // Skip non-scannable elements
403
+ }
404
+
405
+ // Build UI Element
406
+ const element: UIElement = {
407
+ key: `e${++this.elementCounter}`,
408
+ tag: tagName.toLowerCase(),
409
+ role: attributes['role'] || this.inferRole(tagName),
410
+ name: attributes['name'],
411
+ id: attributes['id'],
412
+ placeholder: attributes['placeholder'],
413
+ ariaLabel: attributes['aria-label'] || attributes['ariaLabel'],
414
+ testId: attributes['data-testid'] || attributes['data-test-id'],
415
+ text: textContent || attributes['value'],
416
+ source: sourceFile,
417
+ props: attributes
418
+ };
419
+
420
+ // Add label if associated
421
+ if (attributes['aria-labelledby']) {
422
+ element.label = `[ref:${attributes['aria-labelledby']}]`;
423
+ }
424
+
425
+ this.elements.push(element);
426
+ }
427
+
428
+ /**
429
+ * PHASE 1: Detect and extract conditionally rendered elements
430
+ * Handles patterns: {condition && <Element />}, {condition ? <A /> : <B />}, {arr.map(...)}
431
+ * PHASE 1.5: Now also traverses nested children
432
+ */
433
+ private detectConditionalElements(
434
+ path: NodePath<t.JSXExpressionContainer>,
435
+ sourceFile: string,
436
+ fullFilePath: string,
437
+ importMap: Map<string, string>
438
+ ): void {
439
+ const expr = path.node.expression;
440
+
441
+ // Pattern 1: Logical AND (condition && <Element />)
442
+ if (t.isLogicalExpression(expr, { operator: '&&' })) {
443
+ const rightSide = expr.right;
444
+
445
+ if (t.isJSXElement(rightSide)) {
446
+ // Extract the direct element
447
+ this.extractConditionalElement(rightSide, sourceFile, fullFilePath, importMap, 'conditional-and');
448
+
449
+ // PHASE 1.5: Also traverse nested children
450
+ this.traverseConditionalChildren(rightSide, sourceFile, fullFilePath, importMap, 'conditional-nested');
451
+ } else if (t.isJSXFragment(rightSide)) {
452
+ // Handle {condition && (<><Element1 /><Element2 /></>)}
453
+ this.extractFragmentElements(rightSide, sourceFile, fullFilePath, importMap, 'conditional-and');
454
+ }
455
+ }
456
+
457
+ // Pattern 2: Ternary operator (condition ? <A /> : <B />)
458
+ if (t.isConditionalExpression(expr)) {
459
+ const consequent = expr.consequent;
460
+ const alternate = expr.alternate;
461
+
462
+ if (t.isJSXElement(consequent)) {
463
+ this.extractConditionalElement(consequent, sourceFile, fullFilePath, importMap, 'conditional-true');
464
+ // PHASE 1.5: Traverse nested children
465
+ this.traverseConditionalChildren(consequent, sourceFile, fullFilePath, importMap, 'conditional-nested');
466
+ }
467
+
468
+ if (t.isJSXElement(alternate)) {
469
+ this.extractConditionalElement(alternate, sourceFile, fullFilePath, importMap, 'conditional-false');
470
+ // PHASE 1.5: Traverse nested children
471
+ this.traverseConditionalChildren(alternate, sourceFile, fullFilePath, importMap, 'conditional-nested');
472
+ }
473
+ }
474
+
475
+ // Pattern 3: Array.map() rendering (arr.map(item => <Element />))
476
+ if (t.isCallExpression(expr)) {
477
+ const callee = expr.callee;
478
+
479
+ // Check if it's a .map() call
480
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property, { name: 'map' })) {
481
+ const mapCallback = expr.arguments[0];
482
+
483
+ if (t.isArrowFunctionExpression(mapCallback) || t.isFunctionExpression(mapCallback)) {
484
+ const body = mapCallback.body;
485
+
486
+ // Case 1: Direct return - arr.map(item => <Element />)
487
+ if (t.isJSXElement(body)) {
488
+ this.extractConditionalElement(body, sourceFile, fullFilePath, importMap, 'loop-item');
489
+ }
490
+
491
+ // Case 2: Block with return - arr.map(item => { return <Element /> })
492
+ if (t.isBlockStatement(body)) {
493
+ for (const statement of body.body) {
494
+ if (t.isReturnStatement(statement) && statement.argument && t.isJSXElement(statement.argument)) {
495
+ this.extractConditionalElement(statement.argument, sourceFile, fullFilePath, importMap, 'loop-item');
496
+ }
497
+ }
498
+ }
499
+ }
500
+ }
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Extract elements from JSX Fragment
506
+ */
507
+ private extractFragmentElements(
508
+ fragment: t.JSXFragment,
509
+ sourceFile: string,
510
+ fullFilePath: string,
511
+ importMap: Map<string, string>,
512
+ renderType: string
513
+ ): void {
514
+ for (const child of fragment.children) {
515
+ if (t.isJSXElement(child)) {
516
+ this.extractConditionalElement(child, sourceFile, fullFilePath, importMap, renderType);
517
+ }
518
+ }
519
+ }
520
+
521
+ /**
522
+ * PHASE 1.5: Traverse nested children of conditional elements
523
+ * Example: {error && <div><p>{error}</p></div>} - detect the nested <p> tag
524
+ */
525
+ private traverseConditionalChildren(
526
+ element: t.JSXElement,
527
+ sourceFile: string,
528
+ fullFilePath: string,
529
+ importMap: Map<string, string>,
530
+ renderType: string
531
+ ): void {
532
+ for (const child of element.children) {
533
+ // Traverse nested JSX elements
534
+ if (t.isJSXElement(child)) {
535
+ const openingElement = child.openingElement;
536
+ const tagName = this.getTagName(openingElement.name);
537
+ const attributes = this.extractAttributes(openingElement.attributes);
538
+ const textContent = this.extractTextContent(child);
539
+
540
+ // Check if this nested element should be scanned
541
+ if (this.shouldScanElement(tagName, attributes, textContent, undefined)) {
542
+ this.extractConditionalElement(child, sourceFile, fullFilePath, importMap, renderType);
543
+ }
544
+
545
+ // Recursively traverse deeper children
546
+ this.traverseConditionalChildren(child, sourceFile, fullFilePath, importMap, renderType);
547
+ }
548
+
549
+ // Traverse JSX expression containers (for nested dynamic content)
550
+ if (t.isJSXExpressionContainer(child)) {
551
+ const expr = child.expression;
552
+
553
+ // Handle nested JSX elements in expressions
554
+ if (t.isJSXElement(expr)) {
555
+ this.extractConditionalElement(expr, sourceFile, fullFilePath, importMap, renderType);
556
+ }
557
+ }
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Extract a conditionally rendered JSX element
563
+ */
564
+ private extractConditionalElement(
565
+ jsxElement: t.JSXElement,
566
+ sourceFile: string,
567
+ fullFilePath: string,
568
+ importMap: Map<string, string>,
569
+ renderType: string
570
+ ): void {
571
+ const openingElement = jsxElement.openingElement;
572
+ const tagName = this.getTagName(openingElement.name);
573
+
574
+ // Extract attributes and text
575
+ const attributes = this.extractAttributes(openingElement.attributes);
576
+ const textContent = this.extractTextContent(jsxElement);
577
+
578
+ // Check if this is a custom component
579
+ const isCustomComponent = /^[A-Z]/.test(tagName);
580
+ if (isCustomComponent && importMap.has(tagName)) {
581
+ const importPath = importMap.get(tagName)!;
582
+ const alreadyTracked = this.detectedComponents.some(c => c.name === tagName && c.importPath === importPath);
583
+ if (!alreadyTracked) {
584
+ this.detectedComponents.push({
585
+ name: tagName,
586
+ importPath,
587
+ sourceFile: fullFilePath
588
+ });
589
+ }
590
+ }
591
+
592
+ // Use smart filter to decide if we should scan this element
593
+ if (!this.shouldScanElement(tagName, attributes, textContent, undefined)) {
594
+ return;
595
+ }
596
+
597
+ // Build UI Element with conditional marker
598
+ const element: UIElement = {
599
+ key: `e${++this.elementCounter}`,
600
+ tag: tagName.toLowerCase(),
601
+ role: attributes['role'] || this.inferRole(tagName),
602
+ name: attributes['name'],
603
+ id: attributes['id'],
604
+ placeholder: attributes['placeholder'],
605
+ ariaLabel: attributes['aria-label'] || attributes['ariaLabel'],
606
+ testId: attributes['data-testid'] || attributes['data-test-id'],
607
+ text: textContent || attributes['value'],
608
+ source: sourceFile,
609
+ props: {
610
+ ...attributes,
611
+ isConditional: renderType // Mark as conditional with type
612
+ }
613
+ };
614
+
615
+ if (attributes['aria-labelledby']) {
616
+ element.label = `[ref:${attributes['aria-labelledby']}]`;
617
+ }
618
+
619
+ this.elements.push(element);
620
+
621
+ if (this.config.verbose) {
622
+ console.log(` Found conditional element: ${tagName} (${renderType})`);
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Get tag name from JSX element
628
+ */
629
+ private getTagName(name: t.JSXElement['openingElement']['name']): string {
630
+ if (t.isJSXIdentifier(name)) {
631
+ return name.name;
632
+ }
633
+ if (t.isJSXMemberExpression(name)) {
634
+ // Handle cases like <Form.Input />
635
+ return this.getTagName(name.property);
636
+ }
637
+ return 'unknown';
638
+ }
639
+
640
+ /**
641
+ * Extract attributes from JSX element
642
+ */
643
+ private extractAttributes(attributes: (t.JSXAttribute | t.JSXSpreadAttribute)[]): Record<string, string> {
644
+ const result: Record<string, string> = {};
645
+
646
+ for (const attr of attributes) {
647
+ if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
648
+ const name = attr.name.name;
649
+ const value = this.getAttributeValue(attr.value);
650
+ if (value !== null) {
651
+ result[name] = value;
652
+ }
653
+ }
654
+ }
655
+
656
+ return result;
657
+ }
658
+
659
+ /**
660
+ * Get attribute value
661
+ */
662
+ private getAttributeValue(value: t.JSXAttribute['value']): string | null {
663
+ if (!value) return null;
664
+
665
+ if (t.isStringLiteral(value)) {
666
+ return value.value;
667
+ }
668
+
669
+ if (t.isJSXExpressionContainer(value)) {
670
+ const expr = value.expression;
671
+
672
+ // Handle string literals in expressions
673
+ if (t.isStringLiteral(expr)) {
674
+ return expr.value;
675
+ }
676
+
677
+ // Handle template literals
678
+ if (t.isTemplateLiteral(expr) && expr.quasis.length === 1) {
679
+ return expr.quasis[0].value.cooked || '';
680
+ }
681
+
682
+ // For complex expressions, return placeholder
683
+ return '[expression]';
684
+ }
685
+
686
+ return null;
687
+ }
688
+
689
+ /**
690
+ * Extract text content from JSX element
691
+ * PHASE 1 ENHANCED: Extract text from dynamic expressions
692
+ */
693
+ private extractTextContent(node: t.JSXElement): string | null {
694
+ const children = node.children;
695
+ const textParts: string[] = [];
696
+
697
+ for (const child of children) {
698
+ if (t.isJSXText(child)) {
699
+ const text = child.value.trim();
700
+ if (text) textParts.push(text);
701
+ }
702
+
703
+ if (t.isJSXExpressionContainer(child)) {
704
+ const expr = child.expression;
705
+
706
+ // Static string literal
707
+ if (t.isStringLiteral(expr)) {
708
+ textParts.push(expr.value);
709
+ }
710
+
711
+ // PHASE 1: Extract text from ternary (condition ? 'Text A' : 'Text B')
712
+ if (t.isConditionalExpression(expr)) {
713
+ const texts: string[] = [];
714
+
715
+ if (t.isStringLiteral(expr.consequent)) {
716
+ texts.push(expr.consequent.value);
717
+ }
718
+
719
+ if (t.isStringLiteral(expr.alternate)) {
720
+ texts.push(expr.alternate.value);
721
+ }
722
+
723
+ if (texts.length > 0) {
724
+ textParts.push(texts.join(' / ')); // e.g., "Copy / Copied!"
725
+ }
726
+ }
727
+
728
+ // PHASE 1: Extract text from logical OR (defaultValue || 'Fallback')
729
+ if (t.isLogicalExpression(expr, { operator: '||' })) {
730
+ if (t.isStringLiteral(expr.right)) {
731
+ textParts.push(expr.right.value);
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+ return textParts.length > 0 ? textParts.join(' ') : null;
738
+ }
739
+
740
+ /**
741
+ * Infer ARIA role from tag name
742
+ */
743
+ private inferRole(tagName: string): string | undefined {
744
+ const roleMap: Record<string, string> = {
745
+ 'button': 'button',
746
+ 'input': 'textbox',
747
+ 'textarea': 'textbox',
748
+ 'select': 'combobox',
749
+ 'a': 'link',
750
+ 'form': 'form'
751
+ };
752
+
753
+ return roleMap[tagName.toLowerCase()];
754
+ }
755
+
756
+ /**
757
+ * Resolve component import path to actual file path
758
+ */
759
+ private resolveComponentPath(importPath: string, sourceFile: string): string | null {
760
+ try {
761
+ // Handle different import path formats
762
+ let resolvedPath: string;
763
+
764
+ if (importPath.startsWith('@/')) {
765
+ // Alias import: @/components/ChatInput → PROJECT_ROOT/components/ChatInput.tsx
766
+ // Note: @ alias points to project root, not sourceRoot (app/)
767
+ const projectRoot = path.resolve(this.config.sourceRoot, '..');
768
+ const relativePath = importPath.replace('@/', '');
769
+ resolvedPath = path.join(projectRoot, relativePath);
770
+ } else if (importPath.startsWith('./') || importPath.startsWith('../')) {
771
+ // Relative import: ./components/ChatInput
772
+ const sourceDir = path.dirname(sourceFile);
773
+ resolvedPath = path.join(sourceDir, importPath);
774
+ } else {
775
+ // Absolute or node_modules import - skip
776
+ return null;
777
+ }
778
+
779
+ // Try different file extensions
780
+ const extensions = ['.tsx', '.ts', '.jsx', '.js'];
781
+ for (const ext of extensions) {
782
+ const fullPath = resolvedPath + ext;
783
+ if (fs.existsSync(fullPath)) {
784
+ return fullPath;
785
+ }
786
+ }
787
+
788
+ // Try index files
789
+ for (const ext of extensions) {
790
+ const indexPath = path.join(resolvedPath, `index${ext}`);
791
+ if (fs.existsSync(indexPath)) {
792
+ return indexPath;
793
+ }
794
+ }
795
+
796
+ return null;
797
+ } catch (error) {
798
+ if (this.config.verbose) {
799
+ console.log(` Could not resolve: ${importPath}`);
800
+ }
801
+ return null;
802
+ }
803
+ }
804
+
805
+ /**
806
+ * NEW: Recursively scan components at multiple depths
807
+ * @param components - List of components to scan
808
+ * @param currentDepth - Current depth level
809
+ * @param maxDepth - Maximum depth to scan
810
+ */
811
+ private async scanComponentsRecursive(
812
+ components: ComponentInfo[],
813
+ currentDepth: number,
814
+ maxDepth: number
815
+ ): Promise<void> {
816
+ if (currentDepth > maxDepth) {
817
+ return; // Stop recursion at max depth
818
+ }
819
+
820
+ const startTime = Date.now();
821
+ const componentsScanned: ComponentInfo[] = [];
822
+
823
+ // Scan all components at current level
824
+ for (const component of components) {
825
+ const componentPath = this.resolveComponentPath(component.importPath, component.sourceFile);
826
+
827
+ if (!componentPath) {
828
+ continue; // Skip if path cannot be resolved
829
+ }
830
+
831
+ // Skip node_modules
832
+ if (componentPath.includes('node_modules')) {
833
+ if (this.config.verbose) {
834
+ console.log(` Skipping node_modules: ${componentPath}`);
835
+ }
836
+ continue;
837
+ }
838
+
839
+ // Skip if already scanned (avoid duplicates)
840
+ if (this.scannedComponentPaths.has(componentPath)) {
841
+ continue;
842
+ }
843
+
844
+ // Mark as scanned
845
+ this.scannedComponentPaths.add(componentPath);
846
+
847
+ // Store the count of detected components before scanning
848
+ const detectedCountBefore = this.detectedComponents.length;
849
+
850
+ // Scan the component file
851
+ await this.scanComponentFile(componentPath, component.name);
852
+
853
+ // Track newly detected components from this scan
854
+ const newlyDetected = this.detectedComponents.slice(detectedCountBefore);
855
+ componentsScanned.push(...newlyDetected);
856
+ }
857
+
858
+ const elapsed = Date.now() - startTime;
859
+ if (this.config.verbose && componentsScanned.length > 0) {
860
+ console.log(` [Depth ${currentDepth}] Scanned ${components.length} component(s), found ${componentsScanned.length} sub-component(s) (${elapsed}ms)`);
861
+ }
862
+
863
+ // Recursively scan newly detected components at next depth
864
+ if (componentsScanned.length > 0 && currentDepth < maxDepth) {
865
+ await this.scanComponentsRecursive(componentsScanned, currentDepth + 1, maxDepth);
866
+ }
867
+ }
868
+
869
+ /**
870
+ * Scan component file and extract elements
871
+ */
872
+ private async scanComponentFile(componentPath: string, componentName: string): Promise<void> {
873
+ if (this.config.verbose) {
874
+ console.log(` Scanning component: ${componentName} at ${componentPath}`);
875
+ }
876
+
877
+ const code = fs.readFileSync(componentPath, 'utf-8');
878
+
879
+ // Parse with Babel
880
+ const ast = parser.parse(code, {
881
+ sourceType: 'module',
882
+ plugins: ['typescript', 'jsx']
883
+ });
884
+
885
+ const importMap = new Map<string, string>(); // Empty import map for component scanning
886
+
887
+ // Traverse AST and extract JSX elements (no nested component scanning)
888
+ traverse(ast, {
889
+ JSXElement: (path: NodePath<t.JSXElement>) => {
890
+ const openingElement = path.node.openingElement;
891
+ const tagName = this.getTagName(openingElement.name);
892
+
893
+ // Extract attributes and text
894
+ const attributes = this.extractAttributes(openingElement.attributes);
895
+ const textContent = this.extractTextContent(path.node);
896
+
897
+ // Use smart filter (pass path for container detection)
898
+ if (!this.shouldScanElement(tagName, attributes, textContent, path)) {
899
+ return;
900
+ }
901
+
902
+ // Build UI Element with component context
903
+ const element: UIElement = {
904
+ key: `e${++this.elementCounter}`,
905
+ tag: tagName.toLowerCase(),
906
+ role: attributes['role'] || this.inferRole(tagName),
907
+ name: attributes['name'],
908
+ id: attributes['id'],
909
+ placeholder: attributes['placeholder'],
910
+ ariaLabel: attributes['aria-label'] || attributes['ariaLabel'],
911
+ testId: attributes['data-testid'] || attributes['data-test-id'],
912
+ text: textContent || attributes['value'],
913
+ source: `${componentName}.tsx`, // Mark as from component
914
+ props: attributes
915
+ };
916
+
917
+ // Add label if associated
918
+ if (attributes['aria-labelledby']) {
919
+ element.label = `[ref:${attributes['aria-labelledby']}]`;
920
+ }
921
+
922
+ this.elements.push(element);
923
+ },
924
+
925
+ // PHASE 1: Detect conditional rendering in components too
926
+ JSXExpressionContainer: (path: NodePath<t.JSXExpressionContainer>) => {
927
+ this.detectConditionalElements(path, `${componentName}.tsx`, componentPath, importMap);
928
+ }
929
+ });
930
+ }
931
+
932
+ /**
933
+ * Generate hash for cache key
934
+ */
935
+ private generateHash(elements: UIElement[]): string {
936
+ const crypto = require('crypto');
937
+ const content = JSON.stringify(elements);
938
+ return crypto.createHash('sha256').update(content).digest('hex');
939
+ }
940
+ }
941
+
942
+ // ============================================================================
943
+ // Factory Function
944
+ // ============================================================================
945
+
946
+ export async function buildUIModel(
947
+ screenId: string,
948
+ config: ScanConfig
949
+ ): Promise<UIModel> {
950
+ const scanner = new ReactScanner(config);
951
+ return scanner.buildUIModel(screenId);
952
+ }
953
+
954
+ export async function discoverScreens(
955
+ config: ScanConfig
956
+ ): Promise<string[]> {
957
+ const scanner = new ReactScanner(config);
958
+ return scanner.discoverScreens();
959
+ }