mcp-lab-agent 2.1.10 → 2.2.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.
package/README.md CHANGED
@@ -4,48 +4,127 @@
4
4
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-green)](https://nodejs.org)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
6
 
7
- **Assistente de teste que aprende com falhas.** Reduz tempo de debug, elimina flaky e mantém seletores estáveis. Executa testes, analisa causas de falha, corrige automaticamente e aprende padrões que melhoram as próximas gerações. Integra ao Cursor, Cline, Windsurf ou Slack.
7
+ **PT-BR** | [English](#english)
8
+
9
+ ---
10
+
11
+ ## Português (PT-BR)
12
+
13
+ **Sistema de QA autônomo com IA.** Reduz tempo de debug de testes, elimina flaky e mantém seletores estáveis — com um sistema de aprendizado que melhora a cada correção.
14
+
15
+ > **TL;DR para recrutadores:** QA autônomo que explica *por que* os testes falharam em linguagem clara e aplica correções automaticamente. Testes que se autocorrigem e aprendem a cada fix. Integra com IDE (Cursor) e Slack. Feito para QA Engineers, SDETs e roles de Automação/IA.
16
+
17
+ ### Por que isso importa
18
+
19
+ | Problema real | Impacto no mercado | O que o mcp-lab-agent faz |
20
+ |---------------|--------------------|---------------------------|
21
+ | **Testes flaky** | Times gastam 5–10h/semana. Microsoft: ~25% das falhas em CI são flaky; Slack tinha 56% antes de remediar. | Detecta padrões flaky, sugere correções, retry automático com fixes |
22
+ | **"Por que falhou?"** | QAs e devs perdem horas lendo stack traces e logs. "Teste falhou" genérico não ajuda. | **Causa + correção em 30 segundos.** Diagnóstico em linguagem clara: o que aconteceu, por que e como corrigir |
23
+ | **Seletores quebrados** | Refactors de UI quebram testes. Seletores frágeis (classes CSS, XPath longo) exigem manutenção manual. | Auto-fix de seletores, sugere `data-testid`, aplica correções e tenta de novo |
24
+
25
+ ### O WOW: Testes que se autocorrigem e aprendem
26
+
27
+ **Quando um teste falha, você recebe a causa e a correção em 30 segundos. Sem cavar em stack traces.**
28
+
29
+ Cada correção bem-sucedida é salva e reutilizada. Na próxima falha similar, o agente aplica o padrão aprendido automaticamente. **A taxa de sucesso na primeira tentativa melhora ao longo do tempo** — mensurável via `mcp-lab-agent stats`.
8
30
 
9
31
  ```bash
10
32
  npx mcp-lab-agent auto "login flow" --max-retries 5
11
33
  ```
12
34
 
13
- **1 comando. Análise completa.**
35
+ *Um comando. Análise completa. Autocorreção. Aprendizado.*
14
36
 
15
- > Teste falhou? Em 30 segundos: o que aconteceu, por que e como corrigir. O mcp-lab-agent analisa causas, corrige e acumula conhecimento.
37
+ ### Principais resultados
16
38
 
17
- **Foco:** [Top 3 problemas de QA](docs/TOP3_QA_PROBLEMAS_E_ROADMAP.md)flaky, "por que falhou?", manutenção de seletores.
39
+ - **Reduz tempo de debug** — "Por que falhou?" em linguagem clara, não stack traces
40
+ - **Corta manutenção de flaky** — Detecção, diagnóstico e sugestões de correção
41
+ - **Escala QA sem escalar headcount** — Agente no IDE + Slack bot; funciona com Cypress, Playwright, Appium, Jest e 11+ frameworks
42
+ - **Pronto para enterprise** — Socket Mode (sem URL pública), Ollama (offline), Learning Hub para times
18
43
 
19
- ---
44
+ ### Como funciona
20
45
 
21
- ## O que é
46
+ **🤖 Agente no IDE (Cursor, Cline, Windsurf)** — Pergunte no chat: *"Gere teste para login"*, *"Por que o teste falhou?"*, *"Roda o teste X"*. O agente detecta o projeto, executa testes, analisa falhas, aplica correções e aprende.
22
47
 
23
- O **mcp-lab-agent** é um sistema de inteligência em qualidade de software não uma ferramenta de teste isolada. Ele entende o seu projeto, identifica frameworks (Cypress, Playwright, Jest, Appium, Robot, pytest e outros), gera testes com base em contexto e memória, executa, analisa falhas e aplica correções automaticamente. O valor central está no **learning**: cada correção bem-sucedida é salva e usada nas próximas gerações, aumentando a taxa de sucesso na primeira tentativa.
48
+ **💬 Slack Bot** Mencione o bot em qualquer canalele executa testes e posta o relatório. Funciona em ambiente corporativo (Socket Mode, sem ngrok). QA no fluxo da conversa.
24
49
 
25
- Com o **Learning Hub**, os aprendizados são centralizados e agregados entre projetos e — em deploy compartilhado — entre times e empresas, formando uma base de conhecimento em qualidade que escala além do repositório.
26
-
27
- ---
28
-
29
- ## Para quem
50
+ ### Para quem é
30
51
 
31
52
  | Perfil | Benefício |
32
53
  |--------|-----------|
33
- | **QAs e SDETs** | Geração assistida de testes, análise de falhas com sugestões de correção, detecção de flakiness |
34
- | **Desenvolvedores** | "Por que falhou?", análise de arquivos e métodos, integração direta no IDE |
54
+ | **QAs e SDETs** | Geração assistida de testes, análise de falhas com sugestões de correção, detecção de flaky |
55
+ | **Desenvolvedores** | "Por que falhou?" em segundos, análise de arquivos/métodos, integração direta no IDE |
35
56
  | **Tech leads** | Visão de risco por área, métricas de estabilidade, relatórios para decisão |
36
- | **Empresas** | Learning Hub centralizado, escala entre squads e organizações, CI/CD, Ollama (offline), Slack para QA via chat |
37
-
38
- ---
57
+ | **Times** | Learning Hub, Slack bot para QA no chat, CI/CD, Ollama (offline) |
39
58
 
40
- ## Comparação
59
+ ### Como é diferente
41
60
 
42
61
  | Outras ferramentas | mcp-lab-agent |
43
62
  |--------------------|---------------|
44
- | Só executam testes | Executa, analisa causa da falha e sugere correção |
45
- | Saída genérica "teste falhou" | Diagnóstico: "login falha 30% das vezes (timing)" |
46
- | Sem visão de risco | Identifica áreas sem testes e classifica risco (alto/médio/baixo) |
47
- | Sem memória entre execuções | Learning system: cada padrão de falha vira correção aplicada nas próximas gerações |
48
- | Uma ferramenta por tarefa | Sistema de inteligência: geração, execução, análise, relatórios, predição, learning |
63
+ | Só executam testes | Executa, analisa causa, sugere fix, aplica correção |
64
+ | "Teste falhou" genérico | Linguagem clara: "Login falha 30% das vezes (timing). Adicione waitForDisplayed." |
65
+ | Sem memória entre execuções | Learning system: cada fix melhora as próximas gerações |
66
+ | Uma ferramenta por tarefa | End-to-end: gera, executa, analisa, reporta, aprende |
67
+
68
+ ---
69
+
70
+ <a name="english"></a>
71
+
72
+ ## English
73
+
74
+ **AI-powered autonomous QA system.** Reduces test debugging time, eliminates flaky tests, and keeps selectors stable — with a learning system that gets smarter with every fix.
75
+
76
+ > **TL;DR for recruiters:** Autonomous QA that explains *why* tests fail in plain language and applies fixes automatically. Self-healing tests that learn from each fix. Integrates with IDE (Cursor) and Slack. Built for QA Engineers, SDETs, and AI/Automation roles.
77
+
78
+ ### Why this matters
79
+
80
+ | Real problem | Industry impact | What mcp-lab-agent does |
81
+ |--------------|-----------------|-------------------------|
82
+ | **Flaky tests** | Teams spend 5–10h/week. Microsoft: ~25% of CI failures are flaky; Slack had 56% before remediation. | Detects flaky patterns, suggests fixes, auto-retries with corrections |
83
+ | **"Why did it fail?"** | QAs and devs lose hours reading stack traces and logs. Generic "test failed" doesn't help. | **Cause + fix in 30 seconds.** Plain-language diagnosis: what happened, why, and how to fix |
84
+ | **Broken selectors** | UI refactors break tests. Fragile selectors (CSS classes, long XPath) require manual maintenance. | Auto-fix selectors, suggests `data-testid`, applies corrections and retries |
85
+
86
+ ### The WOW: Self-healing tests that learn
87
+
88
+ **When a test fails, you get the cause and fix in 30 seconds. No more digging through stack traces.**
89
+
90
+ Each successful fix is saved and reused. The next time a similar failure happens, the agent applies the learned pattern automatically. **First-attempt success rate improves over time** — measurable via `mcp-lab-agent stats`.
91
+
92
+ ```bash
93
+ npx mcp-lab-agent auto "login flow" --max-retries 5
94
+ ```
95
+
96
+ *One command. Full analysis. Self-correction. Learning.*
97
+
98
+ ### Key outcomes
99
+
100
+ - **Reduce debugging time** — "Why did it fail?" in plain language, not stack traces
101
+ - **Cut flaky test maintenance** — Detection, diagnosis, and suggested fixes
102
+ - **Scale QA without scaling headcount** — IDE agent + Slack bot; works with Cypress, Playwright, Appium, Jest, and 11+ frameworks
103
+ - **Enterprise-ready** — Socket Mode (no public URL), Ollama (offline), Learning Hub for teams
104
+
105
+ ### How it works
106
+
107
+ **🤖 IDE Agent (Cursor, Cline, Windsurf)** — Ask in chat: *"Generate a test for login"*, *"Why did the test fail?"*, *"Run test X"*. The agent detects your project, runs tests, analyzes failures, applies fixes, and learns.
108
+
109
+ **💬 Slack Bot** — Mention the bot in any channel — it runs tests and posts the report. Works in corporate environments (Socket Mode, no ngrok). QA in the flow of conversation.
110
+
111
+ ### Who it's for
112
+
113
+ | Role | Benefit |
114
+ |------|---------|
115
+ | **QAs & SDETs** | Assisted test generation, failure analysis with fix suggestions, flaky detection |
116
+ | **Developers** | "Why did it fail?" in seconds, file/method analysis, direct IDE integration |
117
+ | **Tech leads** | Risk visibility by area, stability metrics, decision-ready reports |
118
+ | **Teams** | Learning Hub, Slack bot for QA in chat, CI/CD integration, Ollama (offline) |
119
+
120
+ ### How it's different
121
+
122
+ | Other tools | mcp-lab-agent |
123
+ |-------------|---------------|
124
+ | Run tests only | Run, analyze cause, suggest fix, apply correction |
125
+ | Generic "test failed" | Plain-language: "Login fails 30% of the time (timing). Add waitForDisplayed." |
126
+ | No memory between runs | Learning system: each fix improves future generations |
127
+ | One tool per task | End-to-end: generate, run, analyze, report, learn |
49
128
 
50
129
  ---
51
130
 
@@ -299,6 +378,7 @@ npm install playwright
299
378
  - [CHANGELOG.md](CHANGELOG.md) — Histórico de versões
300
379
  - [slack-bot/README.md](slack-bot/README.md) — Slack Bot
301
380
  - [learning-hub/README.md](learning-hub/README.md) — Learning Hub
381
+ - [docs/PORTFOLIO_COPY_PT-BR.md](docs/PORTFOLIO_COPY_PT-BR.md) — Copy em PT-BR para portfólio (Vercel)
302
382
 
303
383
  ---
304
384
 
package/dist/index.js CHANGED
@@ -1542,16 +1542,19 @@ async function handleAutoCommand() {
1542
1542
  [Tentativa ${attempt}/${maxRetries}] Gerando teste...`);
1543
1543
  const { provider, apiKey, baseUrl, model } = llm;
1544
1544
  const memoryHints = memory.learnings?.filter((l) => l.success).slice(-10).map((l) => l.fix).join("\n") || "";
1545
+ const packageInfo = structure.packageJson || {};
1546
+ const isESM = packageInfo.type === "module";
1545
1547
  const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
1546
1548
  ${memoryHints ? `
1547
1549
  Aprendizados anteriores (use como refer\xEAncia):
1548
1550
  ${memoryHints.slice(0, 1e3)}` : ""}
1551
+ ${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
1549
1552
  Retorne SOMENTE o c\xF3digo, sem markdown.`;
1550
1553
  const userPrompt = `Contexto:
1551
1554
  ${contextLines}
1552
1555
 
1553
1556
  Gere teste para: ${cleanRequest}
1554
- Framework: ${fw}`;
1557
+ Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
1555
1558
  try {
1556
1559
  let specContent = "";
1557
1560
  if (provider === "gemini") {
@@ -1565,6 +1568,9 @@ Framework: ${fw}`;
1565
1568
  })
1566
1569
  });
1567
1570
  const data = await res.json();
1571
+ if (data.error) {
1572
+ throw new Error(`Gemini API Error: ${data.error.message || JSON.stringify(data.error)}`);
1573
+ }
1568
1574
  specContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
1569
1575
  } else {
1570
1576
  const res = await fetch(`${baseUrl}/chat/completions`, {
@@ -1578,10 +1584,20 @@ Framework: ${fw}`;
1578
1584
  })
1579
1585
  });
1580
1586
  const data = await res.json();
1587
+ if (data.error) {
1588
+ throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
1589
+ }
1581
1590
  specContent = data.choices?.[0]?.message?.content || "";
1582
1591
  }
1592
+ console.log(`[DEBUG] Resposta do LLM recebida: ${specContent.length} caracteres`);
1593
+ if (!specContent || specContent.trim().length === 0) {
1594
+ throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
1595
+ }
1583
1596
  specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1584
1597
  testContent = specContent;
1598
+ if (!testContent || testContent.trim().length === 0) {
1599
+ throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
1600
+ }
1585
1601
  if (!testFilePath) {
1586
1602
  const fileName = cleanRequest.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
1587
1603
  const { ext, baseDir } = getExtensionAndBaseDir(fw, structure);
@@ -1590,11 +1606,16 @@ Framework: ${fw}`;
1590
1606
  if (!fs5.existsSync(baseDir)) fs5.mkdirSync(baseDir, { recursive: true });
1591
1607
  }
1592
1608
  fs5.writeFileSync(testFilePath, testContent, "utf8");
1593
- console.log(`\u2705 Teste gravado: ${testFilePath}`);
1609
+ const fileSize = fs5.statSync(testFilePath).size;
1610
+ if (fileSize === 0) {
1611
+ throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
1612
+ }
1613
+ console.log(`\u2705 Teste gravado: ${testFilePath} (${fileSize} bytes)`);
1594
1614
  console.log(`
1595
1615
  [Tentativa ${attempt}/${maxRetries}] Executando teste...`);
1616
+ const runArg = fw === "playwright" ? path5.relative(PROJECT_ROOT5, testFilePath).replace(/\\/g, "/") : testFilePath;
1596
1617
  const runResult = await new Promise((resolve) => {
1597
- const child = spawn("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", testFilePath], {
1618
+ const child = spawn("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
1598
1619
  cwd: PROJECT_ROOT5,
1599
1620
  stdio: ["inherit", "pipe", "pipe"],
1600
1621
  shell: process.platform === "win32"
@@ -1641,8 +1662,59 @@ ${runResult.output.slice(0, 800)}
1641
1662
  console.log(`\u26A0\uFE0F Flaky detectado (${flakyAnalysis.confidence.toFixed(2)}): ${flakyAnalysis.patterns.map((p) => p.pattern).join(", ")}`);
1642
1663
  }
1643
1664
  console.log(`
1644
- [Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o (simulada)...`);
1645
- console.log(`\u26A0\uFE0F Corre\xE7\xE3o autom\xE1tica ainda n\xE3o implementada nesta vers\xE3o CLI. Tentando novamente...`);
1665
+ [Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o...`);
1666
+ try {
1667
+ const fixPrompt = `Voc\xEA \xE9 um engenheiro de QA. O teste falhou com o seguinte erro:
1668
+
1669
+ ${runResult.output.slice(0, 1e3)}
1670
+
1671
+ C\xF3digo atual do teste:
1672
+ ${testContent}
1673
+
1674
+ Analise o erro e corrija o teste. Considere:
1675
+ - Seletores podem estar errados (verifique se os elementos existem)
1676
+ - Pode precisar de waits (waitForSelector, waitForLoadState)
1677
+ - Rotas podem estar erradas (/buscar vs /busca)
1678
+ - Elementos podem ter nomes diferentes
1679
+
1680
+ Retorne APENAS o c\xF3digo corrigido, sem explica\xE7\xF5es.${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
1681
+ let fixedContent = "";
1682
+ if (provider === "gemini") {
1683
+ const url = `${baseUrl}/models/${model}:generateContent?key=${apiKey}`;
1684
+ const res = await fetch(url, {
1685
+ method: "POST",
1686
+ headers: { "Content-Type": "application/json" },
1687
+ body: JSON.stringify({
1688
+ contents: [{ parts: [{ text: fixPrompt }] }],
1689
+ generationConfig: { temperature: 0.3, maxOutputTokens: 4096 }
1690
+ })
1691
+ });
1692
+ const data = await res.json();
1693
+ fixedContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
1694
+ } else {
1695
+ const res = await fetch(`${baseUrl}/chat/completions`, {
1696
+ method: "POST",
1697
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
1698
+ body: JSON.stringify({
1699
+ model,
1700
+ messages: [{ role: "user", content: fixPrompt }],
1701
+ temperature: 0.3,
1702
+ max_tokens: 4096
1703
+ })
1704
+ });
1705
+ const data = await res.json();
1706
+ fixedContent = data.choices?.[0]?.message?.content || "";
1707
+ }
1708
+ fixedContent = fixedContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1709
+ if (fixedContent && fixedContent.length > 50) {
1710
+ testContent = fixedContent;
1711
+ console.log(`\u2705 Corre\xE7\xE3o gerada pelo LLM.`);
1712
+ } else {
1713
+ console.log(`\u26A0\uFE0F Corre\xE7\xE3o vazia, tentando novamente...`);
1714
+ }
1715
+ } catch (fixErr) {
1716
+ console.log(`\u26A0\uFE0F Erro ao gerar corre\xE7\xE3o: ${fixErr.message}. Tentando novamente...`);
1717
+ }
1646
1718
  } catch (err) {
1647
1719
  console.error(`
1648
1720
  \u274C Erro na tentativa ${attempt}: ${err.message}
@@ -2394,8 +2466,18 @@ Framework alvo: ${fw}${referenceBlock}`;
2394
2466
  }
2395
2467
  specContent = specContent.replace(/^```(?:js|javascript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
2396
2468
  const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 40);
2469
+ if (!specContent) {
2470
+ return {
2471
+ content: [{ type: "text", text: "Erro: LLM retornou conte\xFAdo vazio. Verifique API key (GROQ_API_KEY, GEMINI_API_KEY) e tente novamente." }],
2472
+ structuredContent: { ok: false, error: "Empty LLM response" }
2473
+ };
2474
+ }
2475
+ const textWithCode = `Spec gerado (${specContent.length} chars). Use write_test para gravar com name="${fileName}" e content abaixo:
2476
+
2477
+ --- C\xF3digo (passe em content para write_test) ---
2478
+ ${specContent}`;
2397
2479
  return {
2398
- content: [{ type: "text", text: `Spec gerado (${specContent.length} chars). Use write_test para gravar.` }],
2480
+ content: [{ type: "text", text: textWithCode }],
2399
2481
  structuredContent: {
2400
2482
  ok: true,
2401
2483
  specContent,
@@ -2477,6 +2559,12 @@ server.registerTool(
2477
2559
  structuredContent: { ok: false, error: "No test framework" }
2478
2560
  };
2479
2561
  }
2562
+ if (!content || !String(content).trim()) {
2563
+ return {
2564
+ content: [{ type: "text", text: "Erro: content n\xE3o pode ser vazio. Chame generate_tests primeiro e passe o specContent retornado em content." }],
2565
+ structuredContent: { ok: false, error: "Empty content" }
2566
+ };
2567
+ }
2480
2568
  const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
2481
2569
  const safeName = name.replace(/[^a-z0-9-_]/gi, "-").replace(/-+/g, "-").replace(/_+/g, "_").replace(/\.(cy|spec|test|robot|feature|py)\.?(js|ts|py)?$/i, "").replace(/^[-_]+|[-_]+$/g, "");
2482
2570
  const fileName = ext.startsWith("_") ? `${safeName}${ext}` : `${safeName}${ext}`;
@@ -4367,17 +4455,20 @@ server.registerTool(
4367
4455
  learnings.push({ attempt, action: "generate_tests", result: "gerando..." });
4368
4456
  const { provider, apiKey, baseUrl, model } = llm;
4369
4457
  const memoryHints = memory.learnings?.filter((l) => l.fix).slice(-10).map((l) => l.fix).join("\n") || "";
4458
+ const packageInfo = structure.packageJson || {};
4459
+ const isESM = packageInfo.type === "module";
4370
4460
  const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
4371
4461
  ${UNIVERSAL_TEST_PRACTICES}
4372
4462
 
4373
4463
  ${memoryHints ? `Aprendizados anteriores (use como refer\xEAncia):
4374
4464
  ${memoryHints.slice(0, 1e3)}` : ""}
4465
+ ${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
4375
4466
  Retorne SOMENTE o c\xF3digo, sem markdown.`;
4376
4467
  const userPrompt = `Contexto:
4377
4468
  ${contextLines}
4378
4469
 
4379
4470
  Gere teste para: ${request}
4380
- Framework: ${fw}`;
4471
+ Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
4381
4472
  try {
4382
4473
  let specContent = "";
4383
4474
  if (provider === "gemini") {
@@ -4404,10 +4495,23 @@ Framework: ${fw}`;
4404
4495
  })
4405
4496
  });
4406
4497
  const data = await res.json();
4498
+ if (data.error) {
4499
+ learnings.push({ attempt, action: "llm_call", result: `\u274C API error: ${data.error.message}` });
4500
+ throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
4501
+ }
4407
4502
  specContent = data.choices?.[0]?.message?.content || "";
4408
4503
  }
4504
+ if (!specContent || specContent.trim().length === 0) {
4505
+ learnings.push({ attempt, action: "generate_test", result: "\u274C LLM retornou vazio" });
4506
+ throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
4507
+ }
4508
+ learnings.push({ attempt, action: "generate_test", result: `\u2705 recebido ${specContent.length} chars` });
4409
4509
  specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
4410
4510
  testContent = specContent;
4511
+ if (!testContent || testContent.trim().length === 0) {
4512
+ learnings.push({ attempt, action: "parse_code", result: "\u274C c\xF3digo vazio ap\xF3s parsing" });
4513
+ throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
4514
+ }
4411
4515
  if (!testFilePath) {
4412
4516
  const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
4413
4517
  const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
@@ -4416,10 +4520,16 @@ Framework: ${fw}`;
4416
4520
  if (!fs6.existsSync(baseDir)) fs6.mkdirSync(baseDir, { recursive: true });
4417
4521
  }
4418
4522
  fs6.writeFileSync(testFilePath, testContent, "utf8");
4419
- learnings.push({ attempt, action: "write_test", result: `gravado: ${testFilePath}` });
4523
+ const writtenFileSize = fs6.statSync(testFilePath).size;
4524
+ if (writtenFileSize === 0) {
4525
+ learnings.push({ attempt, action: "write_test", result: "\u274C arquivo vazio ap\xF3s gravar" });
4526
+ throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
4527
+ }
4528
+ learnings.push({ attempt, action: "write_test", result: `gravado: ${testFilePath} (${writtenFileSize} bytes)` });
4420
4529
  learnings.push({ attempt, action: "run_tests", result: "executando..." });
4530
+ const runArg = fw === "playwright" ? path6.relative(PROJECT_ROOT6, testFilePath).replace(/\\/g, "/") : testFilePath;
4421
4531
  const runResult = await new Promise((resolve) => {
4422
- const child = spawn2("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", testFilePath], {
4532
+ const child = spawn2("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
4423
4533
  cwd: PROJECT_ROOT6,
4424
4534
  stdio: ["inherit", "pipe", "pipe"],
4425
4535
  shell: process.platform === "win32"
@@ -4477,9 +4587,14 @@ ${runResult.output.slice(0, 500)}${learnedAppendix2}` }],
4477
4587
  }
4478
4588
  learnings.push({ attempt, action: "apply_fix", result: "aplicando corre\xE7\xE3o..." });
4479
4589
  const fixedCode = explainResult.structuredContent.sugestaoCorrecao;
4590
+ if (!fixedCode || fixedCode.trim().length === 0) {
4591
+ learnings.push({ attempt, action: "apply_fix", result: "\u274C corre\xE7\xE3o vazia" });
4592
+ continue;
4593
+ }
4480
4594
  testContent = fixedCode;
4481
4595
  fs6.writeFileSync(testFilePath, testContent, "utf8");
4482
- learnings.push({ attempt, action: "apply_fix", result: "corre\xE7\xE3o aplicada" });
4596
+ const fixedFileSize = fs6.statSync(testFilePath).size;
4597
+ learnings.push({ attempt, action: "apply_fix", result: `corre\xE7\xE3o aplicada (${fixedFileSize} bytes)` });
4483
4598
  if (flakyAnalysis.isLikelyFlaky) {
4484
4599
  const inferredPattern = inferFailurePattern(runResult.output, fw);
4485
4600
  const learningType = inferredPattern?.learningType || (flakyAnalysis.patterns[0]?.pattern === "selector" ? "selector_fix" : "timing_fix");