@silasfmartins/testhub 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 (296) hide show
  1. package/.github/copilot-instructions.md +520 -0
  2. package/biome.json +37 -0
  3. package/dist/index.d.ts +45 -0
  4. package/dist/index.js +169 -0
  5. package/dist/scripts/consumer-postinstall.d.ts +15 -0
  6. package/dist/scripts/consumer-postinstall.js +785 -0
  7. package/dist/scripts/generate-docs.d.ts +16 -0
  8. package/dist/scripts/generate-docs.js +1363 -0
  9. package/dist/scripts/generate-index.d.ts +2 -0
  10. package/dist/scripts/generate-index.js +314 -0
  11. package/dist/scripts/init-api.d.ts +2 -0
  12. package/dist/scripts/init-api.js +525 -0
  13. package/dist/scripts/init-banco.d.ts +2 -0
  14. package/dist/scripts/init-banco.js +347 -0
  15. package/dist/scripts/init-frontend.d.ts +2 -0
  16. package/dist/scripts/init-frontend.js +627 -0
  17. package/dist/scripts/init-mobile.d.ts +2 -0
  18. package/dist/scripts/init-mobile.js +481 -0
  19. package/dist/scripts/init-scenarios.d.ts +2 -0
  20. package/dist/scripts/init-scenarios.js +846 -0
  21. package/dist/scripts/init-ssh.d.ts +2 -0
  22. package/dist/scripts/init-ssh.js +639 -0
  23. package/dist/scripts/package-versions.d.ts +57 -0
  24. package/dist/scripts/package-versions.js +768 -0
  25. package/dist/scripts/postinstall.d.ts +1 -0
  26. package/dist/scripts/postinstall.js +527 -0
  27. package/dist/scripts/robust-build.d.ts +7 -0
  28. package/dist/scripts/robust-build.js +88 -0
  29. package/dist/scripts/setup-local-packages.d.ts +31 -0
  30. package/dist/scripts/setup-local-packages.js +237 -0
  31. package/dist/scripts/smart-override.d.ts +2 -0
  32. package/dist/scripts/smart-override.js +1360 -0
  33. package/dist/scripts/sync-configs.d.ts +27 -0
  34. package/dist/scripts/sync-configs.js +248 -0
  35. package/dist/scripts/test-biome-parse.d.ts +5 -0
  36. package/dist/scripts/test-biome-parse.js +84 -0
  37. package/dist/scripts/ultracite-setup.d.ts +4 -0
  38. package/dist/scripts/ultracite-setup.js +310 -0
  39. package/dist/scripts/update-all-init-scripts.d.ts +2 -0
  40. package/dist/scripts/update-all-init-scripts.js +52 -0
  41. package/dist/scripts/update-biome-schema.d.ts +15 -0
  42. package/dist/scripts/update-biome-schema.js +124 -0
  43. package/dist/src/AutoCoreFacade.d.ts +145 -0
  44. package/dist/src/AutoCoreFacade.js +217 -0
  45. package/dist/src/api/ApiActions.d.ts +297 -0
  46. package/dist/src/api/ApiActions.js +1905 -0
  47. package/dist/src/api/Certificate.d.ts +60 -0
  48. package/dist/src/api/Certificate.js +79 -0
  49. package/dist/src/api/JsonResponse.d.ts +116 -0
  50. package/dist/src/api/JsonResponse.js +206 -0
  51. package/dist/src/appium/DeviceFarmViewer.d.ts +79 -0
  52. package/dist/src/appium/DeviceFarmViewer.js +1083 -0
  53. package/dist/src/appium/MobileActions.d.ts +347 -0
  54. package/dist/src/appium/MobileActions.js +1632 -0
  55. package/dist/src/appium/MobileConnection.d.ts +160 -0
  56. package/dist/src/appium/MobileConnection.js +772 -0
  57. package/dist/src/config/envLoader.d.ts +123 -0
  58. package/dist/src/config/envLoader.js +361 -0
  59. package/dist/src/config/jest-safe-setup.d.ts +19 -0
  60. package/dist/src/config/jest-safe-setup.js +369 -0
  61. package/dist/src/config/timeouts.d.ts +32 -0
  62. package/dist/src/config/timeouts.js +38 -0
  63. package/dist/src/desktop/DesktopActions.d.ts +46 -0
  64. package/dist/src/desktop/DesktopActions.js +398 -0
  65. package/dist/src/desktop/DesktopConnection.d.ts +32 -0
  66. package/dist/src/desktop/DesktopConnection.js +84 -0
  67. package/dist/src/domain/entities/TestExecution.d.ts +117 -0
  68. package/dist/src/domain/entities/TestExecution.js +150 -0
  69. package/dist/src/domain/entities/TestReport.d.ts +114 -0
  70. package/dist/src/domain/entities/TestReport.js +179 -0
  71. package/dist/src/domain/repositories/ITestRepository.d.ts +196 -0
  72. package/dist/src/domain/repositories/ITestRepository.js +14 -0
  73. package/dist/src/domain/schemas/ValidationSchemas.d.ts +159 -0
  74. package/dist/src/domain/schemas/ValidationSchemas.js +181 -0
  75. package/dist/src/functions/errors/BaseError.d.ts +78 -0
  76. package/dist/src/functions/errors/BaseError.js +245 -0
  77. package/dist/src/functions/errors/ConfigurationError.d.ts +16 -0
  78. package/dist/src/functions/errors/ConfigurationError.js +48 -0
  79. package/dist/src/functions/errors/ErrorCatalog.d.ts +148 -0
  80. package/dist/src/functions/errors/ErrorCatalog.js +157 -0
  81. package/dist/src/functions/errors/GlobalErrorHandler.d.ts +101 -0
  82. package/dist/src/functions/errors/GlobalErrorHandler.js +281 -0
  83. package/dist/src/functions/errors/IntegrationError.d.ts +17 -0
  84. package/dist/src/functions/errors/IntegrationError.js +51 -0
  85. package/dist/src/functions/errors/SecurityError.d.ts +14 -0
  86. package/dist/src/functions/errors/SecurityError.js +42 -0
  87. package/dist/src/functions/errors/SystemError.d.ts +12 -0
  88. package/dist/src/functions/errors/SystemError.js +36 -0
  89. package/dist/src/functions/errors/ValidationError.d.ts +14 -0
  90. package/dist/src/functions/errors/ValidationError.js +61 -0
  91. package/dist/src/functions/errors/index.d.ts +12 -0
  92. package/dist/src/functions/errors/index.js +13 -0
  93. package/dist/src/global-setup.d.ts +1 -0
  94. package/dist/src/global-setup.js +1037 -0
  95. package/dist/src/helpers/BancoActions.d.ts +188 -0
  96. package/dist/src/helpers/BancoActions.js +581 -0
  97. package/dist/src/helpers/EnviromentHelper.d.ts +17 -0
  98. package/dist/src/helpers/EnviromentHelper.js +66 -0
  99. package/dist/src/helpers/ParallelExecutionHelper.d.ts +183 -0
  100. package/dist/src/helpers/ParallelExecutionHelper.js +375 -0
  101. package/dist/src/helpers/SyncSignal.d.ts +15 -0
  102. package/dist/src/helpers/SyncSignal.js +44 -0
  103. package/dist/src/hubdocs/CategoryDetector.d.ts +83 -0
  104. package/dist/src/hubdocs/CategoryDetector.js +401 -0
  105. package/dist/src/hubdocs/DirectStatementInterceptor.d.ts +54 -0
  106. package/dist/src/hubdocs/DirectStatementInterceptor.js +243 -0
  107. package/dist/src/hubdocs/ExecutionTracker.d.ts +107 -0
  108. package/dist/src/hubdocs/ExecutionTracker.js +702 -0
  109. package/dist/src/hubdocs/HubDocs.d.ts +395 -0
  110. package/dist/src/hubdocs/HubDocs.js +3586 -0
  111. package/dist/src/hubdocs/StatementMethodFilter.d.ts +71 -0
  112. package/dist/src/hubdocs/StatementMethodFilter.js +618 -0
  113. package/dist/src/hubdocs/StatementTracker.d.ts +417 -0
  114. package/dist/src/hubdocs/StatementTracker.js +2419 -0
  115. package/dist/src/hubdocs/SwaggerGenerator.d.ts +59 -0
  116. package/dist/src/hubdocs/SwaggerGenerator.js +405 -0
  117. package/dist/src/hubdocs/index.d.ts +9 -0
  118. package/dist/src/hubdocs/index.js +9 -0
  119. package/dist/src/hubdocs/types.d.ts +114 -0
  120. package/dist/src/hubdocs/types.js +5 -0
  121. package/dist/src/infrastructure/DependencyContainer.d.ts +142 -0
  122. package/dist/src/infrastructure/DependencyContainer.js +250 -0
  123. package/dist/src/infrastructure/adapters/AppiumAdapter.d.ts +168 -0
  124. package/dist/src/infrastructure/adapters/AppiumAdapter.js +468 -0
  125. package/dist/src/infrastructure/adapters/OracleAdapter.d.ts +150 -0
  126. package/dist/src/infrastructure/adapters/OracleAdapter.js +388 -0
  127. package/dist/src/infrastructure/adapters/PlaywrightAdapter.d.ts +192 -0
  128. package/dist/src/infrastructure/adapters/PlaywrightAdapter.js +382 -0
  129. package/dist/src/infrastructure/adapters/SSHAdapter.d.ts +141 -0
  130. package/dist/src/infrastructure/adapters/SSHAdapter.js +428 -0
  131. package/dist/src/interfaces.d.ts +501 -0
  132. package/dist/src/interfaces.js +25 -0
  133. package/dist/src/internal/fakes/__fake-actions__.d.ts +17 -0
  134. package/dist/src/internal/fakes/__fake-actions__.js +21 -0
  135. package/dist/src/internal/fakes/__forbidden__.d.ts +10 -0
  136. package/dist/src/internal/fakes/__forbidden__.js +18 -0
  137. package/dist/src/internal/fakes/__honeypot__.d.ts +15 -0
  138. package/dist/src/internal/fakes/__honeypot__.js +24 -0
  139. package/dist/src/octane/OctaneReporter.d.ts +13 -0
  140. package/dist/src/octane/OctaneReporter.js +61 -0
  141. package/dist/src/playwright/CryptoActions.d.ts +20 -0
  142. package/dist/src/playwright/CryptoActions.js +75 -0
  143. package/dist/src/playwright/EnhancedWebActions.d.ts +7 -0
  144. package/dist/src/playwright/EnhancedWebActions.js +65 -0
  145. package/dist/src/playwright/WebActions.d.ts +1599 -0
  146. package/dist/src/playwright/WebActions.js +11788 -0
  147. package/dist/src/playwright/actions/ActionTimeline.d.ts +36 -0
  148. package/dist/src/playwright/actions/ActionTimeline.js +101 -0
  149. package/dist/src/playwright/actions/RecoveryQueue.d.ts +82 -0
  150. package/dist/src/playwright/actions/RecoveryQueue.js +130 -0
  151. package/dist/src/playwright/actions/SelectorCache.d.ts +53 -0
  152. package/dist/src/playwright/actions/SelectorCache.js +96 -0
  153. package/dist/src/playwright/actions/index.d.ts +13 -0
  154. package/dist/src/playwright/actions/index.js +14 -0
  155. package/dist/src/playwright/actions/types.d.ts +147 -0
  156. package/dist/src/playwright/actions/types.js +5 -0
  157. package/dist/src/playwright/fixtures.d.ts +112 -0
  158. package/dist/src/playwright/fixtures.js +718 -0
  159. package/dist/src/playwright/network-logs-reporter.d.ts +7 -0
  160. package/dist/src/playwright/network-logs-reporter.js +66 -0
  161. package/dist/src/playwright/registerRecoveryWrappers.d.ts +1 -0
  162. package/dist/src/playwright/registerRecoveryWrappers.js +54 -0
  163. package/dist/src/security/BuildSecurity.d.ts +12 -0
  164. package/dist/src/security/BuildSecurity.js +138 -0
  165. package/dist/src/security/EulaProtection.d.ts +70 -0
  166. package/dist/src/security/EulaProtection.js +155 -0
  167. package/dist/src/security/HoneypotManager.d.ts +46 -0
  168. package/dist/src/security/HoneypotManager.js +234 -0
  169. package/dist/src/security/KeysManager.d.ts +36 -0
  170. package/dist/src/security/KeysManager.js +158 -0
  171. package/dist/src/security/ProofOfWorkIntegration.d.ts +64 -0
  172. package/dist/src/security/ProofOfWorkIntegration.js +206 -0
  173. package/dist/src/security/SecurityValidation.d.ts +21 -0
  174. package/dist/src/security/SecurityValidation.js +163 -0
  175. package/dist/src/security/SourceMapProtection.d.ts +55 -0
  176. package/dist/src/security/SourceMapProtection.js +220 -0
  177. package/dist/src/security/protector.d.ts +1 -0
  178. package/dist/src/security/protector.js +97 -0
  179. package/dist/src/ssh/SSHActions.d.ts +262 -0
  180. package/dist/src/ssh/SSHActions.js +790 -0
  181. package/dist/src/ssh/SSHClient.d.ts +99 -0
  182. package/dist/src/ssh/SSHClient.js +409 -0
  183. package/dist/src/statements/BaseStatement.d.ts +38 -0
  184. package/dist/src/statements/BaseStatement.js +78 -0
  185. package/dist/src/testContext/AuthStateManager.d.ts +93 -0
  186. package/dist/src/testContext/AuthStateManager.js +256 -0
  187. package/dist/src/testContext/CoverageManager.d.ts +198 -0
  188. package/dist/src/testContext/CoverageManager.js +917 -0
  189. package/dist/src/testContext/TestAnnotations.d.ts +476 -0
  190. package/dist/src/testContext/TestAnnotations.js +2647 -0
  191. package/dist/src/testContext/TestContext.d.ts +138 -0
  192. package/dist/src/testContext/TestContext.js +369 -0
  193. package/dist/src/testContext/UnifiedHtmlGenerator.d.ts +7 -0
  194. package/dist/src/testContext/UnifiedHtmlGenerator.js +264 -0
  195. package/dist/src/testContext/UnifiedReportManager.d.ts +211 -0
  196. package/dist/src/testContext/UnifiedReportManager.js +1206 -0
  197. package/dist/src/testhub/DynamicConfigManager.d.ts +121 -0
  198. package/dist/src/testhub/DynamicConfigManager.js +320 -0
  199. package/dist/src/testhub/SystemsManager.d.ts +119 -0
  200. package/dist/src/testhub/SystemsManager.js +365 -0
  201. package/dist/src/testhub/TestHubClient.d.ts +335 -0
  202. package/dist/src/testhub/TestHubClient.js +1215 -0
  203. package/dist/src/testhub/TestHubReporter.d.ts +62 -0
  204. package/dist/src/testhub/TestHubReporter.js +576 -0
  205. package/dist/src/testhub/TestHubVars.d.ts +116 -0
  206. package/dist/src/testhub/TestHubVars.js +273 -0
  207. package/dist/src/utils/ActionInterceptor.d.ts +59 -0
  208. package/dist/src/utils/ActionInterceptor.js +741 -0
  209. package/dist/src/utils/ArtifactsCompressor.d.ts +43 -0
  210. package/dist/src/utils/ArtifactsCompressor.js +181 -0
  211. package/dist/src/utils/AutoLogsFinal.d.ts +47 -0
  212. package/dist/src/utils/AutoLogsFinal.js +148 -0
  213. package/dist/src/utils/CodeGenSession.d.ts +114 -0
  214. package/dist/src/utils/CodeGenSession.js +264 -0
  215. package/dist/src/utils/ConfigLogger.d.ts +133 -0
  216. package/dist/src/utils/ConfigLogger.js +611 -0
  217. package/dist/src/utils/CustomReporter.d.ts +22 -0
  218. package/dist/src/utils/CustomReporter.js +352 -0
  219. package/dist/src/utils/DataStore.d.ts +171 -0
  220. package/dist/src/utils/DataStore.js +484 -0
  221. package/dist/src/utils/DatabaseInterceptor.d.ts +19 -0
  222. package/dist/src/utils/DatabaseInterceptor.js +295 -0
  223. package/dist/src/utils/DateHelper.d.ts +16 -0
  224. package/dist/src/utils/DateHelper.js +120 -0
  225. package/dist/src/utils/DateValidator.d.ts +4 -0
  226. package/dist/src/utils/DateValidator.js +51 -0
  227. package/dist/src/utils/DocumentGenerator.d.ts +35 -0
  228. package/dist/src/utils/DocumentGenerator.js +129 -0
  229. package/dist/src/utils/EvidenceCapture.d.ts +90 -0
  230. package/dist/src/utils/EvidenceCapture.js +600 -0
  231. package/dist/src/utils/EvidenceReportGenerator.d.ts +70 -0
  232. package/dist/src/utils/EvidenceReportGenerator.js +799 -0
  233. package/dist/src/utils/FrameManagementUtil.d.ts +42 -0
  234. package/dist/src/utils/FrameManagementUtil.js +75 -0
  235. package/dist/src/utils/GlobalStatementsInterceptor.d.ts +1 -0
  236. package/dist/src/utils/GlobalStatementsInterceptor.js +1 -0
  237. package/dist/src/utils/HTMLTemplate.d.ts +1 -0
  238. package/dist/src/utils/HTMLTemplate.js +1034 -0
  239. package/dist/src/utils/InterceptacaoMagica.d.ts +23 -0
  240. package/dist/src/utils/InterceptacaoMagica.js +365 -0
  241. package/dist/src/utils/LogSanitizer.d.ts +35 -0
  242. package/dist/src/utils/LogSanitizer.js +110 -0
  243. package/dist/src/utils/Logger.d.ts +65 -0
  244. package/dist/src/utils/Logger.js +284 -0
  245. package/dist/src/utils/McpLocalClient.d.ts +141 -0
  246. package/dist/src/utils/McpLocalClient.js +871 -0
  247. package/dist/src/utils/PDFEvidenceGenerator.d.ts +20 -0
  248. package/dist/src/utils/PDFEvidenceGenerator.js +156 -0
  249. package/dist/src/utils/SpecFileAnalyzer.d.ts +35 -0
  250. package/dist/src/utils/SpecFileAnalyzer.js +209 -0
  251. package/dist/src/utils/StatementInterceptor.d.ts +18 -0
  252. package/dist/src/utils/StatementInterceptor.js +87 -0
  253. package/dist/src/utils/StatementLogger.d.ts +33 -0
  254. package/dist/src/utils/StatementLogger.js +113 -0
  255. package/dist/src/utils/StatementsInterceptor.d.ts +1 -0
  256. package/dist/src/utils/StatementsInterceptor.js +1 -0
  257. package/dist/src/utils/TeamsFlushHook.d.ts +17 -0
  258. package/dist/src/utils/TeamsFlushHook.js +168 -0
  259. package/dist/src/utils/TerminalLogCapture.d.ts +158 -0
  260. package/dist/src/utils/TerminalLogCapture.js +531 -0
  261. package/dist/src/utils/TestMethodLogger.d.ts +70 -0
  262. package/dist/src/utils/TestMethodLogger.js +95 -0
  263. package/dist/src/utils/UnifiedTeardown.d.ts +4 -0
  264. package/dist/src/utils/UnifiedTeardown.js +400 -0
  265. package/dist/src/utils/XPathCatalog.d.ts +152 -0
  266. package/dist/src/utils/XPathCatalog.js +350 -0
  267. package/dist/src/utils/generators.d.ts +90 -0
  268. package/dist/src/utils/generators.js +167 -0
  269. package/dist/src/utils/testRecovery/ResilientPlaywright.d.ts +152 -0
  270. package/dist/src/utils/testRecovery/ResilientPlaywright.js +715 -0
  271. package/dist/src/utils/testRecovery/TestRecoveryClient.d.ts +801 -0
  272. package/dist/src/utils/testRecovery/TestRecoveryClient.js +1415 -0
  273. package/dist/src/utils/testRecovery/autoFixCode.d.ts +65 -0
  274. package/dist/src/utils/testRecovery/autoFixCode.js +32 -0
  275. package/dist/vitest.config.d.ts +2 -0
  276. package/dist/vitest.config.js +59 -0
  277. package/dist/wdio.conf.d.ts +1 -0
  278. package/dist/wdio.conf.js +420 -0
  279. package/package.json +137 -0
  280. package/protect-loader.mjs +643 -0
  281. package/scripts/consumer-postinstall.ts +975 -0
  282. package/scripts/generate-index.ts +343 -0
  283. package/scripts/init-api.ts +613 -0
  284. package/scripts/init-banco.ts +437 -0
  285. package/scripts/init-frontend.ts +727 -0
  286. package/scripts/init-mobile.ts +558 -0
  287. package/scripts/init-scenarios.ts +925 -0
  288. package/scripts/init-ssh.ts +734 -0
  289. package/scripts/package-versions.ts +978 -0
  290. package/scripts/postinstall.ts +605 -0
  291. package/scripts/smart-override.ts +1675 -0
  292. package/scripts/sync-configs.ts +302 -0
  293. package/scripts/ultracite-setup.ts +370 -0
  294. package/src/types/globals.d.ts +48 -0
  295. package/tsconfig.json +29 -0
  296. package/types/autocore-sync-signal.d.ts +10 -0
@@ -0,0 +1,871 @@
1
+ /**
2
+ * MCP Local JSON-RPC Client (v2.7.44-r3 — items 22, 26, 27, 28)
3
+ *
4
+ * Permite ao framework chamar tools no MCP local (sidecar/background)
5
+ * via JSON-RPC sobre STDIO ou HTTP, sem depender de GitHub Copilot Chat.
6
+ *
7
+ * Transporte (ordem de prioridade — item 26.2):
8
+ * 1. STDIO direto (`autocore-hub-mcp`) com Content-Length framing (MCP oficial) — caminho principal
9
+ * 2. HTTP local (`HUB_MCP_LOCAL_URL`) — bridge HTTP explícita
10
+ * 3. Backend MCP remoto — fallback/control-plane (backend decide reroute local ou fallback)
11
+ *
12
+ * Uso principal: `fix_xpath_in_attributes` para auto-fix de XPaths
13
+ * nos arquivos Attributes* do projeto local após recovery bem-sucedido.
14
+ */
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ import { Logger } from "./Logger.js";
18
+ // ---------------------------------------------------------------------------
19
+ // Dedupe tracker (evitar edições repetidas na mesma execução)
20
+ // ---------------------------------------------------------------------------
21
+ const _appliedFixes = new Set();
22
+ function dedupeKey(oldXPath, newXPath, methodName) {
23
+ return `${oldXPath}||${newXPath}||${methodName || ""}`;
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Configuração (lazy reads — pega da env do Windows a cada chamada)
27
+ // ---------------------------------------------------------------------------
28
+ function getMcpLocalUrl() {
29
+ return (process.env.HUB_MCP_LOCAL_URL || "").trim();
30
+ }
31
+ function getMcpBackendUrl() {
32
+ return (process.env.AUTOCORE_HUB_URL ||
33
+ process.env.AUTOCORE_HUB_IP ||
34
+ "http://brtlvlty0559pl:3333");
35
+ }
36
+ const MCP_LOCAL_TIMEOUT = 15_000;
37
+ const MCP_LOCAL_MAX_RETRIES = 2;
38
+ /** Lê user_hash das variáveis de ambiente do Windows (lazy, a cada chamada) */
39
+ function getMcpUserHash() {
40
+ return process.env.HUB_MCP_USER_HASH || process.env.MCP_USER_HASH || "";
41
+ }
42
+ function makeId() {
43
+ return `autofix-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
44
+ }
45
+ function enrichArgsWithUserHash(args) {
46
+ const userHash = getMcpUserHash();
47
+ if (!userHash)
48
+ return { ...args };
49
+ return {
50
+ ...args,
51
+ user_hash: args.user_hash || userHash,
52
+ userHash: args.userHash || userHash,
53
+ mcpUserHash: args.mcpUserHash || userHash,
54
+ };
55
+ }
56
+ // Cache de tools/list por canal para evitar health-check repetido na mesma execução
57
+ const _toolsListCache = new Map();
58
+ // ---------------------------------------------------------------------------
59
+ // 1) STDIO transport com Content-Length framing (MCP oficial — item 27.1/28.2)
60
+ // ---------------------------------------------------------------------------
61
+ let _stdioProcess = null;
62
+ const _stdioPendingRequests = new Map();
63
+ let _stdioAvailable = null; // null = not checked yet
64
+ function splitCommandLine(value) {
65
+ const normalized = value.trim();
66
+ if (!normalized)
67
+ return [];
68
+ return (normalized
69
+ .match(/(?:[^\s"]+|"[^"]*")+/g)
70
+ ?.map((part) => part.replace(/^"|"$/g, "")) || []);
71
+ }
72
+ function getMcpStdioCandidates() {
73
+ const candidates = [];
74
+ const custom = String(process.env.HUB_MCP_STDIO_BIN || "").trim();
75
+ if (custom) {
76
+ const parts = splitCommandLine(custom);
77
+ if (parts.length > 0) {
78
+ const [command, ...rest] = parts;
79
+ const args = rest.includes("--stdio") ? rest : [...rest, "--stdio"];
80
+ candidates.push({
81
+ command,
82
+ args,
83
+ label: `custom:${custom}`,
84
+ });
85
+ }
86
+ }
87
+ candidates.push({
88
+ command: "autocore-hub-mcp",
89
+ args: ["--stdio"],
90
+ label: "global:autocore-hub-mcp",
91
+ });
92
+ candidates.push({
93
+ command: "npx",
94
+ args: ["-y", "@silasfmartins/testhub-orchestrator-mcp", "--stdio"],
95
+ label: "npx:@silasfmartins/testhub-orchestrator-mcp",
96
+ });
97
+ // remover duplicados mantendo ordem
98
+ const dedupe = new Set();
99
+ return candidates.filter((candidate) => {
100
+ const key = `${candidate.command} ${candidate.args.join(" ")}`;
101
+ if (dedupe.has(key))
102
+ return false;
103
+ dedupe.add(key);
104
+ return true;
105
+ });
106
+ }
107
+ function findHeaderEnd(buf) {
108
+ // Procura \r\n\r\n
109
+ for (let i = 0; i < buf.length - 3; i++) {
110
+ if (buf[i] === 0x0d &&
111
+ buf[i + 1] === 0x0a &&
112
+ buf[i + 2] === 0x0d &&
113
+ buf[i + 3] === 0x0a) {
114
+ return i;
115
+ }
116
+ }
117
+ return -1;
118
+ }
119
+ function attachStdioHandlers(processRef, label) {
120
+ processRef.on("error", (err) => {
121
+ Logger.warning(`[McpLocalClient] Falha no processo STDIO (${label}): ${err instanceof Error ? err.message : String(err)}`);
122
+ _stdioAvailable = false;
123
+ _stdioProcess = null;
124
+ });
125
+ processRef.on("exit", () => {
126
+ _stdioProcess = null;
127
+ for (const [id, pending] of _stdioPendingRequests) {
128
+ clearTimeout(pending.timer);
129
+ pending.resolve({
130
+ jsonrpc: "2.0",
131
+ id,
132
+ error: { code: -1, message: "STDIO process exited" },
133
+ });
134
+ }
135
+ _stdioPendingRequests.clear();
136
+ });
137
+ // Parser Content-Length (MCP/LSP framing — item 27.1/28.2)
138
+ // Formato: "Content-Length: <n>\r\n\r\n<json-body>"
139
+ // Também aceita newline-delimited como fallback para compatibilidade.
140
+ let rawBuffer = Buffer.alloc(0);
141
+ processRef.stdout?.on("data", (chunk) => {
142
+ rawBuffer = Buffer.concat([rawBuffer, chunk]);
143
+ parseStdioBuffer();
144
+ });
145
+ function parseStdioBuffer() {
146
+ // eslint-disable-next-line no-constant-condition
147
+ while (true) {
148
+ // Tenta Content-Length framing primeiro
149
+ const headerEnd = findHeaderEnd(rawBuffer);
150
+ if (headerEnd !== -1) {
151
+ const headerStr = rawBuffer.subarray(0, headerEnd).toString("utf-8");
152
+ const clMatch = headerStr.match(/Content-Length:\s*(\d+)/i);
153
+ if (clMatch) {
154
+ const bodyLen = parseInt(clMatch[1], 10);
155
+ const bodyStart = headerEnd + 4; // após \r\n\r\n
156
+ if (rawBuffer.length >= bodyStart + bodyLen) {
157
+ const bodyStr = rawBuffer
158
+ .subarray(bodyStart, bodyStart + bodyLen)
159
+ .toString("utf-8");
160
+ rawBuffer = rawBuffer.subarray(bodyStart + bodyLen);
161
+ dispatchStdioMessage(bodyStr);
162
+ continue; // pode haver mais mensagens no buffer
163
+ }
164
+ break; // aguardar mais dados
165
+ }
166
+ }
167
+ // Fallback: newline-delimited (compatibilidade)
168
+ const nlIdx = rawBuffer.indexOf(0x0a); // \n
169
+ if (nlIdx === -1)
170
+ break;
171
+ const line = rawBuffer.subarray(0, nlIdx).toString("utf-8").trim();
172
+ rawBuffer = rawBuffer.subarray(nlIdx + 1);
173
+ if (line)
174
+ dispatchStdioMessage(line);
175
+ }
176
+ }
177
+ function dispatchStdioMessage(text) {
178
+ try {
179
+ const msg = JSON.parse(text);
180
+ if (msg.id && _stdioPendingRequests.has(String(msg.id))) {
181
+ const pending = _stdioPendingRequests.get(String(msg.id));
182
+ clearTimeout(pending.timer);
183
+ _stdioPendingRequests.delete(String(msg.id));
184
+ pending.resolve(msg);
185
+ }
186
+ }
187
+ catch {
188
+ /* ignore non-JSON */
189
+ }
190
+ }
191
+ }
192
+ async function spawnCandidateWithProbe(candidate) {
193
+ const { spawn } = await import("child_process");
194
+ // No Windows, executáveis globais do npm são .cmd e precisam de shell: true
195
+ const isWindows = process.platform === "win32";
196
+ const processRef = spawn(candidate.command, candidate.args, {
197
+ stdio: ["pipe", "pipe", "pipe"],
198
+ env: { ...process.env },
199
+ windowsHide: true,
200
+ shell: isWindows,
201
+ });
202
+ return await new Promise((resolve) => {
203
+ let settled = false;
204
+ const finish = (result) => {
205
+ if (settled)
206
+ return;
207
+ settled = true;
208
+ processRef.off("error", onError);
209
+ processRef.off("exit", onExit);
210
+ clearTimeout(timer);
211
+ resolve(result);
212
+ };
213
+ const onError = () => {
214
+ try {
215
+ processRef.kill();
216
+ }
217
+ catch { }
218
+ finish(null);
219
+ };
220
+ const onExit = (code) => {
221
+ if (code === null)
222
+ return;
223
+ finish(null);
224
+ };
225
+ const timer = setTimeout(() => finish(processRef), 450);
226
+ processRef.once("error", onError);
227
+ processRef.once("exit", onExit);
228
+ });
229
+ }
230
+ /**
231
+ * Spawna o processo MCP local via STDIO se ainda não ativo.
232
+ * Protocolo: JSON-RPC sobre Content-Length framing (padrão MCP/LSP).
233
+ */
234
+ async function ensureStdioProcess() {
235
+ if (_stdioAvailable === false)
236
+ return false;
237
+ if (_stdioProcess && !_stdioProcess.killed)
238
+ return true;
239
+ try {
240
+ const candidates = getMcpStdioCandidates();
241
+ for (const candidate of candidates) {
242
+ const processRef = await spawnCandidateWithProbe(candidate);
243
+ if (!processRef) {
244
+ Logger.info(`[McpLocalClient] MCP local indisponível via ${candidate.label}, tentando próximo...`);
245
+ continue;
246
+ }
247
+ _stdioProcess = processRef;
248
+ attachStdioHandlers(processRef, candidate.label);
249
+ _stdioAvailable = true;
250
+ Logger.info(`[McpLocalClient] MCP local iniciado via ${candidate.label}`);
251
+ return true;
252
+ }
253
+ _stdioAvailable = false;
254
+ _stdioProcess = null;
255
+ return false;
256
+ }
257
+ catch {
258
+ _stdioAvailable = false;
259
+ _stdioProcess = null;
260
+ return false;
261
+ }
262
+ }
263
+ /**
264
+ * Envia JSON-RPC via STDIO com Content-Length framing e aguarda resposta
265
+ */
266
+ async function callMcpStdio(method, params) {
267
+ if (!_stdioProcess?.stdin?.writable) {
268
+ throw new Error("STDIO process not available");
269
+ }
270
+ const id = makeId();
271
+ const request = { jsonrpc: "2.0", id, method, params };
272
+ const body = JSON.stringify(request);
273
+ return new Promise((resolve) => {
274
+ const timer = setTimeout(() => {
275
+ _stdioPendingRequests.delete(id);
276
+ resolve({
277
+ jsonrpc: "2.0",
278
+ id,
279
+ error: { code: -1, message: "STDIO timeout" },
280
+ });
281
+ }, MCP_LOCAL_TIMEOUT);
282
+ _stdioPendingRequests.set(id, { resolve, timer });
283
+ // Content-Length framing (MCP/LSP padrão)
284
+ const frame = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
285
+ _stdioProcess.stdin.write(frame, "utf-8");
286
+ });
287
+ }
288
+ // ---------------------------------------------------------------------------
289
+ // 2) HTTP transport (local ou backend remoto)
290
+ // ---------------------------------------------------------------------------
291
+ async function callMcpHttp(toolName, args, baseUrl) {
292
+ const reqBody = {
293
+ jsonrpc: "2.0",
294
+ id: makeId(),
295
+ method: "tools/call",
296
+ params: { name: toolName, arguments: args },
297
+ };
298
+ const controller = new AbortController();
299
+ const timeoutId = setTimeout(() => controller.abort(), MCP_LOCAL_TIMEOUT);
300
+ try {
301
+ const userHash = getMcpUserHash();
302
+ let url = `${baseUrl.replace(/\/$/, "")}/mcp`;
303
+ if (userHash) {
304
+ url += `?user_hash=${encodeURIComponent(userHash)}`;
305
+ }
306
+ const headers = {
307
+ "Content-Type": "application/json",
308
+ };
309
+ if (userHash) {
310
+ headers["x-user-hash"] = userHash;
311
+ }
312
+ const res = await fetch(url, {
313
+ method: "POST",
314
+ headers,
315
+ body: JSON.stringify(reqBody),
316
+ signal: controller.signal,
317
+ });
318
+ if (!res.ok) {
319
+ const text = await res.text().catch(() => "");
320
+ return {
321
+ jsonrpc: "2.0",
322
+ id: reqBody.id,
323
+ error: { code: res.status, message: `HTTP ${res.status}: ${text}` },
324
+ };
325
+ }
326
+ return (await res.json());
327
+ }
328
+ finally {
329
+ clearTimeout(timeoutId);
330
+ }
331
+ }
332
+ // ---------------------------------------------------------------------------
333
+ // 3) Health-check: tools/list (item 26.3)
334
+ // ---------------------------------------------------------------------------
335
+ /**
336
+ * Verifica se uma tool existe no canal informado via tools/list.
337
+ * Resultado é cacheado por canal para a execução inteira.
338
+ */
339
+ async function hasToolOnChannel(channel, toolName) {
340
+ if (_toolsListCache.has(channel)) {
341
+ const cached = _toolsListCache.get(channel);
342
+ return cached ? cached.has(toolName) : false;
343
+ }
344
+ try {
345
+ let response;
346
+ if (channel === "stdio") {
347
+ const ok = await ensureStdioProcess();
348
+ if (!ok) {
349
+ _toolsListCache.set(channel, null);
350
+ return false;
351
+ }
352
+ response = await callMcpStdio("tools/list", {});
353
+ }
354
+ else {
355
+ const baseUrl = channel === "http-local" ? getMcpLocalUrl() : getMcpBackendUrl();
356
+ if (!baseUrl) {
357
+ _toolsListCache.set(channel, null);
358
+ return false;
359
+ }
360
+ const userHash = getMcpUserHash();
361
+ // HTTP requer user_hash (item 28.1)
362
+ if (!userHash) {
363
+ _toolsListCache.set(channel, null);
364
+ return false;
365
+ }
366
+ const reqBody = {
367
+ jsonrpc: "2.0",
368
+ id: makeId(),
369
+ method: "tools/list",
370
+ params: {},
371
+ };
372
+ const controller = new AbortController();
373
+ const timeoutId = setTimeout(() => controller.abort(), 8_000);
374
+ try {
375
+ let url = `${baseUrl.replace(/\/$/, "")}/mcp`;
376
+ if (userHash)
377
+ url += `?user_hash=${encodeURIComponent(userHash)}`;
378
+ const headers = {
379
+ "Content-Type": "application/json",
380
+ };
381
+ if (userHash)
382
+ headers["x-user-hash"] = userHash;
383
+ const res = await fetch(url, {
384
+ method: "POST",
385
+ headers,
386
+ body: JSON.stringify(reqBody),
387
+ signal: controller.signal,
388
+ });
389
+ if (!res.ok) {
390
+ _toolsListCache.set(channel, null);
391
+ return false;
392
+ }
393
+ response = (await res.json());
394
+ }
395
+ finally {
396
+ clearTimeout(timeoutId);
397
+ }
398
+ }
399
+ if (response.error) {
400
+ _toolsListCache.set(channel, null);
401
+ return false;
402
+ }
403
+ // Extrair nomes das tools
404
+ const tools = response.result;
405
+ const toolSet = new Set();
406
+ if (tools?.tools && Array.isArray(tools.tools)) {
407
+ for (const t of tools.tools) {
408
+ if (t.name)
409
+ toolSet.add(t.name);
410
+ }
411
+ }
412
+ _toolsListCache.set(channel, toolSet);
413
+ return toolSet.has(toolName);
414
+ }
415
+ catch {
416
+ _toolsListCache.set(channel, null);
417
+ return false;
418
+ }
419
+ }
420
+ // ---------------------------------------------------------------------------
421
+ // 4) Chamada com fallback por canal (items 26.2, 28.5)
422
+ // ---------------------------------------------------------------------------
423
+ /**
424
+ * Tenta chamar tool no MCP seguindo ordem de prioridade:
425
+ * 1. STDIO direto (não requer user_hash — item 28.1)
426
+ * 2. HTTP local (HUB_MCP_LOCAL_URL) — requer user_hash
427
+ * 3. Backend MCP remoto — quando habilitado em options.allowBackend
428
+ *
429
+ * Faz health-check (tools/list) antes de chamar, para evitar -32601.
430
+ */
431
+ async function callToolWithFallback(toolName, args, options = {}) {
432
+ const channels = options.preferBackend && options.allowBackend
433
+ ? ["http-backend", "stdio", "http-local"]
434
+ : ["stdio", "http-local"];
435
+ if (options.allowBackend && !channels.includes("http-backend")) {
436
+ channels.push("http-backend");
437
+ }
438
+ for (const channel of channels) {
439
+ const available = await hasToolOnChannel(channel, toolName);
440
+ if (!available) {
441
+ Logger.info(`[McpLocalClient] Tool '${toolName}' não disponível via ${channel}, tentando próximo...`);
442
+ continue;
443
+ }
444
+ try {
445
+ let response;
446
+ if (channel === "stdio") {
447
+ response = await callMcpStdio("tools/call", {
448
+ name: toolName,
449
+ arguments: args,
450
+ });
451
+ }
452
+ else {
453
+ const baseUrl = channel === "http-local" ? getMcpLocalUrl() : getMcpBackendUrl();
454
+ if (!baseUrl) {
455
+ Logger.info(`[McpLocalClient] URL não configurada para canal ${channel}, pulando...`);
456
+ continue;
457
+ }
458
+ response = await callMcpHttp(toolName, args, baseUrl);
459
+ }
460
+ if (response.error?.code === -32601) {
461
+ Logger.warning(`[McpLocalClient] Tool '${toolName}' retornou -32601 via ${channel}.`);
462
+ _toolsListCache.delete(channel);
463
+ continue;
464
+ }
465
+ return { response, channel };
466
+ }
467
+ catch (err) {
468
+ Logger.warning(`[McpLocalClient] Erro no canal ${channel}: ${err instanceof Error ? err.message : String(err)}`);
469
+ continue;
470
+ }
471
+ }
472
+ return {
473
+ response: {
474
+ jsonrpc: "2.0",
475
+ id: makeId(),
476
+ error: {
477
+ code: -1,
478
+ message: "Nenhum canal MCP disponível para a tool solicitada.",
479
+ },
480
+ },
481
+ channel: "http-backend",
482
+ };
483
+ }
484
+ // ---------------------------------------------------------------------------
485
+ // 5) Parse robusto de resposta (item 27.2/28.3)
486
+ // ---------------------------------------------------------------------------
487
+ /**
488
+ * Extrai FixXPathInAttributesResult da resposta do MCP.
489
+ *
490
+ * Tenta na ordem:
491
+ * 1. response.result como objeto estruturado (top-level)
492
+ * 2. response.result.content[].text com JSON serializado
493
+ * 3. null se nenhum formato reconhecido
494
+ */
495
+ function parseFixResult(raw) {
496
+ if (!raw || typeof raw !== "object")
497
+ return null;
498
+ const obj = raw;
499
+ // 0) Alguns retornos vêm encapsulados em payload
500
+ if (obj.payload && typeof obj.payload === "object") {
501
+ const payloadResult = parseFixResult(obj.payload);
502
+ if (payloadResult)
503
+ return payloadResult;
504
+ }
505
+ // 1) Top-level estruturado: { success, filePath, ... }
506
+ if ("success" in obj || "fileUpdated" in obj || "nextAction" in obj) {
507
+ return {
508
+ success: obj.success === true ||
509
+ obj.fileUpdated === true ||
510
+ obj.nextAction === "retry",
511
+ filePath: obj.filePath || undefined,
512
+ className: obj.className || undefined,
513
+ methodName: obj.methodName || undefined,
514
+ fileUpdated: obj.fileUpdated === true,
515
+ executedOn: obj.executedOn || undefined,
516
+ localMcpExecuted: obj.localMcpExecuted === true,
517
+ localMcpRequestId: obj.localMcpRequestId || undefined,
518
+ nextAction: obj.nextAction || undefined,
519
+ error: obj.error || undefined,
520
+ proof: obj.proof && typeof obj.proof === "object"
521
+ ? obj.proof
522
+ : undefined,
523
+ };
524
+ }
525
+ // 2) content[].text com JSON serializado
526
+ const content = obj.content;
527
+ if (Array.isArray(content)) {
528
+ for (const item of content) {
529
+ if (item && typeof item === "object" && typeof item.text === "string") {
530
+ try {
531
+ const parsed = JSON.parse(item.text);
532
+ if (parsed && typeof parsed === "object") {
533
+ return parseFixResult(parsed); // recurse
534
+ }
535
+ }
536
+ catch {
537
+ /* não é JSON válido, ignorar */
538
+ }
539
+ }
540
+ }
541
+ }
542
+ return null;
543
+ }
544
+ /**
545
+ * Determina se o resultado é válido o suficiente para contabilizar no dedupe (item 28.4).
546
+ * Só deduplica quando houve processamento real:
547
+ * - success=true, ou
548
+ * - nextAction=manual_review com payload válido
549
+ */
550
+ function isValidForDedupe(result) {
551
+ if (!result)
552
+ return false;
553
+ if (result.success)
554
+ return true;
555
+ if (result.nextAction === "manual_review" &&
556
+ (result.filePath || result.className))
557
+ return true;
558
+ return false;
559
+ }
560
+ function verifyFixOnDisk(params) {
561
+ const normalize = (value) => value
562
+ .replace(/\r\n/g, "\n")
563
+ .replace(/["']/g, "'")
564
+ .replace(/\s+/g, " ")
565
+ .trim();
566
+ const oldNormalized = normalize(params.oldXPath);
567
+ const newNormalized = normalize(params.newXPath);
568
+ if (!newNormalized)
569
+ return false;
570
+ try {
571
+ if (params.filePath && fs.existsSync(params.filePath)) {
572
+ const content = fs.readFileSync(params.filePath, "utf-8");
573
+ const normalizedContent = normalize(content);
574
+ return (normalizedContent.includes(newNormalized) &&
575
+ (!oldNormalized || !normalizedContent.includes(oldNormalized)));
576
+ }
577
+ }
578
+ catch {
579
+ // segue para fallback
580
+ }
581
+ // fallback: verificar arquivos Attributes dentro do projectRoot
582
+ try {
583
+ const queue = [params.projectRoot];
584
+ let scanned = 0;
585
+ const maxScanned = 400;
586
+ while (queue.length > 0 && scanned < maxScanned) {
587
+ const current = queue.shift();
588
+ let entries = [];
589
+ try {
590
+ entries = fs.readdirSync(current, { withFileTypes: true });
591
+ }
592
+ catch {
593
+ continue;
594
+ }
595
+ for (const entry of entries) {
596
+ if (scanned >= maxScanned)
597
+ break;
598
+ const fullPath = path.join(current, entry.name);
599
+ if (entry.isDirectory()) {
600
+ if (entry.name === "node_modules" ||
601
+ entry.name === ".git" ||
602
+ entry.name === "dist") {
603
+ continue;
604
+ }
605
+ queue.push(fullPath);
606
+ continue;
607
+ }
608
+ scanned += 1;
609
+ const lower = entry.name.toLowerCase();
610
+ if (!lower.endsWith(".ts"))
611
+ continue;
612
+ if (!lower.includes("attributes"))
613
+ continue;
614
+ let content = "";
615
+ try {
616
+ content = fs.readFileSync(fullPath, "utf-8");
617
+ }
618
+ catch {
619
+ continue;
620
+ }
621
+ const normalizedContent = normalize(content);
622
+ if (normalizedContent.includes(newNormalized) &&
623
+ (!oldNormalized || !normalizedContent.includes(oldNormalized))) {
624
+ return true;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ catch {
630
+ // ignore
631
+ }
632
+ return false;
633
+ }
634
+ // ---------------------------------------------------------------------------
635
+ // Public API
636
+ // ---------------------------------------------------------------------------
637
+ /**
638
+ * Chama `fix_xpath_in_attributes` no MCP local para corrigir XPath em Attributes*.
639
+ *
640
+ * Transporte (item 26.2/28.5):
641
+ * 1. STDIO direto (Content-Length framing) — não requer user_hash (item 28.1)
642
+ * 2. HTTP local (HUB_MCP_LOCAL_URL) — requer user_hash
643
+ * 3. Backend MCP remoto (control-plane para reroute local por user_hash)
644
+ *
645
+ * - Faz health-check (tools/list) antes de chamar (item 26.3)
646
+ * - Parse robusto: top-level → content[].text (item 27.2/28.3)
647
+ * - Dedupe somente após resultado válido (item 28.4)
648
+ * - Retry curto (2 tentativas) para erros transitórios
649
+ * - Não bloqueia o teste em caso de falha
650
+ * - Pula em execução Azure (sem filesystem local)
651
+ *
652
+ * @returns Resultado da correção ou null se ignorado/falhou
653
+ */
654
+ export async function fixXPathViaLocalMcp(params) {
655
+ // Não executar em Azure/CI
656
+ if (process.env.TF_BUILD === "True" ||
657
+ process.env.AGENT_NAME ||
658
+ process.env.CI === "true") {
659
+ return null;
660
+ }
661
+ // (item 28.1) user_hash agora é exigido apenas para HTTP, não para STDIO.
662
+ // A validação ocorre dentro de hasToolOnChannel/callMcpHttp para canais HTTP.
663
+ // Dedupe
664
+ const key = dedupeKey(params.oldXPath, params.newXPath, params.methodName);
665
+ if (_appliedFixes.has(key)) {
666
+ Logger.info(`[McpLocalClient] Auto-fix já aplicado nesta execução, ignorando: ${params.methodName || params.oldXPath}`);
667
+ return null;
668
+ }
669
+ // Enviar projectRoot exatamente como recebido (Windows/UNC)
670
+ let projectRoot = params.projectRoot || process.env.AUTOCORE_PROJECT_ROOT || "";
671
+ if (!projectRoot) {
672
+ try {
673
+ projectRoot = process.cwd();
674
+ }
675
+ catch { }
676
+ }
677
+ if (!projectRoot) {
678
+ Logger.warning("[McpLocalClient] projectRoot não disponível, auto-fix de arquivo ignorado.");
679
+ return null;
680
+ }
681
+ // Não normalizar nem converter barras; aceitar UNC/Windows
682
+ const args = {
683
+ projectRoot,
684
+ oldXPath: params.oldXPath,
685
+ newXPath: params.newXPath,
686
+ confidence: params.confidence ?? 0.8,
687
+ executionSource: params.executionSource || "Local",
688
+ };
689
+ if (params.attributesFile)
690
+ args.attributesFile = params.attributesFile;
691
+ if (params.className)
692
+ args.className = params.className;
693
+ if (params.methodName)
694
+ args.methodName = params.methodName;
695
+ for (let attempt = 1; attempt <= MCP_LOCAL_MAX_RETRIES; attempt++) {
696
+ try {
697
+ const { response, channel } = await callToolWithFallback("fix_xpath_in_attributes", args, {
698
+ allowBackend: true,
699
+ });
700
+ // (item 27.3) Log do canal utilizado
701
+ Logger.info(`[McpLocalClient] Canal utilizado: ${channel}`);
702
+ if (response.error) {
703
+ if (response.error.code === -32601 || response.error.code === -1) {
704
+ Logger.warning(`[McpLocalClient] fix_xpath_in_attributes indisponível em todos os canais.`);
705
+ return null;
706
+ }
707
+ throw new Error(response.error.message);
708
+ }
709
+ // (item 27.2/28.3) Parse robusto da resposta
710
+ const result = parseFixResult(response.result);
711
+ if (!result) {
712
+ Logger.warning("[McpLocalClient] Auto-fix sem payload estruturado de retorno.");
713
+ return null;
714
+ }
715
+ const diskConfirmed = verifyFixOnDisk({
716
+ filePath: result.filePath,
717
+ projectRoot,
718
+ oldXPath: params.oldXPath,
719
+ newXPath: params.newXPath,
720
+ });
721
+ const effectiveSuccess = result.success === true &&
722
+ (result.fileUpdated === true || diskConfirmed);
723
+ const normalizedResult = {
724
+ ...result,
725
+ success: effectiveSuccess,
726
+ fileUpdated: result.fileUpdated === true || diskConfirmed,
727
+ };
728
+ if (isValidForDedupe(normalizedResult)) {
729
+ _appliedFixes.add(key);
730
+ }
731
+ if (effectiveSuccess) {
732
+ Logger.success(`[McpLocalClient] ✅ XPath corrigido via MCP (${channel}): ${normalizedResult.filePath || "arquivo não informado"} (${normalizedResult.className || "?"}.${normalizedResult.methodName || "?"})`);
733
+ }
734
+ else {
735
+ Logger.warning(`[McpLocalClient] Auto-fix não confirmou escrita local (${channel}). Motivo: ${normalizedResult.error || "resultado incompleto"}`);
736
+ }
737
+ if (channel === "http-backend" &&
738
+ normalizedResult.executedOn === "backend-fallback") {
739
+ Logger.warning("[McpLocalClient] ⚠️ Auto-fix executado no backend-fallback; validação local recomendada.");
740
+ }
741
+ return normalizedResult;
742
+ }
743
+ catch (err) {
744
+ const msg = err instanceof Error ? err.message : String(err);
745
+ if (attempt < MCP_LOCAL_MAX_RETRIES) {
746
+ Logger.warning(`[McpLocalClient] Tentativa ${attempt}/${MCP_LOCAL_MAX_RETRIES} falhou: ${msg}. Retentando...`);
747
+ await new Promise((r) => setTimeout(r, 500 * attempt));
748
+ }
749
+ else {
750
+ Logger.warning(`[McpLocalClient] fix_xpath_in_attributes falhou após ${MCP_LOCAL_MAX_RETRIES} tentativas: ${msg}`);
751
+ }
752
+ }
753
+ }
754
+ return null;
755
+ }
756
+ // ---------------------------------------------------------------------------
757
+ // CodeGen local-first reroute (item 27.4)
758
+ // ---------------------------------------------------------------------------
759
+ /**
760
+ * Chama uma tool MCP (genérica) priorizando MCP local.
761
+ * Se o backend responder com `requiresLocalMcp=true`, reencaminha automaticamente
762
+ * para o STDIO local com o mesmo payload.
763
+ *
764
+ * Usado para `codegen`/ações Playwright que precisam rodar na máquina do usuário.
765
+ */
766
+ export async function callMcpToolLocalFirst(toolName, args) {
767
+ // Não executar em Azure/CI
768
+ if (process.env.TF_BUILD === "True" ||
769
+ process.env.AGENT_NAME ||
770
+ process.env.CI === "true") {
771
+ return null;
772
+ }
773
+ try {
774
+ const requestArgs = enrichArgsWithUserHash(args);
775
+ const { response, channel } = await callToolWithFallback(toolName, requestArgs, {
776
+ allowBackend: true,
777
+ });
778
+ if (response.error) {
779
+ // Verificar se backend indicou que precisa de MCP local
780
+ const errMsg = response.error.message || "";
781
+ const errData = response.error.data;
782
+ if (errData?.requiresLocalMcp === true ||
783
+ errMsg.includes("requiresLocalMcp")) {
784
+ Logger.info(`[McpLocalClient] Backend requer MCP local para '${toolName}', reencaminhando via STDIO...`);
785
+ const ok = await ensureStdioProcess();
786
+ if (ok) {
787
+ const localResponse = await callMcpStdio("tools/call", {
788
+ name: toolName,
789
+ arguments: requestArgs,
790
+ });
791
+ if (!localResponse.error) {
792
+ return { result: localResponse.result, channel: "stdio" };
793
+ }
794
+ Logger.warning(`[McpLocalClient] Reroute STDIO falhou: ${localResponse.error.message}`);
795
+ }
796
+ }
797
+ Logger.warning(`[McpLocalClient] ${toolName} falhou: ${response.error.message}`);
798
+ return null;
799
+ }
800
+ return { result: response.result, channel };
801
+ }
802
+ catch (err) {
803
+ Logger.warning(`[McpLocalClient] Erro em ${toolName}: ${err instanceof Error ? err.message : String(err)}`);
804
+ return null;
805
+ }
806
+ }
807
+ export async function callMcpToolBackendFirst(toolName, args) {
808
+ if (process.env.TF_BUILD === "True" ||
809
+ process.env.AGENT_NAME ||
810
+ process.env.CI === "true") {
811
+ return null;
812
+ }
813
+ try {
814
+ const requestArgs = enrichArgsWithUserHash(args);
815
+ const { response, channel } = await callToolWithFallback(toolName, requestArgs, {
816
+ allowBackend: true,
817
+ preferBackend: true,
818
+ });
819
+ if (response.error) {
820
+ Logger.warning(`[McpLocalClient] ${toolName} falhou em modo backend-first: ${response.error.message}`);
821
+ return null;
822
+ }
823
+ return { result: response.result, channel };
824
+ }
825
+ catch (err) {
826
+ Logger.warning(`[McpLocalClient] Erro em ${toolName} (backend-first): ${err instanceof Error ? err.message : String(err)}`);
827
+ return null;
828
+ }
829
+ }
830
+ export async function getPortalSavedTests(params = {}) {
831
+ const response = await callMcpToolBackendFirst("get_portal_saved_tests", params);
832
+ if (!response?.result || typeof response.result !== "object")
833
+ return null;
834
+ const raw = response.result;
835
+ const portalTests = Array.isArray(raw.portalTests)
836
+ ? raw.portalTests
837
+ : [];
838
+ return {
839
+ total: typeof raw.total === "number" ? raw.total : portalTests.length,
840
+ includeCode: raw.includeCode === true,
841
+ retentionDays: typeof raw.retentionDays === "number" ? raw.retentionDays : undefined,
842
+ message: typeof raw.message === "string" ? raw.message : undefined,
843
+ portalTests,
844
+ };
845
+ }
846
+ export async function runLocalNpmScriptViaMcp(params) {
847
+ const response = await callMcpToolLocalFirst("run_local_npm_script", params);
848
+ if (!response?.result || typeof response.result !== "object")
849
+ return null;
850
+ const raw = response.result;
851
+ const payload = raw.payload && typeof raw.payload === "object"
852
+ ? raw.payload
853
+ : raw;
854
+ return {
855
+ ...payload,
856
+ success: payload.success === true || payload.exitCode === 0,
857
+ message: typeof payload.message === "string" ? payload.message : undefined,
858
+ command: typeof payload.command === "string" ? payload.command : undefined,
859
+ exitCode: typeof payload.exitCode === "number" ? payload.exitCode : undefined,
860
+ stdout: typeof payload.stdout === "string" ? payload.stdout : undefined,
861
+ stderr: typeof payload.stderr === "string" ? payload.stderr : undefined,
862
+ };
863
+ }
864
+ /** Limpa tracker de dedupe (chamar no início de cada execução, se desejado) */
865
+ export function clearFixDedupeTracker() {
866
+ _appliedFixes.clear();
867
+ }
868
+ /** Limpa cache de health-check de tools (útil para testes) */
869
+ export function clearToolsListCache() {
870
+ _toolsListCache.clear();
871
+ }