@yasserkhanorg/impact-gate 2.1.2 → 2.1.4

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.
@@ -109,13 +109,22 @@ export async function runQAAgent(inputConfig) {
109
109
  }
110
110
  }
111
111
  // -----------------------------------------------------------------------
112
+ // Compute remaining findings (exclude verified fixes)
113
+ // -----------------------------------------------------------------------
114
+ const verifiedIds = new Set((phase25?.fixes ?? [])
115
+ .filter((f) => f.status === 'verified')
116
+ .map((f) => f.findingId));
117
+ const remainingFindings = verifiedIds.size > 0
118
+ ? phase2.findings.filter((f) => !verifiedIds.has(f.id))
119
+ : phase2.findings;
120
+ // -----------------------------------------------------------------------
112
121
  // Regression comparison (optional)
113
122
  // -----------------------------------------------------------------------
114
123
  let regressionComparison;
115
124
  if (config.regression) {
116
125
  const baseline = loadBaseline(outputDir);
117
126
  if (baseline) {
118
- regressionComparison = compareBaselines(healthScore, phase2.findings, baseline);
127
+ regressionComparison = compareBaselines(healthScore, remainingFindings, baseline);
119
128
  logger.info('Regression comparison', {
120
129
  scoreDelta: regressionComparison.scoreDelta,
121
130
  fixedIssues: regressionComparison.fixedIssues.length,
@@ -126,8 +135,8 @@ export async function runQAAgent(inputConfig) {
126
135
  logger.info('No baseline found — saving current run as baseline');
127
136
  }
128
137
  }
129
- // Always save baseline for future comparisons
130
- saveBaseline(outputDir, healthScore, phase2.findings, config.baseUrl);
138
+ // Always save baseline for future comparisons (use remaining findings, not stale originals)
139
+ saveBaseline(outputDir, healthScore, remainingFindings, config.baseUrl);
131
140
  // -----------------------------------------------------------------------
132
141
  // Phase 3: Report + Spec Generation + Verdict
133
142
  // -----------------------------------------------------------------------
@@ -47,7 +47,9 @@ ${evidence.consoleErrors?.length ? `- **Console errors:** ${evidence.consoleErro
47
47
  - Make the SMALLEST change that fixes the issue. Do NOT refactor surrounding code.
48
48
  - Only modify files directly related to the bug.
49
49
  - If you can't find the source after 3 search attempts, report that the fix is not possible.
50
- - If type checking fails after your fix, revert with git_revert.
50
+ - If type checking or lint fails BEFORE you commit, use git_restore to discard your edits, then report the fix is not possible.
51
+ - If you already committed and verification fails, use git_revert to undo the commit.
52
+ - NEVER leave uncommitted edits behind. Always either commit or restore.
51
53
  - The base URL is ${baseUrl}.
52
54
  - When done, respond with text only (no tool use) explaining the result.`;
53
55
  }
@@ -92,6 +94,7 @@ export async function runFixLoop(config, findings, browser, projectRoot) {
92
94
  screenshotDir,
93
95
  screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
94
96
  qaCommitHashes: new Set(),
97
+ pendingWrittenFiles: new Set(),
95
98
  };
96
99
  for (const finding of fixable) {
97
100
  if (wtf.shouldStop()) {
@@ -80,6 +80,15 @@ export const FIX_TOOL_DEFINITIONS = [
80
80
  required: [],
81
81
  },
82
82
  },
83
+ {
84
+ name: 'git_restore',
85
+ description: 'Discard all uncommitted changes in the working tree. Use this if validation fails BEFORE you have committed, to clean up your attempted edits.',
86
+ input_schema: {
87
+ type: 'object',
88
+ properties: {},
89
+ required: [],
90
+ },
91
+ },
83
92
  {
84
93
  name: 'verify_in_browser',
85
94
  description: 'Navigate to a URL, take a screenshot, and report whether the fix resolved the issue. You MUST set fixed=true only if the original bug is no longer present. Set fixed=false if the bug still reproduces or if you cannot confirm.',
@@ -152,6 +161,7 @@ export function executeFixTool(ctx, name, input) {
152
161
  }
153
162
  const fullPath = resolve(ctx.projectRoot, filePath);
154
163
  writeFileSync(fullPath, String(input.content), 'utf-8');
164
+ ctx.pendingWrittenFiles.add(filePath);
155
165
  return { output: `Written: ${filePath}`, filesChanged: [filePath] };
156
166
  }
157
167
  case 'search_code': {
@@ -218,6 +228,7 @@ export function executeFixTool(ctx, name, input) {
218
228
  execFileSync('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
219
229
  const hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
220
230
  ctx.qaCommitHashes.add(hash);
231
+ ctx.pendingWrittenFiles.clear();
221
232
  return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
222
233
  }
223
234
  catch (err) {
@@ -242,6 +253,21 @@ export function executeFixTool(ctx, name, input) {
242
253
  return { output: `Git revert failed: ${error.stderr || String(err)}` };
243
254
  }
244
255
  }
256
+ case 'git_restore': {
257
+ const filesToRestore = [...ctx.pendingWrittenFiles];
258
+ if (filesToRestore.length === 0) {
259
+ return { output: 'No pending edits to restore.' };
260
+ }
261
+ try {
262
+ execFileSync('git', ['checkout', '--', ...filesToRestore], { cwd: ctx.projectRoot, encoding: 'utf-8' });
263
+ ctx.pendingWrittenFiles.clear();
264
+ return { output: `Restored ${filesToRestore.length} file(s): ${filesToRestore.join(', ')}` };
265
+ }
266
+ catch (err) {
267
+ const error = err;
268
+ return { output: `Git restore failed: ${error.stderr || String(err)}` };
269
+ }
270
+ }
245
271
  case 'verify_in_browser': {
246
272
  const url = String(input.url);
247
273
  const label = String(input.label || 'verify').replace(/[^a-zA-Z0-9_-]/g, '_');
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/qa-agent/orchestrator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0D,QAAQ,EAAE,QAAQ,EAAoC,MAAM,YAAY,CAAC;AAgB/I,wBAAsB,UAAU,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqJzE"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/qa-agent/orchestrator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0D,QAAQ,EAAE,QAAQ,EAAoC,MAAM,YAAY,CAAC;AAgB/I,wBAAsB,UAAU,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiKzE"}
@@ -112,13 +112,22 @@ async function runQAAgent(inputConfig) {
112
112
  }
113
113
  }
114
114
  // -----------------------------------------------------------------------
115
+ // Compute remaining findings (exclude verified fixes)
116
+ // -----------------------------------------------------------------------
117
+ const verifiedIds = new Set((phase25?.fixes ?? [])
118
+ .filter((f) => f.status === 'verified')
119
+ .map((f) => f.findingId));
120
+ const remainingFindings = verifiedIds.size > 0
121
+ ? phase2.findings.filter((f) => !verifiedIds.has(f.id))
122
+ : phase2.findings;
123
+ // -----------------------------------------------------------------------
115
124
  // Regression comparison (optional)
116
125
  // -----------------------------------------------------------------------
117
126
  let regressionComparison;
118
127
  if (config.regression) {
119
128
  const baseline = (0, baseline_js_1.loadBaseline)(outputDir);
120
129
  if (baseline) {
121
- regressionComparison = (0, baseline_js_1.compareBaselines)(healthScore, phase2.findings, baseline);
130
+ regressionComparison = (0, baseline_js_1.compareBaselines)(healthScore, remainingFindings, baseline);
122
131
  logger_js_1.logger.info('Regression comparison', {
123
132
  scoreDelta: regressionComparison.scoreDelta,
124
133
  fixedIssues: regressionComparison.fixedIssues.length,
@@ -129,8 +138,8 @@ async function runQAAgent(inputConfig) {
129
138
  logger_js_1.logger.info('No baseline found — saving current run as baseline');
130
139
  }
131
140
  }
132
- // Always save baseline for future comparisons
133
- (0, baseline_js_1.saveBaseline)(outputDir, healthScore, phase2.findings, config.baseUrl);
141
+ // Always save baseline for future comparisons (use remaining findings, not stale originals)
142
+ (0, baseline_js_1.saveBaseline)(outputDir, healthScore, remainingFindings, config.baseUrl);
134
143
  // -----------------------------------------------------------------------
135
144
  // Phase 3: Report + Spec Generation + Verdict
136
145
  // -----------------------------------------------------------------------
@@ -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;AAuD7D,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"}
@@ -53,7 +53,9 @@ ${evidence.consoleErrors?.length ? `- **Console errors:** ${evidence.consoleErro
53
53
  - Make the SMALLEST change that fixes the issue. Do NOT refactor surrounding code.
54
54
  - Only modify files directly related to the bug.
55
55
  - If you can't find the source after 3 search attempts, report that the fix is not possible.
56
- - If type checking fails after your fix, revert with git_revert.
56
+ - If type checking or lint fails BEFORE you commit, use git_restore to discard your edits, then report the fix is not possible.
57
+ - If you already committed and verification fails, use git_revert to undo the commit.
58
+ - NEVER leave uncommitted edits behind. Always either commit or restore.
57
59
  - The base URL is ${baseUrl}.
58
60
  - When done, respond with text only (no tool use) explaining the result.`;
59
61
  }
@@ -98,6 +100,7 @@ async function runFixLoop(config, findings, browser, projectRoot) {
98
100
  screenshotDir,
99
101
  screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
100
102
  qaCommitHashes: new Set(),
103
+ pendingWrittenFiles: new Set(),
101
104
  };
102
105
  for (const finding of fixable) {
103
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,EAuFhD,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,CAoJf"}
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,CAqKf"}
@@ -84,6 +84,15 @@ exports.FIX_TOOL_DEFINITIONS = [
84
84
  required: [],
85
85
  },
86
86
  },
87
+ {
88
+ name: 'git_restore',
89
+ description: 'Discard all uncommitted changes in the working tree. Use this if validation fails BEFORE you have committed, to clean up your attempted edits.',
90
+ input_schema: {
91
+ type: 'object',
92
+ properties: {},
93
+ required: [],
94
+ },
95
+ },
87
96
  {
88
97
  name: 'verify_in_browser',
89
98
  description: 'Navigate to a URL, take a screenshot, and report whether the fix resolved the issue. You MUST set fixed=true only if the original bug is no longer present. Set fixed=false if the bug still reproduces or if you cannot confirm.',
@@ -156,6 +165,7 @@ function executeFixTool(ctx, name, input) {
156
165
  }
157
166
  const fullPath = (0, path_1.resolve)(ctx.projectRoot, filePath);
158
167
  (0, fs_1.writeFileSync)(fullPath, String(input.content), 'utf-8');
168
+ ctx.pendingWrittenFiles.add(filePath);
159
169
  return { output: `Written: ${filePath}`, filesChanged: [filePath] };
160
170
  }
161
171
  case 'search_code': {
@@ -222,6 +232,7 @@ function executeFixTool(ctx, name, input) {
222
232
  (0, child_process_1.execFileSync)('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
223
233
  const hash = (0, child_process_1.execFileSync)('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
224
234
  ctx.qaCommitHashes.add(hash);
235
+ ctx.pendingWrittenFiles.clear();
225
236
  return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
226
237
  }
227
238
  catch (err) {
@@ -246,6 +257,21 @@ function executeFixTool(ctx, name, input) {
246
257
  return { output: `Git revert failed: ${error.stderr || String(err)}` };
247
258
  }
248
259
  }
260
+ case 'git_restore': {
261
+ const filesToRestore = [...ctx.pendingWrittenFiles];
262
+ if (filesToRestore.length === 0) {
263
+ return { output: 'No pending edits to restore.' };
264
+ }
265
+ try {
266
+ (0, child_process_1.execFileSync)('git', ['checkout', '--', ...filesToRestore], { cwd: ctx.projectRoot, encoding: 'utf-8' });
267
+ ctx.pendingWrittenFiles.clear();
268
+ return { output: `Restored ${filesToRestore.length} file(s): ${filesToRestore.join(', ')}` };
269
+ }
270
+ catch (err) {
271
+ const error = err;
272
+ return { output: `Git restore failed: ${error.stderr || String(err)}` };
273
+ }
274
+ }
249
275
  case 'verify_in_browser': {
250
276
  const url = String(input.url);
251
277
  const label = String(input.label || 'verify').replace(/[^a-zA-Z0-9_-]/g, '_');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/impact-gate",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
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",