@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.
- package/dist/esm/qa-agent/orchestrator.js +12 -3
- package/dist/esm/qa-agent/phase25/fix_loop.js +4 -1
- package/dist/esm/qa-agent/phase25/fix_tools.js +26 -0
- package/dist/qa-agent/orchestrator.d.ts.map +1 -1
- package/dist/qa-agent/orchestrator.js +12 -3
- package/dist/qa-agent/phase25/fix_loop.d.ts.map +1 -1
- package/dist/qa-agent/phase25/fix_loop.js +4 -1
- package/dist/qa-agent/phase25/fix_tools.d.ts +2 -0
- package/dist/qa-agent/phase25/fix_tools.d.ts.map +1 -1
- package/dist/qa-agent/phase25/fix_tools.js +26 -0
- package/package.json +1 -1
|
@@ -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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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;
|
|
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
|
|
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,
|
|
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.
|
|
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",
|