@yasserkhanorg/impact-gate 2.1.3 → 2.1.5

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.
@@ -94,6 +94,7 @@ export async function runFixLoop(config, findings, browser, projectRoot) {
94
94
  screenshotDir,
95
95
  screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
96
96
  qaCommitHashes: new Set(),
97
+ pendingWrittenFiles: new Set(),
97
98
  };
98
99
  for (const finding of fixable) {
99
100
  if (wtf.shouldStop()) {
@@ -159,8 +159,21 @@ export function executeFixTool(ctx, name, input) {
159
159
  if (!isPathSafe(ctx.projectRoot, filePath)) {
160
160
  return { output: `Blocked: "${filePath}" is outside the project or a restricted path.` };
161
161
  }
162
+ // Refuse to overwrite files that already have uncommitted user changes
163
+ if (!ctx.pendingWrittenFiles.has(filePath)) {
164
+ try {
165
+ const status = execFileSync('git', ['status', '--porcelain', '--', filePath], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
166
+ if (status.length > 0) {
167
+ return { output: `Blocked: "${filePath}" has uncommitted user changes. Choose a different file or skip this fix.` };
168
+ }
169
+ }
170
+ catch {
171
+ // git status failed — allow the write (file may be new/untracked)
172
+ }
173
+ }
162
174
  const fullPath = resolve(ctx.projectRoot, filePath);
163
175
  writeFileSync(fullPath, String(input.content), 'utf-8');
176
+ ctx.pendingWrittenFiles.add(filePath);
164
177
  return { output: `Written: ${filePath}`, filesChanged: [filePath] };
165
178
  }
166
179
  case 'search_code': {
@@ -227,6 +240,7 @@ export function executeFixTool(ctx, name, input) {
227
240
  execFileSync('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
228
241
  const hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
229
242
  ctx.qaCommitHashes.add(hash);
243
+ ctx.pendingWrittenFiles.clear();
230
244
  return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
231
245
  }
232
246
  catch (err) {
@@ -252,9 +266,14 @@ export function executeFixTool(ctx, name, input) {
252
266
  }
253
267
  }
254
268
  case 'git_restore': {
269
+ const filesToRestore = [...ctx.pendingWrittenFiles];
270
+ if (filesToRestore.length === 0) {
271
+ return { output: 'No pending edits to restore.' };
272
+ }
255
273
  try {
256
- execFileSync('git', ['checkout', '--', '.'], { cwd: ctx.projectRoot, encoding: 'utf-8' });
257
- return { output: 'Restored working tree to last commit state. All uncommitted edits discarded.' };
274
+ execFileSync('git', ['checkout', '--', ...filesToRestore], { cwd: ctx.projectRoot, encoding: 'utf-8' });
275
+ ctx.pendingWrittenFiles.clear();
276
+ return { output: `Restored ${filesToRestore.length} file(s): ${filesToRestore.join(', ')}` };
258
277
  }
259
278
  catch (err) {
260
279
  const error = err;
@@ -1 +1 @@
1
- {"version":3,"file":"fix_loop.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase25/fix_loop.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,OAAO,EAAqC,aAAa,EAAE,QAAQ,EAAC,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAC;AAyD7D,wBAAsB,UAAU,CAC5B,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,CAAC,CA4FxB"}
1
+ {"version":3,"file":"fix_loop.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase25/fix_loop.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,OAAO,EAAqC,aAAa,EAAE,QAAQ,EAAC,MAAM,aAAa,CAAC;AACrG,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAC;AAyD7D,wBAAsB,UAAU,CAC5B,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,CAAC,CA6FxB"}
@@ -100,6 +100,7 @@ async function runFixLoop(config, findings, browser, projectRoot) {
100
100
  screenshotDir,
101
101
  screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
102
102
  qaCommitHashes: new Set(),
103
+ pendingWrittenFiles: new Set(),
103
104
  };
104
105
  for (const finding of fixable) {
105
106
  if (wtf.shouldStop()) {
@@ -9,6 +9,8 @@ export interface FixToolContext {
9
9
  screenshotCounter: number;
10
10
  /** Commit hashes created by the fix loop. Only these can be reverted. */
11
11
  qaCommitHashes: Set<string>;
12
+ /** Files written by the current fix attempt. Only these are restored on failure. */
13
+ pendingWrittenFiles: Set<string>;
12
14
  }
13
15
  export interface FixToolResult {
14
16
  output: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fix_tools.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase25/fix_tools.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAC;AAM7D,eAAO,MAAM,oBAAoB,EAAE,SAAS,CAAC,IAAI,EAgGhD,CAAC;AAMF,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2CD,wBAAgB,cAAc,CAC1B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,aAAa,CA8Jf"}
1
+ {"version":3,"file":"fix_tools.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase25/fix_tools.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAC;AAM7D,eAAO,MAAM,oBAAoB,EAAE,SAAS,CAAC,IAAI,EAgGhD,CAAC;AAMF,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,oFAAoF;IACpF,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2CD,wBAAgB,cAAc,CAC1B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,aAAa,CAmLf"}
@@ -163,8 +163,21 @@ function executeFixTool(ctx, name, input) {
163
163
  if (!isPathSafe(ctx.projectRoot, filePath)) {
164
164
  return { output: `Blocked: "${filePath}" is outside the project or a restricted path.` };
165
165
  }
166
+ // Refuse to overwrite files that already have uncommitted user changes
167
+ if (!ctx.pendingWrittenFiles.has(filePath)) {
168
+ try {
169
+ const status = (0, child_process_1.execFileSync)('git', ['status', '--porcelain', '--', filePath], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
170
+ if (status.length > 0) {
171
+ return { output: `Blocked: "${filePath}" has uncommitted user changes. Choose a different file or skip this fix.` };
172
+ }
173
+ }
174
+ catch {
175
+ // git status failed — allow the write (file may be new/untracked)
176
+ }
177
+ }
166
178
  const fullPath = (0, path_1.resolve)(ctx.projectRoot, filePath);
167
179
  (0, fs_1.writeFileSync)(fullPath, String(input.content), 'utf-8');
180
+ ctx.pendingWrittenFiles.add(filePath);
168
181
  return { output: `Written: ${filePath}`, filesChanged: [filePath] };
169
182
  }
170
183
  case 'search_code': {
@@ -231,6 +244,7 @@ function executeFixTool(ctx, name, input) {
231
244
  (0, child_process_1.execFileSync)('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
232
245
  const hash = (0, child_process_1.execFileSync)('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
233
246
  ctx.qaCommitHashes.add(hash);
247
+ ctx.pendingWrittenFiles.clear();
234
248
  return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
235
249
  }
236
250
  catch (err) {
@@ -256,9 +270,14 @@ function executeFixTool(ctx, name, input) {
256
270
  }
257
271
  }
258
272
  case 'git_restore': {
273
+ const filesToRestore = [...ctx.pendingWrittenFiles];
274
+ if (filesToRestore.length === 0) {
275
+ return { output: 'No pending edits to restore.' };
276
+ }
259
277
  try {
260
- (0, child_process_1.execFileSync)('git', ['checkout', '--', '.'], { cwd: ctx.projectRoot, encoding: 'utf-8' });
261
- return { output: 'Restored working tree to last commit state. All uncommitted edits discarded.' };
278
+ (0, child_process_1.execFileSync)('git', ['checkout', '--', ...filesToRestore], { cwd: ctx.projectRoot, encoding: 'utf-8' });
279
+ ctx.pendingWrittenFiles.clear();
280
+ return { output: `Restored ${filesToRestore.length} file(s): ${filesToRestore.join(', ')}` };
262
281
  }
263
282
  catch (err) {
264
283
  const error = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/impact-gate",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "Diff-aware E2E impact analysis and coverage gating for Playwright/Cypress teams. Optional AI features can suggest, generate, and heal tests once your project has a route-families.json manifest.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",