deflake 1.2.36 → 1.2.38

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 (2) hide show
  1. package/cli.js +89 -10
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -46,15 +46,63 @@ async function main() {
46
46
  console.log(`šŸ” Detected Framework: ${C.GREEN}${fw.toUpperCase()}${C.RESET}`);
47
47
 
48
48
  if (commandToRun) {
49
- const { code, output } = await runNative(commandToRun);
50
- if (code !== 0) {
49
+ const MAX_ATTEMPTS = 5;
50
+ let { code, output } = await runNative(commandToRun);
51
+
52
+ if (code !== 0 && argv.fix) {
51
53
  console.log(`\nšŸ”“ Command failed with code ${code}. Activating DeFlake...`);
52
- const artifacts = detectFailures();
53
- const applied = await analyzeAndFix(artifacts, client, argv, output);
54
- if (applied > 0 && argv.fix) {
55
- console.log(`\n${C.BRIGHT}šŸ’‰ Fixes applied. Re-running tests to verify...${C.RESET}`);
56
- await runNative(commandToRun);
54
+
55
+ // Save backups of all source files before any modifications
56
+ const backups = new Map(); // path -> original content
57
+ let prevFailCount = Infinity;
58
+
59
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
60
+ console.log(`\n${C.BRIGHT}${C.CYAN}šŸ”„ Fix attempt ${attempt}/${MAX_ATTEMPTS}${C.RESET}`);
61
+
62
+ const artifacts = detectFailures();
63
+ const currentFailCount = artifacts.length;
64
+
65
+ if (currentFailCount === 0) {
66
+ console.log(`${C.GREEN}šŸŽ‰ All tests passing! No failures detected.${C.RESET}`);
67
+ break;
68
+ }
69
+
70
+ // If failures INCREASED since last attempt, rollback all changes
71
+ if (attempt > 1 && currentFailCount > prevFailCount) {
72
+ console.log(`${C.RED}āš ļø Failures increased (${prevFailCount} → ${currentFailCount}). Rolling back all changes...${C.RESET}`);
73
+ for (const [filePath, originalContent] of backups) {
74
+ fs.writeFileSync(filePath, originalContent);
75
+ console.log(` ${C.YELLOW}ā†©ļø Restored: ${path.basename(filePath)}${C.RESET}`);
76
+ }
77
+ break;
78
+ }
79
+ prevFailCount = currentFailCount;
80
+
81
+ const applied = await analyzeAndFix(artifacts, client, argv, output, backups);
82
+
83
+ if (applied === 0) {
84
+ console.log(`${C.YELLOW}āš ļø No fixes could be applied. Stopping retry loop.${C.RESET}`);
85
+ break;
86
+ }
87
+
88
+
89
+ console.log(`\n${C.BRIGHT}šŸ’‰ ${applied} fix(es) applied. Re-running tests to verify (attempt ${attempt})...${C.RESET}`);
90
+ const rerun = await runNative(commandToRun);
91
+ output = rerun.output; // Use new output for next iteration's AI context
92
+
93
+ if (rerun.code === 0) {
94
+ console.log(`\n${C.GREEN}${C.BRIGHT}šŸŽ‰ All tests passing after attempt ${attempt}!${C.RESET}`);
95
+ break;
96
+ }
97
+
98
+ if (attempt === MAX_ATTEMPTS) {
99
+ console.log(`\n${C.YELLOW}āš ļø Max attempts (${MAX_ATTEMPTS}) reached. Some failures may persist.${C.RESET}`);
100
+ }
57
101
  }
102
+ } else if (code !== 0) {
103
+ console.log(`\nšŸ”“ Command failed with code ${code}. Activating DeFlake...`);
104
+ const artifacts = detectFailures();
105
+ await analyzeAndFix(artifacts, client, argv, output, new Map());
58
106
  }
59
107
  }
60
108
  if (argv.report) showReport();
@@ -110,7 +158,7 @@ function detectFailures() {
110
158
  return results;
111
159
  }
112
160
 
113
- async function analyzeAndFix(artifacts, client, argv, capturedOutput = '') {
161
+ async function analyzeAndFix(artifacts, client, argv, capturedOutput = '', backups = new Map()) {
114
162
  if (artifacts.length === 0) {
115
163
  console.log(` ${C.YELLOW}āš ļø No failure artifacts detected. Run 'npx deflake doctor' to check permissions.${C.RESET}`);
116
164
  return 0;
@@ -186,7 +234,7 @@ async function analyzeAndFix(artifacts, client, argv, capturedOutput = '') {
186
234
  }
187
235
  if (fixLoc) {
188
236
  console.log(` ${C.BRIGHT}šŸ’‰ Applying patches...${C.RESET}`);
189
- if (await applyFix(res, fixLoc)) {
237
+ if (await applyFix(res, fixLoc, backups)) {
190
238
  count++;
191
239
  console.log(` ${C.GREEN}${C.BRIGHT}āœ… Fix applied to ${path.basename(fixLoc.path)}:${fixLoc.line}${C.RESET}`);
192
240
  } else {
@@ -216,10 +264,30 @@ async function analyzeAndFix(artifacts, client, argv, capturedOutput = '') {
216
264
  }
217
265
  console.log(`${C.BRIGHT}━━━ DeFlake Summary: ${count}/${artifacts.length} fix(es) applied ━━━${C.RESET}\n`);
218
266
  return count;
267
+ }
219
268
 
269
+ function checkBalance(code) {
270
+ const pairs = { '(': ')', '{': '}', '[': ']' };
271
+ const stack = [];
272
+ // Skip strings and comments for accuracy
273
+ let inString = false, stringChar = '', escaped = false;
274
+ for (const ch of code) {
275
+ if (escaped) { escaped = false; continue; }
276
+ if (ch === '\\') { escaped = true; continue; }
277
+ if (inString) { if (ch === stringChar) inString = false; continue; }
278
+ if (ch === '"' || ch === "'" || ch === '`') { inString = true; stringChar = ch; continue; }
279
+ if (pairs[ch]) stack.push(pairs[ch]);
280
+ else if (ch === ')' || ch === '}' || ch === ']') {
281
+ if (stack.length === 0 || stack.pop() !== ch) {
282
+ return { balanced: false, detail: `unexpected '${ch}'` };
283
+ }
284
+ }
285
+ }
286
+ if (stack.length > 0) return { balanced: false, detail: `unclosed '${stack[stack.length - 1] === ')' ? '(' : stack[stack.length - 1] === '}' ? '{' : '['}'` };
287
+ return { balanced: true, detail: 'ok' };
220
288
  }
221
289
 
222
- async function applyFix(res, loc) {
290
+ async function applyFix(res, loc, backups = new Map()) {
223
291
  if (!loc?.path) {
224
292
  console.log(` ${C.YELLOW}āš ļø Cannot apply fix: failure location not detected.${C.RESET}`);
225
293
  return false;
@@ -231,6 +299,10 @@ async function applyFix(res, loc) {
231
299
  return false;
232
300
  }
233
301
  const original = fs.readFileSync(loc.path, 'utf8');
302
+ // Save backup before first modification
303
+ if (!backups.has(loc.path)) {
304
+ backups.set(loc.path, original);
305
+ }
234
306
  let content = original;
235
307
  let appliedCount = 0;
236
308
  for (const p of patches) {
@@ -270,6 +342,13 @@ async function applyFix(res, loc) {
270
342
  }
271
343
  }
272
344
  if (appliedCount > 0) {
345
+ // SAFETY: Bracket balance check — rollback if patches broke syntax
346
+ const originalBalance = checkBalance(original);
347
+ const patchedBalance = checkBalance(content);
348
+ if (originalBalance.balanced && !patchedBalance.balanced) {
349
+ console.log(` ${C.YELLOW}ā­ļø Rollback: patches broke bracket balance (${patchedBalance.detail}). Reverting file.${C.RESET}`);
350
+ return false;
351
+ }
273
352
  fs.writeFileSync(loc.path, content);
274
353
  return true;
275
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {