@yasserkhanorg/impact-gate 2.1.1 → 2.1.3
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/finding_taxonomy.js +6 -1
- package/dist/esm/qa-agent/orchestrator.js +12 -3
- package/dist/esm/qa-agent/phase2/agent_loop.js +2 -1
- package/dist/esm/qa-agent/phase2/tools.js +2 -2
- package/dist/esm/qa-agent/phase25/fix_loop.js +19 -6
- package/dist/esm/qa-agent/phase25/fix_tools.js +34 -5
- package/dist/esm/qa-agent/regression/baseline.js +12 -5
- package/dist/qa-agent/finding_taxonomy.d.ts.map +1 -1
- package/dist/qa-agent/finding_taxonomy.js +6 -1
- package/dist/qa-agent/orchestrator.d.ts.map +1 -1
- package/dist/qa-agent/orchestrator.js +12 -3
- package/dist/qa-agent/phase2/agent_loop.d.ts.map +1 -1
- package/dist/qa-agent/phase2/agent_loop.js +2 -1
- package/dist/qa-agent/phase2/tools.js +2 -2
- package/dist/qa-agent/phase25/fix_loop.d.ts.map +1 -1
- package/dist/qa-agent/phase25/fix_loop.js +19 -6
- package/dist/qa-agent/phase25/fix_tools.d.ts +4 -0
- package/dist/qa-agent/phase25/fix_tools.d.ts.map +1 -1
- package/dist/qa-agent/phase25/fix_tools.js +34 -5
- package/dist/qa-agent/regression/baseline.d.ts +0 -3
- package/dist/qa-agent/regression/baseline.d.ts.map +1 -1
- package/dist/qa-agent/regression/baseline.js +12 -5
- package/dist/qa-agent/types.d.ts +1 -1
- package/dist/qa-agent/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ const LEGACY_TO_CATEGORY = {
|
|
|
10
10
|
'ux-issue': 'ux',
|
|
11
11
|
};
|
|
12
12
|
const CANONICAL_CATEGORIES = new Set([
|
|
13
|
-
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility',
|
|
13
|
+
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links',
|
|
14
14
|
]);
|
|
15
15
|
/**
|
|
16
16
|
* Normalize any FindingType (legacy or canonical) to a canonical FindingCategory.
|
|
@@ -46,6 +46,11 @@ export const SEVERITY_DEFINITIONS = {
|
|
|
46
46
|
// Category definitions
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
48
48
|
export const CATEGORY_DEFINITIONS = {
|
|
49
|
+
links: {
|
|
50
|
+
label: 'Links',
|
|
51
|
+
description: 'Broken links (404), wrong destinations, dead anchors, external links that fail.',
|
|
52
|
+
examples: ['404 on nav link', 'Link goes to wrong page', 'Anchor target missing', 'External link returns 500'],
|
|
53
|
+
},
|
|
49
54
|
visual: {
|
|
50
55
|
label: 'Visual/UI',
|
|
51
56
|
description: 'Layout breaks, broken images, z-index issues, font/color inconsistencies, animation glitches, alignment issues, dark mode problems.',
|
|
@@ -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
|
// -----------------------------------------------------------------------
|
|
@@ -49,7 +49,8 @@ Pick dimensions that matter for THIS flow. Example: for "channel settings" → p
|
|
|
49
49
|
## Finding Categories
|
|
50
50
|
When reporting findings, use the most specific category:
|
|
51
51
|
- **visual** — Layout breaks, broken images, z-index issues, alignment, animation glitches, dark mode problems
|
|
52
|
-
- **
|
|
52
|
+
- **links** — Broken links (404), wrong destinations, dead anchors, external links that fail
|
|
53
|
+
- **functional** — Dead buttons, form validation failures, incorrect redirects, race conditions, state not persisting
|
|
53
54
|
- **ux** — Confusing navigation, missing loading indicators, slow interactions (>500ms), unclear error messages, no confirmation before destructive actions
|
|
54
55
|
- **content** — Typos, grammar errors, placeholder/lorem ipsum left in, truncated text, wrong labels
|
|
55
56
|
- **performance** — Slow page loads (>3s), janky scrolling, layout shifts (CLS), excessive network requests
|
|
@@ -99,7 +99,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
99
99
|
input_schema: {
|
|
100
100
|
type: 'object',
|
|
101
101
|
properties: {
|
|
102
|
-
type: { type: 'string', enum: ['visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'bug', 'visual-regression', 'ux-issue', 'gap'] },
|
|
102
|
+
type: { type: 'string', enum: ['visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links', 'bug', 'visual-regression', 'ux-issue', 'gap'] },
|
|
103
103
|
severity: { type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'] },
|
|
104
104
|
summary: { type: 'string', description: 'What you found' },
|
|
105
105
|
repro_steps: {
|
|
@@ -217,7 +217,7 @@ export function executeTool(ctx, name, input) {
|
|
|
217
217
|
}
|
|
218
218
|
case 'report_finding': {
|
|
219
219
|
const VALID_TYPES = new Set([
|
|
220
|
-
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility',
|
|
220
|
+
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links',
|
|
221
221
|
'bug', 'visual-regression', 'ux-issue', 'gap',
|
|
222
222
|
]);
|
|
223
223
|
const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low', 'info']);
|
|
@@ -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
|
}
|
|
@@ -91,6 +93,7 @@ export async function runFixLoop(config, findings, browser, projectRoot) {
|
|
|
91
93
|
baseUrl: config.baseUrl,
|
|
92
94
|
screenshotDir,
|
|
93
95
|
screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
|
|
96
|
+
qaCommitHashes: new Set(),
|
|
94
97
|
};
|
|
95
98
|
for (const finding of fixable) {
|
|
96
99
|
if (wtf.shouldStop()) {
|
|
@@ -111,7 +114,10 @@ export async function runFixLoop(config, findings, browser, projectRoot) {
|
|
|
111
114
|
costUSD += result.costUSD;
|
|
112
115
|
wtf.recordAttempt(result.fix.status, result.fix.filesChanged?.length || 0);
|
|
113
116
|
}
|
|
114
|
-
|
|
117
|
+
// Exclude verified fixes from the post-fix score so it reflects actual remaining issues
|
|
118
|
+
const verifiedIds = new Set(fixes.filter((f) => f.status === 'verified').map((f) => f.findingId));
|
|
119
|
+
const remainingFindings = findings.filter((f) => !verifiedIds.has(f.id));
|
|
120
|
+
const healthScoreAfter = computeHealthScore(remainingFindings);
|
|
115
121
|
return {
|
|
116
122
|
fixes,
|
|
117
123
|
fixesAttempted: fixes.filter((f) => f.status !== 'skipped').length,
|
|
@@ -134,6 +140,7 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
134
140
|
let filesChanged = [];
|
|
135
141
|
let beforeScreenshot;
|
|
136
142
|
let afterScreenshot;
|
|
143
|
+
let verifiedFixed = false;
|
|
137
144
|
let status = 'skipped';
|
|
138
145
|
// Take "before" screenshot
|
|
139
146
|
try {
|
|
@@ -176,9 +183,10 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
176
183
|
// If no tool use, the agent is done
|
|
177
184
|
const toolUseBlocks = assistantContent.filter((b) => b.type === 'tool_use');
|
|
178
185
|
if (toolUseBlocks.length === 0) {
|
|
179
|
-
// Determine status
|
|
186
|
+
// Determine status: 'verified' requires both a commit and explicit confirmation
|
|
187
|
+
// from verify_in_browser that the bug is gone. A screenshot alone is not proof.
|
|
180
188
|
if (commitHash) {
|
|
181
|
-
status =
|
|
189
|
+
status = verifiedFixed ? 'verified' : 'best-effort';
|
|
182
190
|
}
|
|
183
191
|
break;
|
|
184
192
|
}
|
|
@@ -194,8 +202,13 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
194
202
|
if (result.filesChanged) {
|
|
195
203
|
filesChanged = [...filesChanged, ...result.filesChanged];
|
|
196
204
|
}
|
|
197
|
-
if (
|
|
198
|
-
|
|
205
|
+
if (block.name === 'verify_in_browser') {
|
|
206
|
+
if (result.screenshotPath) {
|
|
207
|
+
afterScreenshot = result.screenshotPath;
|
|
208
|
+
}
|
|
209
|
+
if (result.verifiedFixed === true) {
|
|
210
|
+
verifiedFixed = true;
|
|
211
|
+
}
|
|
199
212
|
}
|
|
200
213
|
if (block.name === 'git_revert') {
|
|
201
214
|
status = 'reverted';
|
|
@@ -80,16 +80,26 @@ 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
|
-
description: 'Navigate to a URL
|
|
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.',
|
|
86
95
|
input_schema: {
|
|
87
96
|
type: 'object',
|
|
88
97
|
properties: {
|
|
89
98
|
url: { type: 'string', description: 'URL to navigate to for verification' },
|
|
90
99
|
label: { type: 'string', description: 'Label for the screenshot (e.g. "after-fix-001")' },
|
|
100
|
+
fixed: { type: 'boolean', description: 'true if the original bug is gone, false if it still reproduces or uncertain' },
|
|
91
101
|
},
|
|
92
|
-
required: ['url', 'label'],
|
|
102
|
+
required: ['url', 'label', 'fixed'],
|
|
93
103
|
},
|
|
94
104
|
},
|
|
95
105
|
];
|
|
@@ -216,6 +226,7 @@ export function executeFixTool(ctx, name, input) {
|
|
|
216
226
|
execFileSync('git', ['add', ...files], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
217
227
|
execFileSync('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
218
228
|
const hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
229
|
+
ctx.qaCommitHashes.add(hash);
|
|
219
230
|
return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
|
|
220
231
|
}
|
|
221
232
|
catch (err) {
|
|
@@ -224,16 +235,32 @@ export function executeFixTool(ctx, name, input) {
|
|
|
224
235
|
}
|
|
225
236
|
}
|
|
226
237
|
case 'git_revert': {
|
|
238
|
+
// Safety: only revert commits created by the fix loop
|
|
239
|
+
const currentHead = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
240
|
+
if (!ctx.qaCommitHashes.has(currentHead)) {
|
|
241
|
+
return { output: `Blocked: HEAD (${currentHead}) was not created by the fix loop. Refusing to revert a user commit.` };
|
|
242
|
+
}
|
|
227
243
|
try {
|
|
228
244
|
execFileSync('git', ['revert', '--no-edit', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
ctx.qaCommitHashes.delete(currentHead);
|
|
246
|
+
const newHash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
247
|
+
return { output: `Reverted ${currentHead}. New HEAD: ${newHash}`, commitHash: newHash };
|
|
231
248
|
}
|
|
232
249
|
catch (err) {
|
|
233
250
|
const error = err;
|
|
234
251
|
return { output: `Git revert failed: ${error.stderr || String(err)}` };
|
|
235
252
|
}
|
|
236
253
|
}
|
|
254
|
+
case 'git_restore': {
|
|
255
|
+
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.' };
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
const error = err;
|
|
261
|
+
return { output: `Git restore failed: ${error.stderr || String(err)}` };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
237
264
|
case 'verify_in_browser': {
|
|
238
265
|
const url = String(input.url);
|
|
239
266
|
const label = String(input.label || 'verify').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
@@ -254,7 +281,9 @@ export function executeFixTool(ctx, name, input) {
|
|
|
254
281
|
catch {
|
|
255
282
|
// Not available
|
|
256
283
|
}
|
|
257
|
-
|
|
284
|
+
const fixed = input.fixed === true;
|
|
285
|
+
const verdict = fixed ? 'Bug appears resolved.' : 'Bug still reproduces or unconfirmed.';
|
|
286
|
+
return { output: `Screenshot saved: ${screenshotPath}. ${verdict}${consoleErrors}`, screenshotPath, verifiedFixed: fixed };
|
|
258
287
|
}
|
|
259
288
|
default:
|
|
260
289
|
return { output: `Unknown fix tool: ${name}` };
|
|
@@ -55,18 +55,25 @@ export function loadBaseline(outputDir) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Compare current findings against a saved baseline.
|
|
57
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* Build a fingerprint for a finding that includes flow, type, and summary
|
|
60
|
+
* to avoid collapsing distinct issues with the same generic summary.
|
|
61
|
+
*/
|
|
62
|
+
function issueFingerprint(issue) {
|
|
63
|
+
return `${issue.flow}|${issue.type}|${issue.summary}`.toLowerCase();
|
|
64
|
+
}
|
|
58
65
|
export function compareBaselines(currentScore, currentFindings, baseline) {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
66
|
+
const baselineFingerprints = new Set(baseline.issues.map((i) => issueFingerprint(i)));
|
|
67
|
+
const currentFingerprints = new Set(currentFindings
|
|
61
68
|
.filter((f) => f.type !== 'verified-ok')
|
|
62
|
-
.map((f) => f
|
|
69
|
+
.map((f) => issueFingerprint(f)));
|
|
63
70
|
// Issues in baseline but not in current = fixed
|
|
64
71
|
const fixedIssues = baseline.issues
|
|
65
|
-
.filter((i) => !
|
|
72
|
+
.filter((i) => !currentFingerprints.has(issueFingerprint(i)))
|
|
66
73
|
.map((i) => `${i.id}: ${i.summary}`);
|
|
67
74
|
// Issues in current but not in baseline = new
|
|
68
75
|
const newIssues = currentFindings
|
|
69
|
-
.filter((f) => f.type !== 'verified-ok' && !
|
|
76
|
+
.filter((f) => f.type !== 'verified-ok' && !baselineFingerprints.has(issueFingerprint(f)))
|
|
70
77
|
.map((f) => `${f.id}: ${f.summary}`);
|
|
71
78
|
// Category deltas
|
|
72
79
|
const categoryDeltas = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"finding_taxonomy.d.ts","sourceRoot":"","sources":["../../src/qa-agent/finding_taxonomy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAiBrH;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAKjE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,GAAG,mBAAmB,CAK/E;AAMD,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMhE,CAAC;AAMF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAC,
|
|
1
|
+
{"version":3,"file":"finding_taxonomy.d.ts","sourceRoot":"","sources":["../../src/qa-agent/finding_taxonomy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,EAAC,MAAM,YAAY,CAAC;AAiBrH;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAKjE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,GAAG,mBAAmB,CAK/E;AAMD,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAMhE,CAAC;AAMF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAC,CAyClH,CAAC;AAYF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAKlE"}
|
|
@@ -16,7 +16,7 @@ const LEGACY_TO_CATEGORY = {
|
|
|
16
16
|
'ux-issue': 'ux',
|
|
17
17
|
};
|
|
18
18
|
const CANONICAL_CATEGORIES = new Set([
|
|
19
|
-
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility',
|
|
19
|
+
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links',
|
|
20
20
|
]);
|
|
21
21
|
/**
|
|
22
22
|
* Normalize any FindingType (legacy or canonical) to a canonical FindingCategory.
|
|
@@ -52,6 +52,11 @@ exports.SEVERITY_DEFINITIONS = {
|
|
|
52
52
|
// Category definitions
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
54
54
|
exports.CATEGORY_DEFINITIONS = {
|
|
55
|
+
links: {
|
|
56
|
+
label: 'Links',
|
|
57
|
+
description: 'Broken links (404), wrong destinations, dead anchors, external links that fail.',
|
|
58
|
+
examples: ['404 on nav link', 'Link goes to wrong page', 'Anchor target missing', 'External link returns 500'],
|
|
59
|
+
},
|
|
55
60
|
visual: {
|
|
56
61
|
label: 'Visual/UI',
|
|
57
62
|
description: 'Layout breaks, broken images, z-index issues, font/color inconsistencies, animation glitches, alignment issues, dark mode problems.',
|
|
@@ -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":"agent_loop.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase2/agent_loop.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA2C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"agent_loop.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/phase2/agent_loop.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA2C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAC,MAAM,aAAa,CAAC;AAwJ9G,wBAAsB,YAAY,CAC9B,MAAM,EAAE,QAAQ,EAChB,KAAK,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,YAAY,CAAC,CAyOvB"}
|
|
@@ -55,7 +55,8 @@ Pick dimensions that matter for THIS flow. Example: for "channel settings" → p
|
|
|
55
55
|
## Finding Categories
|
|
56
56
|
When reporting findings, use the most specific category:
|
|
57
57
|
- **visual** — Layout breaks, broken images, z-index issues, alignment, animation glitches, dark mode problems
|
|
58
|
-
- **
|
|
58
|
+
- **links** — Broken links (404), wrong destinations, dead anchors, external links that fail
|
|
59
|
+
- **functional** — Dead buttons, form validation failures, incorrect redirects, race conditions, state not persisting
|
|
59
60
|
- **ux** — Confusing navigation, missing loading indicators, slow interactions (>500ms), unclear error messages, no confirmation before destructive actions
|
|
60
61
|
- **content** — Typos, grammar errors, placeholder/lorem ipsum left in, truncated text, wrong labels
|
|
61
62
|
- **performance** — Slow page loads (>3s), janky scrolling, layout shifts (CLS), excessive network requests
|
|
@@ -103,7 +103,7 @@ exports.TOOL_DEFINITIONS = [
|
|
|
103
103
|
input_schema: {
|
|
104
104
|
type: 'object',
|
|
105
105
|
properties: {
|
|
106
|
-
type: { type: 'string', enum: ['visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'bug', 'visual-regression', 'ux-issue', 'gap'] },
|
|
106
|
+
type: { type: 'string', enum: ['visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links', 'bug', 'visual-regression', 'ux-issue', 'gap'] },
|
|
107
107
|
severity: { type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'] },
|
|
108
108
|
summary: { type: 'string', description: 'What you found' },
|
|
109
109
|
repro_steps: {
|
|
@@ -221,7 +221,7 @@ function executeTool(ctx, name, input) {
|
|
|
221
221
|
}
|
|
222
222
|
case 'report_finding': {
|
|
223
223
|
const VALID_TYPES = new Set([
|
|
224
|
-
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility',
|
|
224
|
+
'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility', 'links',
|
|
225
225
|
'bug', 'visual-regression', 'ux-issue', 'gap',
|
|
226
226
|
]);
|
|
227
227
|
const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low', 'info']);
|
|
@@ -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,CA4FxB"}
|
|
@@ -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
|
}
|
|
@@ -97,6 +99,7 @@ async function runFixLoop(config, findings, browser, projectRoot) {
|
|
|
97
99
|
baseUrl: config.baseUrl,
|
|
98
100
|
screenshotDir,
|
|
99
101
|
screenshotCounter: 100, // Start at 100 to avoid collisions with Phase 2 screenshots
|
|
102
|
+
qaCommitHashes: new Set(),
|
|
100
103
|
};
|
|
101
104
|
for (const finding of fixable) {
|
|
102
105
|
if (wtf.shouldStop()) {
|
|
@@ -117,7 +120,10 @@ async function runFixLoop(config, findings, browser, projectRoot) {
|
|
|
117
120
|
costUSD += result.costUSD;
|
|
118
121
|
wtf.recordAttempt(result.fix.status, result.fix.filesChanged?.length || 0);
|
|
119
122
|
}
|
|
120
|
-
|
|
123
|
+
// Exclude verified fixes from the post-fix score so it reflects actual remaining issues
|
|
124
|
+
const verifiedIds = new Set(fixes.filter((f) => f.status === 'verified').map((f) => f.findingId));
|
|
125
|
+
const remainingFindings = findings.filter((f) => !verifiedIds.has(f.id));
|
|
126
|
+
const healthScoreAfter = (0, health_score_js_1.computeHealthScore)(remainingFindings);
|
|
121
127
|
return {
|
|
122
128
|
fixes,
|
|
123
129
|
fixesAttempted: fixes.filter((f) => f.status !== 'skipped').length,
|
|
@@ -140,6 +146,7 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
140
146
|
let filesChanged = [];
|
|
141
147
|
let beforeScreenshot;
|
|
142
148
|
let afterScreenshot;
|
|
149
|
+
let verifiedFixed = false;
|
|
143
150
|
let status = 'skipped';
|
|
144
151
|
// Take "before" screenshot
|
|
145
152
|
try {
|
|
@@ -182,9 +189,10 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
182
189
|
// If no tool use, the agent is done
|
|
183
190
|
const toolUseBlocks = assistantContent.filter((b) => b.type === 'tool_use');
|
|
184
191
|
if (toolUseBlocks.length === 0) {
|
|
185
|
-
// Determine status
|
|
192
|
+
// Determine status: 'verified' requires both a commit and explicit confirmation
|
|
193
|
+
// from verify_in_browser that the bug is gone. A screenshot alone is not proof.
|
|
186
194
|
if (commitHash) {
|
|
187
|
-
status =
|
|
195
|
+
status = verifiedFixed ? 'verified' : 'best-effort';
|
|
188
196
|
}
|
|
189
197
|
break;
|
|
190
198
|
}
|
|
@@ -200,8 +208,13 @@ async function fixSingleFinding(client, model, config, finding, toolCtx) {
|
|
|
200
208
|
if (result.filesChanged) {
|
|
201
209
|
filesChanged = [...filesChanged, ...result.filesChanged];
|
|
202
210
|
}
|
|
203
|
-
if (
|
|
204
|
-
|
|
211
|
+
if (block.name === 'verify_in_browser') {
|
|
212
|
+
if (result.screenshotPath) {
|
|
213
|
+
afterScreenshot = result.screenshotPath;
|
|
214
|
+
}
|
|
215
|
+
if (result.verifiedFixed === true) {
|
|
216
|
+
verifiedFixed = true;
|
|
217
|
+
}
|
|
205
218
|
}
|
|
206
219
|
if (block.name === 'git_revert') {
|
|
207
220
|
status = 'reverted';
|
|
@@ -7,12 +7,16 @@ export interface FixToolContext {
|
|
|
7
7
|
baseUrl: string;
|
|
8
8
|
screenshotDir: string;
|
|
9
9
|
screenshotCounter: number;
|
|
10
|
+
/** Commit hashes created by the fix loop. Only these can be reverted. */
|
|
11
|
+
qaCommitHashes: Set<string>;
|
|
10
12
|
}
|
|
11
13
|
export interface FixToolResult {
|
|
12
14
|
output: string;
|
|
13
15
|
filesChanged?: string[];
|
|
14
16
|
commitHash?: string;
|
|
15
17
|
screenshotPath?: string;
|
|
18
|
+
/** Explicit verification signal from verify_in_browser */
|
|
19
|
+
verifiedFixed?: boolean;
|
|
16
20
|
}
|
|
17
21
|
export declare function executeFixTool(ctx: FixToolContext, name: string, input: Record<string, unknown>): FixToolResult;
|
|
18
22
|
//# sourceMappingURL=fix_tools.d.ts.map
|
|
@@ -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;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"}
|
|
@@ -84,16 +84,26 @@ 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
|
-
description: 'Navigate to a URL
|
|
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.',
|
|
90
99
|
input_schema: {
|
|
91
100
|
type: 'object',
|
|
92
101
|
properties: {
|
|
93
102
|
url: { type: 'string', description: 'URL to navigate to for verification' },
|
|
94
103
|
label: { type: 'string', description: 'Label for the screenshot (e.g. "after-fix-001")' },
|
|
104
|
+
fixed: { type: 'boolean', description: 'true if the original bug is gone, false if it still reproduces or uncertain' },
|
|
95
105
|
},
|
|
96
|
-
required: ['url', 'label'],
|
|
106
|
+
required: ['url', 'label', 'fixed'],
|
|
97
107
|
},
|
|
98
108
|
},
|
|
99
109
|
];
|
|
@@ -220,6 +230,7 @@ function executeFixTool(ctx, name, input) {
|
|
|
220
230
|
(0, child_process_1.execFileSync)('git', ['add', ...files], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
221
231
|
(0, child_process_1.execFileSync)('git', ['commit', '-m', message], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
222
232
|
const hash = (0, child_process_1.execFileSync)('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
233
|
+
ctx.qaCommitHashes.add(hash);
|
|
223
234
|
return { output: `Committed: ${hash} — ${message}`, commitHash: hash, filesChanged: files };
|
|
224
235
|
}
|
|
225
236
|
catch (err) {
|
|
@@ -228,16 +239,32 @@ function executeFixTool(ctx, name, input) {
|
|
|
228
239
|
}
|
|
229
240
|
}
|
|
230
241
|
case 'git_revert': {
|
|
242
|
+
// Safety: only revert commits created by the fix loop
|
|
243
|
+
const currentHead = (0, child_process_1.execFileSync)('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
244
|
+
if (!ctx.qaCommitHashes.has(currentHead)) {
|
|
245
|
+
return { output: `Blocked: HEAD (${currentHead}) was not created by the fix loop. Refusing to revert a user commit.` };
|
|
246
|
+
}
|
|
231
247
|
try {
|
|
232
248
|
(0, child_process_1.execFileSync)('git', ['revert', '--no-edit', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' });
|
|
233
|
-
|
|
234
|
-
|
|
249
|
+
ctx.qaCommitHashes.delete(currentHead);
|
|
250
|
+
const newHash = (0, child_process_1.execFileSync)('git', ['rev-parse', '--short', 'HEAD'], { cwd: ctx.projectRoot, encoding: 'utf-8' }).trim();
|
|
251
|
+
return { output: `Reverted ${currentHead}. New HEAD: ${newHash}`, commitHash: newHash };
|
|
235
252
|
}
|
|
236
253
|
catch (err) {
|
|
237
254
|
const error = err;
|
|
238
255
|
return { output: `Git revert failed: ${error.stderr || String(err)}` };
|
|
239
256
|
}
|
|
240
257
|
}
|
|
258
|
+
case 'git_restore': {
|
|
259
|
+
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.' };
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
const error = err;
|
|
265
|
+
return { output: `Git restore failed: ${error.stderr || String(err)}` };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
241
268
|
case 'verify_in_browser': {
|
|
242
269
|
const url = String(input.url);
|
|
243
270
|
const label = String(input.label || 'verify').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
@@ -258,7 +285,9 @@ function executeFixTool(ctx, name, input) {
|
|
|
258
285
|
catch {
|
|
259
286
|
// Not available
|
|
260
287
|
}
|
|
261
|
-
|
|
288
|
+
const fixed = input.fixed === true;
|
|
289
|
+
const verdict = fixed ? 'Bug appears resolved.' : 'Bug still reproduces or unconfirmed.';
|
|
290
|
+
return { output: `Screenshot saved: ${screenshotPath}. ${verdict}${consoleErrors}`, screenshotPath, verifiedFixed: fixed };
|
|
262
291
|
}
|
|
263
292
|
default:
|
|
264
293
|
return { output: `Unknown fix tool: ${name}` };
|
|
@@ -7,8 +7,5 @@ export declare function saveBaseline(outputDir: string, healthScore: HealthScore
|
|
|
7
7
|
* Load a previously saved baseline. Returns null if no baseline exists.
|
|
8
8
|
*/
|
|
9
9
|
export declare function loadBaseline(outputDir: string): RegressionBaseline | null;
|
|
10
|
-
/**
|
|
11
|
-
* Compare current findings against a saved baseline.
|
|
12
|
-
*/
|
|
13
10
|
export declare function compareBaselines(currentScore: HealthScore, currentFindings: Finding[], baseline: RegressionBaseline): RegressionComparison;
|
|
14
11
|
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/regression/baseline.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AAKrH;;GAEG;AACH,wBAAgB,YAAY,CACxB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACZ,IAAI,CA2BN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAazE;
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../src/qa-agent/regression/baseline.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AAKrH;;GAEG;AACH,wBAAgB,YAAY,CACxB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACZ,IAAI,CA2BN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAazE;AAaD,wBAAgB,gBAAgB,CAC5B,YAAY,EAAE,WAAW,EACzB,eAAe,EAAE,OAAO,EAAE,EAC1B,QAAQ,EAAE,kBAAkB,GAC7B,oBAAoB,CAqCtB"}
|
|
@@ -60,18 +60,25 @@ function loadBaseline(outputDir) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Compare current findings against a saved baseline.
|
|
62
62
|
*/
|
|
63
|
+
/**
|
|
64
|
+
* Build a fingerprint for a finding that includes flow, type, and summary
|
|
65
|
+
* to avoid collapsing distinct issues with the same generic summary.
|
|
66
|
+
*/
|
|
67
|
+
function issueFingerprint(issue) {
|
|
68
|
+
return `${issue.flow}|${issue.type}|${issue.summary}`.toLowerCase();
|
|
69
|
+
}
|
|
63
70
|
function compareBaselines(currentScore, currentFindings, baseline) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
71
|
+
const baselineFingerprints = new Set(baseline.issues.map((i) => issueFingerprint(i)));
|
|
72
|
+
const currentFingerprints = new Set(currentFindings
|
|
66
73
|
.filter((f) => f.type !== 'verified-ok')
|
|
67
|
-
.map((f) => f
|
|
74
|
+
.map((f) => issueFingerprint(f)));
|
|
68
75
|
// Issues in baseline but not in current = fixed
|
|
69
76
|
const fixedIssues = baseline.issues
|
|
70
|
-
.filter((i) => !
|
|
77
|
+
.filter((i) => !currentFingerprints.has(issueFingerprint(i)))
|
|
71
78
|
.map((i) => `${i.id}: ${i.summary}`);
|
|
72
79
|
// Issues in current but not in baseline = new
|
|
73
80
|
const newIssues = currentFindings
|
|
74
|
-
.filter((f) => f.type !== 'verified-ok' && !
|
|
81
|
+
.filter((f) => f.type !== 'verified-ok' && !baselineFingerprints.has(issueFingerprint(f)))
|
|
75
82
|
.map((f) => `${f.id}: ${f.summary}`);
|
|
76
83
|
// Category deltas
|
|
77
84
|
const categoryDeltas = {};
|
package/dist/qa-agent/types.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface BrowserAction {
|
|
|
31
31
|
timestamp: number;
|
|
32
32
|
}
|
|
33
33
|
/** Canonical finding categories (v1.1) */
|
|
34
|
-
export type FindingCategory = 'visual' | 'functional' | 'ux' | 'content' | 'performance' | 'console' | 'accessibility';
|
|
34
|
+
export type FindingCategory = 'visual' | 'functional' | 'ux' | 'content' | 'performance' | 'console' | 'accessibility' | 'links';
|
|
35
35
|
/** Legacy finding types kept for backward compatibility */
|
|
36
36
|
export type LegacyFindingType = 'bug' | 'visual-regression' | 'ux-issue' | 'gap' | 'verified-ok';
|
|
37
37
|
/** Accepts both canonical categories and legacy type names */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/qa-agent/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAMpD,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;AAMxD,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,MAAM,iBAAiB,GACvB,UAAU,GACV,OAAO,GACP,MAAM,GACN,MAAM,GACN,OAAO,GACP,WAAW,GACX,QAAQ,GACR,MAAM,GACN,SAAS,GACT,YAAY,GACZ,iBAAiB,GACjB,UAAU,GACV,SAAS,GACT,WAAW,GACX,UAAU,GACV,MAAM,GACN,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,UAAU,GACV,YAAY,CAAC;AAEnB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAMD,0CAA0C;AAC1C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/qa-agent/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/impact-gate",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
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",
|