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/src/bin/yam.ts CHANGED
@@ -10,9 +10,12 @@ import process from 'node:process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import {
12
12
  TRUTH_STATUSES,
13
+ buildMediaGenerationProof,
13
14
  buildMissionPatchEnvelope,
14
15
  buildRollbackHint,
16
+ buildRuntimeBackendEvidence,
15
17
  buildUeyeVisualProvenance,
18
+ buildUeyeRunReport,
16
19
  buildYamCompletionProof,
17
20
  detectDbSafetyText as detectTrustDbSafetyText,
18
21
  isTruthStatus
@@ -136,6 +139,8 @@ Usage:
136
139
  yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
137
140
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
138
141
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
142
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--json]
143
+ yam media proof [--requested] [--attempted] [--output file] [--json]
139
144
  yam release report [--json]
140
145
  yam safety [text...]
141
146
  yam memory <init|add|list|summary|resolve> [dir] [options]
@@ -144,7 +149,7 @@ Usage:
144
149
  yam tune-log [dir]
145
150
  yam install
146
151
  yam uninstall
147
- yam doctor
152
+ yam doctor [--json]
148
153
  yam examples
149
154
  yam path
150
155
  yam version
@@ -375,7 +380,33 @@ function failOrReport(label, issues, quiet = false) {
375
380
  return issues.length;
376
381
  }
377
382
 
378
- async function doctor() {
383
+ async function doctor(args = []) {
384
+ const flags = parseSimpleFlags(args, new Set(['json']));
385
+ const report = await buildDoctorReport();
386
+ if (flags.json) {
387
+ console.log(JSON.stringify(report, null, 2));
388
+ if (!report.ok) process.exitCode = 1;
389
+ return;
390
+ }
391
+ if (report.ok) {
392
+ console.log('yam doctor: ok');
393
+ console.log('No hooks, automations, or global config are required.');
394
+ console.log('Skill install state is reported by `yam status`.');
395
+ console.log(`yam-lite hook: ${report.yamLiteHook} (optional)`);
396
+ return;
397
+ }
398
+
399
+ console.log('yam doctor: issues');
400
+ for (const issue of report.issues) console.log(`- ${issue}`);
401
+ if (report.nextActions.length) {
402
+ console.log('');
403
+ console.log('Next actions:');
404
+ for (const action of report.nextActions) console.log(`- ${action}`);
405
+ }
406
+ process.exitCode = 1;
407
+ }
408
+
409
+ async function buildDoctorReport() {
379
410
  const issues = [];
380
411
  for (const skill of SKILLS) {
381
412
  if (!await exists(path.join(ROOT, 'skills', skill, 'SKILL.md'))) issues.push(`missing source skill: ${skill}`);
@@ -398,19 +429,28 @@ async function doctor() {
398
429
  }
399
430
  const verifyIssues = await verify({ quiet: true });
400
431
  if (verifyIssues > 0) issues.push(`verify reported ${verifyIssues} issue(s)`);
432
+ const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
433
+ return {
434
+ schema: 'yam.doctor.v1',
435
+ generatedAt: new Date().toISOString(),
436
+ ok: issues.length === 0,
437
+ issues,
438
+ nextActions: doctorNextActions(issues),
439
+ yamLiteHook: hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'
440
+ };
441
+ }
401
442
 
402
- if (!issues.length) {
403
- console.log('yam doctor: ok');
404
- const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
405
- console.log('No hooks, automations, or global config are required.');
406
- console.log('Skill install state is reported by `yam status`.');
407
- console.log(`yam-lite hook: ${hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'} (optional)`);
408
- return;
443
+ function doctorNextActions(issues = []) {
444
+ const actions = [];
445
+ for (const issue of issues) {
446
+ if (/missing source skill/i.test(issue)) actions.push('run from a complete yam-flow checkout or reinstall the published package');
447
+ else if (/missing truth matrix/i.test(issue)) actions.push('restore references/truth-matrix.md before publishing');
448
+ else if (/unexpected local hooks/i.test(issue)) actions.push('remove project hooks unless this repo is intentionally dogfooding them');
449
+ else if (/unexpected .*automation/i.test(issue)) actions.push('remove stale yam/timeto automations before claiming clean install state');
450
+ else if (/legacy|retired/i.test(issue)) actions.push('run `yam install` to replace old skill entries');
451
+ else if (/verify reported/i.test(issue)) actions.push('run `npm run verify` and fix the reported package boundary or metadata issue');
409
452
  }
410
-
411
- console.log('yam doctor: issues');
412
- for (const issue of issues) console.log(`- ${issue}`);
413
- process.exitCode = 1;
453
+ return [...new Set(actions)];
414
454
  }
415
455
 
416
456
  async function tools(args = []) {
@@ -471,6 +511,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
471
511
  packageInfo.packageJson ? JSON.stringify(packageInfo.pkg?.devDependencies || {}) : ''
472
512
  ].join('\n'));
473
513
  const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
514
+ const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
474
515
  const sqlScan = await scanSqlFiles(dir);
475
516
  const rows = [
476
517
  readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
@@ -500,8 +541,16 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
500
541
  packageManager: detection.packageJson ? detection.packageManager : null,
501
542
  instructionSurfaces,
502
543
  projectSurfaces,
544
+ frameworkChecklist,
503
545
  sqlScan,
504
546
  riskNotes,
547
+ nextActions: toolsDoctorNextActions({
548
+ pack,
549
+ rows,
550
+ riskNotes,
551
+ frameworkChecklist,
552
+ commands: detection.packageJson ? detection.commands : {}
553
+ }),
505
554
  routeRecommendations: {
506
555
  dbSupabase: safety.hits.length || sqlScan.findings.length ? '$deep required before claiming safe' : '$deep when destructive or production mutation appears',
507
556
  currentDocs: 'use current-docs proof only when version/freshness matters',
@@ -534,6 +583,14 @@ function printToolsDoctorReport(data) {
534
583
  for (const surface of data.projectSurfaces) console.log(`- ${surface}`);
535
584
  }
536
585
 
586
+ if (data.frameworkChecklist?.detected) {
587
+ console.log('');
588
+ console.log(`Framework checklist: ${data.frameworkChecklist.framework}`);
589
+ for (const check of data.frameworkChecklist.checks) {
590
+ console.log(`- ${check.id}: ${check.label} (${check.route})`);
591
+ }
592
+ }
593
+
537
594
  if (data.sqlScan.filesScanned > 0) {
538
595
  console.log('');
539
596
  console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
@@ -548,6 +605,12 @@ function printToolsDoctorReport(data) {
548
605
  for (const note of data.riskNotes) console.log(`- ${note.level}: ${note.reason}`);
549
606
  }
550
607
 
608
+ if (data.nextActions?.length) {
609
+ console.log('');
610
+ console.log('Next actions:');
611
+ for (const action of data.nextActions) console.log(`- ${action}`);
612
+ }
613
+
551
614
  console.log('');
552
615
  console.log('Route recommendations:');
553
616
  console.log(`- DB/Supabase destructive or production write work: ${data.routeRecommendations.dbSupabase}`);
@@ -620,6 +683,56 @@ async function detectProjectToolSurfaces(dir, pkg = null) {
620
683
  return surfaces;
621
684
  }
622
685
 
686
+ async function detectFrameworkChecklist(dir, pkg = null) {
687
+ const deps = {
688
+ ...pkg?.dependencies,
689
+ ...pkg?.devDependencies
690
+ };
691
+ const depNames = Object.keys(deps || {});
692
+ const hasReact = depNames.includes('react');
693
+ 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'));
694
+ const hasVite = depNames.includes('vite') || await exists(path.join(dir, 'vite.config.js')) || await exists(path.join(dir, 'vite.config.ts'));
695
+ const framework = hasNext ? 'next-app' : hasReact && hasVite ? 'react-vite' : hasReact ? 'react' : pkg ? 'node-package' : 'unknown';
696
+ const uiLike = hasReact || hasNext || hasVite || await exists(path.join(dir, 'app')) || await exists(path.join(dir, 'src', 'app')) || await exists(path.join(dir, 'pages'));
697
+ const checks = [];
698
+ if (uiLike) {
699
+ checks.push(
700
+ checklistRow('states', 'default/loading/error/empty states are reachable before claiming complete', '$ueye'),
701
+ checklistRow('responsive', 'mobile and desktop layouts avoid overlap and clipped text', '$ueye'),
702
+ checklistRow('accessibility', 'interactive controls have accessible labels and keyboard path', '$ueye'),
703
+ checklistRow('runtime', 'browser or in-app runtime evidence is recorded when claiming visual verified', '$ueye')
704
+ );
705
+ }
706
+ if (hasNext) {
707
+ checks.push(checklistRow('boundaries', 'server/client boundaries and async data states match the framework pattern', '$deep when risky'));
708
+ }
709
+ if (pkg) {
710
+ checks.push(checklistRow('package', 'run the smallest honest type/build/package check before publishing', '$quick or $deep'));
711
+ }
712
+ return {
713
+ schema: 'yam.framework-checklist.v1',
714
+ detected: checks.length > 0,
715
+ uiLike,
716
+ framework,
717
+ routeHint: uiLike ? '$ueye for visual claims; $deep when runtime/data risk appears' : '$quick by default; deepen on risk',
718
+ checks
719
+ };
720
+ }
721
+
722
+ function checklistRow(id, label, route) {
723
+ return { id, label, route };
724
+ }
725
+
726
+ function toolsDoctorNextActions({ pack, rows, riskNotes, frameworkChecklist, commands }) {
727
+ const actions = [];
728
+ if (!pack) actions.push('create or refresh yam.project.md so route choices reuse local project context');
729
+ if (rows.some((row) => row.name === 'Yam skills' && row.status !== 'ready')) actions.push('run `yam install` and restart Codex before relying on installed skills');
730
+ if (riskNotes.some((note) => note.level === 'danger')) actions.push('use $deep before claiming destructive database or production write safety');
731
+ if (frameworkChecklist?.detected && !commands?.build && !commands?.typecheck) actions.push('add or document a small build/typecheck command for implementation verification');
732
+ if (frameworkChecklist?.uiLike) actions.push('for Ueye verified claims, record reference, actual screenshot, and comparison result');
733
+ return [...new Set(actions)];
734
+ }
735
+
623
736
  async function scanSqlFiles(rootDir, { maxFiles = 30, maxBytes = 200000, maxDepth = 5 } = {}) {
624
737
  const root = path.resolve(rootDir || process.cwd());
625
738
  const files = [];
@@ -1019,6 +1132,7 @@ async function ueye(args = []) {
1019
1132
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return ueyeUsage();
1020
1133
  if (subcommand === 'capture') return ueyeCapture(args.slice(1));
1021
1134
  if (subcommand === 'compare') return ueyeCompare(args.slice(1));
1135
+ if (subcommand === 'report') return ueyeReport(args.slice(1));
1022
1136
  console.error(`unknown ueye command: ${subcommand}`);
1023
1137
  return ueyeUsage();
1024
1138
  }
@@ -1031,10 +1145,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
1031
1145
  Usage:
1032
1146
  yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
1033
1147
  yam ueye compare --reference ref.png --actual screenshot.png [--json]
1148
+ yam ueye report [--reference ref.png] [--actual screenshot.png] [--design-quality pass|needs-polish|fails|not-checked] [--json]
1034
1149
 
1035
1150
  Notes:
1036
1151
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
1037
1152
  compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
1153
+ report produces a proof-ready Ueye visual run report without requiring a new capture.
1038
1154
  `);
1039
1155
  }
1040
1156
 
@@ -1155,6 +1271,118 @@ async function ueyeCompare(args = []) {
1155
1271
  }
1156
1272
  }
1157
1273
 
1274
+ async function ueyeReport(args = []) {
1275
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
1276
+ const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'json']));
1277
+ const reference = String(flags.reference || flags.ref || '');
1278
+ const actual = String(flags.actual || flags.screenshot || '');
1279
+ const referenceSources = [];
1280
+ const implementationSources = [];
1281
+ let comparisonResult = 'not-verified';
1282
+ let blockedReason = String(flags.blocked_reason || '');
1283
+
1284
+ try {
1285
+ if (reference) {
1286
+ const info = await imageFileInfo(reference);
1287
+ referenceSources.push(buildUeyeVisualProvenance({
1288
+ source_kind: 'reference_image',
1289
+ source_path: info.path,
1290
+ source_hash: info.sha256,
1291
+ reference_id: path.basename(info.path),
1292
+ local_only: true,
1293
+ operator_provided: true,
1294
+ comparison_result: 'reference-recorded',
1295
+ truth_status: 'partial'
1296
+ }));
1297
+ }
1298
+ if (actual) {
1299
+ const info = await imageFileInfo(actual);
1300
+ implementationSources.push(buildUeyeVisualProvenance({
1301
+ source_kind: 'implementation_screenshot',
1302
+ source_path: info.path,
1303
+ source_hash: info.sha256,
1304
+ screenshot_id: path.basename(info.path),
1305
+ local_only: true,
1306
+ operator_provided: false,
1307
+ comparison_result: 'implementation-recorded',
1308
+ truth_status: 'partial'
1309
+ }));
1310
+ }
1311
+ if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
1312
+ comparisonResult = 'matched';
1313
+ } else if (reference && actual) {
1314
+ comparisonResult = 'different';
1315
+ }
1316
+ } catch (error) {
1317
+ blockedReason = errorMessage(error);
1318
+ }
1319
+
1320
+ const report = buildUeyeRunReport({
1321
+ reference_sources: referenceSources,
1322
+ implementation_sources: implementationSources,
1323
+ comparison_result: comparisonResult,
1324
+ design_quality: String(flags.design_quality || 'not-checked'),
1325
+ blocked_reason: blockedReason,
1326
+ next_action: String(flags.next_action || '')
1327
+ });
1328
+ const result = {
1329
+ ...report,
1330
+ capture_backend: String(flags.capture_backend || 'not-recorded'),
1331
+ compare_backend: String(flags.compare_backend || 'local-file-hash')
1332
+ };
1333
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
1334
+ if (report.truth_status === 'blocked') process.exitCode = 1;
1335
+ }
1336
+
1337
+ async function media(args = []) {
1338
+ const subcommand = args[0] || 'help';
1339
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return mediaUsage();
1340
+ if (subcommand === 'proof') return mediaProof(args.slice(1));
1341
+ console.error(`unknown media command: ${subcommand}`);
1342
+ return mediaUsage();
1343
+ }
1344
+
1345
+ function mediaUsage() {
1346
+ console.log(`yam media
1347
+
1348
+ Opt-in media generation proof helpers. Generated media can support visual direction, but it cannot verify implemented UI without implementation screenshot evidence.
1349
+
1350
+ Usage:
1351
+ yam media proof [--tool name] [--requested] [--attempted] [--output file] [--wait-loop] [--blocked-reason text] [--json]
1352
+ `);
1353
+ }
1354
+
1355
+ async function mediaProof(args = []) {
1356
+ if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return mediaUsage();
1357
+ const flags = parseSimpleFlags(args, new Set(['tool', 'requested', 'attempted', 'output', 'wait-loop', 'blocked-reason', 'next-action', 'json']));
1358
+ const output = String(flags.output || '');
1359
+ let outputHash = 'unknown';
1360
+ let outputPath = '';
1361
+ let blockedReason = String(flags.blocked_reason || '');
1362
+ if (output) {
1363
+ try {
1364
+ const info = await imageFileInfo(output);
1365
+ outputHash = info.sha256;
1366
+ outputPath = info.path;
1367
+ } catch (error) {
1368
+ blockedReason = blockedReason || errorMessage(error);
1369
+ outputPath = path.resolve(expandHome(output));
1370
+ }
1371
+ }
1372
+ const proof = buildMediaGenerationProof({
1373
+ tool_name: String(flags.tool || ''),
1374
+ generation_requested: Boolean(flags.requested),
1375
+ generation_attempted: Boolean(flags.attempted || output),
1376
+ output_path: outputPath,
1377
+ output_hash: outputHash,
1378
+ wait_loop_checked: Boolean(flags.wait_loop),
1379
+ blocked_reason: blockedReason,
1380
+ next_action: String(flags.next_action || '')
1381
+ });
1382
+ printJsonOrHuman(proof, Boolean(flags.json), 'Media proof');
1383
+ if (proof.truth_status === 'blocked') process.exitCode = 1;
1384
+ }
1385
+
1158
1386
  function parseSimpleFlags(args = [], allowed = new Set<string>()) {
1159
1387
  const flags: AnyRecord = {};
1160
1388
  const positionals: string[] = [];
@@ -1168,7 +1396,7 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
1168
1396
  const key = rawKey.slice(2);
1169
1397
  if (allowed.size && !allowed.has(key)) continue;
1170
1398
  const normalizedKey = key.replace(/-/g, '_');
1171
- if (['json', 'full-page'].includes(key)) {
1399
+ if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
1172
1400
  flags[normalizedKey] = true;
1173
1401
  continue;
1174
1402
  }
@@ -1309,9 +1537,11 @@ async function proof(args = []) {
1309
1537
  printProofList('Evidence', summary.evidence);
1310
1538
  printProofList('Visual evidence', summary.visual);
1311
1539
  printProofList('Runtime evidence', summary.runtime);
1540
+ printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
1312
1541
  printProofList('Visual provenance', summary.visualProvenance || []);
1313
1542
  printProofList('Mission patch envelope', summary.missionEnvelope || []);
1314
1543
  printProofList('Rollback hint', summary.rollbackHint || []);
1544
+ printProofList('Media proof', summary.mediaProof || []);
1315
1545
  printProofList('Changed surfaces', summary.changed);
1316
1546
  printProofList('Skipped', summary.skipped);
1317
1547
  printProofList('Blocked', summary.blocked);
@@ -1386,6 +1616,11 @@ function buildProofSummary(parsed, artifact) {
1386
1616
  evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
1387
1617
  visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
1388
1618
  runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
1619
+ runtimeBackendEvidence: [
1620
+ ...structuredEvidenceList(artifact.runtimeBackendEvidence),
1621
+ ...structuredEvidenceList(parsed.flags.runtime_backend_evidence),
1622
+ ...directRuntimeBackendEvidence(parsed.flags)
1623
+ ],
1389
1624
  visualProvenance: [
1390
1625
  ...structuredEvidenceList(artifact.visualProvenance),
1391
1626
  ...structuredEvidenceList(parsed.flags.visual_provenance)
@@ -1398,6 +1633,10 @@ function buildProofSummary(parsed, artifact) {
1398
1633
  ...structuredEvidenceList(artifact.rollbackHint),
1399
1634
  ...structuredEvidenceList(parsed.flags.rollback_hint)
1400
1635
  ],
1636
+ mediaProof: [
1637
+ ...structuredEvidenceList(artifact.mediaProof),
1638
+ ...structuredEvidenceList(parsed.flags.media_proof)
1639
+ ],
1401
1640
  cleanup: parsed.flags.cleanup || artifact.cleanup || '',
1402
1641
  changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
1403
1642
  skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
@@ -1448,6 +1687,9 @@ function renderProofMarkdown(summary) {
1448
1687
  '## Runtime evidence',
1449
1688
  ...renderProofMarkdownList(summary.runtime),
1450
1689
  '',
1690
+ '## Runtime backend evidence',
1691
+ ...renderProofMarkdownList(summary.runtimeBackendEvidence || []),
1692
+ '',
1451
1693
  '## Visual provenance',
1452
1694
  ...renderProofMarkdownList(summary.visualProvenance || []),
1453
1695
  '',
@@ -1457,6 +1699,9 @@ function renderProofMarkdown(summary) {
1457
1699
  '## Rollback hint',
1458
1700
  ...renderProofMarkdownList(summary.rollbackHint || []),
1459
1701
  '',
1702
+ '## Media proof',
1703
+ ...renderProofMarkdownList(summary.mediaProof || []),
1704
+ '',
1460
1705
  '## Changed surfaces',
1461
1706
  ...renderProofMarkdownList(summary.changed),
1462
1707
  '',
@@ -1489,7 +1734,7 @@ function renderProofMarkdownList(values) {
1489
1734
  function parseProofArgs(args = []) {
1490
1735
  const flags: AnyRecord = {};
1491
1736
  const positionals: string[] = [];
1492
- 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']);
1737
+ 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']);
1493
1738
  for (let index = 0; index < args.length; index += 1) {
1494
1739
  const arg = args[index];
1495
1740
  if (arg.startsWith('--')) {
@@ -1497,13 +1742,13 @@ function parseProofArgs(args = []) {
1497
1742
  const key = rawKey.slice(2);
1498
1743
  if (!aliases.has(key)) continue;
1499
1744
  const normalizedKey = key.replace(/-/g, '_');
1500
- if (key === 'json' || key.startsWith('require-')) {
1745
+ if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
1501
1746
  flags[normalizedKey] = true;
1502
1747
  continue;
1503
1748
  }
1504
1749
  const value = inlineValue ?? args[index + 1] ?? '';
1505
1750
  if (inlineValue === undefined) index += 1;
1506
- if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1751
+ if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
1507
1752
  flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
1508
1753
  } else {
1509
1754
  flags[normalizedKey] = value;
@@ -1541,9 +1786,11 @@ function emptyProofArtifact() {
1541
1786
  evidence: [],
1542
1787
  visual: [],
1543
1788
  runtime: [],
1789
+ runtimeBackendEvidence: [],
1544
1790
  visualProvenance: [],
1545
1791
  missionEnvelope: [],
1546
1792
  rollbackHint: [],
1793
+ mediaProof: [],
1547
1794
  cleanup: '',
1548
1795
  changed: [],
1549
1796
  skipped: [],
@@ -1566,9 +1813,11 @@ async function parseProofJson(file, empty) {
1566
1813
  evidence: arrayFlag(data.evidence),
1567
1814
  visual: arrayFlag(data.visual || data.visualEvidence),
1568
1815
  runtime: arrayFlag(data.runtime || data.runtimeEvidence),
1816
+ runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
1569
1817
  visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
1570
1818
  missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
1571
1819
  rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
1820
+ mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
1572
1821
  cleanup: String(data.cleanup || data.cleanupStatus || ''),
1573
1822
  changed: arrayFlag(data.changed || data.files),
1574
1823
  skipped: arrayFlag(data.skipped),
@@ -1594,9 +1843,11 @@ async function parseProofMarkdown(file, empty) {
1594
1843
  evidence: readProofList(text, 'Evidence'),
1595
1844
  visual: readProofList(text, 'Visual evidence'),
1596
1845
  runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
1846
+ runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
1597
1847
  visualProvenance: readProofList(text, 'Visual provenance'),
1598
1848
  missionEnvelope: readProofList(text, 'Mission patch envelope'),
1599
1849
  rollbackHint: readProofList(text, 'Rollback hint'),
1850
+ mediaProof: readProofList(text, 'Media proof'),
1600
1851
  cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
1601
1852
  changed: firstProofList(text, ['Changed surfaces', 'Files']),
1602
1853
  skipped: readProofList(text, 'Skipped'),
@@ -1647,6 +1898,18 @@ function structuredEvidenceList(value) {
1647
1898
  return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
1648
1899
  }
1649
1900
 
1901
+ function directRuntimeBackendEvidence(flags) {
1902
+ if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked) return [];
1903
+ return [JSON.stringify(buildRuntimeBackendEvidence({
1904
+ backend: flags.runtime_backend,
1905
+ claim: flags.runtime_claim,
1906
+ evidence_id: flags.runtime_evidence_id,
1907
+ command: flags.runtime_command,
1908
+ cleanup_checked: Boolean(flags.cleanup_checked),
1909
+ note: flags.runtime_note
1910
+ }))];
1911
+ }
1912
+
1650
1913
  function formatStructuredEvidence(value) {
1651
1914
  if (!value) return '';
1652
1915
  if (typeof value === 'object') return JSON.stringify(value);
@@ -1664,6 +1927,12 @@ function formatStructuredEvidence(value) {
1664
1927
  if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
1665
1928
  return JSON.stringify(buildRollbackHint(data));
1666
1929
  }
1930
+ if (data.schema === 'yam.runtime-backend-evidence.v1' || data.backend || data.evidence_id) {
1931
+ return JSON.stringify(buildRuntimeBackendEvidence(data));
1932
+ }
1933
+ if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
1934
+ return JSON.stringify(buildMediaGenerationProof(data));
1935
+ }
1667
1936
  return JSON.stringify(data);
1668
1937
  } catch {
1669
1938
  return text;
@@ -1672,7 +1941,7 @@ function formatStructuredEvidence(value) {
1672
1941
 
1673
1942
  function hasProofEvidence(parsed) {
1674
1943
  const flags = parsed.flags || {};
1675
- return ['command', 'evidence', 'visual', 'runtime', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
1944
+ 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);
1676
1945
  }
1677
1946
 
1678
1947
  function printProofList(label, values) {
@@ -1924,7 +2193,8 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
1924
2193
  lint: null,
1925
2194
  test: null,
1926
2195
  build: null
1927
- }
2196
+ },
2197
+ frameworkChecklist: null
1928
2198
  };
1929
2199
  if (!result.packageJson) {
1930
2200
  if (!quiet) {
@@ -1942,6 +2212,7 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
1942
2212
  result.commands.lint = runCommand(result.packageManager, pickScript(scripts, ['lint']));
1943
2213
  result.commands.test = runCommand(result.packageManager, pickScript(scripts, ['test', 'spec']));
1944
2214
  result.commands.build = runCommand(result.packageManager, pickScript(scripts, ['build']));
2215
+ result.frameworkChecklist = await detectFrameworkChecklist(dir, pkg);
1945
2216
 
1946
2217
  if (!quiet) printDetection(result);
1947
2218
  return result;
@@ -1960,6 +2231,13 @@ function printDetection(result) {
1960
2231
  console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'Level 0 read/inspect; no command detected'}`);
1961
2232
  console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
1962
2233
  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}`}`);
2234
+ if (result.frameworkChecklist?.detected) {
2235
+ console.log('');
2236
+ console.log(`Framework checklist: ${result.frameworkChecklist.framework}`);
2237
+ for (const check of result.frameworkChecklist.checks) {
2238
+ console.log(`- ${check.id}: ${check.label} (${check.route})`);
2239
+ }
2240
+ }
1963
2241
  }
1964
2242
 
1965
2243
  function budget(routeArg = '') {
@@ -2365,6 +2643,7 @@ async function main() {
2365
2643
  if (command === 'tools') return tools(process.argv.slice(3));
2366
2644
  if (command === 'proof') return proof(process.argv.slice(3));
2367
2645
  if (command === 'ueye') return ueye(process.argv.slice(3));
2646
+ if (command === 'media') return media(process.argv.slice(3));
2368
2647
  if (command === 'release') return release(process.argv.slice(3));
2369
2648
  if (command === 'safety') return safety(process.argv.slice(3));
2370
2649
  if (command === 'memory') return memory(process.argv.slice(3));
@@ -2378,7 +2657,7 @@ async function main() {
2378
2657
  }
2379
2658
  if (command === 'list') return list();
2380
2659
  if (command === 'verify') return verify();
2381
- if (command === 'doctor') return doctor();
2660
+ if (command === 'doctor') return doctor(process.argv.slice(3));
2382
2661
  if (command === 'examples') return examples();
2383
2662
  if (command === 'path') return showPath();
2384
2663
  if (command === 'init-project') return initProject(process.argv[3]);