yam-flow 0.1.5 → 0.1.6

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.
@@ -26,7 +26,7 @@ Ueye is one route. It should not split into separate lite/deep skills. Use these
26
26
  ```bash
27
27
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
28
28
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png --json
29
- yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality needs-polish --provider-context local --execution-surface in-app-browser --browser-surface in-app-browser --preserved-state --json
29
+ yam ueye report --reference ./reference.png --actual .yam/screens/home.png --completion-claim done --design-quality pass --direction-locked --reference-read --states-checked --mobile-checked --contrast-checked --cta-checked --provider-context local --execution-surface in-app-browser --browser-surface in-app-browser --preserved-state --json
30
30
  ```
31
31
 
32
32
  - `capture` uses a project-local capture backend when present, then reports `blocked` instead of installing dependencies or pretending a screenshot exists.
@@ -190,6 +190,55 @@ Dimensions:
190
190
  Report findings as P0-P3. Keep P2/P3 short unless the user requested a full polish pass.
191
191
  For each relevant design dimension, use `pass`, `needs-polish`, or `fails`.
192
192
 
193
+ ### Design Completion Gate
194
+
195
+ Purpose: prevent Ueye from declaring visual work complete before the design evidence supports that claim.
196
+
197
+ Use when the output says the UI is done, complete, polished, final, or verified.
198
+
199
+ Minimum fields:
200
+
201
+ - `completion_claim`: draft / needs-polish / done
202
+ - `mode`: fast / strict
203
+ - `ready_to_claim_done`
204
+ - `design_score`, when used
205
+ - `min_design_score`
206
+ - `checks`
207
+ - `p0`
208
+ - `p1`
209
+ - `warnings`
210
+ - `blockers`
211
+ - `next_action`
212
+ - `truth_status`
213
+
214
+ Required for a done claim when relevant:
215
+
216
+ - direction locked
217
+ - reference read proof for reference-led work
218
+ - implementation screenshot or source-screen evidence
219
+ - reference comparison for reference-led work
220
+ - design quality pass
221
+ - no P0 blockers
222
+ - no unresolved P1 issues unless explicitly deferred
223
+ - CTA affordance checked
224
+ - primary states checked
225
+ - mobile/responsive behavior checked
226
+ - contrast/accessibility-relevant visuals checked
227
+
228
+ Example:
229
+
230
+ ```bash
231
+ yam ueye report --reference ./reference.png --actual .yam/screens/home.png --completion-claim done --design-quality pass --direction-locked --reference-read --states-checked --mobile-checked --contrast-checked --cta-checked --json
232
+ ```
233
+
234
+ Attach to proof when needed:
235
+
236
+ ```bash
237
+ yam proof --route ueye --truth verified --visual "implementation screenshot evidence recorded" --design-completion '{"completion_claim":"done","has_implementation_screenshot":true,"design_quality":"pass","states_checked":true,"mobile_checked":true,"contrast_checked":true,"cta_checked":true,"direction_locked":true,"truth_status":"verified"}'
238
+ ```
239
+
240
+ The design completion gate is a completion judgment. Keep actual source-screen proof in visual evidence, visual provenance, or a Ueye run report. Do not use self-reported gate fields to pretend a missing screenshot was captured.
241
+
193
242
  ### Review Continuity And Comparison Report
194
243
 
195
244
  Use this when a Ueye task continues a previous visual review, rechecks a fix, or compares two review runs.
@@ -222,3 +271,4 @@ Do not claim continuity from memory alone. Link it to a previous report, screens
222
271
  - Reference-only evidence can support direction, not implementation proof.
223
272
  - Generated-only evidence can support ideation or annotation, not implemented-screen verification.
224
273
  - Missing screenshots, unavailable browser, or text-only review should cap the result at `partial`, `blocked`, or `assumed`.
274
+ - A done claim with a missing or failing design completion gate should cap the result at `partial` or `blocked`.
@@ -66,6 +66,7 @@ Use these when the task depends on visual truth, reference matching, or design q
66
66
  3. Reference vs Implementation Matrix.
67
67
  4. Design Quality Review.
68
68
  5. Review Continuity and Comparison Report.
69
+ 6. Design Completion Gate.
69
70
 
70
71
  Default bound:
71
72
 
@@ -146,6 +147,27 @@ Review dimensions:
146
147
  For each relevant dimension, record `pass`, `needs-polish`, or `fails`.
147
148
  Keep actionable findings in P0-P3 order. Prefer fixing P0/P1 and cheap local P2 issues before broad polish.
148
149
 
150
+ ### Design Completion Gate
151
+
152
+ Use before claiming a Ueye implementation or serious review is done. This is the design-side completion check, not a separate route.
153
+
154
+ Record:
155
+
156
+ - `completion_claim`: draft, needs-polish, or done.
157
+ - `mode`: fast or strict.
158
+ - `design_score`, when score-based completion is useful.
159
+ - P0 and P1 open findings.
160
+ - Whether direction, reference read proof, implementation evidence, comparison, design quality, CTA affordance, state coverage, mobile/responsive behavior, and contrast/accessibility visuals were checked.
161
+ - blockers, warnings, next action, and truth status.
162
+
163
+ Gate behavior:
164
+
165
+ - Draft and needs-polish can stay fast.
166
+ - `done` automatically behaves as strict.
167
+ - P0 or blocked reason should return blocked.
168
+ - Missing required checks for a done claim should cap truth at partial.
169
+ - Only claim done when the gate says `ready_to_claim_done: true`.
170
+
149
171
  ### Review Continuity and Comparison Report
150
172
 
151
173
  Use when a screen has more than one visual pass, a reference needs follow-up, or a previous Ueye report should stay connected to the next one.
@@ -199,9 +221,10 @@ Keep this metadata descriptive. It should support honest visual claims, not forc
199
221
  6. Screenshot/browser recheck when feasible.
200
222
  7. Reference vs Implementation Matrix when reference fidelity matters.
201
223
  8. Design Quality Review.
202
- 9. Review Continuity and Comparison Report when the work spans multiple passes.
203
- 10. P0/P1 closeout.
204
- 11. Truth status.
224
+ 9. Design Completion Gate before any done claim.
225
+ 10. Review Continuity and Comparison Report when the work spans multiple passes.
226
+ 11. P0/P1 closeout.
227
+ 12. Truth status.
205
228
 
206
229
  ## Truth Caps
207
230
 
@@ -210,3 +233,4 @@ Keep this metadata descriptive. It should support honest visual claims, not forc
210
233
  - Blocked: requested visual claim cannot be inspected.
211
234
  - Assumed: implementation follows code patterns but visual result was not observed.
212
235
  - Reference-only or generated-only evidence cannot upgrade a result to full visual verification.
236
+ - A done claim without a passing design completion gate cannot upgrade beyond partial or blocked.
@@ -41,6 +41,9 @@ Do not use for:
41
41
  - Record surface context when it matters: provider context, provider badge, execution surface, app surface, browser surface, control mode, preserved URL/state, and evidence id.
42
42
  - Generated media can guide direction, but it cannot verify the implemented screen without implementation screenshot evidence.
43
43
  - Prefer the in-app browser for interactive visual checks. Do not switch to a profile-dependent browser unless the user explicitly asks or the task requires existing cookies, sessions, extensions, or profile state.
44
+ - Ueye owns the design completion gate. Do not rely on a separate code-risk route to catch sloppy visual completion.
45
+ - Draft or needs-polish work may stay fast; a `done` claim requires recorded design gate evidence.
46
+ - Do not claim a Ueye result is done when implementation evidence, design quality, P0/P1 status, state coverage, mobile/responsive behavior, contrast/accessibility visuals, CTA affordance, or reference-read/comparison evidence is missing where relevant.
44
47
 
45
48
  ## Workflow
46
49
 
@@ -78,8 +81,17 @@ Do not use for:
78
81
  - accessibility
79
82
  - brand or mood fit
80
83
  - mark each relevant dimension as pass, needs-polish, or fails.
81
- 11. Produce a P0-P3 visual issue ledger and fix path.
82
- 12. Recheck changed/high-risk screens after fixes when feasible.
84
+ 11. Run the Design Completion Gate before any "done" claim:
85
+ - direction locked
86
+ - reference read proof exists when reference-led
87
+ - implementation screenshot or source-screen evidence exists
88
+ - reference comparison is recorded when reference-led
89
+ - design quality is pass
90
+ - P0 is empty
91
+ - P1 is fixed or explicitly deferred
92
+ - CTA, states, mobile/responsive behavior, and contrast/accessibility visuals were checked
93
+ 12. Produce a P0-P3 visual issue ledger and fix path.
94
+ 13. Recheck changed/high-risk screens after fixes when feasible.
83
95
 
84
96
  ## Required Ueye Artifacts
85
97
 
@@ -106,7 +118,11 @@ Use these artifacts for reference-led implementation or serious visual review. K
106
118
  - use pass, needs-polish, or fails for each relevant design dimension.
107
119
  7. Review Continuity and Comparison Report, when this is a follow-up review:
108
120
  - record previous source/report, current source/report, comparison delta, resolved findings, new findings, still-open findings, truth status, and next action.
109
- 8. Media Generation Proof, when generated images are part of the task:
121
+ 8. Design Completion Gate, when implementation or review is claimed done:
122
+ - record completion claim, design score when used, P0/P1 status, gate checks, blockers, warnings, next action, and truth status.
123
+ - use `yam ueye report --completion-claim done --design-quality pass --direction-locked --states-checked --mobile-checked --contrast-checked --cta-checked --json` when a compact proof-ready gate is needed.
124
+ - if the gate is partial or blocked, report the Ueye result as partial or blocked instead of complete.
125
+ 9. Media Generation Proof, when generated images are part of the task:
110
126
  - record whether generation was requested, attempted, blocked, or produced an output.
111
127
  - use `yam media proof --requested --attempted --output path.png --wait-loop --json` when a generated image materially affects the design direction.
112
128
  - never use generated media proof as a substitute for implemented screen evidence.
@@ -120,6 +136,7 @@ Use these artifacts for reference-led implementation or serious visual review. K
120
136
  - Inspect 1-3 primary images by default; expand only for P0/P1 risk, responsive breakage, or user-requested deep visual QA.
121
137
  - Keep source-screen inventory to the 5 most important rows by default.
122
138
  - Screenshot unavailable: cap the result at `partial` or `blocked`.
139
+ - Completion gate missing for a `done` claim: cap the result at `partial` or `blocked`.
123
140
  - Text-only review: cap the result at `partial`.
124
141
  - Mock or invented screenshots: cap the result at `partial` and mark the source.
125
142
  - Browser unavailable but source files reviewed: report implementation verification separately from visual verification.
package/src/bin/yam.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  buildMissionPatchEnvelope,
15
15
  buildRollbackHint,
16
16
  buildRuntimeBackendEvidence,
17
+ buildUeyeDesignCompletionGate,
17
18
  buildUeyeVisualProvenance,
18
19
  buildUeyeRunReport,
19
20
  buildUeyeSurfaceContext,
@@ -1349,12 +1350,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
1349
1350
  Usage:
1350
1351
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
1351
1352
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
1352
- yam ueye report [--reference ref.png] [--actual screenshot.png] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
1353
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
1353
1354
 
1354
1355
  Notes:
1355
1356
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
1356
1357
  compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
1357
- report produces a proof-ready Ueye visual run report and continuity/comparison record without requiring a new capture.
1358
+ report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
1358
1359
  `);
1359
1360
  }
1360
1361
 
@@ -1520,7 +1521,7 @@ async function ueyeCompare(args = []) {
1520
1521
 
1521
1522
  async function ueyeReport(args = []) {
1522
1523
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
1523
- const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'json']));
1524
+ const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
1524
1525
  const reference = String(flags.reference || flags.ref || '');
1525
1526
  const actual = String(flags.actual || flags.screenshot || '');
1526
1527
  const referenceSources = [];
@@ -1586,7 +1587,9 @@ async function ueyeReport(args = []) {
1586
1587
  truth_status: 'partial'
1587
1588
  }));
1588
1589
  }
1589
- if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1590
+ if (flags.comparison_result) {
1591
+ comparisonResult = normalizeUeyeComparisonResult(flags.comparison_result);
1592
+ } else if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1590
1593
  comparisonResult = 'matched';
1591
1594
  } else if (reference && actual) {
1592
1595
  comparisonResult = 'different';
@@ -1605,6 +1608,22 @@ async function ueyeReport(args = []) {
1605
1608
  },
1606
1609
  comparison_result: comparisonResult,
1607
1610
  design_quality: String(flags.design_quality || 'not-checked'),
1611
+ design_completion_gate: {
1612
+ mode: flags.strict || flags.gate_mode === 'strict' ? 'strict' : 'fast',
1613
+ completion_claim: flags.completion_claim || flags.completion_status || 'draft',
1614
+ design_score: numberOrNull(flags.design_score),
1615
+ min_design_score: numberOrNull(flags.min_design_score) ?? 8,
1616
+ p0: [...arrayFlag(flags.p0), ...arrayFlag(flags.open_p0)],
1617
+ p1: [...arrayFlag(flags.p1), ...arrayFlag(flags.open_p1)],
1618
+ states_checked: Boolean(flags.states_checked),
1619
+ mobile_checked: Boolean(flags.mobile_checked),
1620
+ responsive_checked: Boolean(flags.responsive_checked),
1621
+ contrast_checked: Boolean(flags.contrast_checked),
1622
+ accessibility_checked: Boolean(flags.accessibility_checked),
1623
+ cta_checked: Boolean(flags.cta_checked),
1624
+ direction_locked: Boolean(flags.direction_locked),
1625
+ reference_read: Boolean(flags.reference_read)
1626
+ },
1608
1627
  blocked_reason: blockedReason,
1609
1628
  next_action: String(flags.next_visual_action || flags.next_action || '')
1610
1629
  });
@@ -1614,6 +1633,7 @@ async function ueyeReport(args = []) {
1614
1633
  referenceSources,
1615
1634
  implementationSources,
1616
1635
  surfaceContext: report.surface_context,
1636
+ designCompletionGate: report.design_completion_gate,
1617
1637
  comparisonResult,
1618
1638
  designQuality: String(flags.design_quality || 'not-checked'),
1619
1639
  similar: arrayFlag(flags.similar),
@@ -1672,6 +1692,7 @@ function buildUeyeComparisonReport({
1672
1692
  referenceSources,
1673
1693
  implementationSources,
1674
1694
  surfaceContext,
1695
+ designCompletionGate,
1675
1696
  comparisonResult,
1676
1697
  designQuality,
1677
1698
  similar,
@@ -1713,6 +1734,7 @@ function buildUeyeComparisonReport({
1713
1734
  implementation_count: implementationSources.length
1714
1735
  },
1715
1736
  surface_context: surfaceContext || buildUeyeSurfaceContext(),
1737
+ design_completion_gate: designCompletionGate || null,
1716
1738
  comparison_result: comparisonResult,
1717
1739
  design_quality: designQuality,
1718
1740
  similar,
@@ -1732,6 +1754,12 @@ function buildUeyeComparisonReport({
1732
1754
  };
1733
1755
  }
1734
1756
 
1757
+ function normalizeUeyeComparisonResult(value = '') {
1758
+ const text = String(value || '').toLowerCase();
1759
+ if (['matched', 'similar', 'different', 'not-verified', 'not-applicable'].includes(text)) return text;
1760
+ return 'not-verified';
1761
+ }
1762
+
1735
1763
  async function media(args = []) {
1736
1764
  const subcommand = args[0] || 'help';
1737
1765
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return mediaUsage();
@@ -2015,7 +2043,7 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
2015
2043
  const key = rawKey.slice(2);
2016
2044
  if (allowed.size && !allowed.has(key)) continue;
2017
2045
  const normalizedKey = key.replace(/-/g, '_');
2018
- if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
2046
+ if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read'].includes(key)) {
2019
2047
  flags[normalizedKey] = true;
2020
2048
  continue;
2021
2049
  }
@@ -2165,6 +2193,7 @@ async function proof(args = []) {
2165
2193
  printProofList('Mission patch envelope', summary.missionEnvelope || []);
2166
2194
  printProofList('Rollback hint', summary.rollbackHint || []);
2167
2195
  printProofList('Media proof', summary.mediaProof || []);
2196
+ printProofList('Design completion', summary.designCompletion || []);
2168
2197
  printProofList('Changed surfaces', summary.changed);
2169
2198
  printProofList('Skipped', summary.skipped);
2170
2199
  printProofList('Blocked', summary.blocked);
@@ -2260,6 +2289,10 @@ function buildProofSummary(parsed, artifact) {
2260
2289
  ...structuredEvidenceList(artifact.mediaProof),
2261
2290
  ...structuredEvidenceList(parsed.flags.media_proof)
2262
2291
  ],
2292
+ designCompletion: [
2293
+ ...structuredEvidenceList(artifact.designCompletion),
2294
+ ...structuredEvidenceList(parsed.flags.design_completion)
2295
+ ],
2263
2296
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
2264
2297
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
2265
2298
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -2325,6 +2358,9 @@ function renderProofMarkdown(summary) {
2325
2358
  '## Media proof',
2326
2359
  ...renderProofMarkdownList(summary.mediaProof || []),
2327
2360
  '',
2361
+ '## Design completion',
2362
+ ...renderProofMarkdownList(summary.designCompletion || []),
2363
+ '',
2328
2364
  '## Changed surfaces',
2329
2365
  ...renderProofMarkdownList(summary.changed),
2330
2366
  '',
@@ -2357,7 +2393,7 @@ function renderProofMarkdownList(values) {
2357
2393
  function parseProofArgs(args = []) {
2358
2394
  const flags: AnyRecord = {};
2359
2395
  const positionals: string[] = [];
2360
- const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
2396
+ const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
2361
2397
  for (let index = 0; index < args.length; index += 1) {
2362
2398
  const arg = args[index];
2363
2399
  if (arg.startsWith('--')) {
@@ -2371,7 +2407,7 @@ function parseProofArgs(args = []) {
2371
2407
  }
2372
2408
  const value = inlineValue ?? args[index + 1] ?? '';
2373
2409
  if (inlineValue === undefined) index += 1;
2374
- if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
2410
+ if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
2375
2411
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
2376
2412
  } else {
2377
2413
  flags[normalizedKey] = value;
@@ -2414,6 +2450,7 @@ function emptyProofArtifact() {
2414
2450
  missionEnvelope: [],
2415
2451
  rollbackHint: [],
2416
2452
  mediaProof: [],
2453
+ designCompletion: [],
2417
2454
  cleanup: '',
2418
2455
  changed: [],
2419
2456
  skipped: [],
@@ -2441,6 +2478,7 @@ async function parseProofJson(file, empty) {
2441
2478
  missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
2442
2479
  rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
2443
2480
  mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
2481
+ designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
2444
2482
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
2445
2483
  changed: arrayFlag(data.changed || data.files),
2446
2484
  skipped: arrayFlag(data.skipped),
@@ -2471,6 +2509,7 @@ async function parseProofMarkdown(file, empty) {
2471
2509
  missionEnvelope: readProofList(text, 'Mission patch envelope'),
2472
2510
  rollbackHint: readProofList(text, 'Rollback hint'),
2473
2511
  mediaProof: readProofList(text, 'Media proof'),
2512
+ designCompletion: readProofList(text, 'Design completion'),
2474
2513
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
2475
2514
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
2476
2515
  skipped: readProofList(text, 'Skipped'),
@@ -2556,6 +2595,9 @@ function formatStructuredEvidence(value) {
2556
2595
  if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
2557
2596
  return JSON.stringify(buildMediaGenerationProof(data));
2558
2597
  }
2598
+ if (data.schema === 'yam.ueye-design-completion-gate.v1' || data.completion_claim || data.ready_to_claim_done !== undefined) {
2599
+ return JSON.stringify(buildUeyeDesignCompletionGate(data));
2600
+ }
2559
2601
  return JSON.stringify(data);
2560
2602
  } catch {
2561
2603
  return text;
@@ -2564,7 +2606,7 @@ function formatStructuredEvidence(value) {
2564
2606
 
2565
2607
  function hasProofEvidence(parsed) {
2566
2608
  const flags = parsed.flags || {};
2567
- return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'media_proof', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup || flags.runtime_backend || flags.runtime_claim);
2609
+ return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'media_proof', 'design_completion', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup || flags.runtime_backend || flags.runtime_claim);
2568
2610
  }
2569
2611
 
2570
2612
  function printProofList(label, values) {
@@ -88,6 +88,30 @@ export interface UeyeSurfaceContext {
88
88
  truth_status: TruthStatus;
89
89
  }
90
90
 
91
+ export interface UeyeDesignGateCheck {
92
+ id: string;
93
+ label: string;
94
+ status: 'pass' | 'fail' | 'warning' | 'not_checked';
95
+ required: boolean;
96
+ next_action: string;
97
+ }
98
+
99
+ export interface UeyeDesignCompletionGate {
100
+ schema: 'yam.ueye-design-completion-gate.v1';
101
+ mode: 'fast' | 'strict';
102
+ completion_claim: 'draft' | 'needs-polish' | 'done';
103
+ ready_to_claim_done: boolean;
104
+ design_score: number | null;
105
+ min_design_score: number;
106
+ checks: UeyeDesignGateCheck[];
107
+ p0: string[];
108
+ p1: string[];
109
+ warnings: string[];
110
+ blockers: string[];
111
+ next_action: string;
112
+ truth_status: TruthStatus;
113
+ }
114
+
91
115
  export interface RollbackHint {
92
116
  schema: 'yam.rollback-hint.v1';
93
117
  touched_files: string[];
@@ -122,6 +146,7 @@ export interface UeyeRunReport {
122
146
  reference_sources: UeyeVisualProvenance[];
123
147
  implementation_sources: UeyeVisualProvenance[];
124
148
  surface_context: UeyeSurfaceContext;
149
+ design_completion_gate: UeyeDesignCompletionGate;
125
150
  comparison_result: string;
126
151
  design_quality: string;
127
152
  blocked_reason: string;
@@ -129,6 +154,10 @@ export interface UeyeRunReport {
129
154
  truth_status: TruthStatus;
130
155
  }
131
156
 
157
+ export interface UeyeRunReportInput extends Partial<Omit<UeyeRunReport, 'design_completion_gate'>> {
158
+ design_completion_gate?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>;
159
+ }
160
+
132
161
  export interface MediaGenerationProof {
133
162
  schema: 'yam.media-generation-proof.v1';
134
163
  tool_name: string;
@@ -171,6 +200,7 @@ export interface ProofSummary {
171
200
  rollbackHint?: unknown;
172
201
  runtimeBackendEvidence?: unknown;
173
202
  mediaProof?: unknown;
203
+ designCompletion?: unknown;
174
204
  }
175
205
 
176
206
  export interface ProofOptions {
@@ -206,6 +236,7 @@ export interface YamCompletionProof {
206
236
  rollbackHint: string[];
207
237
  runtimeBackendEvidence: string[];
208
238
  mediaProof: string[];
239
+ designCompletion: string[];
209
240
  cleanup: unknown;
210
241
  changed: string[];
211
242
  skipped: string[];
@@ -262,6 +293,94 @@ export function buildUeyeSurfaceContext(input: Partial<UeyeSurfaceContext> = {})
262
293
  };
263
294
  }
264
295
 
296
+ export function buildUeyeDesignCompletionGate(input: Partial<UeyeDesignCompletionGate> & Record<string, unknown> = {}): UeyeDesignCompletionGate {
297
+ const completionClaim = normalizeUeyeCompletionClaim(input.completion_claim);
298
+ const mode = String(input.mode || '').toLowerCase() === 'strict' || completionClaim === 'done' ? 'strict' : 'fast';
299
+ const designScore = numberOrNull(input.design_score);
300
+ const minDesignScore = numberOrNull(input.min_design_score) ?? 8;
301
+ const p0 = asList(input.p0);
302
+ const p1 = asList(input.p1);
303
+ const hasReference = Boolean(input.has_reference);
304
+ const hasImplementation = Boolean(input.has_implementation_screenshot);
305
+ const comparisonResult = String(input.comparison_result || 'not-verified');
306
+ const designQuality = String(input.design_quality || 'not-checked');
307
+ const blockedReason = String(input.blocked_reason || '');
308
+ const strictOrDone = mode === 'strict' || completionClaim === 'done';
309
+ const checks: UeyeDesignGateCheck[] = [
310
+ designGateCheck('direction_lock', 'Design direction and target screen were locked before judging completion', Boolean(input.direction_locked) ? 'pass' : 'not_checked', strictOrDone, 'record the target screen and design direction before claiming done'),
311
+ designGateCheck('reference_read', 'Reference read proof exists when reference is used', !hasReference ? 'not_checked' : Boolean(input.reference_read) ? 'pass' : 'fail', hasReference && strictOrDone, 'record concrete reference observations before claiming reference-led work is done'),
312
+ designGateCheck('implementation_screenshot', 'Real implementation screenshot or screen evidence exists', hasImplementation ? 'pass' : 'fail', strictOrDone, 'capture or provide implementation screenshot before claiming done'),
313
+ designGateCheck('reference_comparison', 'Reference comparison is recorded when reference is used', !hasReference ? 'not_checked' : ['matched', 'similar', 'not-applicable'].includes(comparisonResult) ? 'pass' : 'fail', hasReference, 'compare against the reference or mark the difference as intentional before claiming done'),
314
+ designGateCheck('design_quality', 'Design quality result is pass', designQuality === 'pass' ? 'pass' : designQuality === 'needs-polish' ? 'warning' : 'fail', strictOrDone, 'resolve design quality issues or keep the result as needs-polish/partial'),
315
+ designGateCheck('p0_clear', 'No P0 visual blockers remain', p0.length ? 'fail' : 'pass', true, 'fix P0 blockers before any done claim'),
316
+ designGateCheck('p1_clear', 'No P1 major visual issues remain', p1.length ? 'fail' : 'pass', strictOrDone, 'fix or explicitly defer P1 issues before claiming done'),
317
+ designGateCheck('cta_affordance', 'Primary CTA and interaction affordances were checked', Boolean(input.cta_checked) ? 'pass' : 'not_checked', strictOrDone, 'check primary CTA clarity and interaction affordances before claiming done'),
318
+ designGateCheck('state_coverage', 'Primary UI states were checked', Boolean(input.states_checked) ? 'pass' : 'not_checked', strictOrDone, 'check default/loading/error/empty/disabled states or keep completion partial'),
319
+ designGateCheck('mobile_coverage', 'Mobile/responsive behavior was checked', Boolean(input.mobile_checked || input.responsive_checked) ? 'pass' : 'not_checked', strictOrDone, 'check mobile/responsive behavior before claiming done'),
320
+ designGateCheck('accessibility_visuals', 'Contrast/accessibility-relevant visuals were checked', Boolean(input.contrast_checked || input.accessibility_checked) ? 'pass' : 'not_checked', strictOrDone, 'check contrast/accessibility-relevant visuals before claiming done')
321
+ ];
322
+ if (designScore !== null || strictOrDone) {
323
+ checks.push(designGateCheck('design_score', `Design score is at least ${minDesignScore}`, designScore !== null && designScore >= minDesignScore ? 'pass' : designScore === null ? 'not_checked' : 'fail', false, `record --design-score ${minDesignScore} or higher when using score-based completion`));
324
+ }
325
+ const blockers = [
326
+ ...p0.map((item) => `P0: ${item}`),
327
+ ...(blockedReason ? [blockedReason] : []),
328
+ ...checks
329
+ .filter((check) => check.required && check.status !== 'pass')
330
+ .map((check) => `${check.id}: ${check.next_action}`)
331
+ ];
332
+ const warnings = [
333
+ ...p1.map((item) => `P1: ${item}`),
334
+ ...checks
335
+ .filter((check) => !check.required && ['fail', 'warning', 'not_checked'].includes(check.status))
336
+ .map((check) => `${check.id}: ${check.next_action}`)
337
+ ];
338
+ const ready = completionClaim === 'done' && blockers.length === 0;
339
+ const truth: TruthStatus = blockers.some((item) => item.startsWith('P0:')) || blockedReason ? 'blocked'
340
+ : ready ? 'verified'
341
+ : strictOrDone ? 'partial'
342
+ : hasImplementation ? 'partial'
343
+ : 'assumed';
344
+ return {
345
+ schema: 'yam.ueye-design-completion-gate.v1',
346
+ mode,
347
+ completion_claim: completionClaim,
348
+ ready_to_claim_done: ready,
349
+ design_score: designScore,
350
+ min_design_score: minDesignScore,
351
+ checks,
352
+ p0,
353
+ p1,
354
+ warnings,
355
+ blockers,
356
+ next_action: blockers[0] || (ready ? 'done claim is supported by the recorded design gate' : warnings[0] || 'keep the Ueye result as draft or needs-polish until a done claim is needed'),
357
+ truth_status: truth
358
+ };
359
+ }
360
+
361
+ function normalizeUeyeCompletionClaim(value: unknown = ''): UeyeDesignCompletionGate['completion_claim'] {
362
+ const text = String(value || '').toLowerCase();
363
+ if (text === 'done' || text === 'complete' || text === 'completed') return 'done';
364
+ if (text === 'needs-polish' || text === 'needs_polish' || text === 'polish') return 'needs-polish';
365
+ return 'draft';
366
+ }
367
+
368
+ function designGateCheck(id: string, label: string, status: UeyeDesignGateCheck['status'], required: boolean, nextAction: string): UeyeDesignGateCheck {
369
+ return {
370
+ id,
371
+ label,
372
+ status,
373
+ required,
374
+ next_action: nextAction
375
+ };
376
+ }
377
+
378
+ function numberOrNull(value: unknown = null): number | null {
379
+ if (value === undefined || value === null || value === '') return null;
380
+ const number = Number(value);
381
+ return Number.isFinite(number) ? number : null;
382
+ }
383
+
265
384
  export function buildRollbackHint(input: Partial<RollbackHint> = {}): RollbackHint {
266
385
  return {
267
386
  schema: 'yam.rollback-hint.v1',
@@ -302,7 +421,7 @@ export function buildRuntimeBackendEvidence(input: Partial<RuntimeBackendEvidenc
302
421
  };
303
422
  }
304
423
 
305
- export function buildUeyeRunReport(input: Partial<UeyeRunReport> = {}): UeyeRunReport {
424
+ export function buildUeyeRunReport(input: UeyeRunReportInput = {}): UeyeRunReport {
306
425
  const references = Array.isArray(input.reference_sources)
307
426
  ? input.reference_sources.map((item) => buildUeyeVisualProvenance(item))
308
427
  : [];
@@ -317,19 +436,30 @@ export function buildUeyeRunReport(input: Partial<UeyeRunReport> = {}): UeyeRunR
317
436
  : comparisonResult === 'matched' && hasImplementation ? 'verified'
318
437
  : hasImplementation ? 'partial'
319
438
  : 'partial';
439
+ const gate = buildUeyeDesignCompletionGate({
440
+ ...input.design_completion_gate,
441
+ has_reference: references.length > 0,
442
+ has_implementation_screenshot: hasImplementation,
443
+ comparison_result: comparisonResult,
444
+ design_quality: String(input.design_quality || 'not-checked'),
445
+ blocked_reason: blockedReason
446
+ });
447
+ const shouldApplyDesignGate = gate.mode === 'strict' || gate.completion_claim === 'done' || gate.blockers.length > 0;
448
+ const finalTruth = shouldApplyDesignGate ? weakestTruth(derivedTruth, gate.truth_status) : derivedTruth;
320
449
  return {
321
450
  schema: 'yam.ueye-run-report.v1',
322
451
  reference_sources: references,
323
452
  implementation_sources: implementations,
324
453
  surface_context: buildUeyeSurfaceContext({
325
454
  ...input.surface_context,
326
- truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : derivedTruth
455
+ truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : finalTruth
327
456
  }),
457
+ design_completion_gate: gate,
328
458
  comparison_result: comparisonResult,
329
459
  design_quality: String(input.design_quality || 'not-checked'),
330
460
  blocked_reason: blockedReason,
331
- next_action: String(input.next_action || (derivedTruth === 'verified' ? 'no action required' : 'capture or provide implementation screenshot before claiming verified visual status')),
332
- truth_status: isTruthStatus(input.truth_status) ? input.truth_status : derivedTruth
461
+ next_action: String(input.next_action || gate.blockers[0] || (finalTruth === 'verified' ? 'no action required' : 'capture or provide implementation screenshot before claiming verified visual status')),
462
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : finalTruth
333
463
  };
334
464
  }
335
465
 
@@ -444,6 +574,9 @@ export function classifyEvidenceTruth(summary: ProofSummary = {}, options: Proof
444
574
  for (const media of asList(summary.mediaProof)) {
445
575
  rows.push(evidenceRow('evidence', media, readStructuredTruth(media, 'partial')));
446
576
  }
577
+ for (const designCompletion of asList(summary.designCompletion)) {
578
+ rows.push(evidenceRow('visual', designCompletion, readStructuredTruth(designCompletion, 'partial')));
579
+ }
447
580
  for (const runtime of asList(summary.runtime)) {
448
581
  rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
449
582
  }
@@ -473,6 +606,14 @@ export function applyProofTruthCaps(summary: ProofSummary = {}, options: ProofOp
473
606
  cappedTruth = weakestTruth(cappedTruth, 'partial');
474
607
  unverified.push('real_visual_evidence_missing');
475
608
  }
609
+ const designCompletionTruth = designCompletionCap(summary.designCompletion);
610
+ if (designCompletionTruth) {
611
+ cappedTruth = weakestTruth(cappedTruth, designCompletionTruth);
612
+ if (designCompletionTruth === 'blocked') blockers.push('ueye_design_completion_blocked');
613
+ if (!['verified', 'proven'].includes(designCompletionTruth)) {
614
+ unverified.push('ueye_design_completion_not_verified');
615
+ }
616
+ }
476
617
  if (requested !== cappedTruth) {
477
618
  caps.push(`requested ${requested} capped to ${cappedTruth} by evidence`);
478
619
  }
@@ -532,6 +673,7 @@ export function buildYamCompletionProof(summary: ProofSummary = {}, options: Pro
532
673
  rollbackHint: asList(summary.rollbackHint),
533
674
  runtimeBackendEvidence: asList(summary.runtimeBackendEvidence),
534
675
  mediaProof: asList(summary.mediaProof),
676
+ designCompletion: asList(summary.designCompletion),
535
677
  cleanup: summary.cleanup || '',
536
678
  changed: asList(summary.changed),
537
679
  skipped: asList(summary.skipped),
@@ -601,6 +743,12 @@ function readStructuredTruth(value: unknown = '', fallback: TruthStatus = 'parti
601
743
  }
602
744
  }
603
745
 
746
+ function designCompletionCap(value: unknown = ''): TruthStatus | null {
747
+ const items = asList(value);
748
+ if (!items.length) return null;
749
+ return items.reduce<TruthStatus>((weakest, item) => weakestTruth(weakest, readStructuredTruth(item, 'partial')), 'proven');
750
+ }
751
+
604
752
  function strongestSupportedTruth(rows: EvidenceRow[] = []): TruthStatus {
605
753
  if (rows.some((row) => row.truth === 'blocked')) return 'blocked';
606
754
  if (rows.some((row) => row.truth === 'real_required_missing')) return 'real_required_missing';