deflake 1.2.21 β†’ 1.2.27

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 (4) hide show
  1. package/README.md +6 -3
  2. package/cli.js +158 -94
  3. package/client.js +5 -3
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -22,13 +22,16 @@ npx deflake npx playwright test
22
22
  npx deflake npx cypress run
23
23
  ```
24
24
 
25
- ### πŸš‘ Diagnostics (Doctor Mode)
26
- Validate your environment, API key, and quota without running tests:
27
-
28
25
  ```bash
29
26
  npx deflake doctor
30
27
  ```
31
28
 
29
+ ### 🍎 macOS Permission Requirements
30
+ On macOS, if your project folder is located in **Documents** or **Desktop**, DeFlake may be blocked from reading test results.
31
+
32
+ * **Move Project**: Move your folder out of Documents (e.g., to `/Users/hugo/code/`).
33
+ * **Full Disk Access**: Grant "Full Disk Access" to your Terminal/iTerm in *System Settings > Privacy & Security*.
34
+
32
35
  ### Manual Mode
33
36
  Analyze an existing error log and HTML/Image snapshot:
34
37
 
package/cli.js CHANGED
@@ -18,8 +18,10 @@ const C = {
18
18
  BLUE: "\x1b[34m",
19
19
  MAGENTA: "\x1b[35m",
20
20
  GRAY: "\x1b[90m",
21
+ WHITE: "\x1b[37m",
21
22
  BG_BLUE: "\x1b[44m",
22
- BG_GREEN: "\x1b[42m"
23
+ BG_GREEN: "\x1b[42m",
24
+ BG_RED: "\x1b[41m"
23
25
  };
24
26
 
25
27
  const rawArgs = process.argv.slice(2);
@@ -61,25 +63,39 @@ async function main() {
61
63
  async function runNative(fullCommand) {
62
64
  return new Promise((resolve) => {
63
65
  console.log(`πŸš€ Running command: ${C.CYAN}${fullCommand}${C.RESET}`);
64
- const child = spawn(fullCommand, { shell: true, stdio: 'inherit' });
66
+ const child = spawn(fullCommand, {
67
+ shell: true,
68
+ stdio: 'inherit',
69
+ env: { ...process.env, PLAYWRIGHT_HTML_OPEN: 'never' }
70
+ });
65
71
  child.on('close', (code) => resolve({ code }));
66
72
  });
67
73
  }
68
74
 
69
75
  function detectFailures() {
70
76
  const results = [];
77
+ console.log(`\n${C.CYAN}πŸ“‚ Scanning for failure artifacts...${C.RESET}`);
71
78
  ['test-results', 'playwright-report'].forEach(dir => {
72
- if (!fs.existsSync(dir)) return;
79
+ if (!fs.existsSync(dir)) {
80
+ console.log(` ${C.GRAY}β”œβ”€ ${dir}/: not found (skipping)${C.RESET}`);
81
+ return;
82
+ }
73
83
  try {
74
84
  const files = fs.readdirSync(dir, { recursive: true });
85
+ let found = 0;
75
86
  files.forEach(f => {
76
87
  const fullPath = path.join(dir, f);
77
88
  if (f.endsWith('error-context.md')) {
78
89
  results.push({ name: path.basename(path.dirname(fullPath)), htmlPath: fullPath });
90
+ found++;
79
91
  }
80
92
  });
81
- } catch (e) {}
93
+ console.log(` ${C.GRAY}β”œβ”€ ${dir}/:${C.RESET} ${found > 0 ? C.GREEN + found + ' failure artifact(s)' : C.GRAY + 'no failures'}${C.RESET}`);
94
+ } catch (e) {
95
+ console.log(` ${C.GRAY}β”œβ”€ ${dir}/:${C.RESET} ${C.RED}SCAN ERROR β€” ${e.message}${C.RESET}`);
96
+ }
82
97
  });
98
+ console.log(` ${C.GRAY}└─ Total:${C.RESET} ${C.BRIGHT}${results.length} artifact(s) to analyze${C.RESET}\n`);
83
99
  return results;
84
100
  }
85
101
 
@@ -88,82 +104,140 @@ async function analyzeAndFix(artifacts, client, argv) {
88
104
  console.log(` ${C.YELLOW}⚠️ No failure artifacts detected. Run 'npx deflake doctor' to check permissions.${C.RESET}`);
89
105
  return 0;
90
106
  }
91
- console.log(`πŸ” Analyzing ${artifacts.length} failure(s)...`);
107
+ console.log(`${C.BRIGHT}πŸ” Analyzing ${artifacts.length} failure(s)...${C.RESET}\n`);
92
108
  let count = 0;
93
- for (const art of artifacts) {
94
- process.stdout.write(`⏳ Analyzing ${art.name}...`);
109
+ for (let i = 0; i < artifacts.length; i++) {
110
+ const art = artifacts[i];
111
+ console.log(`${C.CYAN}━━━ [${i+1}/${artifacts.length}] ${art.name} ━━━${C.RESET}`);
95
112
  try {
96
113
  const content = fs.readFileSync(art.htmlPath, 'utf8');
97
114
  const loc = extractLoc(content);
115
+
116
+ // Step 1: Location extraction
117
+ if (loc) {
118
+ console.log(` ${C.GRAY}πŸ“ Location:${C.RESET} ${path.basename(loc.path)}:${loc.line}`);
119
+ } else {
120
+ console.log(` ${C.YELLOW}πŸ“ Location: Could not extract (regex mismatch)${C.RESET}`);
121
+ }
122
+
123
+ // Step 2: Source code reading
98
124
  const source = loc && fs.existsSync(loc.path) ? fs.readFileSync(loc.path, 'utf8') : null;
125
+ console.log(` ${C.GRAY}πŸ“„ Source:${C.RESET} ${source ? 'Loaded (' + source.split('\n').length + ' lines)' : C.YELLOW + 'Not available' + C.RESET}`);
126
+
127
+ // Step 3: API call
128
+ console.log(` ${C.GRAY}🌐 Calling DeFlake API...${C.RESET}`);
99
129
  const res = await client.heal(null, art.htmlPath, loc, source, argv.fix);
100
- if (res?.status === 'success') {
101
- if (argv.fix && await applyFix(res, loc)) count++;
102
- process.stdout.write(`\rβœ… Analysis complete for ${art.name}\n`);
103
- printFix(res.fix, loc);
130
+
131
+ if (res && res.status === 'success') {
132
+ console.log(` ${C.GREEN}βœ… API Response: SUCCESS${C.RESET}`);
133
+ try {
134
+ const parsed = JSON.parse(res.fix);
135
+ if (parsed.explanation) {
136
+ console.log(` ${C.CYAN}🧠 Insight:${C.RESET} ${C.GRAY}${parsed.explanation}${C.RESET}`);
137
+ }
138
+ if (parsed.patches && parsed.patches.length > 0) {
139
+ console.log(` ${C.GRAY}🩹 Patches:${C.RESET} ${parsed.patches.length} patch(es) suggested`);
140
+ for (const p of parsed.patches) {
141
+ console.log(` ${C.GRAY}β†’ ${p.action} at line ${p.line}: ${p.new_line?.substring(0, 70)}${C.RESET}`);
142
+ }
143
+ if (argv.fix) {
144
+ console.log(` ${C.BRIGHT}πŸ’‰ Applying patches...${C.RESET}`);
145
+ if (await applyFix(res, loc)) {
146
+ count++;
147
+ console.log(` ${C.GREEN}${C.BRIGHT}βœ… Fix applied to ${path.basename(loc.path)}:${loc.line}${C.RESET}`);
148
+ } else {
149
+ console.log(` ${C.YELLOW}⚠️ Patches could not be applied (see details above)${C.RESET}`);
150
+ }
151
+ } else {
152
+ console.log(` ${C.YELLOW}ℹ️ Use --fix flag to auto-apply these patches${C.RESET}`);
153
+ }
154
+ } else {
155
+ console.log(` ${C.YELLOW}⚠️ API returned no patches${C.RESET}`);
156
+ }
157
+ } catch (e) {
158
+ console.log(` ${C.CYAN}🧠 Insight:${C.RESET} ${res.fix}`);
159
+ }
160
+ } else if (res && res.detail === 'QUOTA_EXCEEDED') {
161
+ console.log(` ${C.RED}🚫 QUOTA EXCEEDED β€” No more fixes available this month${C.RESET}`);
162
+ } else {
163
+ console.log(` ${C.RED}❌ API Response: ${res?.status || 'null'} β€” ${res?.message || res?.detail || 'Unknown error'}${C.RESET}`);
104
164
  }
105
165
  } catch (e) {
106
- console.log(`\n ❌ ${C.RED}Error: ${e.message}${C.RESET}`);
166
+ console.log(` ${C.RED}❌ Error: ${e.message}${C.RESET}`);
107
167
  }
168
+ console.log('');
108
169
  }
170
+ console.log(`${C.BRIGHT}━━━ DeFlake Summary: ${count}/${artifacts.length} fix(es) applied ━━━${C.RESET}\n`);
109
171
  return count;
172
+
110
173
  }
111
174
 
112
175
  async function applyFix(res, loc) {
113
- if (!loc?.path) return false;
176
+ if (!loc?.path) {
177
+ console.log(` ${C.YELLOW}⚠️ Cannot apply fix: failure location not detected.${C.RESET}`);
178
+ return false;
179
+ }
114
180
  try {
115
181
  const patches = JSON.parse(res.fix).patches || [];
182
+ if (patches.length === 0) {
183
+ console.log(` ${C.YELLOW}⚠️ No patches provided by AI.${C.RESET}`);
184
+ return false;
185
+ }
116
186
  const original = fs.readFileSync(loc.path, 'utf8');
117
187
  let content = original;
188
+ let appliedCount = 0;
118
189
  for (const p of patches) {
119
190
  const lines = content.split('\n');
120
191
  const idx = p.line - 1;
121
192
  if (idx >= 0 && idx < lines.length) {
122
- const indent = lines[idx].match(/^\s*/)[0];
193
+ const originalLine = lines[idx];
194
+ const indent = originalLine.match(/^\s*/)[0];
123
195
  const cleanLine = indent + p.new_line.trim();
124
- if (cleanLine.includes('await') && !content.includes('async')) continue;
125
- if (!content.includes(p.new_line.trim())) {
126
- if (p.action === 'INSERT_AFTER') lines.splice(p.line, 0, cleanLine);
127
- else lines[idx] = cleanLine;
128
- content = lines.join('\n');
196
+ // Safety: skip if the new line is already present (dedup)
197
+ if (content.includes(p.new_line.trim())) {
198
+ console.log(` ${C.GRAY}⏭️ Skipped (already present): ${p.new_line.trim().substring(0, 60)}...${C.RESET}`);
199
+ continue;
129
200
  }
201
+ if (p.action === 'INSERT_AFTER') {
202
+ lines.splice(p.line, 0, cleanLine);
203
+ console.log(` ${C.GREEN}+ Line ${p.line + 1}: ${cleanLine.trim()}${C.RESET}`);
204
+ } else {
205
+ lines[idx] = cleanLine;
206
+ console.log(` ${C.RED}- Line ${p.line}: ${originalLine.trim()}${C.RESET}`);
207
+ console.log(` ${C.GREEN}+ Line ${p.line}: ${cleanLine.trim()}${C.RESET}`);
208
+ }
209
+ content = lines.join('\n');
210
+ appliedCount++;
130
211
  }
131
212
  }
132
- fs.writeFileSync(loc.path, content);
133
- try {
134
- if (content.match(/class\s+\w+\s+\{[\s\S]*?await\s+/)) throw new Error("Illegal await");
213
+ if (appliedCount > 0) {
214
+ fs.writeFileSync(loc.path, content);
135
215
  return true;
136
- } catch(e) {
137
- fs.writeFileSync(loc.path, original);
138
- return false;
139
216
  }
140
- } catch(e) { return false; }
217
+ return false;
218
+ } catch(e) {
219
+ console.log(` ${C.RED}❌ Patch error: ${e.message}${C.RESET}`);
220
+ return false;
221
+ }
141
222
  }
142
223
 
143
224
  function extractLoc(text) {
144
- const m = text.match(/at\s+(.*?\.ts):(\d+):(\d+)/);
145
- return m ? { path: path.resolve(m[1]), line: m[2] } : null;
146
- }
147
-
148
- function printFix(fix, loc) {
149
- try {
150
- const p = JSON.parse(fix);
151
- console.log(`\n${C.CYAN}${C.BRIGHT}πŸ’‘ SUGGESTED FIX:${C.RESET}`);
152
- console.log(` ${C.BRIGHT}Reason:${C.RESET} ${p.explanation}`);
153
- } catch(e) {}
225
+ // Support .ts, .js, .cy.ts, .cy.js, .spec.ts, .spec.js
226
+ const m = text.match(/at\s+(.*?\.(?:cy\.)?(?:spec\.)?(?:ts|js)):(\d+):(\d+)/);
227
+ return m ? { path: path.resolve(m[1]), line: parseInt(m[2]) } : null;
154
228
  }
155
229
 
156
230
  async function runDoctor() {
157
- console.log(`\n${C.BG_BLUE}${C.WHITE}${C.BRIGHT} DEFLAKE MISSION CONTROL - DIAGNOSTIC CENTER ${C.RESET}\n`);
231
+ console.log(`\n${C.BG_BLUE}${C.WHITE}${C.BRIGHT} DEFLAKE MISSION CONTROL - DEEP DIAGNOSTIC ${C.RESET}\n`);
158
232
 
159
- // 1. SYSTEM HUB
233
+ // 1. SYSTEM CORE
160
234
  console.log(`${C.CYAN}1. SYSTEM CORE${C.RESET}`);
161
235
  console.log(` ${C.GRAY}β”œβ”€ Version: ${C.RESET}${C.BRIGHT}v${pkg.version}${C.RESET}`);
162
236
  console.log(` ${C.GRAY}β”œβ”€ Platform: ${C.RESET}${process.platform} (${process.arch})`);
163
237
  console.log(` ${C.GRAY}└─ Project: ${C.RESET}${path.basename(process.cwd())}\n`);
164
238
 
165
- // 2. PROJECT AUDIT (Discovery Phase)
166
- console.log(`${C.CYAN}2. PROJECT DISCOVERY & VALIDATION${C.RESET}`);
239
+ // 2. PROJECT AUDIT
240
+ console.log(`${C.CYAN}2. PROJECT AUDIT${C.RESET}`);
167
241
  const fw = DeFlakeClient.detectFramework();
168
242
  console.log(` ${C.GRAY}β”œβ”€ Framework: ${C.RESET}${C.GREEN}${fw.toUpperCase()}${C.RESET}`);
169
243
 
@@ -175,95 +249,85 @@ async function runDoctor() {
175
249
  files.forEach(f => allFiles.push({ name: f, full: path.join(d, f) }));
176
250
  }
177
251
  });
178
-
179
- if (allFiles.length > 0) {
180
- console.log(` ${C.GRAY}β”œβ”€ Discovery: ${C.RESET}${C.GREEN}${allFiles.length} files detected${C.RESET}`);
181
- allFiles.slice(0, 8).forEach(file => {
182
- process.stdout.write(` ${C.GRAY}β”‚ β”œβ”€ ${path.basename(file.name).padEnd(25)}${C.RESET} `);
183
- try {
184
- execSync(`node --check "${file.full}"`, { stdio: 'ignore' });
185
- console.log(`${C.GREEN}βœ“ Syntax OK${C.RESET}`);
186
- } catch (e) {
187
- console.log(`${C.RED}❗ SYNTAX ERROR${C.RESET}`);
188
- }
189
- });
190
- if (allFiles.length > 8) console.log(` ${C.GRAY}β”‚ └─ ... and ${allFiles.length - 8} more files validated.${C.RESET}`);
191
- else console.log(` ${C.GRAY}β”‚ └─ All source files validated.${C.RESET}`);
192
- } else {
193
- console.log(` ${C.GRAY}└─ Discovery: ${C.RED}No test/page files found!${C.RESET}`);
194
- }
252
+ console.log(` ${C.GRAY}β”œβ”€ Discovery: ${C.RESET}${C.GREEN}${allFiles.length} files matched${C.RESET}`);
253
+ if (allFiles.length === 0) console.log(` ${C.GRAY}└─ ${C.RED}Warning: No test files found in expected directories.${C.RESET}`);
254
+ else console.log(` ${C.GRAY}└─ All source folders are accessible.${C.RESET}`);
195
255
  console.log("");
196
256
 
197
- // 3. API & SUBSCRIPTION HUB
198
- console.log(`${C.CYAN}3. API & CLOUD HUB${C.RESET}`);
257
+ // 3. API CONNECTIVITY
258
+ console.log(`${C.CYAN}3. API CONNECTIVITY${C.RESET}`);
199
259
  const apiKey = process.env.DEFLAKE_API_KEY || "NOT_SET";
200
- const masked = apiKey === "NOT_SET" ? apiKey : (apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length - 4));
201
- console.log(` ${C.GRAY}β”œβ”€ API Key: ${C.RESET}${apiKey === "NOT_SET" ? C.RED : C.GREEN}${masked}${C.RESET}`);
202
-
203
260
  if (apiKey !== "NOT_SET") {
204
261
  try {
205
- process.stdout.write(` ${C.GRAY}β”œβ”€ Gateway: ${C.RESET}πŸ“‘ Pinging...`);
206
262
  const client = new DeFlakeClient();
207
- const start = Date.now();
208
263
  const usage = await client.getUsage();
209
- const latency = Date.now() - start;
210
264
  if (usage?.status === 'success') {
211
- process.stdout.write(`\r ${C.GRAY}β”œβ”€ Gateway: ${C.RESET}${C.GREEN}ONLINE (${latency}ms)${C.RESET} \n`);
212
- console.log(` ${C.GRAY}└─ Quota: ${C.RESET}${C.MAGENTA}${usage.data.usage}/${usage.data.limit} fixes used${C.RESET}`);
265
+ console.log(` ${C.GRAY}β”œβ”€ Connection: ${C.RESET}${C.GREEN}ONLINE${C.RESET}`);
266
+ console.log(` ${C.GRAY}└─ Quota: ${C.RESET}${C.MAGENTA}${usage.data.usage}/${usage.data.limit} monthly fixes used${C.RESET}`);
213
267
  } else {
214
- process.stdout.write(`\r ${C.GRAY}β”œβ”€ Gateway: ${C.RESET}${C.RED}Connection Failed (${usage?.message || 'Unauthorized'})${C.RESET} \n`);
268
+ console.log(` ${C.GRAY}└─ Connection: ${C.RESET}${C.RED}OFFLINE (Unauthorized)${C.RESET}`);
215
269
  }
216
270
  } catch (e) {
217
- process.stdout.write(`\r ${C.GRAY}β”œβ”€ Gateway: ${C.RESET}${C.RED}Link Down (${e.message})${C.RESET} \n`);
271
+ console.log(` ${C.GRAY}└─ Connection: ${C.RESET}${C.RED}OFFLINE (${e.message})${C.RESET}`);
218
272
  }
219
273
  } else {
220
- console.log(` ${C.GRAY}└─ Status: ${C.RED}Awaiting credentials...${C.RESET}`);
274
+ console.log(` ${C.GRAY}└─ API Key: ${C.RED}MISSING${C.RESET}`);
221
275
  }
222
276
  console.log("");
223
277
 
224
- // 4. THE LIBERATION AUDIT (Permission Status)
225
- console.log(`${C.CYAN}4. LIBERATION AUDIT (Permission Status)${C.RESET}`);
278
+ // 4. DEEP ACCESS AUDIT (The Truth Teller)
279
+ console.log(`${C.CYAN}4. DEEP ACCESS AUDIT (The Truth Teller)${C.RESET}`);
226
280
  const artDirs = [
227
281
  { path: 'test-results', label: 'Test Results' },
228
282
  { path: 'playwright-report', label: 'HTML Report' }
229
283
  ];
230
- let allLiberated = true;
231
- let totalItems = 0;
284
+ let allPristine = true;
285
+ let sandboxed = false;
232
286
 
233
287
  for (const d of artDirs) {
234
288
  if (fs.existsSync(d.path)) {
235
- process.stdout.write(` ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}πŸ” Checking...`);
236
- let dItems = 0;
289
+ process.stdout.write(` ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}πŸ” Scanning Deep...`);
237
290
  try {
238
- const scan = (p) => {
239
- dItems++;
240
- totalItems++;
241
- fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK);
242
- if (fs.statSync(p).isDirectory()) fs.readdirSync(p).forEach(f => scan(path.join(p, f)));
243
- };
244
- scan(d.path);
245
- process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.GREEN}${C.BRIGHT}[LIBERADOS]${C.RESET}${C.GREEN} (${dItems} Γ­tems accesibles)${C.RESET} \n`);
291
+ // A shallow accessSync is NOT enough for Mac Sandbox. Must list and stat.
292
+ const items = fs.readdirSync(d.path);
293
+ if (items.length > 0) {
294
+ process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.GREEN}${C.BRIGHT}[LIBERADOS]${C.RESET}${C.GREEN} (${items.length} top-level objects)${C.RESET} \n`);
295
+ } else {
296
+ process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}Vacia (No hay resultados)${C.RESET} \n`);
297
+ }
246
298
  } catch (e) {
247
- process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.RED}${C.BRIGHT}[BLOQUEADOS]${C.RESET}${C.RED} (Error en: ${path.basename(e.path)})${C.RESET} \n`);
248
- allLiberated = false;
299
+ if (e.code === 'EPERM' || e.message.includes('Operation not permitted')) {
300
+ process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.BG_RED}${C.WHITE} BLOQUEADO POR MAC PRIVACY ${C.RESET} \n`);
301
+ sandboxed = true;
302
+ } else {
303
+ process.stdout.write(`\r ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.RED}[ERROR] (${e.code})${C.RESET} \n`);
304
+ }
305
+ allPristine = false;
249
306
  }
250
307
  } else {
251
- console.log(` ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}Sin carpeta (AΓΊn no hay resultados)${C.RESET}`);
308
+ console.log(` ${C.GRAY}β”œβ”€ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}No detectada${C.RESET}`);
252
309
  }
253
310
  }
254
311
 
255
- if (!allLiberated) {
256
- console.log(`\n ${C.BG_BLUE}${C.WHITE}${C.BRIGHT} ACCIΓ“N REQUERIDA: LIBERAR PERMISOS ${C.RESET}`);
312
+ if (sandboxed) {
313
+ console.log(`\n ${C.BG_RED}${C.WHITE}${C.BRIGHT} ALERTA: MAC SANDBOX DETECTADO ${C.RESET}`);
314
+ console.log(` ${C.BRIGHT}Tu proyecto estΓ‘ en Documents/Desktop y Mac bloquea el acceso profundo.${C.RESET}`);
315
+ console.log(` ${C.CYAN}SOLUCIΓ“N 1:${C.RESET} Dale "Full Disk Access" a tu Terminal en Settings > Privacy.`);
316
+ console.log(` ${C.CYAN}SOLUCIΓ“N 2:${C.RESET} Mueve el proyecto fuera de Documents (ej: /Users/hugo/code/...)`);
317
+ } else if (!allPristine) {
318
+ console.log(`\n ${C.YELLOW}⚠️ PERMISOS DE ARCHIVO REQUERIDOS:${C.RESET}`);
257
319
  const fix = process.platform === 'win32' ? 'icacls . /grant ${env:USERNAME}:(OI)(CI)F /T' : 'sudo chown -R $(whoami) .';
258
- console.log(` Ejecuta esto en tu terminal: ${C.CYAN}${fix}${C.RESET}`);
320
+ console.log(` Ejecuta: ${C.CYAN}${fix}${C.RESET}`);
259
321
  } else {
260
- console.log(` ${C.GRAY}└─ AuditorΓ­a: ${C.RESET}${C.GREEN}${totalItems} archivos verificados con Γ©xito.${C.RESET}`);
261
- console.log(`\n${C.BG_GREEN}${C.WHITE}${C.BRIGHT} ESTADO DEL SISTEMA: LIBERADO Y OPERATIVO ${C.RESET}`);
262
- console.log(` Ya puedes correr: ${C.CYAN}npx deflake --fix --report npx playwright test${C.RESET}`);
322
+ console.log(`\n${C.BG_GREEN}${C.WHITE}${C.BRIGHT} SISTEMA SALUDABLE Y LIBERADO ${C.RESET}`);
263
323
  }
264
- console.log("\n" + C.GRAY + "─".repeat(50) + C.RESET + "\n");
324
+ console.log("\n" + C.GRAY + "─".repeat(55) + C.RESET + "\n");
265
325
  }
266
326
 
267
327
  function showReport() {
328
+ // Kill any existing report server to prevent EADDRINUSE
329
+ try {
330
+ execSync('lsof -ti:9323 | xargs kill -9 2>/dev/null', { stdio: 'ignore' });
331
+ } catch (e) { /* no process to kill, that's fine */ }
268
332
  spawn('npx playwright show-report', { shell: true, stdio: 'inherit' }).on('error', () => {});
269
333
  }
package/client.js CHANGED
@@ -105,11 +105,13 @@ class DeFlakeClient {
105
105
 
106
106
  async heal(logPath, htmlPath, failureLocation = null, sourceCode = null, applyFix = false) {
107
107
  try {
108
- // ... (rest of the check logic) ...
109
- if (!fs.existsSync(logPath)) throw new Error(`Log file not found: ${logPath}`);
110
108
  if (!fs.existsSync(htmlPath)) throw new Error(`HTML file not found: ${htmlPath}`);
111
109
 
112
- const logContent = fs.readFileSync(logPath, 'utf8');
110
+ // logPath is optional β€” when null, we use htmlPath as the primary error context
111
+ let logContent = '';
112
+ if (logPath && fs.existsSync(logPath)) {
113
+ logContent = fs.readFileSync(logPath, 'utf8');
114
+ }
113
115
 
114
116
  // Handle binary artifacts (Screenshots) vs Text artifacts (HTML/MD)
115
117
  const isImage = htmlPath.toLowerCase().endsWith('.png');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.21",
3
+ "version": "1.2.27",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {