mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(check): opt-in --layout proseCoverageFloor (#2834)
* feat(check): opt-in --layout proseCoverageFloor for text_occluded Keep the default prose coverage floor at 0.15 for all callers, and allow stricter agents (e.g. Zephyr) to lower it via --layout "proseCoverageFloor=0.05" without changing other layout gates. Co-authored-by: Cursor <cursoragent@cursor.com> * style(check): collapse --layout comments and docs to one line Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check): strict proseCoverageFloor parse + pin 0.07 floor tests Reject trailing-garbage fractions that Number.parseFloat would accept, and pin the existing ~0.07 coverage fixture for default vs floor=0.05 (atomic labels unchanged) plus a collectLayout forwarding assertion. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(check): share parseNumberStrict across layout and frame-check Sweep the sibling --frame-check tol parser (and caption fractions) onto the same strict Number() helper so trailing garbage cannot prefix-parse. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
type CheckReport,
|
||||
type CheckSection,
|
||||
} from "../utils/checkPipeline.js";
|
||||
import type { CaptionZoneOptions, FrameCheckOptions } from "../utils/checkTypes.js";
|
||||
import type { CaptionZoneOptions, FrameCheckOptions, LayoutOptions } from "../utils/checkTypes.js";
|
||||
|
||||
export const examples: Example[] = [
|
||||
["Run the full verification gate", "hyperframes check"],
|
||||
@@ -120,6 +120,10 @@ export function createCheckCommand(
|
||||
description:
|
||||
'Bare --frame-check uses defaults (tol=2px, severity=warning, seek=.5; breach floor=max(120px, 6% of shorter canvas edge)); or pass "severity=error;seek=.25,.75;tol=4" to tune',
|
||||
},
|
||||
layout: {
|
||||
type: "string",
|
||||
description: 'Layout knobs: "proseCoverageFloor=0.05" (0–1; default 0.15).',
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const asJson = args.json === true;
|
||||
@@ -168,6 +172,7 @@ function parseCheckOptions(args: Record<string, unknown>): CheckOptions {
|
||||
snapshots: args.snapshots === true,
|
||||
captionZone: parseCaptionZone(args["caption-zone"]),
|
||||
frameCheck: parseFrameCheck(args["frame-check"]),
|
||||
layout: parseLayout(args.layout),
|
||||
autoProxy: args.proxy as boolean | undefined,
|
||||
};
|
||||
}
|
||||
@@ -176,6 +181,8 @@ const CAPTION_ZONE_FIELDS = new Set(["x0", "y0", "x1", "y1", "severity", "seek"]
|
||||
|
||||
const FRAME_CHECK_FIELDS = new Set(["severity", "seek", "tol"]);
|
||||
|
||||
const LAYOUT_FIELDS = new Set(["proseCoverageFloor"]);
|
||||
|
||||
// Mirrors --caption-zone's spec grammar so the EF bridge's severity/seek/tol
|
||||
// options survive the migration instead of being silently dropped by a
|
||||
// boolean flag (bare --frame-check keeps today's defaults).
|
||||
@@ -206,8 +213,8 @@ function parseFrameCheckFields(value: string): Map<string, string> {
|
||||
|
||||
function parseFrameCheckTolerance(raw: string | undefined): number | undefined {
|
||||
if (raw === undefined) return undefined;
|
||||
const tol = Number.parseFloat(raw);
|
||||
if (!Number.isFinite(tol) || tol < 0) throw frameCheckError();
|
||||
const tol = parseNumberStrict(raw);
|
||||
if (tol === null || tol < 0) throw frameCheckError();
|
||||
return tol;
|
||||
}
|
||||
|
||||
@@ -217,6 +224,52 @@ function frameCheckError(): Error {
|
||||
);
|
||||
}
|
||||
|
||||
/** Parse `--layout "proseCoverageFloor=0.05"` (semicolon-separated key=value, like caption-zone). */
|
||||
export function parseLayout(value: unknown): LayoutOptions | undefined {
|
||||
if (value === undefined || value === null || value === false) return undefined;
|
||||
if (value === true || value === "") throw layoutError();
|
||||
if (typeof value !== "string") throw layoutError();
|
||||
const fields = parseLayoutFields(value);
|
||||
const proseCoverageFloor = parseProseCoverageFloor(fields.get("proseCoverageFloor"));
|
||||
if (proseCoverageFloor === undefined) throw layoutError();
|
||||
return { proseCoverageFloor };
|
||||
}
|
||||
|
||||
function parseLayoutFields(value: string): Map<string, string> {
|
||||
const fields = new Map<string, string>();
|
||||
for (const part of value.split(";")) {
|
||||
const trimmed = part.trim();
|
||||
if (!trimmed) continue;
|
||||
const separator = trimmed.indexOf("=");
|
||||
if (separator <= 0) throw layoutError();
|
||||
const key = trimmed.slice(0, separator).trim();
|
||||
const entry = trimmed.slice(separator + 1).trim();
|
||||
if (!LAYOUT_FIELDS.has(key) || fields.has(key)) throw layoutError();
|
||||
fields.set(key, entry);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
function parseProseCoverageFloor(raw: string | undefined): number | undefined {
|
||||
if (raw === undefined) return undefined;
|
||||
const floor = parseNumberStrict(raw);
|
||||
if (floor === null || floor < 0 || floor > 1) throw layoutError();
|
||||
return floor;
|
||||
}
|
||||
|
||||
function layoutError(): Error {
|
||||
return new Error(
|
||||
'Invalid --layout: use "proseCoverageFloor=0.05" with a fraction from 0 to 1 (inclusive)',
|
||||
);
|
||||
}
|
||||
|
||||
/** Reject trailing garbage that Number.parseFloat would silently accept (`4px`, `0.05abc`). */
|
||||
function parseNumberStrict(raw: string): number | null {
|
||||
if (raw === "") return null;
|
||||
const value = Number(raw);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function parseCaptionZone(value: unknown): CaptionZoneOptions | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
const fields = parseCaptionFields(captionZoneString(value));
|
||||
@@ -279,8 +332,8 @@ function requiredCaptionFraction(fields: Map<string, string>, key: string): numb
|
||||
|
||||
function captionFraction(value: string | undefined): number | null {
|
||||
if (value === undefined || value === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : null;
|
||||
const parsed = parseNumberStrict(value);
|
||||
return parsed !== null && parsed >= 0 && parsed <= 1 ? parsed : null;
|
||||
}
|
||||
|
||||
function captionSeverity(value: string | undefined): "error" | "warning" | undefined {
|
||||
|
||||
Reference in New Issue
Block a user