yam-flow 0.1.5 → 0.1.7

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.
@@ -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[];
@@ -113,6 +137,14 @@ export interface RuntimeBackendEvidence {
113
137
  evidence_id: string;
114
138
  command: string;
115
139
  cleanup_checked: boolean;
140
+ started_at: string;
141
+ stopped_at: string;
142
+ exit_code: number | null;
143
+ pid: number | null;
144
+ port: number | null;
145
+ cleanup_method: string;
146
+ cleanup_observed: boolean;
147
+ left_running_intentionally: boolean;
116
148
  note: string;
117
149
  truth_status: TruthStatus;
118
150
  }
@@ -122,6 +154,7 @@ export interface UeyeRunReport {
122
154
  reference_sources: UeyeVisualProvenance[];
123
155
  implementation_sources: UeyeVisualProvenance[];
124
156
  surface_context: UeyeSurfaceContext;
157
+ design_completion_gate: UeyeDesignCompletionGate;
125
158
  comparison_result: string;
126
159
  design_quality: string;
127
160
  blocked_reason: string;
@@ -129,6 +162,10 @@ export interface UeyeRunReport {
129
162
  truth_status: TruthStatus;
130
163
  }
131
164
 
165
+ export interface UeyeRunReportInput extends Partial<Omit<UeyeRunReport, 'design_completion_gate'>> {
166
+ design_completion_gate?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>;
167
+ }
168
+
132
169
  export interface MediaGenerationProof {
133
170
  schema: 'yam.media-generation-proof.v1';
134
171
  tool_name: string;
@@ -171,6 +208,7 @@ export interface ProofSummary {
171
208
  rollbackHint?: unknown;
172
209
  runtimeBackendEvidence?: unknown;
173
210
  mediaProof?: unknown;
211
+ designCompletion?: unknown;
174
212
  }
175
213
 
176
214
  export interface ProofOptions {
@@ -206,6 +244,7 @@ export interface YamCompletionProof {
206
244
  rollbackHint: string[];
207
245
  runtimeBackendEvidence: string[];
208
246
  mediaProof: string[];
247
+ designCompletion: string[];
209
248
  cleanup: unknown;
210
249
  changed: string[];
211
250
  skipped: string[];
@@ -262,6 +301,94 @@ export function buildUeyeSurfaceContext(input: Partial<UeyeSurfaceContext> = {})
262
301
  };
263
302
  }
264
303
 
304
+ export function buildUeyeDesignCompletionGate(input: Partial<UeyeDesignCompletionGate> & Record<string, unknown> = {}): UeyeDesignCompletionGate {
305
+ const completionClaim = normalizeUeyeCompletionClaim(input.completion_claim);
306
+ const mode = String(input.mode || '').toLowerCase() === 'strict' || completionClaim === 'done' ? 'strict' : 'fast';
307
+ const designScore = numberOrNull(input.design_score);
308
+ const minDesignScore = numberOrNull(input.min_design_score) ?? 8;
309
+ const p0 = asList(input.p0);
310
+ const p1 = asList(input.p1);
311
+ const hasReference = Boolean(input.has_reference);
312
+ const hasImplementation = Boolean(input.has_implementation_screenshot);
313
+ const comparisonResult = String(input.comparison_result || 'not-verified');
314
+ const designQuality = String(input.design_quality || 'not-checked');
315
+ const blockedReason = String(input.blocked_reason || '');
316
+ const strictOrDone = mode === 'strict' || completionClaim === 'done';
317
+ const checks: UeyeDesignGateCheck[] = [
318
+ 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'),
319
+ 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'),
320
+ designGateCheck('implementation_screenshot', 'Real implementation screenshot or screen evidence exists', hasImplementation ? 'pass' : 'fail', strictOrDone, 'capture or provide implementation screenshot before claiming done'),
321
+ 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'),
322
+ 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'),
323
+ designGateCheck('p0_clear', 'No P0 visual blockers remain', p0.length ? 'fail' : 'pass', true, 'fix P0 blockers before any done claim'),
324
+ designGateCheck('p1_clear', 'No P1 major visual issues remain', p1.length ? 'fail' : 'pass', strictOrDone, 'fix or explicitly defer P1 issues before claiming done'),
325
+ 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'),
326
+ 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'),
327
+ 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'),
328
+ 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')
329
+ ];
330
+ if (designScore !== null || strictOrDone) {
331
+ 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`));
332
+ }
333
+ const blockers = [
334
+ ...p0.map((item) => `P0: ${item}`),
335
+ ...(blockedReason ? [blockedReason] : []),
336
+ ...checks
337
+ .filter((check) => check.required && check.status !== 'pass')
338
+ .map((check) => `${check.id}: ${check.next_action}`)
339
+ ];
340
+ const warnings = [
341
+ ...p1.map((item) => `P1: ${item}`),
342
+ ...checks
343
+ .filter((check) => !check.required && ['fail', 'warning', 'not_checked'].includes(check.status))
344
+ .map((check) => `${check.id}: ${check.next_action}`)
345
+ ];
346
+ const ready = completionClaim === 'done' && blockers.length === 0;
347
+ const truth: TruthStatus = blockers.some((item) => item.startsWith('P0:')) || blockedReason ? 'blocked'
348
+ : ready ? 'verified'
349
+ : strictOrDone ? 'partial'
350
+ : hasImplementation ? 'partial'
351
+ : 'assumed';
352
+ return {
353
+ schema: 'yam.ueye-design-completion-gate.v1',
354
+ mode,
355
+ completion_claim: completionClaim,
356
+ ready_to_claim_done: ready,
357
+ design_score: designScore,
358
+ min_design_score: minDesignScore,
359
+ checks,
360
+ p0,
361
+ p1,
362
+ warnings,
363
+ blockers,
364
+ 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'),
365
+ truth_status: truth
366
+ };
367
+ }
368
+
369
+ function normalizeUeyeCompletionClaim(value: unknown = ''): UeyeDesignCompletionGate['completion_claim'] {
370
+ const text = String(value || '').toLowerCase();
371
+ if (text === 'done' || text === 'complete' || text === 'completed') return 'done';
372
+ if (text === 'needs-polish' || text === 'needs_polish' || text === 'polish') return 'needs-polish';
373
+ return 'draft';
374
+ }
375
+
376
+ function designGateCheck(id: string, label: string, status: UeyeDesignGateCheck['status'], required: boolean, nextAction: string): UeyeDesignGateCheck {
377
+ return {
378
+ id,
379
+ label,
380
+ status,
381
+ required,
382
+ next_action: nextAction
383
+ };
384
+ }
385
+
386
+ function numberOrNull(value: unknown = null): number | null {
387
+ if (value === undefined || value === null || value === '') return null;
388
+ const number = Number(value);
389
+ return Number.isFinite(number) ? number : null;
390
+ }
391
+
265
392
  export function buildRollbackHint(input: Partial<RollbackHint> = {}): RollbackHint {
266
393
  return {
267
394
  schema: 'yam.rollback-hint.v1',
@@ -290,6 +417,13 @@ export function buildMissionPatchEnvelope(input: Partial<MissionPatchEnvelope> =
290
417
  export function buildRuntimeBackendEvidence(input: Partial<RuntimeBackendEvidence> = {}): RuntimeBackendEvidence {
291
418
  const backend = normalizeRuntimeBackend(input.backend);
292
419
  const claim = normalizeRuntimeClaim(input.claim);
420
+ const cleanupObserved = Boolean(input.cleanup_observed);
421
+ const leftRunningIntentionally = Boolean(input.left_running_intentionally);
422
+ const exitCode = numberOrNull(input.exit_code);
423
+ const pid = numberOrNull(input.pid);
424
+ const port = numberOrNull(input.port);
425
+ const stoppedAt = String(input.stopped_at || '');
426
+ const cleanupMethod = String(input.cleanup_method || '');
293
427
  return {
294
428
  schema: 'yam.runtime-backend-evidence.v1',
295
429
  backend,
@@ -297,12 +431,28 @@ export function buildRuntimeBackendEvidence(input: Partial<RuntimeBackendEvidenc
297
431
  evidence_id: String(input.evidence_id || ''),
298
432
  command: String(input.command || ''),
299
433
  cleanup_checked: Boolean(input.cleanup_checked),
434
+ started_at: String(input.started_at || ''),
435
+ stopped_at: stoppedAt,
436
+ exit_code: exitCode,
437
+ pid,
438
+ port,
439
+ cleanup_method: cleanupMethod,
440
+ cleanup_observed: cleanupObserved,
441
+ left_running_intentionally: leftRunningIntentionally,
300
442
  note: String(input.note || ''),
301
- truth_status: isTruthStatus(input.truth_status) ? input.truth_status : runtimeBackendTruth(backend, claim, Boolean(input.cleanup_checked))
443
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : runtimeBackendTruth(backend, claim, {
444
+ cleanupChecked: Boolean(input.cleanup_checked),
445
+ cleanupObserved,
446
+ leftRunningIntentionally,
447
+ stoppedAt,
448
+ exitCode,
449
+ pid,
450
+ cleanupMethod
451
+ })
302
452
  };
303
453
  }
304
454
 
305
- export function buildUeyeRunReport(input: Partial<UeyeRunReport> = {}): UeyeRunReport {
455
+ export function buildUeyeRunReport(input: UeyeRunReportInput = {}): UeyeRunReport {
306
456
  const references = Array.isArray(input.reference_sources)
307
457
  ? input.reference_sources.map((item) => buildUeyeVisualProvenance(item))
308
458
  : [];
@@ -317,19 +467,30 @@ export function buildUeyeRunReport(input: Partial<UeyeRunReport> = {}): UeyeRunR
317
467
  : comparisonResult === 'matched' && hasImplementation ? 'verified'
318
468
  : hasImplementation ? 'partial'
319
469
  : 'partial';
470
+ const gate = buildUeyeDesignCompletionGate({
471
+ ...input.design_completion_gate,
472
+ has_reference: references.length > 0,
473
+ has_implementation_screenshot: hasImplementation,
474
+ comparison_result: comparisonResult,
475
+ design_quality: String(input.design_quality || 'not-checked'),
476
+ blocked_reason: blockedReason
477
+ });
478
+ const shouldApplyDesignGate = gate.mode === 'strict' || gate.completion_claim === 'done' || gate.blockers.length > 0;
479
+ const finalTruth = shouldApplyDesignGate ? weakestTruth(derivedTruth, gate.truth_status) : derivedTruth;
320
480
  return {
321
481
  schema: 'yam.ueye-run-report.v1',
322
482
  reference_sources: references,
323
483
  implementation_sources: implementations,
324
484
  surface_context: buildUeyeSurfaceContext({
325
485
  ...input.surface_context,
326
- truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : derivedTruth
486
+ truth_status: isTruthStatus(input.surface_context?.truth_status) ? input.surface_context?.truth_status : finalTruth
327
487
  }),
488
+ design_completion_gate: gate,
328
489
  comparison_result: comparisonResult,
329
490
  design_quality: String(input.design_quality || 'not-checked'),
330
491
  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
492
+ next_action: String(input.next_action || gate.blockers[0] || (finalTruth === 'verified' ? 'no action required' : 'capture or provide implementation screenshot before claiming verified visual status')),
493
+ truth_status: isTruthStatus(input.truth_status) ? input.truth_status : finalTruth
333
494
  };
334
495
  }
335
496
 
@@ -444,6 +605,9 @@ export function classifyEvidenceTruth(summary: ProofSummary = {}, options: Proof
444
605
  for (const media of asList(summary.mediaProof)) {
445
606
  rows.push(evidenceRow('evidence', media, readStructuredTruth(media, 'partial')));
446
607
  }
608
+ for (const designCompletion of asList(summary.designCompletion)) {
609
+ rows.push(evidenceRow('visual', designCompletion, readStructuredTruth(designCompletion, 'partial')));
610
+ }
447
611
  for (const runtime of asList(summary.runtime)) {
448
612
  rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
449
613
  }
@@ -473,6 +637,14 @@ export function applyProofTruthCaps(summary: ProofSummary = {}, options: ProofOp
473
637
  cappedTruth = weakestTruth(cappedTruth, 'partial');
474
638
  unverified.push('real_visual_evidence_missing');
475
639
  }
640
+ const designCompletionTruth = designCompletionCap(summary.designCompletion);
641
+ if (designCompletionTruth) {
642
+ cappedTruth = weakestTruth(cappedTruth, designCompletionTruth);
643
+ if (designCompletionTruth === 'blocked') blockers.push('ueye_design_completion_blocked');
644
+ if (!['verified', 'proven'].includes(designCompletionTruth)) {
645
+ unverified.push('ueye_design_completion_not_verified');
646
+ }
647
+ }
476
648
  if (requested !== cappedTruth) {
477
649
  caps.push(`requested ${requested} capped to ${cappedTruth} by evidence`);
478
650
  }
@@ -532,6 +704,7 @@ export function buildYamCompletionProof(summary: ProofSummary = {}, options: Pro
532
704
  rollbackHint: asList(summary.rollbackHint),
533
705
  runtimeBackendEvidence: asList(summary.runtimeBackendEvidence),
534
706
  mediaProof: asList(summary.mediaProof),
707
+ designCompletion: asList(summary.designCompletion),
535
708
  cleanup: summary.cleanup || '',
536
709
  changed: asList(summary.changed),
537
710
  skipped: asList(summary.skipped),
@@ -601,6 +774,12 @@ function readStructuredTruth(value: unknown = '', fallback: TruthStatus = 'parti
601
774
  }
602
775
  }
603
776
 
777
+ function designCompletionCap(value: unknown = ''): TruthStatus | null {
778
+ const items = asList(value);
779
+ if (!items.length) return null;
780
+ return items.reduce<TruthStatus>((weakest, item) => weakestTruth(weakest, readStructuredTruth(item, 'partial')), 'proven');
781
+ }
782
+
604
783
  function strongestSupportedTruth(rows: EvidenceRow[] = []): TruthStatus {
605
784
  if (rows.some((row) => row.truth === 'blocked')) return 'blocked';
606
785
  if (rows.some((row) => row.truth === 'real_required_missing')) return 'real_required_missing';
@@ -686,11 +865,24 @@ function normalizeRuntimeClaim(value: unknown = ''): RuntimeClaim {
686
865
  return 'unknown';
687
866
  }
688
867
 
689
- function runtimeBackendTruth(backend: RuntimeBackend, claim: RuntimeClaim, cleanupChecked = false): TruthStatus {
868
+ function runtimeBackendTruth(backend: RuntimeBackend, claim: RuntimeClaim, details: {
869
+ cleanupChecked?: boolean;
870
+ cleanupObserved?: boolean;
871
+ leftRunningIntentionally?: boolean;
872
+ stoppedAt?: string;
873
+ exitCode?: number | null;
874
+ pid?: number | null;
875
+ cleanupMethod?: string;
876
+ } = {}): TruthStatus {
690
877
  if (claim === 'blocked') return 'blocked';
691
878
  if (backend === 'none' || claim === 'not_started') return 'skipped';
692
879
  if (backend === 'unknown' || claim === 'unknown') return 'partial';
693
- if (claim === 'cleanup_verified' || cleanupChecked) return 'proven';
880
+ const hasClosureEvidence = Boolean(details.stoppedAt || details.exitCode !== null || details.cleanupMethod);
881
+ if (claim === 'cleanup_verified' || details.cleanupChecked) {
882
+ if (details.cleanupObserved && hasClosureEvidence) return 'proven';
883
+ if (details.leftRunningIntentionally && (details.pid !== null || details.cleanupMethod)) return 'partial';
884
+ return 'real_required_missing';
885
+ }
694
886
  if (claim === 'observed' || claim === 'stopped' || claim === 'started') return 'verified';
695
887
  return 'partial';
696
888
  }
@@ -112,6 +112,36 @@ Use `pass`, `needs-polish`, or `fails` for each relevant design dimension before
112
112
  | Accessibility | needs-polish | |
113
113
  | Brand/mood fit | needs-polish | |
114
114
 
115
+ ## Design Completion Gate
116
+
117
+ | Field | Value |
118
+ | --- | --- |
119
+ | completion_claim | draft / needs-polish / done |
120
+ | mode | fast / strict |
121
+ | ready_to_claim_done | true / false |
122
+ | design_score | |
123
+ | min_design_score | 8 |
124
+ | direction_locked | true / false |
125
+ | reference_read | true / false / not-applicable |
126
+ | implementation_screenshot | pass / fail / not_checked |
127
+ | reference_comparison | matched / similar / different / not-verified / not-applicable |
128
+ | design_quality | pass / needs-polish / fails / not-checked |
129
+ | p0_clear | pass / fail |
130
+ | p1_clear | pass / fail / deferred |
131
+ | cta_checked | true / false |
132
+ | states_checked | true / false |
133
+ | mobile_checked | true / false |
134
+ | contrast_accessibility_checked | true / false |
135
+ | truth_status | verified / partial / skipped / blocked / assumed |
136
+
137
+ ### Design Gate Blockers
138
+
139
+ - None.
140
+
141
+ ### Design Gate Warnings
142
+
143
+ - None.
144
+
115
145
  ### P0 Blockers
116
146
 
117
147
  - None.
@@ -152,6 +182,18 @@ Use `pass`, `needs-polish`, or `fails` for each relevant design dimension before
152
182
  "preserved_url": "",
153
183
  "truth_status": "partial"
154
184
  },
185
+ "design_completion_gate": {
186
+ "completion_claim": "needs-polish",
187
+ "mode": "fast",
188
+ "ready_to_claim_done": false,
189
+ "design_score": null,
190
+ "min_design_score": 8,
191
+ "p0": [],
192
+ "p1": [],
193
+ "blockers": [],
194
+ "warnings": [],
195
+ "truth_status": "partial"
196
+ },
155
197
  "viewport": "",
156
198
  "state": "",
157
199
  "comparison_result": "not-verified",
@@ -123,6 +123,36 @@ Status values: matched / similar / different / not-verified / not-applicable.
123
123
 
124
124
  Result values: pass / needs-polish / fails.
125
125
 
126
+ ## Design Completion Gate
127
+
128
+ | Field | Value |
129
+ | --- | --- |
130
+ | completion_claim | draft / needs-polish / done |
131
+ | mode | fast / strict |
132
+ | ready_to_claim_done | true / false |
133
+ | design_score | |
134
+ | min_design_score | 8 |
135
+ | direction_locked | true / false |
136
+ | reference_read | true / false / not-applicable |
137
+ | implementation_screenshot | pass / fail / not_checked |
138
+ | reference_comparison | matched / similar / different / not-verified / not-applicable |
139
+ | design_quality | pass / needs-polish / fails / not-checked |
140
+ | p0_clear | pass / fail |
141
+ | p1_clear | pass / fail / deferred |
142
+ | cta_checked | true / false |
143
+ | states_checked | true / false |
144
+ | mobile_checked | true / false |
145
+ | contrast_accessibility_checked | true / false |
146
+ | truth_status | verified / partial / skipped / blocked / assumed |
147
+
148
+ ### Design Gate Blockers
149
+
150
+ - None.
151
+
152
+ ### Design Gate Warnings
153
+
154
+ - None.
155
+
126
156
  ## P0-P3 Issues
127
157
 
128
158
  ### P0 Blockers
@@ -184,6 +214,18 @@ Result values: pass / needs-polish / fails.
184
214
  "preserved_url": "",
185
215
  "truth_status": "partial"
186
216
  },
217
+ "design_completion_gate": {
218
+ "completion_claim": "needs-polish",
219
+ "mode": "fast",
220
+ "ready_to_claim_done": false,
221
+ "design_score": null,
222
+ "min_design_score": 8,
223
+ "p0": [],
224
+ "p1": [],
225
+ "blockers": [],
226
+ "warnings": [],
227
+ "truth_status": "partial"
228
+ },
187
229
  "viewport": "",
188
230
  "state": "",
189
231
  "comparison_result": "not-verified",
@@ -46,6 +46,20 @@
46
46
  - Risky changes:
47
47
  - Public/release changes: scan for external tool names, comparison wording, and attribution wording; remove those hits unless the user explicitly approves keeping them.
48
48
 
49
+ ## Security Policy
50
+
51
+ - Sensitive surfaces:
52
+ - Secrets/env handling:
53
+ - Auth/payment/DB policy:
54
+ - Deployment/release policy:
55
+ - Package/dependency policy:
56
+
57
+ ## Report Policy
58
+
59
+ - Explain changed code in plain language:
60
+ - Always include remaining tasks or useful improvements:
61
+ - Preserve source boundaries for research/proof notes:
62
+
49
63
  ## Known Risks
50
64
 
51
65
  - Auth/payment/DB/security: