yam-flow 0.1.2 → 0.1.3

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/COMMANDS.md CHANGED
@@ -44,6 +44,8 @@ Opt-in visual evidence helpers:
44
44
  ```bash
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
+ yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality needs-polish --json
48
+ yam media proof --requested --attempted --output ./generated.png --wait-loop --json
47
49
  yam proof --route ueye --truth partial --visual "browser/local screenshot comparison executed" --require-visual
48
50
  ```
49
51
 
@@ -146,6 +148,8 @@ yam proof --route ueye --truth verified --visual "reference image only"
146
148
  yam proof --route ueye --truth partial --visual-provenance '{"source_kind":"reference","source_hash":"unknown","comparison_result":"not-verified","truth_status":"partial"}'
147
149
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
148
150
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png --json
151
+ yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality pass --json
152
+ yam media proof --requested --attempted --output ./generated.png --wait-loop --json
149
153
  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"}'
150
154
  yam proof --route deep --truth proven --require-runtime --runtime "mock server fixture"
151
155
  yam proof write /path/to/project --route deep --truth partial --command "npm run build: pass"
package/README.md CHANGED
@@ -129,6 +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 --json
133
+ yam media proof --requested --attempted --output ./generated.png --wait-loop --json
132
134
  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"}'
133
135
  yam release report --json
134
136
  yam safety "supabase db reset"
@@ -174,6 +176,9 @@ Publishing still requires confirming the final npm package name and account acce
174
176
  - fake versus real distinction
175
177
  - runtime truth matrix
176
178
  - visual evidence caps
179
+ - Ueye visual run reports
180
+ - runtime backend evidence
181
+ - media generation proof caps
177
182
  - DB/Supabase safety signals
178
183
 
179
184
  It is implemented locally in `src/lib/trust-kernel.ts` and kept route-scoped. It is not an always-on release gate.
@@ -185,10 +190,19 @@ It is implemented locally in `src/lib/trust-kernel.ts` and kept route-scoped. It
185
190
  ```bash
186
191
  yam ueye capture --url http://localhost:3000 --out .yam/screens/home.png
187
192
  yam ueye compare --reference ./reference.png --actual .yam/screens/home.png --json
193
+ yam ueye report --reference ./reference.png --actual .yam/screens/home.png --design-quality pass --json
188
194
  ```
189
195
 
190
196
  `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.
191
197
 
192
198
  `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.
193
199
 
200
+ `report` gathers reference sources, implementation screenshots, comparison result, design quality judgment, and next action into one compact proof-ready JSON object.
201
+
202
+ Generated media can guide visual direction, but it cannot prove the implemented UI by itself:
203
+
204
+ ```bash
205
+ yam media proof --requested --attempted --output ./generated.png --wait-loop --json
206
+ ```
207
+
194
208
  For agent-driven visual QA, prefer the Codex in-app Browser plugin. Use the user's Chrome browser only when explicitly requested or when Chrome-only session/profile state is required.
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, buildMissionPatchEnvelope, buildRollbackHint, buildUeyeVisualProvenance, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
11
+ import { TRUTH_STATUSES, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildUeyeVisualProvenance, buildUeyeRunReport, 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
  }
@@ -122,6 +122,8 @@ Usage:
122
122
  yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
123
123
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
124
124
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
125
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--json]
126
+ yam media proof [--requested] [--attempted] [--output file] [--json]
125
127
  yam release report [--json]
126
128
  yam safety [text...]
127
129
  yam memory <init|add|list|summary|resolve> [dir] [options]
@@ -130,7 +132,7 @@ Usage:
130
132
  yam tune-log [dir]
131
133
  yam install
132
134
  yam uninstall
133
- yam doctor
135
+ yam doctor [--json]
134
136
  yam examples
135
137
  yam path
136
138
  yam version
@@ -359,7 +361,34 @@ function failOrReport(label, issues, quiet = false) {
359
361
  process.exitCode = 1;
360
362
  return issues.length;
361
363
  }
362
- async function doctor() {
364
+ async function doctor(args = []) {
365
+ const flags = parseSimpleFlags(args, new Set(['json']));
366
+ const report = await buildDoctorReport();
367
+ if (flags.json) {
368
+ console.log(JSON.stringify(report, null, 2));
369
+ if (!report.ok)
370
+ process.exitCode = 1;
371
+ return;
372
+ }
373
+ if (report.ok) {
374
+ console.log('yam doctor: ok');
375
+ console.log('No hooks, automations, or global config are required.');
376
+ console.log('Skill install state is reported by `yam status`.');
377
+ console.log(`yam-lite hook: ${report.yamLiteHook} (optional)`);
378
+ return;
379
+ }
380
+ console.log('yam doctor: issues');
381
+ for (const issue of report.issues)
382
+ console.log(`- ${issue}`);
383
+ if (report.nextActions.length) {
384
+ console.log('');
385
+ console.log('Next actions:');
386
+ for (const action of report.nextActions)
387
+ console.log(`- ${action}`);
388
+ }
389
+ process.exitCode = 1;
390
+ }
391
+ async function buildDoctorReport() {
363
392
  const issues = [];
364
393
  for (const skill of SKILLS) {
365
394
  if (!await exists(path.join(ROOT, 'skills', skill, 'SKILL.md')))
@@ -390,18 +419,33 @@ async function doctor() {
390
419
  const verifyIssues = await verify({ quiet: true });
391
420
  if (verifyIssues > 0)
392
421
  issues.push(`verify reported ${verifyIssues} issue(s)`);
393
- if (!issues.length) {
394
- console.log('yam doctor: ok');
395
- const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
396
- console.log('No hooks, automations, or global config are required.');
397
- console.log('Skill install state is reported by `yam status`.');
398
- console.log(`yam-lite hook: ${hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'} (optional)`);
399
- return;
400
- }
401
- console.log('yam doctor: issues');
402
- for (const issue of issues)
403
- console.log(`- ${issue}`);
404
- process.exitCode = 1;
422
+ const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
423
+ return {
424
+ schema: 'yam.doctor.v1',
425
+ generatedAt: new Date().toISOString(),
426
+ ok: issues.length === 0,
427
+ issues,
428
+ nextActions: doctorNextActions(issues),
429
+ yamLiteHook: hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'
430
+ };
431
+ }
432
+ function doctorNextActions(issues = []) {
433
+ const actions = [];
434
+ for (const issue of issues) {
435
+ if (/missing source skill/i.test(issue))
436
+ actions.push('run from a complete yam-flow checkout or reinstall the published package');
437
+ else if (/missing truth matrix/i.test(issue))
438
+ actions.push('restore references/truth-matrix.md before publishing');
439
+ else if (/unexpected local hooks/i.test(issue))
440
+ actions.push('remove project hooks unless this repo is intentionally dogfooding them');
441
+ else if (/unexpected .*automation/i.test(issue))
442
+ actions.push('remove stale yam/timeto automations before claiming clean install state');
443
+ else if (/legacy|retired/i.test(issue))
444
+ actions.push('run `yam install` to replace old skill entries');
445
+ else if (/verify reported/i.test(issue))
446
+ actions.push('run `npm run verify` and fix the reported package boundary or metadata issue');
447
+ }
448
+ return [...new Set(actions)];
405
449
  }
406
450
  async function tools(args = []) {
407
451
  const subcommand = args[0] || 'doctor';
@@ -460,6 +504,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
460
504
  packageInfo.packageJson ? JSON.stringify(packageInfo.pkg?.devDependencies || {}) : ''
461
505
  ].join('\n'));
462
506
  const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
507
+ const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
463
508
  const sqlScan = await scanSqlFiles(dir);
464
509
  const rows = [
465
510
  readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
@@ -489,8 +534,16 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
489
534
  packageManager: detection.packageJson ? detection.packageManager : null,
490
535
  instructionSurfaces,
491
536
  projectSurfaces,
537
+ frameworkChecklist,
492
538
  sqlScan,
493
539
  riskNotes,
540
+ nextActions: toolsDoctorNextActions({
541
+ pack,
542
+ rows,
543
+ riskNotes,
544
+ frameworkChecklist,
545
+ commands: detection.packageJson ? detection.commands : {}
546
+ }),
494
547
  routeRecommendations: {
495
548
  dbSupabase: safety.hits.length || sqlScan.findings.length ? '$deep required before claiming safe' : '$deep when destructive or production mutation appears',
496
549
  currentDocs: 'use current-docs proof only when version/freshness matters',
@@ -522,6 +575,13 @@ function printToolsDoctorReport(data) {
522
575
  for (const surface of data.projectSurfaces)
523
576
  console.log(`- ${surface}`);
524
577
  }
578
+ if (data.frameworkChecklist?.detected) {
579
+ console.log('');
580
+ console.log(`Framework checklist: ${data.frameworkChecklist.framework}`);
581
+ for (const check of data.frameworkChecklist.checks) {
582
+ console.log(`- ${check.id}: ${check.label} (${check.route})`);
583
+ }
584
+ }
525
585
  if (data.sqlScan.filesScanned > 0) {
526
586
  console.log('');
527
587
  console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
@@ -535,6 +595,12 @@ function printToolsDoctorReport(data) {
535
595
  for (const note of data.riskNotes)
536
596
  console.log(`- ${note.level}: ${note.reason}`);
537
597
  }
598
+ if (data.nextActions?.length) {
599
+ console.log('');
600
+ console.log('Next actions:');
601
+ for (const action of data.nextActions)
602
+ console.log(`- ${action}`);
603
+ }
538
604
  console.log('');
539
605
  console.log('Route recommendations:');
540
606
  console.log(`- DB/Supabase destructive or production write work: ${data.routeRecommendations.dbSupabase}`);
@@ -611,6 +677,53 @@ async function detectProjectToolSurfaces(dir, pkg = null) {
611
677
  surfaces.push('.env file detected; values were not read');
612
678
  return surfaces;
613
679
  }
680
+ async function detectFrameworkChecklist(dir, pkg = null) {
681
+ const deps = {
682
+ ...pkg?.dependencies,
683
+ ...pkg?.devDependencies
684
+ };
685
+ const depNames = Object.keys(deps || {});
686
+ const hasReact = depNames.includes('react');
687
+ const hasNext = depNames.includes('next') || await exists(path.join(dir, 'next.config.js')) || await exists(path.join(dir, 'next.config.mjs')) || await exists(path.join(dir, 'next.config.ts'));
688
+ const hasVite = depNames.includes('vite') || await exists(path.join(dir, 'vite.config.js')) || await exists(path.join(dir, 'vite.config.ts'));
689
+ const framework = hasNext ? 'next-app' : hasReact && hasVite ? 'react-vite' : hasReact ? 'react' : pkg ? 'node-package' : 'unknown';
690
+ const uiLike = hasReact || hasNext || hasVite || await exists(path.join(dir, 'app')) || await exists(path.join(dir, 'src', 'app')) || await exists(path.join(dir, 'pages'));
691
+ const checks = [];
692
+ if (uiLike) {
693
+ checks.push(checklistRow('states', 'default/loading/error/empty states are reachable before claiming complete', '$ueye'), checklistRow('responsive', 'mobile and desktop layouts avoid overlap and clipped text', '$ueye'), checklistRow('accessibility', 'interactive controls have accessible labels and keyboard path', '$ueye'), checklistRow('runtime', 'browser or in-app runtime evidence is recorded when claiming visual verified', '$ueye'));
694
+ }
695
+ if (hasNext) {
696
+ checks.push(checklistRow('boundaries', 'server/client boundaries and async data states match the framework pattern', '$deep when risky'));
697
+ }
698
+ if (pkg) {
699
+ checks.push(checklistRow('package', 'run the smallest honest type/build/package check before publishing', '$quick or $deep'));
700
+ }
701
+ return {
702
+ schema: 'yam.framework-checklist.v1',
703
+ detected: checks.length > 0,
704
+ uiLike,
705
+ framework,
706
+ routeHint: uiLike ? '$ueye for visual claims; $deep when runtime/data risk appears' : '$quick by default; deepen on risk',
707
+ checks
708
+ };
709
+ }
710
+ function checklistRow(id, label, route) {
711
+ return { id, label, route };
712
+ }
713
+ function toolsDoctorNextActions({ pack, rows, riskNotes, frameworkChecklist, commands }) {
714
+ const actions = [];
715
+ if (!pack)
716
+ actions.push('create or refresh yam.project.md so route choices reuse local project context');
717
+ if (rows.some((row) => row.name === 'Yam skills' && row.status !== 'ready'))
718
+ actions.push('run `yam install` and restart Codex before relying on installed skills');
719
+ if (riskNotes.some((note) => note.level === 'danger'))
720
+ actions.push('use $deep before claiming destructive database or production write safety');
721
+ if (frameworkChecklist?.detected && !commands?.build && !commands?.typecheck)
722
+ actions.push('add or document a small build/typecheck command for implementation verification');
723
+ if (frameworkChecklist?.uiLike)
724
+ actions.push('for Ueye verified claims, record reference, actual screenshot, and comparison result');
725
+ return [...new Set(actions)];
726
+ }
614
727
  async function scanSqlFiles(rootDir, { maxFiles = 30, maxBytes = 200000, maxDepth = 5 } = {}) {
615
728
  const root = path.resolve(rootDir || process.cwd());
616
729
  const files = [];
@@ -1029,6 +1142,8 @@ async function ueye(args = []) {
1029
1142
  return ueyeCapture(args.slice(1));
1030
1143
  if (subcommand === 'compare')
1031
1144
  return ueyeCompare(args.slice(1));
1145
+ if (subcommand === 'report')
1146
+ return ueyeReport(args.slice(1));
1032
1147
  console.error(`unknown ueye command: ${subcommand}`);
1033
1148
  return ueyeUsage();
1034
1149
  }
@@ -1040,10 +1155,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
1040
1155
  Usage:
1041
1156
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
1042
1157
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
1158
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--design-quality pass|needs-polish|fails|not-checked] [--json]
1043
1159
 
1044
1160
  Notes:
1045
1161
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
1046
1162
  compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
1163
+ report produces a proof-ready Ueye visual run report without requiring a new capture.
1047
1164
  `);
1048
1165
  }
1049
1166
  async function ueyeCapture(args = []) {
@@ -1165,6 +1282,121 @@ async function ueyeCompare(args = []) {
1165
1282
  process.exitCode = 1;
1166
1283
  }
1167
1284
  }
1285
+ async function ueyeReport(args = []) {
1286
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
1287
+ return ueyeUsage();
1288
+ const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'json']));
1289
+ const reference = String(flags.reference || flags.ref || '');
1290
+ const actual = String(flags.actual || flags.screenshot || '');
1291
+ const referenceSources = [];
1292
+ const implementationSources = [];
1293
+ let comparisonResult = 'not-verified';
1294
+ let blockedReason = String(flags.blocked_reason || '');
1295
+ try {
1296
+ if (reference) {
1297
+ const info = await imageFileInfo(reference);
1298
+ referenceSources.push(buildUeyeVisualProvenance({
1299
+ source_kind: 'reference_image',
1300
+ source_path: info.path,
1301
+ source_hash: info.sha256,
1302
+ reference_id: path.basename(info.path),
1303
+ local_only: true,
1304
+ operator_provided: true,
1305
+ comparison_result: 'reference-recorded',
1306
+ truth_status: 'partial'
1307
+ }));
1308
+ }
1309
+ if (actual) {
1310
+ const info = await imageFileInfo(actual);
1311
+ implementationSources.push(buildUeyeVisualProvenance({
1312
+ source_kind: 'implementation_screenshot',
1313
+ source_path: info.path,
1314
+ source_hash: info.sha256,
1315
+ screenshot_id: path.basename(info.path),
1316
+ local_only: true,
1317
+ operator_provided: false,
1318
+ comparison_result: 'implementation-recorded',
1319
+ truth_status: 'partial'
1320
+ }));
1321
+ }
1322
+ if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1323
+ comparisonResult = 'matched';
1324
+ }
1325
+ else if (reference && actual) {
1326
+ comparisonResult = 'different';
1327
+ }
1328
+ }
1329
+ catch (error) {
1330
+ blockedReason = errorMessage(error);
1331
+ }
1332
+ const report = buildUeyeRunReport({
1333
+ reference_sources: referenceSources,
1334
+ implementation_sources: implementationSources,
1335
+ comparison_result: comparisonResult,
1336
+ design_quality: String(flags.design_quality || 'not-checked'),
1337
+ blocked_reason: blockedReason,
1338
+ next_action: String(flags.next_action || '')
1339
+ });
1340
+ const result = {
1341
+ ...report,
1342
+ capture_backend: String(flags.capture_backend || 'not-recorded'),
1343
+ compare_backend: String(flags.compare_backend || 'local-file-hash')
1344
+ };
1345
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
1346
+ if (report.truth_status === 'blocked')
1347
+ process.exitCode = 1;
1348
+ }
1349
+ async function media(args = []) {
1350
+ const subcommand = args[0] || 'help';
1351
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
1352
+ return mediaUsage();
1353
+ if (subcommand === 'proof')
1354
+ return mediaProof(args.slice(1));
1355
+ console.error(`unknown media command: ${subcommand}`);
1356
+ return mediaUsage();
1357
+ }
1358
+ function mediaUsage() {
1359
+ console.log(`yam media
1360
+
1361
+ Opt-in media generation proof helpers. Generated media can support visual direction, but it cannot verify implemented UI without implementation screenshot evidence.
1362
+
1363
+ Usage:
1364
+ yam media proof [--tool name] [--requested] [--attempted] [--output file] [--wait-loop] [--blocked-reason text] [--json]
1365
+ `);
1366
+ }
1367
+ async function mediaProof(args = []) {
1368
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
1369
+ return mediaUsage();
1370
+ const flags = parseSimpleFlags(args, new Set(['tool', 'requested', 'attempted', 'output', 'wait-loop', 'blocked-reason', 'next-action', 'json']));
1371
+ const output = String(flags.output || '');
1372
+ let outputHash = 'unknown';
1373
+ let outputPath = '';
1374
+ let blockedReason = String(flags.blocked_reason || '');
1375
+ if (output) {
1376
+ try {
1377
+ const info = await imageFileInfo(output);
1378
+ outputHash = info.sha256;
1379
+ outputPath = info.path;
1380
+ }
1381
+ catch (error) {
1382
+ blockedReason = blockedReason || errorMessage(error);
1383
+ outputPath = path.resolve(expandHome(output));
1384
+ }
1385
+ }
1386
+ const proof = buildMediaGenerationProof({
1387
+ tool_name: String(flags.tool || ''),
1388
+ generation_requested: Boolean(flags.requested),
1389
+ generation_attempted: Boolean(flags.attempted || output),
1390
+ output_path: outputPath,
1391
+ output_hash: outputHash,
1392
+ wait_loop_checked: Boolean(flags.wait_loop),
1393
+ blocked_reason: blockedReason,
1394
+ next_action: String(flags.next_action || '')
1395
+ });
1396
+ printJsonOrHuman(proof, Boolean(flags.json), 'Media proof');
1397
+ if (proof.truth_status === 'blocked')
1398
+ process.exitCode = 1;
1399
+ }
1168
1400
  function parseSimpleFlags(args = [], allowed = new Set()) {
1169
1401
  const flags = {};
1170
1402
  const positionals = [];
@@ -1179,7 +1411,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
1179
1411
  if (allowed.size && !allowed.has(key))
1180
1412
  continue;
1181
1413
  const normalizedKey = key.replace(/-/g, '_');
1182
- if (['json', 'full-page'].includes(key)) {
1414
+ if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
1183
1415
  flags[normalizedKey] = true;
1184
1416
  continue;
1185
1417
  }
@@ -1326,9 +1558,11 @@ async function proof(args = []) {
1326
1558
  printProofList('Evidence', summary.evidence);
1327
1559
  printProofList('Visual evidence', summary.visual);
1328
1560
  printProofList('Runtime evidence', summary.runtime);
1561
+ printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
1329
1562
  printProofList('Visual provenance', summary.visualProvenance || []);
1330
1563
  printProofList('Mission patch envelope', summary.missionEnvelope || []);
1331
1564
  printProofList('Rollback hint', summary.rollbackHint || []);
1565
+ printProofList('Media proof', summary.mediaProof || []);
1332
1566
  printProofList('Changed surfaces', summary.changed);
1333
1567
  printProofList('Skipped', summary.skipped);
1334
1568
  printProofList('Blocked', summary.blocked);
@@ -1404,6 +1638,11 @@ function buildProofSummary(parsed, artifact) {
1404
1638
  evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
1405
1639
  visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
1406
1640
  runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
1641
+ runtimeBackendEvidence: [
1642
+ ...structuredEvidenceList(artifact.runtimeBackendEvidence),
1643
+ ...structuredEvidenceList(parsed.flags.runtime_backend_evidence),
1644
+ ...directRuntimeBackendEvidence(parsed.flags)
1645
+ ],
1407
1646
  visualProvenance: [
1408
1647
  ...structuredEvidenceList(artifact.visualProvenance),
1409
1648
  ...structuredEvidenceList(parsed.flags.visual_provenance)
@@ -1416,6 +1655,10 @@ function buildProofSummary(parsed, artifact) {
1416
1655
  ...structuredEvidenceList(artifact.rollbackHint),
1417
1656
  ...structuredEvidenceList(parsed.flags.rollback_hint)
1418
1657
  ],
1658
+ mediaProof: [
1659
+ ...structuredEvidenceList(artifact.mediaProof),
1660
+ ...structuredEvidenceList(parsed.flags.media_proof)
1661
+ ],
1419
1662
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
1420
1663
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
1421
1664
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -1465,6 +1708,9 @@ function renderProofMarkdown(summary) {
1465
1708
  '## Runtime evidence',
1466
1709
  ...renderProofMarkdownList(summary.runtime),
1467
1710
  '',
1711
+ '## Runtime backend evidence',
1712
+ ...renderProofMarkdownList(summary.runtimeBackendEvidence || []),
1713
+ '',
1468
1714
  '## Visual provenance',
1469
1715
  ...renderProofMarkdownList(summary.visualProvenance || []),
1470
1716
  '',
@@ -1474,6 +1720,9 @@ function renderProofMarkdown(summary) {
1474
1720
  '## Rollback hint',
1475
1721
  ...renderProofMarkdownList(summary.rollbackHint || []),
1476
1722
  '',
1723
+ '## Media proof',
1724
+ ...renderProofMarkdownList(summary.mediaProof || []),
1725
+ '',
1477
1726
  '## Changed surfaces',
1478
1727
  ...renderProofMarkdownList(summary.changed),
1479
1728
  '',
@@ -1504,7 +1753,7 @@ function renderProofMarkdownList(values) {
1504
1753
  function parseProofArgs(args = []) {
1505
1754
  const flags = {};
1506
1755
  const positionals = [];
1507
- const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
1756
+ 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']);
1508
1757
  for (let index = 0; index < args.length; index += 1) {
1509
1758
  const arg = args[index];
1510
1759
  if (arg.startsWith('--')) {
@@ -1513,14 +1762,14 @@ function parseProofArgs(args = []) {
1513
1762
  if (!aliases.has(key))
1514
1763
  continue;
1515
1764
  const normalizedKey = key.replace(/-/g, '_');
1516
- if (key === 'json' || key.startsWith('require-')) {
1765
+ if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
1517
1766
  flags[normalizedKey] = true;
1518
1767
  continue;
1519
1768
  }
1520
1769
  const value = inlineValue ?? args[index + 1] ?? '';
1521
1770
  if (inlineValue === undefined)
1522
1771
  index += 1;
1523
- if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1772
+ if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1524
1773
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
1525
1774
  }
1526
1775
  else {
@@ -1559,9 +1808,11 @@ function emptyProofArtifact() {
1559
1808
  evidence: [],
1560
1809
  visual: [],
1561
1810
  runtime: [],
1811
+ runtimeBackendEvidence: [],
1562
1812
  visualProvenance: [],
1563
1813
  missionEnvelope: [],
1564
1814
  rollbackHint: [],
1815
+ mediaProof: [],
1565
1816
  cleanup: '',
1566
1817
  changed: [],
1567
1818
  skipped: [],
@@ -1583,9 +1834,11 @@ async function parseProofJson(file, empty) {
1583
1834
  evidence: arrayFlag(data.evidence),
1584
1835
  visual: arrayFlag(data.visual || data.visualEvidence),
1585
1836
  runtime: arrayFlag(data.runtime || data.runtimeEvidence),
1837
+ runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
1586
1838
  visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
1587
1839
  missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
1588
1840
  rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
1841
+ mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
1589
1842
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
1590
1843
  changed: arrayFlag(data.changed || data.files),
1591
1844
  skipped: arrayFlag(data.skipped),
@@ -1611,9 +1864,11 @@ async function parseProofMarkdown(file, empty) {
1611
1864
  evidence: readProofList(text, 'Evidence'),
1612
1865
  visual: readProofList(text, 'Visual evidence'),
1613
1866
  runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
1867
+ runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
1614
1868
  visualProvenance: readProofList(text, 'Visual provenance'),
1615
1869
  missionEnvelope: readProofList(text, 'Mission patch envelope'),
1616
1870
  rollbackHint: readProofList(text, 'Rollback hint'),
1871
+ mediaProof: readProofList(text, 'Media proof'),
1617
1872
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
1618
1873
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
1619
1874
  skipped: readProofList(text, 'Skipped'),
@@ -1661,6 +1916,18 @@ function arrayFlag(value) {
1661
1916
  function structuredEvidenceList(value) {
1662
1917
  return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
1663
1918
  }
1919
+ function directRuntimeBackendEvidence(flags) {
1920
+ if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked)
1921
+ return [];
1922
+ return [JSON.stringify(buildRuntimeBackendEvidence({
1923
+ backend: flags.runtime_backend,
1924
+ claim: flags.runtime_claim,
1925
+ evidence_id: flags.runtime_evidence_id,
1926
+ command: flags.runtime_command,
1927
+ cleanup_checked: Boolean(flags.cleanup_checked),
1928
+ note: flags.runtime_note
1929
+ }))];
1930
+ }
1664
1931
  function formatStructuredEvidence(value) {
1665
1932
  if (!value)
1666
1933
  return '';
@@ -1682,6 +1949,12 @@ function formatStructuredEvidence(value) {
1682
1949
  if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
1683
1950
  return JSON.stringify(buildRollbackHint(data));
1684
1951
  }
1952
+ if (data.schema === 'yam.runtime-backend-evidence.v1' || data.backend || data.evidence_id) {
1953
+ return JSON.stringify(buildRuntimeBackendEvidence(data));
1954
+ }
1955
+ if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
1956
+ return JSON.stringify(buildMediaGenerationProof(data));
1957
+ }
1685
1958
  return JSON.stringify(data);
1686
1959
  }
1687
1960
  catch {
@@ -1690,7 +1963,7 @@ function formatStructuredEvidence(value) {
1690
1963
  }
1691
1964
  function hasProofEvidence(parsed) {
1692
1965
  const flags = parsed.flags || {};
1693
- return ['command', 'evidence', 'visual', 'runtime', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1966
+ 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);
1694
1967
  }
1695
1968
  function printProofList(label, values) {
1696
1969
  console.log(`- ${label}:`);
@@ -1944,7 +2217,8 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
1944
2217
  lint: null,
1945
2218
  test: null,
1946
2219
  build: null
1947
- }
2220
+ },
2221
+ frameworkChecklist: null
1948
2222
  };
1949
2223
  if (!result.packageJson) {
1950
2224
  if (!quiet) {
@@ -1961,6 +2235,7 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
1961
2235
  result.commands.lint = runCommand(result.packageManager, pickScript(scripts, ['lint']));
1962
2236
  result.commands.test = runCommand(result.packageManager, pickScript(scripts, ['test', 'spec']));
1963
2237
  result.commands.build = runCommand(result.packageManager, pickScript(scripts, ['build']));
2238
+ result.frameworkChecklist = await detectFrameworkChecklist(dir, pkg);
1964
2239
  if (!quiet)
1965
2240
  printDetection(result);
1966
2241
  return result;
@@ -1978,6 +2253,13 @@ function printDetection(result) {
1978
2253
  console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'Level 0 read/inspect; no command detected'}`);
1979
2254
  console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
1980
2255
  console.log(`- $deep: ${[result.commands.typecheck, result.commands.lint, result.commands.test, result.commands.build].filter(Boolean).join(' && ') || `No command detected; define in ${PROJECT_PACK}`}`);
2256
+ if (result.frameworkChecklist?.detected) {
2257
+ console.log('');
2258
+ console.log(`Framework checklist: ${result.frameworkChecklist.framework}`);
2259
+ for (const check of result.frameworkChecklist.checks) {
2260
+ console.log(`- ${check.id}: ${check.label} (${check.route})`);
2261
+ }
2262
+ }
1981
2263
  }
1982
2264
  function budget(routeArg = '') {
1983
2265
  const normalized = normalizeRoute(routeArg);
@@ -2383,6 +2665,8 @@ async function main() {
2383
2665
  return proof(process.argv.slice(3));
2384
2666
  if (command === 'ueye')
2385
2667
  return ueye(process.argv.slice(3));
2668
+ if (command === 'media')
2669
+ return media(process.argv.slice(3));
2386
2670
  if (command === 'release')
2387
2671
  return release(process.argv.slice(3));
2388
2672
  if (command === 'safety')
@@ -2406,7 +2690,7 @@ async function main() {
2406
2690
  if (command === 'verify')
2407
2691
  return verify();
2408
2692
  if (command === 'doctor')
2409
- return doctor();
2693
+ return doctor(process.argv.slice(3));
2410
2694
  if (command === 'examples')
2411
2695
  return examples();
2412
2696
  if (command === 'path')