@rolepod/uiproof 0.5.0 → 0.6.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +2 -2
- package/CHANGELOG.md +130 -0
- package/README.md +15 -0
- package/dist/bin/rolepod-uiproof.js +267 -28
- package/dist/bin/rolepod-uiproof.js.map +1 -1
- package/dist/index.d.ts +53 -9
- package/dist/index.js +267 -26
- package/dist/index.js.map +1 -1
- package/dist/schemas/tools.json +1 -1
- package/package.json +1 -1
- package/skills/audit-a11y/SKILL.md +9 -0
- package/skills/check-errors/SKILL.md +9 -0
- package/skills/scaffold-e2e/SKILL.md +9 -0
- package/skills/verify-ui/SKILL.md +9 -0
- package/skills/visual-diff/SKILL.md +9 -0
|
@@ -175,7 +175,7 @@ function runInstallMobile() {
|
|
|
175
175
|
|
|
176
176
|
// src/cli/replay.ts
|
|
177
177
|
import { readFile } from "fs/promises";
|
|
178
|
-
import { resolve as
|
|
178
|
+
import { resolve as resolve4 } from "path";
|
|
179
179
|
|
|
180
180
|
// src/artifact/ArtifactStore.ts
|
|
181
181
|
import { randomUUID } from "crypto";
|
|
@@ -201,19 +201,74 @@ var log = {
|
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
+
// src/util/rolepodProtocol.ts
|
|
205
|
+
import { execSync } from "child_process";
|
|
206
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
207
|
+
import { join as join2 } from "path";
|
|
208
|
+
function detectRolepodParent(cwd = process.cwd()) {
|
|
209
|
+
let gitRoot = cwd;
|
|
210
|
+
try {
|
|
211
|
+
gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
212
|
+
cwd,
|
|
213
|
+
encoding: "utf8",
|
|
214
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
215
|
+
}).trim();
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
const file = join2(gitRoot, ".rolepod", "parent-active");
|
|
219
|
+
if (!existsSync2(file)) {
|
|
220
|
+
return { active: false, protocol: null, gitRoot };
|
|
221
|
+
}
|
|
222
|
+
const protocol = readFileSync(file, "utf8").trim().split(/\r?\n/)[0] ?? null;
|
|
223
|
+
return { active: true, protocol, gitRoot };
|
|
224
|
+
}
|
|
225
|
+
|
|
204
226
|
// src/artifact/ArtifactStore.ts
|
|
205
227
|
var ArtifactStore = class {
|
|
206
228
|
rootDir;
|
|
229
|
+
mode;
|
|
230
|
+
baselineRoot;
|
|
207
231
|
constructor(opts = {}) {
|
|
208
|
-
|
|
232
|
+
const parent = detectRolepodParent();
|
|
233
|
+
this.mode = opts.mode ?? (parent.active ? "with-parent" : "standalone");
|
|
234
|
+
if (opts.rootDir !== void 0) {
|
|
235
|
+
this.rootDir = opts.rootDir;
|
|
236
|
+
} else if (this.mode === "with-parent") {
|
|
237
|
+
this.rootDir = resolve2(parent.gitRoot, ".rolepod", "evidence");
|
|
238
|
+
} else {
|
|
239
|
+
this.rootDir = resolve2(process.cwd(), ".rolepod-uiproof", "artifacts");
|
|
240
|
+
}
|
|
241
|
+
this.baselineRoot = resolve2(process.cwd(), ".rolepod-uiproof", "baselines");
|
|
209
242
|
}
|
|
210
|
-
/**
|
|
211
|
-
|
|
212
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Allocate a fresh run dir and ensure it exists.
|
|
245
|
+
*
|
|
246
|
+
* - standalone: `./.rolepod-uiproof/artifacts/{prefix}_{ts}_{uuid}/`
|
|
247
|
+
* - with-parent: `<git-root>/.rolepod/evidence/{ts}-rolepod-uiproof-{skill}/`
|
|
248
|
+
*
|
|
249
|
+
* `prefix` is preserved for back-compat with v0.5 callers; new callers
|
|
250
|
+
* should also pass `opts.skill` so the with-parent path can be derived
|
|
251
|
+
* unambiguously and the manifest can be emitted with the canonical
|
|
252
|
+
* skill name.
|
|
253
|
+
*/
|
|
254
|
+
async startRun(prefix = "run", opts = {}) {
|
|
255
|
+
const ts = this.timestampSlug();
|
|
256
|
+
const skill = opts.skill ?? prefix;
|
|
257
|
+
let runId;
|
|
258
|
+
if (this.mode === "with-parent") {
|
|
259
|
+
runId = `${ts}-rolepod-uiproof-${skill}`;
|
|
260
|
+
} else {
|
|
261
|
+
runId = `${prefix}_${ts}_${randomUUID().slice(0, 8)}`;
|
|
262
|
+
}
|
|
213
263
|
const runDir = resolve2(this.rootDir, runId);
|
|
214
264
|
await mkdir(runDir, { recursive: true });
|
|
215
|
-
log.debug("artifact run started", {
|
|
216
|
-
|
|
265
|
+
log.debug("artifact run started", {
|
|
266
|
+
run_id: runId,
|
|
267
|
+
dir: runDir,
|
|
268
|
+
mode: this.mode,
|
|
269
|
+
skill
|
|
270
|
+
});
|
|
271
|
+
return { runId, runDir, skill, mode: this.mode };
|
|
217
272
|
}
|
|
218
273
|
async writeScreenshot(runDir, buf, name) {
|
|
219
274
|
const path = resolve2(runDir, `${name}.png`);
|
|
@@ -241,7 +296,7 @@ var ArtifactStore = class {
|
|
|
241
296
|
}
|
|
242
297
|
/** Root for stored visual baselines: `./.rolepod-uiproof/baselines/`. */
|
|
243
298
|
get baselineDir() {
|
|
244
|
-
return
|
|
299
|
+
return this.baselineRoot;
|
|
245
300
|
}
|
|
246
301
|
timestampSlug() {
|
|
247
302
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -1170,21 +1225,21 @@ var PlaywrightEngine = class {
|
|
|
1170
1225
|
if (s.dialogArming) {
|
|
1171
1226
|
s.dialogArming.resolve(false);
|
|
1172
1227
|
}
|
|
1173
|
-
return new Promise((
|
|
1228
|
+
return new Promise((resolve7) => {
|
|
1174
1229
|
const arming = {
|
|
1175
1230
|
action: opts.action,
|
|
1176
1231
|
text: opts.text,
|
|
1177
1232
|
expiresAt,
|
|
1178
1233
|
resolve: (handled) => {
|
|
1179
1234
|
s.dialogArming = null;
|
|
1180
|
-
|
|
1235
|
+
resolve7({ handled });
|
|
1181
1236
|
}
|
|
1182
1237
|
};
|
|
1183
1238
|
s.dialogArming = arming;
|
|
1184
1239
|
const timer = setTimeout(() => {
|
|
1185
1240
|
if (s.dialogArming === arming) {
|
|
1186
1241
|
s.dialogArming = null;
|
|
1187
|
-
|
|
1242
|
+
resolve7({ handled: false });
|
|
1188
1243
|
}
|
|
1189
1244
|
}, timeoutMs);
|
|
1190
1245
|
timer.unref?.();
|
|
@@ -2035,6 +2090,37 @@ async function ddmin(input, reproduces) {
|
|
|
2035
2090
|
return current;
|
|
2036
2091
|
}
|
|
2037
2092
|
|
|
2093
|
+
// src/util/manifest.ts
|
|
2094
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
2095
|
+
import { resolve as resolve3 } from "path";
|
|
2096
|
+
var ROLEPOD_PROTOCOL_VERSION = "rolepod/v1";
|
|
2097
|
+
async function writeManifest(input) {
|
|
2098
|
+
const manifest = {
|
|
2099
|
+
protocol: ROLEPOD_PROTOCOL_VERSION,
|
|
2100
|
+
plugin: "rolepod-uiproof",
|
|
2101
|
+
skill: input.skill,
|
|
2102
|
+
phase: input.phase,
|
|
2103
|
+
status: input.status,
|
|
2104
|
+
summary: input.summary,
|
|
2105
|
+
started_at: input.startedAt,
|
|
2106
|
+
finished_at: input.finishedAt,
|
|
2107
|
+
artifacts: input.artifacts,
|
|
2108
|
+
metadata: input.metadata ?? {}
|
|
2109
|
+
};
|
|
2110
|
+
const path = resolve3(input.runDir, "manifest.json");
|
|
2111
|
+
try {
|
|
2112
|
+
await writeFile2(path, JSON.stringify(manifest, null, 2), "utf8");
|
|
2113
|
+
return path;
|
|
2114
|
+
} catch (err) {
|
|
2115
|
+
log.warn("manifest write failed", {
|
|
2116
|
+
run_dir: input.runDir,
|
|
2117
|
+
skill: input.skill,
|
|
2118
|
+
err: String(err)
|
|
2119
|
+
});
|
|
2120
|
+
return void 0;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2038
2124
|
// src/tools/result.ts
|
|
2039
2125
|
function ok(value) {
|
|
2040
2126
|
return {
|
|
@@ -2076,7 +2162,11 @@ var verifyUiFlowTool = {
|
|
|
2076
2162
|
inputShape: verifyUiFlowShape,
|
|
2077
2163
|
build(ctx) {
|
|
2078
2164
|
return safeHandler(async (args) => {
|
|
2079
|
-
const
|
|
2165
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2166
|
+
const { runId, runDir, skill } = await ctx.store.startRun(
|
|
2167
|
+
"verify",
|
|
2168
|
+
{ skill: "verify-ui" }
|
|
2169
|
+
);
|
|
2080
2170
|
const initial = await runFlow(ctx, args, args.steps, runDir, {
|
|
2081
2171
|
captureEvidence: true,
|
|
2082
2172
|
bundleName: "replay.json"
|
|
@@ -2101,10 +2191,49 @@ var verifyUiFlowTool = {
|
|
|
2101
2191
|
attempts: min.attempts
|
|
2102
2192
|
};
|
|
2103
2193
|
}
|
|
2194
|
+
const manifestPath = await writeManifest({
|
|
2195
|
+
runDir,
|
|
2196
|
+
skill,
|
|
2197
|
+
phase: "verify",
|
|
2198
|
+
status: initial.passed ? "pass" : "fail",
|
|
2199
|
+
summary: buildVerifySummary(args, initial),
|
|
2200
|
+
startedAt,
|
|
2201
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2202
|
+
artifacts: flattenVerifyEvidence(initial.evidence),
|
|
2203
|
+
metadata: {
|
|
2204
|
+
mode: args.mode,
|
|
2205
|
+
step_count: args.steps.length,
|
|
2206
|
+
expect_count: args.expect.length,
|
|
2207
|
+
...initial.finalUrl !== void 0 ? { final_url: initial.finalUrl } : {}
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
if (manifestPath) result.manifest = manifestPath;
|
|
2104
2211
|
return ok(result);
|
|
2105
2212
|
});
|
|
2106
2213
|
}
|
|
2107
2214
|
};
|
|
2215
|
+
function buildVerifySummary(args, outcome) {
|
|
2216
|
+
const stepCount = args.steps.length;
|
|
2217
|
+
const expectCount = args.expect.length;
|
|
2218
|
+
if (outcome.passed) {
|
|
2219
|
+
return `${stepCount} step(s), ${expectCount} expect(s) passed`;
|
|
2220
|
+
}
|
|
2221
|
+
if (outcome.failedAtStep !== void 0) {
|
|
2222
|
+
return `failed at step ${outcome.failedAtStep}: ${outcome.failureReason ?? "unknown"}`;
|
|
2223
|
+
}
|
|
2224
|
+
return `failed: ${outcome.failureReason ?? "unknown"}`;
|
|
2225
|
+
}
|
|
2226
|
+
function flattenVerifyEvidence(ev) {
|
|
2227
|
+
const out = [];
|
|
2228
|
+
for (const s of ev.screenshots) out.push({ type: "screenshot", path: s });
|
|
2229
|
+
if (ev.replay_bundle) out.push({ type: "replay_bundle", path: ev.replay_bundle });
|
|
2230
|
+
if (ev.console) out.push({ type: "console", path: ev.console });
|
|
2231
|
+
if (ev.a11y_tree) out.push({ type: "a11y_tree", path: ev.a11y_tree });
|
|
2232
|
+
if (ev.har) out.push({ type: "har", path: ev.har });
|
|
2233
|
+
if (ev.trace) out.push({ type: "trace", path: ev.trace });
|
|
2234
|
+
if (ev.video) for (const v of ev.video) out.push({ type: "video", path: v });
|
|
2235
|
+
return out;
|
|
2236
|
+
}
|
|
2108
2237
|
function buildCaptureOptions(captures, runDir) {
|
|
2109
2238
|
const cap = {};
|
|
2110
2239
|
if (captures.has("har")) {
|
|
@@ -2518,7 +2647,7 @@ function treeHasText(tree, text) {
|
|
|
2518
2647
|
|
|
2519
2648
|
// src/cli/replay.ts
|
|
2520
2649
|
async function runReplay(bundlePath) {
|
|
2521
|
-
const abs =
|
|
2650
|
+
const abs = resolve4(bundlePath);
|
|
2522
2651
|
const raw = await readFile(abs, "utf8");
|
|
2523
2652
|
const bundle = JSON.parse(raw);
|
|
2524
2653
|
if (bundle.version !== 1) {
|
|
@@ -3042,7 +3171,11 @@ var auditA11yTool = {
|
|
|
3042
3171
|
inputShape: auditA11yShape,
|
|
3043
3172
|
build(ctx) {
|
|
3044
3173
|
return safeHandler(async (args) => {
|
|
3045
|
-
const
|
|
3174
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3175
|
+
const { runId, runDir, skill } = await ctx.store.startRun(
|
|
3176
|
+
"audit",
|
|
3177
|
+
{ skill: "audit-a11y" }
|
|
3178
|
+
);
|
|
3046
3179
|
const session = await ctx.registry.open(args.open);
|
|
3047
3180
|
const engine = ctx.registry.engineFor(session.id);
|
|
3048
3181
|
if (!(engine instanceof PlaywrightEngine)) {
|
|
@@ -3110,15 +3243,45 @@ var auditA11yTool = {
|
|
|
3110
3243
|
await ctx.registry.close(session).catch(() => void 0);
|
|
3111
3244
|
}
|
|
3112
3245
|
}
|
|
3246
|
+
const counts = countBySeverity(issues);
|
|
3247
|
+
const status = a11yStatus(counts);
|
|
3248
|
+
const artifacts = reportPath ? [{ type: "report", path: reportPath }] : [];
|
|
3249
|
+
const manifestPath = await writeManifest({
|
|
3250
|
+
runDir,
|
|
3251
|
+
skill,
|
|
3252
|
+
phase: "verify",
|
|
3253
|
+
status,
|
|
3254
|
+
summary: buildAuditSummary(args.level, counts, status),
|
|
3255
|
+
startedAt,
|
|
3256
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3257
|
+
artifacts,
|
|
3258
|
+
metadata: {
|
|
3259
|
+
level: args.level,
|
|
3260
|
+
scope: args.scope,
|
|
3261
|
+
counts,
|
|
3262
|
+
report_format: args.report_format
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3113
3265
|
return ok({
|
|
3114
3266
|
run_id: runId,
|
|
3115
|
-
counts
|
|
3267
|
+
counts,
|
|
3116
3268
|
issues,
|
|
3117
|
-
report_path: reportPath
|
|
3269
|
+
report_path: reportPath,
|
|
3270
|
+
...manifestPath ? { manifest: manifestPath } : {}
|
|
3118
3271
|
});
|
|
3119
3272
|
});
|
|
3120
3273
|
}
|
|
3121
3274
|
};
|
|
3275
|
+
function a11yStatus(counts) {
|
|
3276
|
+
if ((counts.critical ?? 0) + (counts.serious ?? 0) > 0) return "fail";
|
|
3277
|
+
if ((counts.moderate ?? 0) + (counts.minor ?? 0) > 0) return "warn";
|
|
3278
|
+
return "pass";
|
|
3279
|
+
}
|
|
3280
|
+
function buildAuditSummary(level, counts, status) {
|
|
3281
|
+
const total = (counts.critical ?? 0) + (counts.serious ?? 0) + (counts.moderate ?? 0) + (counts.minor ?? 0);
|
|
3282
|
+
if (status === "pass") return `${level}: 0 issues`;
|
|
3283
|
+
return `${level}: ${total} issue(s) \u2014 critical=${counts.critical ?? 0}, serious=${counts.serious ?? 0}, moderate=${counts.moderate ?? 0}, minor=${counts.minor ?? 0}`;
|
|
3284
|
+
}
|
|
3122
3285
|
function pickWcagRef(tags) {
|
|
3123
3286
|
return tags.find((t) => /^wcag\d/.test(t));
|
|
3124
3287
|
}
|
|
@@ -3235,14 +3398,18 @@ function scoreTree(root, tokens) {
|
|
|
3235
3398
|
|
|
3236
3399
|
// src/tools/composite/scaffold_e2e.ts
|
|
3237
3400
|
import { readFile as readFile2 } from "fs/promises";
|
|
3238
|
-
import { resolve as
|
|
3401
|
+
import { resolve as resolve5 } from "path";
|
|
3239
3402
|
var scaffoldE2eTool = {
|
|
3240
3403
|
name: ToolNames.scaffoldE2e,
|
|
3241
3404
|
description: "Generate a runnable e2e test file (playwright-test, vitest+playwright, or pytest+selenium) from a scenario description and optional replay bundle from a prior verify_ui_flow run.",
|
|
3242
3405
|
inputShape: scaffoldE2eShape,
|
|
3243
3406
|
build(ctx) {
|
|
3244
3407
|
return safeHandler(async (args) => {
|
|
3245
|
-
const
|
|
3408
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3409
|
+
const { runId, runDir, skill } = await ctx.store.startRun(
|
|
3410
|
+
"scaffold",
|
|
3411
|
+
{ skill: "scaffold-e2e" }
|
|
3412
|
+
);
|
|
3246
3413
|
const slug = slugify(args.scenario_nl);
|
|
3247
3414
|
const bundle = args.recorded_bundle ? await loadReplay(args.recorded_bundle) : null;
|
|
3248
3415
|
const ctxObj = { args, slug, bundle };
|
|
@@ -3280,19 +3447,35 @@ var scaffoldE2eTool = {
|
|
|
3280
3447
|
);
|
|
3281
3448
|
}
|
|
3282
3449
|
const path = await ctx.store.writeReport(runDir, filename, body);
|
|
3450
|
+
const manifestPath = await writeManifest({
|
|
3451
|
+
runDir,
|
|
3452
|
+
skill,
|
|
3453
|
+
phase: "build",
|
|
3454
|
+
status: "pass",
|
|
3455
|
+
summary: `generated ${args.framework} test "${filename}" from ${bundle ? "replay bundle" : "scenario"}`,
|
|
3456
|
+
startedAt,
|
|
3457
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3458
|
+
artifacts: [{ type: "test_file", path }],
|
|
3459
|
+
metadata: {
|
|
3460
|
+
framework: args.framework,
|
|
3461
|
+
language,
|
|
3462
|
+
from_replay_bundle: Boolean(bundle)
|
|
3463
|
+
}
|
|
3464
|
+
});
|
|
3283
3465
|
return ok({
|
|
3284
3466
|
run_id: runId,
|
|
3285
3467
|
test_file_path: path,
|
|
3286
3468
|
language,
|
|
3287
3469
|
dependencies,
|
|
3288
3470
|
setup_notes: setupNotes,
|
|
3289
|
-
from_replay_bundle: Boolean(bundle)
|
|
3471
|
+
from_replay_bundle: Boolean(bundle),
|
|
3472
|
+
...manifestPath ? { manifest: manifestPath } : {}
|
|
3290
3473
|
});
|
|
3291
3474
|
});
|
|
3292
3475
|
}
|
|
3293
3476
|
};
|
|
3294
3477
|
async function loadReplay(bundlePath) {
|
|
3295
|
-
const raw = await readFile2(
|
|
3478
|
+
const raw = await readFile2(resolve5(bundlePath), "utf8");
|
|
3296
3479
|
return JSON.parse(raw);
|
|
3297
3480
|
}
|
|
3298
3481
|
function slugify(s) {
|
|
@@ -3579,9 +3762,9 @@ function indent(block, n) {
|
|
|
3579
3762
|
}
|
|
3580
3763
|
|
|
3581
3764
|
// src/tools/composite/visual_diff.ts
|
|
3582
|
-
import { existsSync as
|
|
3765
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3583
3766
|
import { readFile as readFile3 } from "fs/promises";
|
|
3584
|
-
import { resolve as
|
|
3767
|
+
import { resolve as resolve6 } from "path";
|
|
3585
3768
|
import pixelmatch from "pixelmatch";
|
|
3586
3769
|
import { PNG } from "pngjs";
|
|
3587
3770
|
var visualDiffTool = {
|
|
@@ -3590,7 +3773,11 @@ var visualDiffTool = {
|
|
|
3590
3773
|
inputShape: visualDiffShape,
|
|
3591
3774
|
build(ctx) {
|
|
3592
3775
|
return safeHandler(async (args) => {
|
|
3593
|
-
const
|
|
3776
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3777
|
+
const { runId, runDir, skill } = await ctx.store.startRun(
|
|
3778
|
+
"vdiff",
|
|
3779
|
+
{ skill: "visual-diff" }
|
|
3780
|
+
);
|
|
3594
3781
|
const session = await ctx.registry.open({
|
|
3595
3782
|
...args.open,
|
|
3596
3783
|
...args.viewport ? { viewport: args.viewport } : {}
|
|
@@ -3609,16 +3796,30 @@ var visualDiffTool = {
|
|
|
3609
3796
|
);
|
|
3610
3797
|
const currentPath = await ctx.store.writeScreenshot(runDir, buf, "current");
|
|
3611
3798
|
await ctx.store.ensureDir(ctx.store.baselineDir);
|
|
3612
|
-
const baselinePath =
|
|
3799
|
+
const baselinePath = resolve6(
|
|
3613
3800
|
ctx.store.baselineDir,
|
|
3614
3801
|
`${args.baseline_id}.png`
|
|
3615
3802
|
);
|
|
3616
|
-
if (!
|
|
3803
|
+
if (!existsSync3(baselinePath)) {
|
|
3617
3804
|
await ctx.store.writeBytes(
|
|
3618
3805
|
ctx.store.baselineDir,
|
|
3619
3806
|
`${args.baseline_id}.png`,
|
|
3620
3807
|
buf
|
|
3621
3808
|
);
|
|
3809
|
+
const manifestPath2 = await writeManifest({
|
|
3810
|
+
runDir,
|
|
3811
|
+
skill,
|
|
3812
|
+
phase: "verify",
|
|
3813
|
+
status: "pass",
|
|
3814
|
+
summary: `baseline "${args.baseline_id}" seeded from current capture`,
|
|
3815
|
+
startedAt,
|
|
3816
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3817
|
+
artifacts: [
|
|
3818
|
+
{ type: "baseline", path: baselinePath },
|
|
3819
|
+
{ type: "screenshot", path: currentPath }
|
|
3820
|
+
],
|
|
3821
|
+
metadata: { baseline_id: args.baseline_id, seeded: true }
|
|
3822
|
+
});
|
|
3622
3823
|
return ok({
|
|
3623
3824
|
run_id: runId,
|
|
3624
3825
|
baseline_id: args.baseline_id,
|
|
@@ -3626,6 +3827,7 @@ var visualDiffTool = {
|
|
|
3626
3827
|
passed: true,
|
|
3627
3828
|
baseline_path: baselinePath,
|
|
3628
3829
|
current_path: currentPath,
|
|
3830
|
+
...manifestPath2 ? { manifest: manifestPath2 } : {},
|
|
3629
3831
|
note: "Baseline did not exist \u2014 current capture saved as the new baseline."
|
|
3630
3832
|
});
|
|
3631
3833
|
}
|
|
@@ -3656,21 +3858,45 @@ var visualDiffTool = {
|
|
|
3656
3858
|
);
|
|
3657
3859
|
const total = baseline.width * baseline.height;
|
|
3658
3860
|
const diffPct = diffPixels / total;
|
|
3861
|
+
const passed = diffPct <= args.threshold_pct;
|
|
3659
3862
|
const diffImagePath = await ctx.store.writeBytes(
|
|
3660
3863
|
runDir,
|
|
3661
3864
|
"diff.png",
|
|
3662
3865
|
PNG.sync.write(diff)
|
|
3663
3866
|
);
|
|
3867
|
+
const artifacts = [
|
|
3868
|
+
{ type: "baseline", path: baselinePath },
|
|
3869
|
+
{ type: "screenshot", path: currentPath },
|
|
3870
|
+
{ type: "diff", path: diffImagePath }
|
|
3871
|
+
];
|
|
3872
|
+
const manifestPath = await writeManifest({
|
|
3873
|
+
runDir,
|
|
3874
|
+
skill,
|
|
3875
|
+
phase: "verify",
|
|
3876
|
+
status: passed ? "pass" : "fail",
|
|
3877
|
+
summary: `diff ${(diffPct * 100).toFixed(3)}% vs baseline "${args.baseline_id}" (threshold ${(args.threshold_pct * 100).toFixed(3)}%)`,
|
|
3878
|
+
startedAt,
|
|
3879
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3880
|
+
artifacts,
|
|
3881
|
+
metadata: {
|
|
3882
|
+
baseline_id: args.baseline_id,
|
|
3883
|
+
diff_pct: Number(diffPct.toFixed(6)),
|
|
3884
|
+
diff_pixels: diffPixels,
|
|
3885
|
+
total_pixels: total,
|
|
3886
|
+
threshold_pct: args.threshold_pct
|
|
3887
|
+
}
|
|
3888
|
+
});
|
|
3664
3889
|
return ok({
|
|
3665
3890
|
run_id: runId,
|
|
3666
3891
|
baseline_id: args.baseline_id,
|
|
3667
3892
|
diff_pct: Number(diffPct.toFixed(6)),
|
|
3668
3893
|
diff_pixels: diffPixels,
|
|
3669
3894
|
total_pixels: total,
|
|
3670
|
-
passed
|
|
3895
|
+
passed,
|
|
3671
3896
|
baseline_path: baselinePath,
|
|
3672
3897
|
current_path: currentPath,
|
|
3673
|
-
diff_image_path: diffImagePath
|
|
3898
|
+
diff_image_path: diffImagePath,
|
|
3899
|
+
...manifestPath ? { manifest: manifestPath } : {}
|
|
3674
3900
|
});
|
|
3675
3901
|
} finally {
|
|
3676
3902
|
if (args.close_on_finish) {
|
|
@@ -3939,8 +4165,19 @@ var toolMetadata = {
|
|
|
3939
4165
|
|
|
3940
4166
|
// src/server.ts
|
|
3941
4167
|
var SERVER_NAME = "rolepod-uiproof";
|
|
3942
|
-
var SERVER_VERSION = "0.
|
|
4168
|
+
var SERVER_VERSION = "0.6.1";
|
|
4169
|
+
var SUPPORTED_PROTOCOL = "v1";
|
|
4170
|
+
function checkProtocolCompat() {
|
|
4171
|
+
const parent = detectRolepodParent();
|
|
4172
|
+
if (!parent.active || !parent.protocol) return;
|
|
4173
|
+
if (parent.protocol !== SUPPORTED_PROTOCOL) {
|
|
4174
|
+
console.warn(
|
|
4175
|
+
`rolepod protocol mismatch: expected ${SUPPORTED_PROTOCOL}, got ${parent.protocol}. Manifest will still be written in ${SUPPORTED_PROTOCOL} shape \u2014 parent may not parse it correctly.`
|
|
4176
|
+
);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
3943
4179
|
function buildServer(opts = {}) {
|
|
4180
|
+
checkProtocolCompat();
|
|
3944
4181
|
const webEngine = createWebEngine();
|
|
3945
4182
|
const registry = new SessionRegistry({ idleTimeoutMs: opts.idleTimeoutMs });
|
|
3946
4183
|
registry.register("web", webEngine);
|
|
@@ -4001,6 +4238,8 @@ function buildServer(opts = {}) {
|
|
|
4001
4238
|
}
|
|
4002
4239
|
log.info("rolepod-uiproof server built", {
|
|
4003
4240
|
version: SERVER_VERSION,
|
|
4241
|
+
protocol: SUPPORTED_PROTOCOL,
|
|
4242
|
+
mode: store.mode,
|
|
4004
4243
|
tools: tools.map((t) => t.name)
|
|
4005
4244
|
});
|
|
4006
4245
|
return {
|