codeloop-mcp-server 0.1.11 → 0.1.13
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/index.js +101 -29
- package/dist/index.js.map +1 -1
- package/dist/runners/figma_fetcher.d.ts +48 -0
- package/dist/runners/figma_fetcher.d.ts.map +1 -0
- package/dist/runners/figma_fetcher.js +149 -0
- package/dist/runners/figma_fetcher.js.map +1 -0
- package/dist/tools/design_compare.d.ts +51 -3
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +407 -76
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/gate_check.d.ts.map +1 -1
- package/dist/tools/gate_check.js +55 -2
- package/dist/tools/gate_check.js.map +1 -1
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +31 -3
- package/dist/tools/verify.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,108 +1,439 @@
|
|
|
1
1
|
import { compareScreenshot } from "../evidence/screenshot_diff.js";
|
|
2
|
-
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { join, resolve } from "path";
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, statSync, } from "fs";
|
|
3
|
+
import { join, resolve, basename, extname } from "path";
|
|
4
4
|
import { getArtifactsBaseDir, getRunDir, listRuns } from "../evidence/artifacts.js";
|
|
5
|
+
import { loadFigmaConfig, resolveFigmaToken, syncFigmaDesigns, } from "../runners/figma_fetcher.js";
|
|
6
|
+
const SUPPORTED_REF_EXTS = [".png", ".jpg", ".jpeg", ".webp"];
|
|
7
|
+
const DEFAULT_DESIGNS_DIR = "designs";
|
|
5
8
|
function screenFileName(screenName) {
|
|
6
9
|
const s = screenName.trim();
|
|
7
|
-
if (s.toLowerCase().endsWith(".png"))
|
|
10
|
+
if (s.toLowerCase().endsWith(".png"))
|
|
8
11
|
return s;
|
|
9
|
-
}
|
|
10
12
|
return `${s}.png`;
|
|
11
13
|
}
|
|
14
|
+
function severityFromScore(score, threshold) {
|
|
15
|
+
if (score >= threshold)
|
|
16
|
+
return "low";
|
|
17
|
+
if (score >= threshold - 0.1)
|
|
18
|
+
return "medium";
|
|
19
|
+
if (score >= threshold - 0.25)
|
|
20
|
+
return "high";
|
|
21
|
+
return "critical";
|
|
22
|
+
}
|
|
12
23
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
24
|
+
* Detects whether the project has any design references that should drive
|
|
25
|
+
* the design_compare loop. Used by gate_check and verify auto-trigger.
|
|
26
|
+
*/
|
|
27
|
+
export function hasDesignReferences(cwd, designsDir = DEFAULT_DESIGNS_DIR) {
|
|
28
|
+
const dir = resolve(cwd, designsDir);
|
|
29
|
+
if (existsSync(dir)) {
|
|
30
|
+
try {
|
|
31
|
+
const files = readdirSync(dir);
|
|
32
|
+
if (files.some((f) => SUPPORTED_REF_EXTS.includes(extname(f).toLowerCase()))) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* fall through */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Also scan one level deep (designs/<viewport>/<screen>.png)
|
|
41
|
+
if (existsSync(dir)) {
|
|
42
|
+
try {
|
|
43
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
const sub = join(dir, entry.name);
|
|
47
|
+
const subFiles = readdirSync(sub);
|
|
48
|
+
if (subFiles.some((f) => SUPPORTED_REF_EXTS.includes(extname(f).toLowerCase()))) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* fall through */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (loadFigmaConfig(cwd))
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Walks `designs/` (and one level of subdirs treated as viewports) and returns
|
|
64
|
+
* every reference image with its derived screen_name and optional viewport.
|
|
65
|
+
*
|
|
66
|
+
* Conventions:
|
|
67
|
+
* - `designs/home.png` -> screen=home
|
|
68
|
+
* - `designs/home@2x.png` -> screen=home, scale=2
|
|
69
|
+
* - `designs/mobile/home.png` -> screen=home, viewport=mobile
|
|
70
|
+
* - `designs/desktop/home@1x.png` -> screen=home, viewport=desktop, scale=1
|
|
71
|
+
*/
|
|
72
|
+
export function listReferenceFiles(cwd, designsDir = DEFAULT_DESIGNS_DIR) {
|
|
73
|
+
const dir = resolve(cwd, designsDir);
|
|
74
|
+
const out = [];
|
|
75
|
+
if (!existsSync(dir))
|
|
76
|
+
return out;
|
|
77
|
+
const visit = (currentDir, viewport) => {
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const name = entry.name;
|
|
87
|
+
const full = join(currentDir, name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
if (viewport === undefined) {
|
|
90
|
+
visit(full, name);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const ext = extname(name).toLowerCase();
|
|
95
|
+
if (!SUPPORTED_REF_EXTS.includes(ext))
|
|
96
|
+
continue;
|
|
97
|
+
const stem = basename(name, ext);
|
|
98
|
+
const scaleMatch = stem.match(/^(.+)@(\d+)x$/);
|
|
99
|
+
const screenName = scaleMatch ? scaleMatch[1] : stem;
|
|
100
|
+
const scale = scaleMatch ? parseInt(scaleMatch[2], 10) : undefined;
|
|
101
|
+
out.push({ path: full, screenName, viewport, scale });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
visit(dir);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function findActualScreenshot(screenshotsDir, screenName, viewport) {
|
|
108
|
+
if (!existsSync(screenshotsDir))
|
|
109
|
+
return null;
|
|
110
|
+
const files = readdirSync(screenshotsDir).filter((f) => f.toLowerCase().endsWith(".png"));
|
|
111
|
+
const candidates = [];
|
|
112
|
+
if (viewport) {
|
|
113
|
+
candidates.push(`${screenName}_${viewport}.png`);
|
|
114
|
+
candidates.push(`${screenName}-${viewport}.png`);
|
|
115
|
+
candidates.push(`${viewport}_${screenName}.png`);
|
|
116
|
+
candidates.push(`${viewport}-${screenName}.png`);
|
|
117
|
+
}
|
|
118
|
+
candidates.push(`${screenName}.png`);
|
|
119
|
+
for (const candidate of candidates) {
|
|
120
|
+
const match = files.find((f) => f.toLowerCase() === candidate.toLowerCase());
|
|
121
|
+
if (match)
|
|
122
|
+
return join(screenshotsDir, match);
|
|
123
|
+
}
|
|
124
|
+
// Fallback: starts-with match (e.g. `home_chromium.png` for screen=home).
|
|
125
|
+
const prefix = files.find((f) => f.toLowerCase().startsWith(screenName.toLowerCase() + "_") ||
|
|
126
|
+
f.toLowerCase().startsWith(screenName.toLowerCase() + "-"));
|
|
127
|
+
if (prefix)
|
|
128
|
+
return join(screenshotsDir, prefix);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function bestRunDir(cwd) {
|
|
132
|
+
const base = getArtifactsBaseDir(cwd);
|
|
133
|
+
const runs = listRuns(base);
|
|
134
|
+
for (const runId of runs) {
|
|
135
|
+
const dir = getRunDir(runId, base);
|
|
136
|
+
const screenshotsDir = join(dir, "screenshots");
|
|
137
|
+
if (existsSync(screenshotsDir)) {
|
|
138
|
+
try {
|
|
139
|
+
const files = readdirSync(screenshotsDir).filter((f) => f.toLowerCase().endsWith(".png"));
|
|
140
|
+
if (files.length > 0)
|
|
141
|
+
return { runId, dir };
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
/* skip */
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (runs.length > 0) {
|
|
149
|
+
return { runId: runs[0], dir: getRunDir(runs[0], base) };
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Single-screen comparison (legacy entry point preserved for the existing
|
|
155
|
+
* `runDesignCompare` MCP tool signature).
|
|
15
156
|
*/
|
|
16
157
|
export async function runDesignCompare(input, config, cwd = process.cwd()) {
|
|
158
|
+
if (input.mode === "all" || (!input.reference_image_path && !input.screen_name)) {
|
|
159
|
+
return runDesignCompareAll(input, config, cwd);
|
|
160
|
+
}
|
|
161
|
+
if (!input.reference_image_path || !input.screen_name) {
|
|
162
|
+
return failureResult("reference", "reference_image_path and screen_name are required in single mode.", "Pass `mode: 'all'` or provide both fields.");
|
|
163
|
+
}
|
|
17
164
|
const refPath = resolve(cwd, input.reference_image_path);
|
|
18
165
|
if (!existsSync(refPath)) {
|
|
19
|
-
return {
|
|
20
|
-
output: {
|
|
21
|
-
match_score: 0,
|
|
22
|
-
differences: [
|
|
23
|
-
{
|
|
24
|
-
area: "reference",
|
|
25
|
-
description: `Reference image not found: ${input.reference_image_path}`,
|
|
26
|
-
severity: "high",
|
|
27
|
-
fix_hint: "Provide a valid reference_image_path.",
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
screenshots: [],
|
|
31
|
-
recommendation: "Add the design reference image and retry.",
|
|
32
|
-
},
|
|
33
|
-
imagePaths: { reference: "", actual: "" },
|
|
34
|
-
};
|
|
166
|
+
return failureResult("reference", `Reference image not found: ${input.reference_image_path}`, "Provide a valid reference_image_path or place the file under designs/.", refPath);
|
|
35
167
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!actualPath
|
|
43
|
-
return {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
: "No verification runs found; cannot locate an actual screenshot.",
|
|
52
|
-
severity: "high",
|
|
53
|
-
fix_hint: "Run codeloop_verify to capture screenshots, or ensure the screen name matches a PNG in artifacts/runs/<run>/screenshots/.",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
screenshots: [refPath],
|
|
57
|
-
recommendation: "Capture the UI first, then compare.",
|
|
58
|
-
},
|
|
59
|
-
imagePaths: { reference: refPath, actual: "" },
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
let diffPath;
|
|
63
|
-
let pixelDiffScore = 0;
|
|
168
|
+
const run = bestRunDir(cwd);
|
|
169
|
+
if (!run) {
|
|
170
|
+
return failureResult("screenshot", "No verification runs found; cannot locate an actual screenshot.", "Run codeloop_verify to capture screenshots first.", refPath);
|
|
171
|
+
}
|
|
172
|
+
const screenshotsDir = join(run.dir, "screenshots");
|
|
173
|
+
const actualPath = findActualScreenshot(screenshotsDir, input.screen_name);
|
|
174
|
+
if (!actualPath) {
|
|
175
|
+
return failureResult("screenshot", `No screenshot found for "${input.screen_name}" in run ${run.runId}.`, "Run codeloop_capture_screenshot for this screen, or ensure the file name matches.", refPath);
|
|
176
|
+
}
|
|
177
|
+
const threshold = config.design_match_threshold ?? 0.8;
|
|
178
|
+
const diffsDir = join(run.dir, "diffs");
|
|
179
|
+
mkdirSync(diffsDir, { recursive: true });
|
|
180
|
+
const diffPath = join(diffsDir, `design_compare_${screenFileName(input.screen_name)}`);
|
|
181
|
+
let pixelMatchScore = 0;
|
|
182
|
+
let diffOk;
|
|
64
183
|
try {
|
|
65
|
-
const diffsDir = latestRun
|
|
66
|
-
? join(getRunDir(latestRun, base), "diffs")
|
|
67
|
-
: join(cwd, ".codeloop", "diffs");
|
|
68
|
-
mkdirSync(diffsDir, { recursive: true });
|
|
69
|
-
diffPath = join(diffsDir, `design_compare_${screenFileName(input.screen_name)}`);
|
|
70
184
|
const diffResult = compareScreenshot(actualPath, refPath, diffPath);
|
|
71
|
-
|
|
185
|
+
pixelMatchScore = 1 - diffResult.diffScore;
|
|
186
|
+
diffOk = existsSync(diffPath) ? diffPath : undefined;
|
|
72
187
|
}
|
|
73
188
|
catch {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
189
|
+
pixelMatchScore = 0;
|
|
190
|
+
diffOk = undefined;
|
|
191
|
+
}
|
|
192
|
+
const passed = pixelMatchScore >= threshold;
|
|
193
|
+
const perScreen = [
|
|
194
|
+
{
|
|
195
|
+
screen_name: input.screen_name,
|
|
196
|
+
viewport: input.viewport_sizes?.[0],
|
|
197
|
+
reference_path: refPath,
|
|
198
|
+
actual_path: actualPath,
|
|
199
|
+
diff_path: diffOk,
|
|
200
|
+
match_score: pixelMatchScore,
|
|
201
|
+
passed,
|
|
202
|
+
severity: severityFromScore(pixelMatchScore, threshold),
|
|
203
|
+
reason: passed
|
|
204
|
+
? `Score ${(pixelMatchScore * 100).toFixed(1)}% >= threshold ${(threshold * 100).toFixed(0)}%`
|
|
205
|
+
: `Score ${(pixelMatchScore * 100).toFixed(1)}% < threshold ${(threshold * 100).toFixed(0)}%`,
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
const uxChecklist = readUxChecklist(input.ux_checklist_path, cwd);
|
|
209
|
+
const summaryPath = join(run.dir, "design_compare_summary.json");
|
|
210
|
+
writeFileSync(summaryPath, JSON.stringify({
|
|
211
|
+
threshold,
|
|
212
|
+
per_screen: perScreen,
|
|
213
|
+
min_score: pixelMatchScore,
|
|
214
|
+
avg_score: pixelMatchScore,
|
|
215
|
+
failed_screens: passed ? [] : [input.screen_name],
|
|
216
|
+
source: "files",
|
|
217
|
+
}, null, 2));
|
|
218
|
+
return {
|
|
219
|
+
output: {
|
|
220
|
+
match_score: pixelMatchScore,
|
|
221
|
+
differences: [],
|
|
222
|
+
screenshots: [refPath, actualPath],
|
|
223
|
+
recommendation: passed
|
|
224
|
+
? "Pixel-level comparison suggests the implementation closely matches the reference. The AI agent will provide detailed visual assessment."
|
|
225
|
+
: "Pixel-level comparison detected significant differences. Fix the highlighted regions and re-run codeloop_verify before gate_check.",
|
|
226
|
+
per_screen: perScreen,
|
|
227
|
+
min_score: pixelMatchScore,
|
|
228
|
+
avg_score: pixelMatchScore,
|
|
229
|
+
failed_screens: passed ? [] : [input.screen_name],
|
|
230
|
+
threshold,
|
|
231
|
+
source: "files",
|
|
232
|
+
},
|
|
233
|
+
imagePaths: { reference: refPath, actual: actualPath, diff: diffOk },
|
|
234
|
+
uxChecklist,
|
|
235
|
+
perScreen,
|
|
236
|
+
source: "files",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Multi-screen / multi-viewport comparison. Auto-discovers references in
|
|
241
|
+
* `designs/` and from `.codeloop/figma.json`, maps each to the latest
|
|
242
|
+
* actual screenshot, runs pixelmatch, and writes a summary that gate_check
|
|
243
|
+
* consumes.
|
|
244
|
+
*/
|
|
245
|
+
export async function runDesignCompareAll(input, config, cwd = process.cwd()) {
|
|
246
|
+
const designsDir = input.designs_dir || DEFAULT_DESIGNS_DIR;
|
|
247
|
+
const threshold = config.design_match_threshold ?? 0.8;
|
|
248
|
+
// 1. Optional Figma sync — non-fatal if it fails.
|
|
249
|
+
let source = "files";
|
|
250
|
+
const figmaErrors = [];
|
|
251
|
+
const figmaConfig = loadFigmaConfig(cwd);
|
|
252
|
+
const wantsFigma = !!input.figma_file_url || !!figmaConfig;
|
|
253
|
+
if (wantsFigma) {
|
|
82
254
|
try {
|
|
83
|
-
|
|
255
|
+
const token = input.figma_token ||
|
|
256
|
+
(figmaConfig ? resolveFigmaToken(figmaConfig) : undefined) ||
|
|
257
|
+
process.env.FIGMA_API_TOKEN;
|
|
258
|
+
const fileUrl = input.figma_file_url || figmaConfig?.file_url;
|
|
259
|
+
if (token && (fileUrl || figmaConfig?.file_key)) {
|
|
260
|
+
const result = await syncFigmaDesigns(cwd, designsDir);
|
|
261
|
+
if (result.fetched.length > 0) {
|
|
262
|
+
source = source === "files" ? "figma" : "mixed";
|
|
263
|
+
}
|
|
264
|
+
figmaErrors.push(...result.errors);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
figmaErrors.push("Figma config detected but token or file URL missing — skipping Figma sync.");
|
|
268
|
+
}
|
|
84
269
|
}
|
|
85
|
-
catch {
|
|
86
|
-
|
|
270
|
+
catch (err) {
|
|
271
|
+
figmaErrors.push(`Figma sync error: ${err.message}`);
|
|
87
272
|
}
|
|
88
273
|
}
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
274
|
+
// 2. Enumerate every reference (post-sync).
|
|
275
|
+
const refs = listReferenceFiles(cwd, designsDir);
|
|
276
|
+
if (refs.length === 0) {
|
|
277
|
+
return failureResult("reference", `No reference images found under ${designsDir}/ and Figma sync produced none.${figmaErrors.length ? " Errors: " + figmaErrors.join("; ") : ""}`, "Add PNG/JPEG/WebP files under designs/, or configure .codeloop/figma.json with FIGMA_API_TOKEN.");
|
|
278
|
+
}
|
|
279
|
+
if (source === "files" && figmaConfig)
|
|
280
|
+
source = "mixed";
|
|
281
|
+
// 3. Resolve actual screenshot for every reference.
|
|
282
|
+
const run = bestRunDir(cwd);
|
|
283
|
+
if (!run) {
|
|
284
|
+
return failureResult("screenshot", "No verification runs with screenshots found; cannot compare designs.", "Run codeloop_verify (or codeloop_capture_screenshot) first to populate artifacts/runs/<run>/screenshots/.");
|
|
285
|
+
}
|
|
286
|
+
const screenshotsDir = join(run.dir, "screenshots");
|
|
287
|
+
const diffsDir = join(run.dir, "diffs");
|
|
288
|
+
mkdirSync(diffsDir, { recursive: true });
|
|
289
|
+
const perScreen = [];
|
|
290
|
+
for (const ref of refs) {
|
|
291
|
+
const actualPath = findActualScreenshot(screenshotsDir, ref.screenName, ref.viewport);
|
|
292
|
+
if (!actualPath) {
|
|
293
|
+
perScreen.push({
|
|
294
|
+
screen_name: ref.screenName,
|
|
295
|
+
viewport: ref.viewport,
|
|
296
|
+
reference_path: ref.path,
|
|
297
|
+
actual_path: "",
|
|
298
|
+
match_score: 0,
|
|
299
|
+
passed: false,
|
|
300
|
+
severity: "high",
|
|
301
|
+
reason: `No screenshot found for "${ref.screenName}"${ref.viewport ? ` (${ref.viewport})` : ""}. Capture it before re-running design compare.`,
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const diffName = ref.viewport
|
|
306
|
+
? `design_compare_${ref.screenName}_${ref.viewport}.png`
|
|
307
|
+
: `design_compare_${ref.screenName}.png`;
|
|
308
|
+
const diffPath = join(diffsDir, diffName);
|
|
309
|
+
let matchScore = 0;
|
|
310
|
+
let diffOk;
|
|
311
|
+
let reason;
|
|
312
|
+
try {
|
|
313
|
+
const diffResult = compareScreenshot(actualPath, ref.path, diffPath);
|
|
314
|
+
matchScore = 1 - diffResult.diffScore;
|
|
315
|
+
diffOk = existsSync(diffPath) ? diffPath : undefined;
|
|
316
|
+
reason = `Pixel diff: ${(diffResult.diffScore * 100).toFixed(2)}% changed (${diffResult.diffPixels}/${diffResult.totalPixels} pixels).`;
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
matchScore = 0;
|
|
320
|
+
diffOk = undefined;
|
|
321
|
+
reason = `Comparison error: ${err.message}`;
|
|
322
|
+
}
|
|
323
|
+
const passed = matchScore >= threshold;
|
|
324
|
+
perScreen.push({
|
|
325
|
+
screen_name: ref.screenName,
|
|
326
|
+
viewport: ref.viewport,
|
|
327
|
+
reference_path: ref.path,
|
|
328
|
+
actual_path: actualPath,
|
|
329
|
+
diff_path: diffOk,
|
|
330
|
+
match_score: matchScore,
|
|
331
|
+
passed,
|
|
332
|
+
severity: severityFromScore(matchScore, threshold),
|
|
333
|
+
reason,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// 4. Aggregate.
|
|
337
|
+
const scores = perScreen.map((p) => p.match_score);
|
|
338
|
+
const minScore = scores.length ? Math.min(...scores) : 0;
|
|
339
|
+
const avgScore = scores.length ? scores.reduce((s, v) => s + v, 0) / scores.length : 0;
|
|
340
|
+
const failedScreens = perScreen.filter((p) => !p.passed).map((p) => p.screen_name);
|
|
341
|
+
const allPassed = failedScreens.length === 0 && perScreen.length > 0;
|
|
342
|
+
// 5. Persist summary for gate_check.
|
|
343
|
+
const summaryPath = join(run.dir, "design_compare_summary.json");
|
|
344
|
+
writeFileSync(summaryPath, JSON.stringify({
|
|
345
|
+
threshold,
|
|
346
|
+
per_screen: perScreen,
|
|
347
|
+
min_score: minScore,
|
|
348
|
+
avg_score: avgScore,
|
|
349
|
+
failed_screens: failedScreens,
|
|
350
|
+
source,
|
|
351
|
+
generated_at: new Date().toISOString(),
|
|
352
|
+
figma_errors: figmaErrors.length ? figmaErrors : undefined,
|
|
353
|
+
}, null, 2));
|
|
354
|
+
// 6. Pick representative images for the MCP response (prefer worst failure).
|
|
355
|
+
const sorted = [...perScreen].sort((a, b) => a.match_score - b.match_score);
|
|
356
|
+
const lead = sorted[0] || perScreen[0];
|
|
357
|
+
const recommendation = allPassed
|
|
358
|
+
? `All ${perScreen.length} screen(s) match designs at or above ${(threshold * 100).toFixed(0)}% threshold (min: ${(minScore * 100).toFixed(1)}%).`
|
|
359
|
+
: `${failedScreens.length}/${perScreen.length} screen(s) below threshold ${(threshold * 100).toFixed(0)}%. Worst: "${lead.screen_name}" at ${(lead.match_score * 100).toFixed(1)}%. Fix and re-run codeloop_verify before gate_check.`;
|
|
360
|
+
const uxChecklist = readUxChecklist(input.ux_checklist_path, cwd);
|
|
93
361
|
return {
|
|
94
362
|
output: {
|
|
95
|
-
match_score:
|
|
363
|
+
match_score: avgScore,
|
|
96
364
|
differences: [],
|
|
97
|
-
screenshots:
|
|
365
|
+
screenshots: perScreen
|
|
366
|
+
.flatMap((p) => [p.reference_path, p.actual_path])
|
|
367
|
+
.filter((p) => p.length > 0),
|
|
98
368
|
recommendation,
|
|
369
|
+
per_screen: perScreen,
|
|
370
|
+
min_score: minScore,
|
|
371
|
+
avg_score: avgScore,
|
|
372
|
+
failed_screens: failedScreens,
|
|
373
|
+
threshold,
|
|
374
|
+
source,
|
|
99
375
|
},
|
|
100
376
|
imagePaths: {
|
|
101
|
-
reference:
|
|
102
|
-
actual:
|
|
103
|
-
diff:
|
|
377
|
+
reference: lead?.reference_path || "",
|
|
378
|
+
actual: lead?.actual_path || "",
|
|
379
|
+
diff: lead?.diff_path,
|
|
104
380
|
},
|
|
105
381
|
uxChecklist,
|
|
382
|
+
perScreen,
|
|
383
|
+
source,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function readUxChecklist(uxChecklistPath, cwd) {
|
|
387
|
+
if (!uxChecklistPath)
|
|
388
|
+
return undefined;
|
|
389
|
+
const uxPath = resolve(cwd, uxChecklistPath);
|
|
390
|
+
if (!existsSync(uxPath))
|
|
391
|
+
return undefined;
|
|
392
|
+
try {
|
|
393
|
+
return readFileSync(uxPath, "utf-8");
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function failureResult(area, description, fixHint, refPath = "") {
|
|
400
|
+
return {
|
|
401
|
+
output: {
|
|
402
|
+
match_score: 0,
|
|
403
|
+
differences: [
|
|
404
|
+
{
|
|
405
|
+
area,
|
|
406
|
+
description,
|
|
407
|
+
severity: "high",
|
|
408
|
+
fix_hint: fixHint,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
screenshots: refPath ? [refPath] : [],
|
|
412
|
+
recommendation: fixHint,
|
|
413
|
+
per_screen: [],
|
|
414
|
+
min_score: 0,
|
|
415
|
+
avg_score: 0,
|
|
416
|
+
failed_screens: [],
|
|
417
|
+
threshold: 0.8,
|
|
418
|
+
source: "files",
|
|
419
|
+
},
|
|
420
|
+
imagePaths: { reference: refPath, actual: "" },
|
|
421
|
+
perScreen: [],
|
|
422
|
+
source: "files",
|
|
106
423
|
};
|
|
107
424
|
}
|
|
425
|
+
export function loadDesignCompareSummary(runDir) {
|
|
426
|
+
const path = join(runDir, "design_compare_summary.json");
|
|
427
|
+
if (!existsSync(path))
|
|
428
|
+
return null;
|
|
429
|
+
try {
|
|
430
|
+
const stat = statSync(path);
|
|
431
|
+
if (stat.size === 0)
|
|
432
|
+
return null;
|
|
433
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
108
439
|
//# sourceMappingURL=design_compare.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design_compare.js","sourceRoot":"","sources":["../../src/tools/design_compare.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"design_compare.js","sourceRoot":"","sources":["../../src/tools/design_compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate_check.d.ts","sourceRoot":"","sources":["../../src/tools/gate_check.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,cAAc,EAEf,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"gate_check.d.ts","sourceRoot":"","sources":["../../src/tools/gate_check.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAY9B,wBAAsB,YAAY,CAChC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAsD1B"}
|
package/dist/tools/gate_check.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { DEFAULT_GATES } from "@codelooptech/shared";
|
|
4
4
|
import { loadRunMeta, getArtifactsBaseDir, getRunDir } from "../evidence/artifacts.js";
|
|
5
5
|
import { detectPlatform } from "./verify.js";
|
|
6
|
+
import { hasDesignReferences, loadDesignCompareSummary } from "./design_compare.js";
|
|
6
7
|
export async function runGateCheck(input, config, cwd = process.cwd()) {
|
|
7
8
|
const baseDir = getArtifactsBaseDir(cwd);
|
|
8
9
|
const meta = loadRunMeta(input.run_id, baseDir);
|
|
@@ -23,7 +24,7 @@ export async function runGateCheck(input, config, cwd = process.cwd()) {
|
|
|
23
24
|
const acceptanceContent = safeReadFile(input.acceptance_path, cwd);
|
|
24
25
|
const evaluations = evaluateGates(meta, config, specContent, acceptanceContent);
|
|
25
26
|
// Add evidence gates for UI projects
|
|
26
|
-
const evidenceGates = evaluateEvidenceGates(input.run_id, cwd);
|
|
27
|
+
const evidenceGates = evaluateEvidenceGates(input.run_id, cwd, config);
|
|
27
28
|
evaluations.push(...evidenceGates);
|
|
28
29
|
const passingGates = evaluations.filter((e) => e.passed).map((e) => e.gate.name);
|
|
29
30
|
const failingGates = evaluations.filter((e) => !e.passed).map((e) => e.gate.name);
|
|
@@ -60,7 +61,7 @@ function isUIProject(cwd) {
|
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
function evaluateEvidenceGates(runId, cwd) {
|
|
64
|
+
function evaluateEvidenceGates(runId, cwd, config) {
|
|
64
65
|
if (!isUIProject(cwd))
|
|
65
66
|
return [];
|
|
66
67
|
const baseDir = getArtifactsBaseDir(cwd);
|
|
@@ -127,6 +128,58 @@ function evaluateEvidenceGates(runId, cwd) {
|
|
|
127
128
|
? "Interaction replay frames exist"
|
|
128
129
|
: "No interaction replay performed. Call codeloop_interaction_replay after stopping the recording.",
|
|
129
130
|
});
|
|
131
|
+
// Design compare evidence gate — only when references exist (designs/ or .codeloop/figma.json).
|
|
132
|
+
if (hasDesignReferences(cwd)) {
|
|
133
|
+
const designGate = {
|
|
134
|
+
name: "design_compare_evidence",
|
|
135
|
+
description: "Design comparison must run and meet threshold for every reference",
|
|
136
|
+
rule: "min_score >= config.design_match_threshold and no missing screenshots",
|
|
137
|
+
input_artifacts: ["design_compare_summary.json"],
|
|
138
|
+
pass_threshold: true,
|
|
139
|
+
severity_if_failed: "blocker",
|
|
140
|
+
retry_allowance: 5,
|
|
141
|
+
escalation_condition: "Design references unmatched after 5 attempts",
|
|
142
|
+
};
|
|
143
|
+
const summary = loadDesignCompareSummary(runDir);
|
|
144
|
+
const threshold = config.design_match_threshold ?? 0.8;
|
|
145
|
+
if (!summary) {
|
|
146
|
+
results.push({
|
|
147
|
+
gate: designGate,
|
|
148
|
+
passed: false,
|
|
149
|
+
reason: "Design references found in designs/ (or .codeloop/figma.json) but no design_compare_summary.json in this run. " +
|
|
150
|
+
"Call codeloop_design_compare with mode='all' before gate_check.",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (summary.per_screen.length === 0) {
|
|
154
|
+
results.push({
|
|
155
|
+
gate: designGate,
|
|
156
|
+
passed: false,
|
|
157
|
+
reason: "Design compare summary contains no per-screen results.",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const failed = summary.failed_screens || [];
|
|
162
|
+
const min = summary.min_score ?? 0;
|
|
163
|
+
if (failed.length === 0 && min >= threshold) {
|
|
164
|
+
results.push({
|
|
165
|
+
gate: designGate,
|
|
166
|
+
passed: true,
|
|
167
|
+
reason: `${summary.per_screen.length} screen(s) match designs (min ${(min * 100).toFixed(1)}% >= ${(threshold * 100).toFixed(0)}% threshold).`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const worst = [...summary.per_screen].sort((a, b) => a.match_score - b.match_score).slice(0, 3);
|
|
172
|
+
const worstSummary = worst
|
|
173
|
+
.map((p) => `${p.screen_name}${p.viewport ? `[${p.viewport}]` : ""}=${(p.match_score * 100).toFixed(1)}%`)
|
|
174
|
+
.join(", ");
|
|
175
|
+
results.push({
|
|
176
|
+
gate: designGate,
|
|
177
|
+
passed: false,
|
|
178
|
+
reason: `${failed.length}/${summary.per_screen.length} screens below ${(threshold * 100).toFixed(0)}% threshold. Worst: ${worstSummary}. Fix and re-run codeloop_verify.`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
130
183
|
return results;
|
|
131
184
|
}
|
|
132
185
|
function evaluateGate(gate, meta, config, specContent, acceptanceContent) {
|