yam-flow 2.2.0 → 2.3.0

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/dist/bin/yam.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process';
3
- import crypto from 'node:crypto';
4
3
  import fs from 'node:fs';
5
4
  import fsp from 'node:fs/promises';
6
5
  import { createRequire } from 'node:module';
@@ -8,7 +7,8 @@ import os from 'node:os';
8
7
  import path from 'node:path';
9
8
  import process from 'node:process';
10
9
  import { fileURLToPath } from 'node:url';
11
- import { TRUTH_STATUSES, buildLoopReport, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildStudyNote, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
10
+ import { TRUTH_STATUSES, buildLoopReport, buildMediaGenerationProof, buildMissionCompletionGate, buildMissionPatchEnvelope, buildMissionSubagentReceipt, buildRollbackHint, buildRuntimeBackendEvidence, buildStudyNote, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
11
+ import { archiveUeyeRevision, inspectImageFile, upsertUeyeAsset, verifyUeyeAssetManifest, verifyUeyeRevisionHistory } from '../lib/ueye-artifacts.js';
12
12
  function errorMessage(error) {
13
13
  return error instanceof Error ? error.message : String(error);
14
14
  }
@@ -129,9 +129,13 @@ Usage:
129
129
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
130
130
  yam ueye preflight [dir] [--json]
131
131
  yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
132
+ yam ueye asset <add|verify> [options]
133
+ yam ueye revision <archive|verify> [options]
132
134
  yam media proof [--requested] [--attempted] [--output file] [--json]
133
135
  yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
134
136
  yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
137
+ yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
138
+ yam mission gate [--expected-thread id] [--receipt file] [--json]
135
139
  yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
136
140
  yam release report [--json]
137
141
  yam safety [text...]
@@ -343,7 +347,7 @@ async function verify({ quiet = false } = {}) {
343
347
  if (!await exists(path.join(ROOT, 'templates', template)))
344
348
  issues.push(`missing template: ${template}`);
345
349
  }
346
- for (const module of ['trust-kernel.js']) {
350
+ for (const module of ['trust-kernel.js', 'ueye-artifacts.js']) {
347
351
  if (!await exists(path.join(ROOT, 'dist', 'lib', module)))
348
352
  issues.push(`missing dist lib module: ${module}`);
349
353
  }
@@ -2251,6 +2255,10 @@ async function ueye(args = []) {
2251
2255
  return ueyePreflight(args.slice(1));
2252
2256
  if (subcommand === 'report')
2253
2257
  return ueyeReport(args.slice(1));
2258
+ if (subcommand === 'asset')
2259
+ return ueyeAsset(args.slice(1));
2260
+ if (subcommand === 'revision')
2261
+ return ueyeRevision(args.slice(1));
2254
2262
  console.error(`unknown ueye command: ${subcommand}`);
2255
2263
  return ueyeUsage();
2256
2264
  }
@@ -2263,14 +2271,97 @@ Usage:
2263
2271
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
2264
2272
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
2265
2273
  yam ueye preflight [dir] [--json]
2266
- yam ueye report [--reference ref.png] [--actual screenshot.png] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--acceptance-criterion text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--residual-risk text] [--stop-condition text] [--resume-hint text] [--deep-visual-check text] [--design-system-evidence text] [--implementation-evidence text] [--state-check default:pass] [--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]
2274
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--asset-manifest file] [--revision-manifest file] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--acceptance-criterion text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--residual-risk text] [--stop-condition text] [--resume-hint text] [--deep-visual-check text] [--design-system-evidence text] [--implementation-evidence text] [--state-check default:pass] [--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]
2275
+ yam ueye asset add [--manifest file] --id id --file image [--source-url URL] [--source-page-url URL] [--license-note text] [--operator-provided] [--do-not-replace] [--allowed-for-edit] [--json]
2276
+ yam ueye asset verify [--manifest file] [--json]
2277
+ yam ueye revision archive --file image --round n [--artifact-id id] [--root dir] [--manifest file] [--json]
2278
+ yam ueye revision verify [--root dir] [--manifest file] [--json]
2267
2279
 
2268
2280
  Notes:
2269
2281
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
2270
2282
  compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
2271
2283
  report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
2284
+ asset records local reference provenance and protection flags; it never downloads remote content.
2285
+ revision copies the current local artifact into a non-overwriting round archive before the live file is edited.
2272
2286
  `);
2273
2287
  }
2288
+ async function ueyeAsset(args = []) {
2289
+ const subcommand = args[0] || 'help';
2290
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
2291
+ return ueyeUsage();
2292
+ const flags = parseSimpleFlags(args.slice(1), new Set(['manifest', 'id', 'file', 'source-url', 'source-page-url', 'retrieved-at', 'license-note', 'operator-provided', 'do-not-replace', 'allowed-for-edit', 'replace', 'json']));
2293
+ const manifest = path.resolve(expandHome(flags.manifest || path.join(process.cwd(), '.yam', 'ueye', 'assets.json')));
2294
+ try {
2295
+ if (subcommand === 'add') {
2296
+ if (!flags.id || !flags.file)
2297
+ throw new Error('asset add requires --id and --file');
2298
+ const result = await upsertUeyeAsset({
2299
+ manifest_path: manifest,
2300
+ id: String(flags.id),
2301
+ file: path.resolve(expandHome(flags.file)),
2302
+ source_url: flags.source_url,
2303
+ source_page_url: flags.source_page_url,
2304
+ retrieved_at: flags.retrieved_at,
2305
+ license_note: flags.license_note,
2306
+ operator_provided: flags.operator_provided ? true : undefined,
2307
+ do_not_replace: flags.do_not_replace ? true : undefined,
2308
+ allowed_for_edit: flags.allowed_for_edit ? true : undefined,
2309
+ replace: Boolean(flags.replace)
2310
+ });
2311
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset');
2312
+ return;
2313
+ }
2314
+ if (subcommand === 'verify') {
2315
+ const result = await verifyUeyeAssetManifest(manifest);
2316
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset verification');
2317
+ if (result.truth_status === 'blocked')
2318
+ process.exitCode = 1;
2319
+ return;
2320
+ }
2321
+ throw new Error(`unknown ueye asset command: ${subcommand}`);
2322
+ }
2323
+ catch (error) {
2324
+ const result = { schema: 'yam.ueye-asset-error.v1', status: 'blocked', reason: errorMessage(error), truth_status: 'blocked' };
2325
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset');
2326
+ process.exitCode = 1;
2327
+ }
2328
+ }
2329
+ async function ueyeRevision(args = []) {
2330
+ const subcommand = args[0] || 'help';
2331
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
2332
+ return ueyeUsage();
2333
+ const flags = parseSimpleFlags(args.slice(1), new Set(['file', 'round', 'artifact-id', 'root', 'manifest', 'json']));
2334
+ const root = path.resolve(expandHome(flags.root || path.join(process.cwd(), '.yam', 'ueye', 'revisions')));
2335
+ const manifest = path.resolve(expandHome(flags.manifest || path.join(root, 'manifest.json')));
2336
+ try {
2337
+ if (subcommand === 'archive') {
2338
+ if (!flags.file || !flags.round)
2339
+ throw new Error('revision archive requires --file and --round');
2340
+ const result = await archiveUeyeRevision({
2341
+ file: path.resolve(expandHome(flags.file)),
2342
+ root,
2343
+ manifest_path: manifest,
2344
+ round: Number(flags.round),
2345
+ artifact_id: flags.artifact_id
2346
+ });
2347
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision');
2348
+ return;
2349
+ }
2350
+ if (subcommand === 'verify') {
2351
+ const result = await verifyUeyeRevisionHistory(manifest);
2352
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision verification');
2353
+ if (result.truth_status === 'blocked')
2354
+ process.exitCode = 1;
2355
+ return;
2356
+ }
2357
+ throw new Error(`unknown ueye revision command: ${subcommand}`);
2358
+ }
2359
+ catch (error) {
2360
+ const result = { schema: 'yam.ueye-revision-error.v1', status: 'blocked', reason: errorMessage(error), truth_status: 'blocked' };
2361
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision');
2362
+ process.exitCode = 1;
2363
+ }
2364
+ }
2274
2365
  async function ueyePreflight(args = []) {
2275
2366
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
2276
2367
  return ueyeUsage();
@@ -2504,7 +2595,7 @@ async function ueyeCompare(args = []) {
2504
2595
  async function ueyeReport(args = []) {
2505
2596
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
2506
2597
  return ueyeUsage();
2507
- 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', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'acceptance-criterion', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'residual-risk', 'stop-condition', 'resume-hint', 'deep-visual-check', 'design-system-evidence', 'implementation-evidence', 'state-check', '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']));
2598
+ 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', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'acceptance-criterion', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'residual-risk', 'stop-condition', 'resume-hint', 'deep-visual-check', 'design-system-evidence', 'implementation-evidence', 'state-check', 'asset-manifest', 'revision-manifest', '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']));
2508
2599
  const reference = String(flags.reference || flags.ref || '');
2509
2600
  const actual = String(flags.actual || flags.screenshot || '');
2510
2601
  const referenceSources = [];
@@ -2584,6 +2675,13 @@ async function ueyeReport(args = []) {
2584
2675
  catch (error) {
2585
2676
  blockedReason = errorMessage(error);
2586
2677
  }
2678
+ const assetManifest = await optionalUeyeAssetVerification(flags.asset_manifest);
2679
+ const revisionHistory = await optionalUeyeRevisionVerification(flags.revision_manifest);
2680
+ const doneClaim = ['done', 'complete', 'completed'].includes(String(flags.completion_claim || flags.completion_status || '').toLowerCase());
2681
+ if (doneClaim && assetManifest?.truth_status === 'blocked')
2682
+ blockedReason = blockedReason || `asset manifest blocked: ${assetManifest.next_action || 'verification failed'}`;
2683
+ if (doneClaim && revisionHistory?.truth_status === 'blocked')
2684
+ blockedReason = blockedReason || `revision history blocked: ${revisionHistory.next_action || 'verification failed'}`;
2587
2685
  const report = buildUeyeRunReport({
2588
2686
  reference_sources: referenceSources,
2589
2687
  implementation_sources: implementationSources,
@@ -2666,12 +2764,34 @@ async function ueyeReport(args = []) {
2666
2764
  capture_backend: String(flags.capture_backend || 'not-recorded'),
2667
2765
  compare_backend: String(flags.compare_backend || 'local-file-hash'),
2668
2766
  continuity: comparisonReport.continuity,
2669
- comparison_report: comparisonReport
2767
+ comparison_report: comparisonReport,
2768
+ asset_manifest: assetManifest,
2769
+ revision_history: revisionHistory
2670
2770
  };
2671
2771
  printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
2672
2772
  if (report.truth_status === 'blocked')
2673
2773
  process.exitCode = 1;
2674
2774
  }
2775
+ async function optionalUeyeAssetVerification(file = '') {
2776
+ if (!file)
2777
+ return null;
2778
+ try {
2779
+ return await verifyUeyeAssetManifest(path.resolve(expandHome(file)));
2780
+ }
2781
+ catch (error) {
2782
+ return { schema: 'yam.ueye-asset-verification.v1', truth_status: 'blocked', reason: errorMessage(error), next_action: 'repair or recreate the asset manifest before a done claim' };
2783
+ }
2784
+ }
2785
+ async function optionalUeyeRevisionVerification(file = '') {
2786
+ if (!file)
2787
+ return null;
2788
+ try {
2789
+ return await verifyUeyeRevisionHistory(path.resolve(expandHome(file)));
2790
+ }
2791
+ catch (error) {
2792
+ return { schema: 'yam.ueye-revision-verification.v1', truth_status: 'blocked', reason: errorMessage(error), next_action: 'repair or recreate the revision manifest before a done claim' };
2793
+ }
2794
+ }
2675
2795
  function buildUeyeDesignBrief(flags = {}) {
2676
2796
  const dimensions = arrayFlag(flags.brief_dimension);
2677
2797
  const constraints = arrayFlag(flags.constraint);
@@ -2925,6 +3045,10 @@ async function mission(args = []) {
2925
3045
  return missionUsage();
2926
3046
  if (subcommand === 'queue' || subcommand === 'patch')
2927
3047
  return missionQueue(args.slice(1));
3048
+ if (subcommand === 'receipt')
3049
+ return missionReceipt(args.slice(1));
3050
+ if (subcommand === 'gate' || subcommand === 'close')
3051
+ return missionGate(args.slice(1));
2928
3052
  console.error(`unknown mission command: ${subcommand}`);
2929
3053
  return missionUsage();
2930
3054
  }
@@ -2933,11 +3057,80 @@ function missionUsage() {
2933
3057
 
2934
3058
  Usage:
2935
3059
  yam mission queue [--lane-id id] [--agent-id id] [--status pending|applied|verified|blocked|reverted] [--scope text] [--changed file] [--depends-on lane] [--generated file] [--verification-hint text] [--rollback-hint text] [--before-check text] [--out file] [--truth status] [--json]
3060
+ yam mission receipt --thread-id id --role implementer|reviewer|ux-verifier|doctor --lifecycle pending|running|stopped|failed|cancelled --outcome passed|failed|blocked|ambiguous [--access-mode read-only|write] [--scope text] [--changed file] [--evidence text] [--remaining-risk text] [--out file] [--json]
3061
+ yam mission gate --expected-thread id --receipt file [--expected-thread id --receipt file] [--out file] [--json]
2936
3062
 
2937
3063
  Notes:
2938
3064
  Produces a patch queue item for mission handoff/review. It does not run workers; persistence is opt-in with --out.
3065
+ Reviewer and doctor receipts default to read-only and are blocked if they claim changed files or write access.
3066
+ A stopped lifecycle is not success by itself; a passed outcome needs explicit verification evidence.
2939
3067
  `);
2940
3068
  }
3069
+ async function missionReceipt(args = []) {
3070
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
3071
+ return missionUsage();
3072
+ const flags = parseSimpleFlags(args, new Set(['receipt-id', 'thread-id', 'lane-id', 'agent-id', 'role', 'access-mode', 'lifecycle', 'outcome', 'scope', 'changed', 'evidence', 'remaining-risk', 'out', 'json']));
3073
+ const receipt = buildMissionSubagentReceipt({
3074
+ receipt_id: String(flags.receipt_id || `receipt-${timestampId()}`),
3075
+ thread_id: String(flags.thread_id || ''),
3076
+ lane_id: String(flags.lane_id || flags.thread_id || ''),
3077
+ agent_id: String(flags.agent_id || ''),
3078
+ role: flags.role,
3079
+ access_mode: flags.access_mode,
3080
+ lifecycle_status: flags.lifecycle,
3081
+ outcome: flags.outcome,
3082
+ assigned_scope: String(flags.scope || ''),
3083
+ changed_files: arrayFlag(flags.changed),
3084
+ verification_evidence: arrayFlag(flags.evidence),
3085
+ remaining_risks: arrayFlag(flags.remaining_risk)
3086
+ });
3087
+ const result = {
3088
+ ...receipt,
3089
+ persistence: flags.out ? 'written' : 'not-persisted',
3090
+ out: flags.out ? path.resolve(expandHome(flags.out)) : '',
3091
+ next_action: receipt.completion_eligible ? 'include this receipt in `yam mission gate`' : receipt.blockers[0] || 'complete the receipt evidence'
3092
+ };
3093
+ if (flags.out)
3094
+ await writeJsonArtifact(flags.out, result);
3095
+ printJsonOrHuman(result, Boolean(flags.json), 'Mission receipt');
3096
+ if (receipt.truth_status === 'blocked')
3097
+ process.exitCode = 1;
3098
+ }
3099
+ async function missionGate(args = []) {
3100
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
3101
+ return missionUsage();
3102
+ const flags = parseSimpleFlags(args, new Set(['expected-thread', 'receipt', 'out', 'json']));
3103
+ const receipts = [];
3104
+ const loadErrors = [];
3105
+ for (const file of arrayFlag(flags.receipt)) {
3106
+ const target = path.resolve(expandHome(file));
3107
+ try {
3108
+ receipts.push(await readJson(target));
3109
+ }
3110
+ catch (error) {
3111
+ loadErrors.push(`${target}: ${errorMessage(error)}`);
3112
+ }
3113
+ }
3114
+ const gate = buildMissionCompletionGate({
3115
+ expected_thread_ids: arrayFlag(flags.expected_thread),
3116
+ receipts
3117
+ });
3118
+ const blockers = [...gate.blockers, ...loadErrors.map((error) => `receipt_load_failed: ${error}`)];
3119
+ const result = {
3120
+ ...gate,
3121
+ blockers,
3122
+ ready_to_claim_complete: gate.ready_to_claim_complete && loadErrors.length === 0,
3123
+ next_action: blockers[0] || gate.next_action,
3124
+ truth_status: blockers.length ? 'blocked' : gate.truth_status,
3125
+ persistence: flags.out ? 'written' : 'not-persisted',
3126
+ out: flags.out ? path.resolve(expandHome(flags.out)) : ''
3127
+ };
3128
+ if (flags.out)
3129
+ await writeJsonArtifact(flags.out, result);
3130
+ printJsonOrHuman(result, Boolean(flags.json), 'Mission completion gate');
3131
+ if (result.truth_status === 'blocked')
3132
+ process.exitCode = 1;
3133
+ }
2941
3134
  async function missionQueue(args = []) {
2942
3135
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
2943
3136
  return missionUsage();
@@ -3095,7 +3288,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
3095
3288
  if (allowed.size && !allowed.has(key))
3096
3289
  continue;
3097
3290
  const normalizedKey = key.replace(/-/g, '_');
3098
- if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'cleanup-observed', 'left-running-intentionally', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'invented-metric', 'placeholder-copy', 'generic-visual'].includes(key)) {
3291
+ if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'cleanup-observed', 'left-running-intentionally', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'invented-metric', 'placeholder-copy', 'generic-visual', 'operator-provided', 'do-not-replace', 'allowed-for-edit', 'replace'].includes(key)) {
3099
3292
  flags[normalizedKey] = true;
3100
3293
  continue;
3101
3294
  }
@@ -3137,58 +3330,7 @@ async function loadOptionalPlaywright() {
3137
3330
  }
3138
3331
  }
3139
3332
  async function imageFileInfo(file) {
3140
- const absolute = path.resolve(expandHome(file));
3141
- const buffer = await fsp.readFile(absolute);
3142
- const stat = await fsp.stat(absolute);
3143
- return {
3144
- path: absolute,
3145
- sha256: crypto.createHash('sha256').update(buffer).digest('hex'),
3146
- bytes: stat.size,
3147
- dimensions: imageDimensions(buffer)
3148
- };
3149
- }
3150
- function imageDimensions(buffer) {
3151
- const png = pngDimensions(buffer);
3152
- if (png)
3153
- return png;
3154
- const jpeg = jpegDimensions(buffer);
3155
- if (jpeg)
3156
- return jpeg;
3157
- const gif = gifDimensions(buffer);
3158
- if (gif)
3159
- return gif;
3160
- return 'unknown';
3161
- }
3162
- function pngDimensions(buffer) {
3163
- if (buffer.length < 24)
3164
- return '';
3165
- if (buffer.readUInt32BE(0) !== 0x89504e47 || buffer.readUInt32BE(4) !== 0x0d0a1a0a)
3166
- return '';
3167
- return `${buffer.readUInt32BE(16)}x${buffer.readUInt32BE(20)}`;
3168
- }
3169
- function gifDimensions(buffer) {
3170
- if (buffer.length < 10)
3171
- return '';
3172
- const signature = buffer.toString('ascii', 0, 6);
3173
- if (signature !== 'GIF87a' && signature !== 'GIF89a')
3174
- return '';
3175
- return `${buffer.readUInt16LE(6)}x${buffer.readUInt16LE(8)}`;
3176
- }
3177
- function jpegDimensions(buffer) {
3178
- if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8)
3179
- return '';
3180
- let offset = 2;
3181
- while (offset < buffer.length) {
3182
- if (buffer[offset] !== 0xff)
3183
- return '';
3184
- const marker = buffer[offset + 1];
3185
- const length = buffer.readUInt16BE(offset + 2);
3186
- if (marker >= 0xc0 && marker <= 0xc3 && offset + 8 < buffer.length) {
3187
- return `${buffer.readUInt16BE(offset + 7)}x${buffer.readUInt16BE(offset + 5)}`;
3188
- }
3189
- offset += 2 + length;
3190
- }
3191
- return '';
3333
+ return inspectImageFile(path.resolve(expandHome(file)));
3192
3334
  }
3193
3335
  function printJsonOrHuman(result, json = false, label = 'yam') {
3194
3336
  if (json) {
@@ -3207,6 +3349,11 @@ function printJsonOrHuman(result, json = false, label = 'yam') {
3207
3349
  }
3208
3350
  }
3209
3351
  }
3352
+ async function writeJsonArtifact(file, value) {
3353
+ const target = path.resolve(expandHome(file));
3354
+ await fsp.mkdir(path.dirname(target), { recursive: true });
3355
+ await fsp.writeFile(target, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
3356
+ }
3210
3357
  function detectDbSafetyText(text = '') {
3211
3358
  return detectTrustDbSafetyText(text);
3212
3359
  }
@@ -3250,6 +3397,8 @@ async function proof(args = []) {
3250
3397
  printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
3251
3398
  printProofList('Visual provenance', summary.visualProvenance || []);
3252
3399
  printProofList('Mission patch envelope', summary.missionEnvelope || []);
3400
+ printProofList('Mission subagent receipt', summary.missionReceipt || []);
3401
+ printProofList('Mission completion gate', summary.missionCompletion || []);
3253
3402
  printProofList('Rollback hint', summary.rollbackHint || []);
3254
3403
  printProofList('Media proof', summary.mediaProof || []);
3255
3404
  printProofList('Design completion', summary.designCompletion || []);
@@ -3341,6 +3490,14 @@ function buildProofSummary(parsed, artifact) {
3341
3490
  ...structuredEvidenceList(artifact.missionEnvelope),
3342
3491
  ...structuredEvidenceList(parsed.flags.mission_envelope)
3343
3492
  ],
3493
+ missionReceipt: [
3494
+ ...structuredEvidenceList(artifact.missionReceipt),
3495
+ ...structuredEvidenceList(parsed.flags.mission_receipt)
3496
+ ],
3497
+ missionCompletion: [
3498
+ ...structuredEvidenceList(artifact.missionCompletion),
3499
+ ...structuredEvidenceList(parsed.flags.mission_completion)
3500
+ ],
3344
3501
  rollbackHint: [
3345
3502
  ...structuredEvidenceList(artifact.rollbackHint),
3346
3503
  ...structuredEvidenceList(parsed.flags.rollback_hint)
@@ -3411,6 +3568,12 @@ function renderProofMarkdown(summary) {
3411
3568
  '## Mission patch envelope',
3412
3569
  ...renderProofMarkdownList(summary.missionEnvelope || []),
3413
3570
  '',
3571
+ '## Mission subagent receipt',
3572
+ ...renderProofMarkdownList(summary.missionReceipt || []),
3573
+ '',
3574
+ '## Mission completion gate',
3575
+ ...renderProofMarkdownList(summary.missionCompletion || []),
3576
+ '',
3414
3577
  '## Rollback hint',
3415
3578
  ...renderProofMarkdownList(summary.rollbackHint || []),
3416
3579
  '',
@@ -3450,7 +3613,7 @@ function renderProofMarkdownList(values) {
3450
3613
  function parseProofArgs(args = []) {
3451
3614
  const flags = {};
3452
3615
  const positionals = [];
3453
- const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'runtime-pid', 'runtime-port', 'runtime-exit-code', 'runtime-started-at', 'runtime-stopped-at', 'runtime-cleanup-method', 'cleanup-observed', 'left-running-intentionally', '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']);
3616
+ const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'runtime-pid', 'runtime-port', 'runtime-exit-code', 'runtime-started-at', 'runtime-stopped-at', 'runtime-cleanup-method', 'cleanup-observed', 'left-running-intentionally', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'mission-receipt', 'mission-completion', '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']);
3454
3617
  for (let index = 0; index < args.length; index += 1) {
3455
3618
  const arg = args[index];
3456
3619
  if (arg.startsWith('--')) {
@@ -3466,7 +3629,7 @@ function parseProofArgs(args = []) {
3466
3629
  const value = inlineValue ?? args[index + 1] ?? '';
3467
3630
  if (inlineValue === undefined)
3468
3631
  index += 1;
3469
- 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)) {
3632
+ if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'mission-receipt', 'mission-completion', 'rollback-hint', 'media-proof', 'design-completion', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
3470
3633
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
3471
3634
  }
3472
3635
  else {
@@ -3508,6 +3671,8 @@ function emptyProofArtifact() {
3508
3671
  runtimeBackendEvidence: [],
3509
3672
  visualProvenance: [],
3510
3673
  missionEnvelope: [],
3674
+ missionReceipt: [],
3675
+ missionCompletion: [],
3511
3676
  rollbackHint: [],
3512
3677
  mediaProof: [],
3513
3678
  designCompletion: [],
@@ -3535,6 +3700,8 @@ async function parseProofJson(file, empty) {
3535
3700
  runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
3536
3701
  visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
3537
3702
  missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
3703
+ missionReceipt: structuredEvidenceList(data.missionReceipt || data.mission_receipt),
3704
+ missionCompletion: structuredEvidenceList(data.missionCompletion || data.mission_completion),
3538
3705
  rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
3539
3706
  mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
3540
3707
  designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
@@ -3566,6 +3733,8 @@ async function parseProofMarkdown(file, empty) {
3566
3733
  runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
3567
3734
  visualProvenance: readProofList(text, 'Visual provenance'),
3568
3735
  missionEnvelope: readProofList(text, 'Mission patch envelope'),
3736
+ missionReceipt: readProofList(text, 'Mission subagent receipt'),
3737
+ missionCompletion: readProofList(text, 'Mission completion gate'),
3569
3738
  rollbackHint: readProofList(text, 'Rollback hint'),
3570
3739
  mediaProof: readProofList(text, 'Media proof'),
3571
3740
  designCompletion: readProofList(text, 'Design completion'),
@@ -3651,6 +3820,12 @@ function formatStructuredEvidence(value) {
3651
3820
  if (data.schema === 'yam.ueye-visual-provenance.v1' || data.source_kind || data.reference_id || data.screenshot_id) {
3652
3821
  return JSON.stringify(buildUeyeVisualProvenance(data));
3653
3822
  }
3823
+ if (data.schema === 'yam.mission-subagent-receipt.v1' || data.thread_id || data.lifecycle_status || data.outcome) {
3824
+ return JSON.stringify(buildMissionSubagentReceipt(data));
3825
+ }
3826
+ if (data.schema === 'yam.mission-completion-gate.v1' || data.expected_thread_ids || data.ready_to_claim_complete !== undefined) {
3827
+ return JSON.stringify(buildMissionCompletionGate(data));
3828
+ }
3654
3829
  if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
3655
3830
  return JSON.stringify(buildMissionPatchEnvelope(data));
3656
3831
  }
@@ -3674,7 +3849,7 @@ function formatStructuredEvidence(value) {
3674
3849
  }
3675
3850
  function hasProofEvidence(parsed) {
3676
3851
  const flags = parsed.flags || {};
3677
- 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);
3852
+ return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'mission_receipt', 'mission_completion', '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);
3678
3853
  }
3679
3854
  function printProofList(label, values) {
3680
3855
  console.log(`- ${label}:`);
@@ -9,6 +9,10 @@ export type LoopStageStatus = 'passed' | 'failed' | 'blocked' | 'skipped' | 'par
9
9
  export type LoopEvidenceLevel = 'none' | 'fixture' | 'smoke' | 'local' | 'real';
10
10
  export type ToolIntent = 'read_only' | 'write' | 'destructive' | 'runtime' | 'visual' | 'publish';
11
11
  export type ReadinessState = 'usable' | 'degraded' | 'blocked' | 'unknown';
12
+ export type MissionLaneRole = 'implementer' | 'reviewer' | 'ux_verifier' | 'doctor' | 'other';
13
+ export type MissionAccessMode = 'read_only' | 'write';
14
+ export type MissionLifecycleStatus = 'pending' | 'running' | 'stopped' | 'failed' | 'cancelled';
15
+ export type MissionOutcome = 'passed' | 'failed' | 'blocked' | 'ambiguous';
12
16
  export interface SafetyHit {
13
17
  level: SafetyLevel;
14
18
  reason: string;
@@ -110,6 +114,50 @@ export interface MissionPatchEnvelope {
110
114
  rollback_hint: RollbackHint;
111
115
  truth_status: TruthStatus;
112
116
  }
117
+ export interface MissionContractCheck {
118
+ id: string;
119
+ label: string;
120
+ status: 'pass' | 'fail' | 'warning';
121
+ next_action: string;
122
+ }
123
+ export interface MissionSubagentReceipt {
124
+ schema: 'yam.mission-subagent-receipt.v1';
125
+ receipt_id: string;
126
+ generated_at: string;
127
+ thread_id: string;
128
+ lane_id: string;
129
+ agent_id: string;
130
+ role: MissionLaneRole;
131
+ access_mode: MissionAccessMode;
132
+ lifecycle_status: MissionLifecycleStatus;
133
+ outcome: MissionOutcome;
134
+ assigned_scope: string;
135
+ changed_files: string[];
136
+ verification_evidence: string[];
137
+ remaining_risks: string[];
138
+ checks: MissionContractCheck[];
139
+ blockers: string[];
140
+ warnings: string[];
141
+ completion_eligible: boolean;
142
+ truth_status: TruthStatus;
143
+ }
144
+ export interface MissionCompletionGate {
145
+ schema: 'yam.mission-completion-gate.v1';
146
+ generated_at: string;
147
+ expected_thread_ids: string[];
148
+ receipt_count: number;
149
+ receipts: MissionSubagentReceipt[];
150
+ missing_thread_ids: string[];
151
+ duplicate_thread_ids: string[];
152
+ unexpected_thread_ids: string[];
153
+ invalid_thread_ids: string[];
154
+ read_only_violations: string[];
155
+ blockers: string[];
156
+ warnings: string[];
157
+ ready_to_claim_complete: boolean;
158
+ next_action: string;
159
+ truth_status: TruthStatus;
160
+ }
113
161
  export interface RuntimeBackendEvidence {
114
162
  schema: 'yam.runtime-backend-evidence.v1';
115
163
  backend: RuntimeBackend;
@@ -266,6 +314,8 @@ export interface ProofSummary {
266
314
  unverified?: unknown;
267
315
  visualProvenance?: unknown;
268
316
  missionEnvelope?: unknown;
317
+ missionReceipt?: unknown;
318
+ missionCompletion?: unknown;
269
319
  rollbackHint?: unknown;
270
320
  runtimeBackendEvidence?: unknown;
271
321
  mediaProof?: unknown;
@@ -299,6 +349,8 @@ export interface YamCompletionProof {
299
349
  runtime: string[];
300
350
  visualProvenance: string[];
301
351
  missionEnvelope: string[];
352
+ missionReceipt: string[];
353
+ missionCompletion: string[];
302
354
  rollbackHint: string[];
303
355
  runtimeBackendEvidence: string[];
304
356
  mediaProof: string[];
@@ -319,6 +371,8 @@ export declare function buildUeyeSurfaceContext(input?: Partial<UeyeSurfaceConte
319
371
  export declare function buildUeyeDesignCompletionGate(input?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>): UeyeDesignCompletionGate;
320
372
  export declare function buildRollbackHint(input?: Partial<RollbackHint>): RollbackHint;
321
373
  export declare function buildMissionPatchEnvelope(input?: Partial<MissionPatchEnvelope>): MissionPatchEnvelope;
374
+ export declare function buildMissionSubagentReceipt(input?: Partial<MissionSubagentReceipt> & Record<string, unknown>): MissionSubagentReceipt;
375
+ export declare function buildMissionCompletionGate(input?: Partial<MissionCompletionGate> & Record<string, unknown>): MissionCompletionGate;
322
376
  export declare function buildRuntimeBackendEvidence(input?: Partial<RuntimeBackendEvidence>): RuntimeBackendEvidence;
323
377
  export declare function buildStudyNote(input?: Partial<StudyNote> & Record<string, unknown>): StudyNote;
324
378
  export declare function buildLoopReport(input?: Partial<LoopReport> & Record<string, unknown>): LoopReport;