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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to yam-flow are documented here.
4
4
 
5
+ ## v0.1.6 - 2026-06-07
6
+
7
+ ### Added
8
+
9
+ - Added the Ueye Design Completion Gate so Ueye can own design/UI/UX completion quality instead of relying on a separate code-risk route to catch unfinished visual work.
10
+ - Added `yam ueye report --completion-claim`, `--strict`, `--design-score`, P0/P1, CTA, state, mobile/responsive, contrast/accessibility, direction, and reference-read gate fields.
11
+ - Added `yam proof --design-completion` so design completion evidence can cap proof truth status when a Ueye done claim is not supported.
12
+ - Added design completion sections to Ueye review and comparison templates.
13
+
14
+ ### Improved
15
+
16
+ - Ueye `done` claims now cap to partial or blocked when implementation evidence, design quality, P0/P1 status, state coverage, mobile/responsive checks, contrast/accessibility checks, CTA checks, or reference-read/comparison evidence is missing where relevant.
17
+ - Ueye report JSON now includes `design_completion_gate` in both the main run report and comparison report.
18
+ - Expanded CLI smoke coverage for Ueye completion gate success, partial, and blocked paths.
19
+
5
20
  ## v0.1.5 - 2026-06-04
6
21
 
7
22
  ### Added
package/COMMANDS.md CHANGED
@@ -45,6 +45,7 @@ Opt-in visual evidence helpers:
45
45
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
46
46
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png
47
47
  yam ueye report --reference ./reference.png --actual .yam/screens/home.png --review-session-id pricing-card-v1 --provider-context local --execution-surface in-app-browser --browser-surface in-app-browser --similar "overall hierarchy" --different "button glow intensity" --missing "mobile state" --design-quality needs-polish --json
48
+ 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
48
49
  yam media proof --requested --attempted --output ./generated.png --wait-loop --json
49
50
  yam proof --route ueye --truth partial --visual "browser/local screenshot comparison executed" --require-visual
50
51
  ```
@@ -150,6 +151,7 @@ yam proof --route ueye --truth partial --visual-provenance '{"source_kind":"refe
150
151
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
151
152
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png --json
152
153
  yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality pass --provider-context local --execution-surface in-app-browser --browser-surface in-app-browser --json
154
+ 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"}'
153
155
  yam media proof --requested --attempted --output ./generated.png --wait-loop --json
154
156
  yam runtime evidence --backend terminal --claim observed --evidence-id dev-server-1 --command "npm run dev" --json
155
157
  yam mission queue --agent-id implementer --scope "checkout form" --changed src/checkout.tsx --verification-hint "npm run typecheck" --rollback-hint "revert checkout form patch if typecheck or smoke fails" --json
package/README.md CHANGED
@@ -129,7 +129,8 @@ yam proof --route ueye --truth verified --visual "reference image only"
129
129
  yam proof --route ueye --truth partial --visual-provenance '{"source_kind":"reference","source_hash":"unknown","comparison_result":"not-verified","truth_status":"partial"}'
130
130
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
131
131
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png
132
- yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality needs-polish --provider-context local --execution-surface in-app-browser --app-surface codex-app --browser-surface in-app-browser --preserved-state --json
132
+ 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 --app-surface codex-app --browser-surface in-app-browser --preserved-state --json
133
+ 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"}'
133
134
  yam media proof --requested --attempted --output ./generated.png --wait-loop --json
134
135
  yam proof --route mission --mission-envelope '{"agent_id":"implementer","assigned_scope":"target component","changed_files":["src/file.ts"],"verification_hint":"npm run typecheck","truth_status":"partial"}'
135
136
  yam release report --json
@@ -178,6 +179,7 @@ Publishing still requires confirming the final npm package name and account acce
178
179
  - visual evidence caps
179
180
  - Ueye visual run reports
180
181
  - Ueye surface context metadata
182
+ - Ueye design completion gate
181
183
  - runtime backend evidence
182
184
  - runtime evidence mini summaries
183
185
  - mission patch queue lite records
@@ -199,6 +201,7 @@ These shapes keep reports machine-readable without turning normal work into a he
199
201
  - Structured diagnostic next action: every doctor/release diagnostic should say what was observed, what evidence supports it, what to do next, owner route, severity, and truth status.
200
202
  - Ueye review continuity/comparison report: `$ueye` run reports can link a previous review, current evidence, comparison delta, design quality result, and next action.
201
203
  - Ueye surface context: `$ueye` run reports can record provider context, provider badge, execution surface, app surface, browser surface, control mode, preserved URL/state, and evidence id.
204
+ - Ueye design completion gate: `$ueye` can stay fast for draft work, but a `done` claim is capped until implementation evidence, comparison/design-quality status, P0/P1 status, key states, mobile/responsive behavior, contrast/accessibility visuals, CTA affordance, and direction/reference-read proof are recorded when relevant.
202
205
 
203
206
  ## Ueye Capture And Compare
204
207
 
@@ -207,14 +210,14 @@ These shapes keep reports machine-readable without turning normal work into a he
207
210
  ```bash
208
211
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
209
212
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png --json
210
- yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality pass --provider-context local --execution-surface in-app-browser --browser-surface in-app-browser --preserved-state --json
213
+ 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
211
214
  ```
212
215
 
213
216
  `capture` uses a Playwright install from the current project when present, then falls back to the package context. It does not install browsers or add runtime dependencies by itself. If capture is unavailable, the visual claim stays `partial` or `blocked` until a real screenshot is supplied.
214
217
 
215
218
  `compare` is local-only and dependency-free. It records file hashes, dimensions, comparison result, and proof-ready visual provenance. Exact image matches can be `verified`; different screenshots stay `partial` so visual parity is not overclaimed.
216
219
 
217
- `report` gathers reference sources, implementation screenshots, comparison result, continuity fields, design quality judgment, surface context, and next action into one compact proof-ready JSON object.
220
+ `report` gathers reference sources, implementation screenshots, comparison result, continuity fields, design quality judgment, design completion gate, surface context, and next action into one compact proof-ready JSON object. Draft and needs-polish work can stay fast; `--completion-claim done` turns on the stricter design gate.
218
221
 
219
222
  Generated media can guide visual direction, but it cannot prove the implemented UI by itself:
220
223
 
package/dist/bin/yam.js CHANGED
@@ -8,7 +8,7 @@ import os from 'node:os';
8
8
  import path from 'node:path';
9
9
  import process from 'node:process';
10
10
  import { fileURLToPath } from 'node:url';
11
- import { TRUTH_STATUSES, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
11
+ import { TRUTH_STATUSES, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
12
12
  function errorMessage(error) {
13
13
  return error instanceof Error ? error.message : String(error);
14
14
  }
@@ -1366,12 +1366,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
1366
1366
  Usage:
1367
1367
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
1368
1368
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
1369
- 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]
1369
+ 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]
1370
1370
 
1371
1371
  Notes:
1372
1372
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
1373
1373
  compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
1374
- report produces a proof-ready Ueye visual run report and continuity/comparison record without requiring a new capture.
1374
+ report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
1375
1375
  `);
1376
1376
  }
1377
1377
  async function ueyeCapture(args = []) {
@@ -1539,7 +1539,7 @@ async function ueyeCompare(args = []) {
1539
1539
  async function ueyeReport(args = []) {
1540
1540
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
1541
1541
  return ueyeUsage();
1542
- 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']));
1542
+ 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']));
1543
1543
  const reference = String(flags.reference || flags.ref || '');
1544
1544
  const actual = String(flags.actual || flags.screenshot || '');
1545
1545
  const referenceSources = [];
@@ -1604,7 +1604,10 @@ async function ueyeReport(args = []) {
1604
1604
  truth_status: 'partial'
1605
1605
  }));
1606
1606
  }
1607
- if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1607
+ if (flags.comparison_result) {
1608
+ comparisonResult = normalizeUeyeComparisonResult(flags.comparison_result);
1609
+ }
1610
+ else if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1608
1611
  comparisonResult = 'matched';
1609
1612
  }
1610
1613
  else if (reference && actual) {
@@ -1624,6 +1627,22 @@ async function ueyeReport(args = []) {
1624
1627
  },
1625
1628
  comparison_result: comparisonResult,
1626
1629
  design_quality: String(flags.design_quality || 'not-checked'),
1630
+ design_completion_gate: {
1631
+ mode: flags.strict || flags.gate_mode === 'strict' ? 'strict' : 'fast',
1632
+ completion_claim: flags.completion_claim || flags.completion_status || 'draft',
1633
+ design_score: numberOrNull(flags.design_score),
1634
+ min_design_score: numberOrNull(flags.min_design_score) ?? 8,
1635
+ p0: [...arrayFlag(flags.p0), ...arrayFlag(flags.open_p0)],
1636
+ p1: [...arrayFlag(flags.p1), ...arrayFlag(flags.open_p1)],
1637
+ states_checked: Boolean(flags.states_checked),
1638
+ mobile_checked: Boolean(flags.mobile_checked),
1639
+ responsive_checked: Boolean(flags.responsive_checked),
1640
+ contrast_checked: Boolean(flags.contrast_checked),
1641
+ accessibility_checked: Boolean(flags.accessibility_checked),
1642
+ cta_checked: Boolean(flags.cta_checked),
1643
+ direction_locked: Boolean(flags.direction_locked),
1644
+ reference_read: Boolean(flags.reference_read)
1645
+ },
1627
1646
  blocked_reason: blockedReason,
1628
1647
  next_action: String(flags.next_visual_action || flags.next_action || '')
1629
1648
  });
@@ -1633,6 +1652,7 @@ async function ueyeReport(args = []) {
1633
1652
  referenceSources,
1634
1653
  implementationSources,
1635
1654
  surfaceContext: report.surface_context,
1655
+ designCompletionGate: report.design_completion_gate,
1636
1656
  comparisonResult,
1637
1657
  designQuality: String(flags.design_quality || 'not-checked'),
1638
1658
  similar: arrayFlag(flags.similar),
@@ -1686,7 +1706,7 @@ async function readPreviousUeyeReport(file = '') {
1686
1706
  };
1687
1707
  }
1688
1708
  }
1689
- function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceSources, implementationSources, surfaceContext, comparisonResult, designQuality, similar, different, missing, notes, resolved, newFindings, stillOpen, regressions, previousScreenshotId, currentScreenshotId, viewport, state, nextAction, truthStatus }) {
1709
+ function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceSources, implementationSources, surfaceContext, designCompletionGate, comparisonResult, designQuality, similar, different, missing, notes, resolved, newFindings, stillOpen, regressions, previousScreenshotId, currentScreenshotId, viewport, state, nextAction, truthStatus }) {
1690
1710
  const hasReference = referenceSources.length > 0;
1691
1711
  const hasImplementation = implementationSources.length > 0;
1692
1712
  const continuityTruth = previousReport?.blocked_reason ? 'blocked' : previousReport ? 'partial' : 'assumed';
@@ -1711,6 +1731,7 @@ function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceS
1711
1731
  implementation_count: implementationSources.length
1712
1732
  },
1713
1733
  surface_context: surfaceContext || buildUeyeSurfaceContext(),
1734
+ design_completion_gate: designCompletionGate || null,
1714
1735
  comparison_result: comparisonResult,
1715
1736
  design_quality: designQuality,
1716
1737
  similar,
@@ -1729,6 +1750,12 @@ function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceS
1729
1750
  truth_status: truthStatus
1730
1751
  };
1731
1752
  }
1753
+ function normalizeUeyeComparisonResult(value = '') {
1754
+ const text = String(value || '').toLowerCase();
1755
+ if (['matched', 'similar', 'different', 'not-verified', 'not-applicable'].includes(text))
1756
+ return text;
1757
+ return 'not-verified';
1758
+ }
1732
1759
  async function media(args = []) {
1733
1760
  const subcommand = args[0] || 'help';
1734
1761
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
@@ -2026,7 +2053,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
2026
2053
  if (allowed.size && !allowed.has(key))
2027
2054
  continue;
2028
2055
  const normalizedKey = key.replace(/-/g, '_');
2029
- if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
2056
+ 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)) {
2030
2057
  flags[normalizedKey] = true;
2031
2058
  continue;
2032
2059
  }
@@ -2183,6 +2210,7 @@ async function proof(args = []) {
2183
2210
  printProofList('Mission patch envelope', summary.missionEnvelope || []);
2184
2211
  printProofList('Rollback hint', summary.rollbackHint || []);
2185
2212
  printProofList('Media proof', summary.mediaProof || []);
2213
+ printProofList('Design completion', summary.designCompletion || []);
2186
2214
  printProofList('Changed surfaces', summary.changed);
2187
2215
  printProofList('Skipped', summary.skipped);
2188
2216
  printProofList('Blocked', summary.blocked);
@@ -2279,6 +2307,10 @@ function buildProofSummary(parsed, artifact) {
2279
2307
  ...structuredEvidenceList(artifact.mediaProof),
2280
2308
  ...structuredEvidenceList(parsed.flags.media_proof)
2281
2309
  ],
2310
+ designCompletion: [
2311
+ ...structuredEvidenceList(artifact.designCompletion),
2312
+ ...structuredEvidenceList(parsed.flags.design_completion)
2313
+ ],
2282
2314
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
2283
2315
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
2284
2316
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -2343,6 +2375,9 @@ function renderProofMarkdown(summary) {
2343
2375
  '## Media proof',
2344
2376
  ...renderProofMarkdownList(summary.mediaProof || []),
2345
2377
  '',
2378
+ '## Design completion',
2379
+ ...renderProofMarkdownList(summary.designCompletion || []),
2380
+ '',
2346
2381
  '## Changed surfaces',
2347
2382
  ...renderProofMarkdownList(summary.changed),
2348
2383
  '',
@@ -2373,7 +2408,7 @@ function renderProofMarkdownList(values) {
2373
2408
  function parseProofArgs(args = []) {
2374
2409
  const flags = {};
2375
2410
  const positionals = [];
2376
- 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']);
2411
+ 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']);
2377
2412
  for (let index = 0; index < args.length; index += 1) {
2378
2413
  const arg = args[index];
2379
2414
  if (arg.startsWith('--')) {
@@ -2389,7 +2424,7 @@ function parseProofArgs(args = []) {
2389
2424
  const value = inlineValue ?? args[index + 1] ?? '';
2390
2425
  if (inlineValue === undefined)
2391
2426
  index += 1;
2392
- if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
2427
+ 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)) {
2393
2428
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
2394
2429
  }
2395
2430
  else {
@@ -2433,6 +2468,7 @@ function emptyProofArtifact() {
2433
2468
  missionEnvelope: [],
2434
2469
  rollbackHint: [],
2435
2470
  mediaProof: [],
2471
+ designCompletion: [],
2436
2472
  cleanup: '',
2437
2473
  changed: [],
2438
2474
  skipped: [],
@@ -2459,6 +2495,7 @@ async function parseProofJson(file, empty) {
2459
2495
  missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
2460
2496
  rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
2461
2497
  mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
2498
+ designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
2462
2499
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
2463
2500
  changed: arrayFlag(data.changed || data.files),
2464
2501
  skipped: arrayFlag(data.skipped),
@@ -2489,6 +2526,7 @@ async function parseProofMarkdown(file, empty) {
2489
2526
  missionEnvelope: readProofList(text, 'Mission patch envelope'),
2490
2527
  rollbackHint: readProofList(text, 'Rollback hint'),
2491
2528
  mediaProof: readProofList(text, 'Media proof'),
2529
+ designCompletion: readProofList(text, 'Design completion'),
2492
2530
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
2493
2531
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
2494
2532
  skipped: readProofList(text, 'Skipped'),
@@ -2575,6 +2613,9 @@ function formatStructuredEvidence(value) {
2575
2613
  if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
2576
2614
  return JSON.stringify(buildMediaGenerationProof(data));
2577
2615
  }
2616
+ if (data.schema === 'yam.ueye-design-completion-gate.v1' || data.completion_claim || data.ready_to_claim_done !== undefined) {
2617
+ return JSON.stringify(buildUeyeDesignCompletionGate(data));
2618
+ }
2578
2619
  return JSON.stringify(data);
2579
2620
  }
2580
2621
  catch {
@@ -2583,7 +2624,7 @@ function formatStructuredEvidence(value) {
2583
2624
  }
2584
2625
  function hasProofEvidence(parsed) {
2585
2626
  const flags = parsed.flags || {};
2586
- 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);
2627
+ 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);
2587
2628
  }
2588
2629
  function printProofList(label, values) {
2589
2630
  console.log(`- ${label}:`);
@@ -68,6 +68,28 @@ export interface UeyeSurfaceContext {
68
68
  local_only: boolean;
69
69
  truth_status: TruthStatus;
70
70
  }
71
+ export interface UeyeDesignGateCheck {
72
+ id: string;
73
+ label: string;
74
+ status: 'pass' | 'fail' | 'warning' | 'not_checked';
75
+ required: boolean;
76
+ next_action: string;
77
+ }
78
+ export interface UeyeDesignCompletionGate {
79
+ schema: 'yam.ueye-design-completion-gate.v1';
80
+ mode: 'fast' | 'strict';
81
+ completion_claim: 'draft' | 'needs-polish' | 'done';
82
+ ready_to_claim_done: boolean;
83
+ design_score: number | null;
84
+ min_design_score: number;
85
+ checks: UeyeDesignGateCheck[];
86
+ p0: string[];
87
+ p1: string[];
88
+ warnings: string[];
89
+ blockers: string[];
90
+ next_action: string;
91
+ truth_status: TruthStatus;
92
+ }
71
93
  export interface RollbackHint {
72
94
  schema: 'yam.rollback-hint.v1';
73
95
  touched_files: string[];
@@ -99,12 +121,16 @@ export interface UeyeRunReport {
99
121
  reference_sources: UeyeVisualProvenance[];
100
122
  implementation_sources: UeyeVisualProvenance[];
101
123
  surface_context: UeyeSurfaceContext;
124
+ design_completion_gate: UeyeDesignCompletionGate;
102
125
  comparison_result: string;
103
126
  design_quality: string;
104
127
  blocked_reason: string;
105
128
  next_action: string;
106
129
  truth_status: TruthStatus;
107
130
  }
131
+ export interface UeyeRunReportInput extends Partial<Omit<UeyeRunReport, 'design_completion_gate'>> {
132
+ design_completion_gate?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>;
133
+ }
108
134
  export interface MediaGenerationProof {
109
135
  schema: 'yam.media-generation-proof.v1';
110
136
  tool_name: string;
@@ -145,6 +171,7 @@ export interface ProofSummary {
145
171
  rollbackHint?: unknown;
146
172
  runtimeBackendEvidence?: unknown;
147
173
  mediaProof?: unknown;
174
+ designCompletion?: unknown;
148
175
  }
149
176
  export interface ProofOptions {
150
177
  requireRealRuntime?: boolean;
@@ -177,6 +204,7 @@ export interface YamCompletionProof {
177
204
  rollbackHint: string[];
178
205
  runtimeBackendEvidence: string[];
179
206
  mediaProof: string[];
207
+ designCompletion: string[];
180
208
  cleanup: unknown;
181
209
  changed: string[];
182
210
  skipped: string[];
@@ -190,10 +218,11 @@ export interface YamCompletionProof {
190
218
  }
191
219
  export declare function buildUeyeVisualProvenance(input?: Partial<UeyeVisualProvenance>): UeyeVisualProvenance;
192
220
  export declare function buildUeyeSurfaceContext(input?: Partial<UeyeSurfaceContext>): UeyeSurfaceContext;
221
+ export declare function buildUeyeDesignCompletionGate(input?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>): UeyeDesignCompletionGate;
193
222
  export declare function buildRollbackHint(input?: Partial<RollbackHint>): RollbackHint;
194
223
  export declare function buildMissionPatchEnvelope(input?: Partial<MissionPatchEnvelope>): MissionPatchEnvelope;
195
224
  export declare function buildRuntimeBackendEvidence(input?: Partial<RuntimeBackendEvidence>): RuntimeBackendEvidence;
196
- export declare function buildUeyeRunReport(input?: Partial<UeyeRunReport>): UeyeRunReport;
225
+ export declare function buildUeyeRunReport(input?: UeyeRunReportInput): UeyeRunReport;
197
226
  export declare function buildMediaGenerationProof(input?: Partial<MediaGenerationProof>): MediaGenerationProof;
198
227
  export declare function isTruthStatus(value?: unknown): value is TruthStatus;
199
228
  export declare function weakestTruth(...statuses: unknown[]): TruthStatus;
@@ -52,6 +52,93 @@ export function buildUeyeSurfaceContext(input = {}) {
52
52
  truth_status: isTruthStatus(input.truth_status) ? input.truth_status : 'partial'
53
53
  };
54
54
  }
55
+ export function buildUeyeDesignCompletionGate(input = {}) {
56
+ const completionClaim = normalizeUeyeCompletionClaim(input.completion_claim);
57
+ const mode = String(input.mode || '').toLowerCase() === 'strict' || completionClaim === 'done' ? 'strict' : 'fast';
58
+ const designScore = numberOrNull(input.design_score);
59
+ const minDesignScore = numberOrNull(input.min_design_score) ?? 8;
60
+ const p0 = asList(input.p0);
61
+ const p1 = asList(input.p1);
62
+ const hasReference = Boolean(input.has_reference);
63
+ const hasImplementation = Boolean(input.has_implementation_screenshot);
64
+ const comparisonResult = String(input.comparison_result || 'not-verified');
65
+ const designQuality = String(input.design_quality || 'not-checked');
66
+ const blockedReason = String(input.blocked_reason || '');
67
+ const strictOrDone = mode === 'strict' || completionClaim === 'done';
68
+ const checks = [
69
+ 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'),
70
+ 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'),
71
+ designGateCheck('implementation_screenshot', 'Real implementation screenshot or screen evidence exists', hasImplementation ? 'pass' : 'fail', strictOrDone, 'capture or provide implementation screenshot before claiming done'),
72
+ 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'),
73
+ 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'),
74
+ designGateCheck('p0_clear', 'No P0 visual blockers remain', p0.length ? 'fail' : 'pass', true, 'fix P0 blockers before any done claim'),
75
+ designGateCheck('p1_clear', 'No P1 major visual issues remain', p1.length ? 'fail' : 'pass', strictOrDone, 'fix or explicitly defer P1 issues before claiming done'),
76
+ 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'),
77
+ 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'),
78
+ 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'),
79
+ 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')
80
+ ];
81
+ if (designScore !== null || strictOrDone) {
82
+ 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`));
83
+ }
84
+ const blockers = [
85
+ ...p0.map((item) => `P0: ${item}`),
86
+ ...(blockedReason ? [blockedReason] : []),
87
+ ...checks
88
+ .filter((check) => check.required && check.status !== 'pass')
89
+ .map((check) => `${check.id}: ${check.next_action}`)
90
+ ];
91
+ const warnings = [
92
+ ...p1.map((item) => `P1: ${item}`),
93
+ ...checks
94
+ .filter((check) => !check.required && ['fail', 'warning', 'not_checked'].includes(check.status))
95
+ .map((check) => `${check.id}: ${check.next_action}`)
96
+ ];
97
+ const ready = completionClaim === 'done' && blockers.length === 0;
98
+ const truth = blockers.some((item) => item.startsWith('P0:')) || blockedReason ? 'blocked'
99
+ : ready ? 'verified'
100
+ : strictOrDone ? 'partial'
101
+ : hasImplementation ? 'partial'
102
+ : 'assumed';
103
+ return {
104
+ schema: 'yam.ueye-design-completion-gate.v1',
105
+ mode,
106
+ completion_claim: completionClaim,
107
+ ready_to_claim_done: ready,
108
+ design_score: designScore,
109
+ min_design_score: minDesignScore,
110
+ checks,
111
+ p0,
112
+ p1,
113
+ warnings,
114
+ blockers,
115
+ 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'),
116
+ truth_status: truth
117
+ };
118
+ }
119
+ function normalizeUeyeCompletionClaim(value = '') {
120
+ const text = String(value || '').toLowerCase();
121
+ if (text === 'done' || text === 'complete' || text === 'completed')
122
+ return 'done';
123
+ if (text === 'needs-polish' || text === 'needs_polish' || text === 'polish')
124
+ return 'needs-polish';
125
+ return 'draft';
126
+ }
127
+ function designGateCheck(id, label, status, required, nextAction) {
128
+ return {
129
+ id,
130
+ label,
131
+ status,
132
+ required,
133
+ next_action: nextAction
134
+ };
135
+ }
136
+ function numberOrNull(value = null) {
137
+ if (value === undefined || value === null || value === '')
138
+ return null;
139
+ const number = Number(value);
140
+ return Number.isFinite(number) ? number : null;
141
+ }
55
142
  export function buildRollbackHint(input = {}) {
56
143
  return {
57
144
  schema: 'yam.rollback-hint.v1',
@@ -104,19 +191,30 @@ export function buildUeyeRunReport(input = {}) {
104
191
  : comparisonResult === 'matched' && hasImplementation ? 'verified'
105
192
  : hasImplementation ? 'partial'
106
193
  : 'partial';
194
+ const gate = buildUeyeDesignCompletionGate({
195
+ ...input.design_completion_gate,
196
+ has_reference: references.length > 0,
197
+ has_implementation_screenshot: hasImplementation,
198
+ comparison_result: comparisonResult,
199
+ design_quality: String(input.design_quality || 'not-checked'),
200
+ blocked_reason: blockedReason
201
+ });
202
+ const shouldApplyDesignGate = gate.mode === 'strict' || gate.completion_claim === 'done' || gate.blockers.length > 0;
203
+ const finalTruth = shouldApplyDesignGate ? weakestTruth(derivedTruth, gate.truth_status) : derivedTruth;
107
204
  return {
108
205
  schema: 'yam.ueye-run-report.v1',
109
206
  reference_sources: references,
110
207
  implementation_sources: implementations,
111
208
  surface_context: buildUeyeSurfaceContext({
112
209
  ...input.surface_context,
113
- truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : derivedTruth
210
+ truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : finalTruth
114
211
  }),
212
+ design_completion_gate: gate,
115
213
  comparison_result: comparisonResult,
116
214
  design_quality: String(input.design_quality || 'not-checked'),
117
215
  blocked_reason: blockedReason,
118
- next_action: String(input.next_action || (derivedTruth === 'verified' ? 'no action required' : 'capture or provide implementation screenshot before claiming verified visual status')),
119
- truth_status: isTruthStatus(input.truth_status) ? input.truth_status : derivedTruth
216
+ next_action: String(input.next_action || gate.blockers[0] || (finalTruth === 'verified' ? 'no action required' : 'capture or provide implementation screenshot before claiming verified visual status')),
217
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : finalTruth
120
218
  };
121
219
  }
122
220
  export function buildMediaGenerationProof(input = {}) {
@@ -229,6 +327,9 @@ export function classifyEvidenceTruth(summary = {}, options = {}) {
229
327
  for (const media of asList(summary.mediaProof)) {
230
328
  rows.push(evidenceRow('evidence', media, readStructuredTruth(media, 'partial')));
231
329
  }
330
+ for (const designCompletion of asList(summary.designCompletion)) {
331
+ rows.push(evidenceRow('visual', designCompletion, readStructuredTruth(designCompletion, 'partial')));
332
+ }
232
333
  for (const runtime of asList(summary.runtime)) {
233
334
  rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
234
335
  }
@@ -262,6 +363,15 @@ export function applyProofTruthCaps(summary = {}, options = {}) {
262
363
  cappedTruth = weakestTruth(cappedTruth, 'partial');
263
364
  unverified.push('real_visual_evidence_missing');
264
365
  }
366
+ const designCompletionTruth = designCompletionCap(summary.designCompletion);
367
+ if (designCompletionTruth) {
368
+ cappedTruth = weakestTruth(cappedTruth, designCompletionTruth);
369
+ if (designCompletionTruth === 'blocked')
370
+ blockers.push('ueye_design_completion_blocked');
371
+ if (!['verified', 'proven'].includes(designCompletionTruth)) {
372
+ unverified.push('ueye_design_completion_not_verified');
373
+ }
374
+ }
265
375
  if (requested !== cappedTruth) {
266
376
  caps.push(`requested ${requested} capped to ${cappedTruth} by evidence`);
267
377
  }
@@ -319,6 +429,7 @@ export function buildYamCompletionProof(summary = {}, options = {}) {
319
429
  rollbackHint: asList(summary.rollbackHint),
320
430
  runtimeBackendEvidence: asList(summary.runtimeBackendEvidence),
321
431
  mediaProof: asList(summary.mediaProof),
432
+ designCompletion: asList(summary.designCompletion),
322
433
  cleanup: summary.cleanup || '',
323
434
  changed: asList(summary.changed),
324
435
  skipped: asList(summary.skipped),
@@ -394,6 +505,12 @@ function readStructuredTruth(value = '', fallback = 'partial') {
394
505
  return fallback;
395
506
  }
396
507
  }
508
+ function designCompletionCap(value = '') {
509
+ const items = asList(value);
510
+ if (!items.length)
511
+ return null;
512
+ return items.reduce((weakest, item) => weakestTruth(weakest, readStructuredTruth(item, 'partial')), 'proven');
513
+ }
397
514
  function strongestSupportedTruth(rows = []) {
398
515
  if (rows.some((row) => row.truth === 'blocked'))
399
516
  return 'blocked';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam-flow",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Progressive proof-first agent flow: start fast, deepen deliberately, stay honest by design.",
5
5
  "type": "module",
6
6
  "author": "0kim0bos",
@@ -16,7 +16,7 @@
16
16
  "node": ">=18"
17
17
  },
18
18
  "bin": {
19
- "yam": "./dist/bin/yam.js"
19
+ "yam": "dist/bin/yam.js"
20
20
  },
21
21
  "types": "dist/lib/trust-kernel.d.ts",
22
22
  "exports": {
@@ -11,6 +11,7 @@ This is the small trust kernel that keeps `yam` honest without turning every tas
11
11
  - fake versus real evidence distinction
12
12
  - runtime truth matrix idea
13
13
  - visual evidence caps
14
+ - Ueye design completion gate
14
15
  - DB/Supabase destructive-signal detection
15
16
 
16
17
  ## Rejected
@@ -38,6 +39,7 @@ The module provides:
38
39
  - fake/real policy
39
40
  - runtime truth matrix
40
41
  - completion proof object creation
42
+ - Ueye design completion gate creation
41
43
 
42
44
  ## Policy
43
45
 
@@ -51,5 +53,6 @@ For example:
51
53
  - Mock/fixture evidence cannot become real runtime `proven`.
52
54
  - Required runtime evidence that is missing becomes `real_required_missing`.
53
55
  - Cleanup claims require cleanup evidence.
56
+ - Ueye `done` claims require a passing design completion gate or stay capped at `partial` or `blocked`.
54
57
 
55
58
  This keeps the trust layer progressive and route-scoped.