yam-flow 0.1.1 → 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 +14 -0
- package/README.md +31 -0
- package/dist/bin/yam.js +576 -21
- package/dist/lib/trust-kernel.d.ts +41 -0
- package/dist/lib/trust-kernel.js +117 -0
- package/package.json +2 -1
- package/references/ueye-proof.md +40 -0
- package/skills/ueye/SKILL.md +13 -0
- package/src/bin/yam.ts +558 -19
- package/src/lib/trust-kernel.ts +147 -0
package/dist/bin/yam.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import fsp from 'node:fs/promises';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
5
7
|
import os from 'node:os';
|
|
6
8
|
import path from 'node:path';
|
|
7
9
|
import process from 'node:process';
|
|
8
10
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
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';
|
|
10
12
|
function errorMessage(error) {
|
|
11
13
|
return error instanceof Error ? error.message : String(error);
|
|
12
14
|
}
|
|
@@ -118,6 +120,10 @@ Usage:
|
|
|
118
120
|
yam tools doctor [dir]
|
|
119
121
|
yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
|
|
120
122
|
yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
|
|
123
|
+
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
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]
|
|
121
127
|
yam release report [--json]
|
|
122
128
|
yam safety [text...]
|
|
123
129
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
@@ -126,7 +132,7 @@ Usage:
|
|
|
126
132
|
yam tune-log [dir]
|
|
127
133
|
yam install
|
|
128
134
|
yam uninstall
|
|
129
|
-
yam doctor
|
|
135
|
+
yam doctor [--json]
|
|
130
136
|
yam examples
|
|
131
137
|
yam path
|
|
132
138
|
yam version
|
|
@@ -355,7 +361,34 @@ function failOrReport(label, issues, quiet = false) {
|
|
|
355
361
|
process.exitCode = 1;
|
|
356
362
|
return issues.length;
|
|
357
363
|
}
|
|
358
|
-
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() {
|
|
359
392
|
const issues = [];
|
|
360
393
|
for (const skill of SKILLS) {
|
|
361
394
|
if (!await exists(path.join(ROOT, 'skills', skill, 'SKILL.md')))
|
|
@@ -386,18 +419,33 @@ async function doctor() {
|
|
|
386
419
|
const verifyIssues = await verify({ quiet: true });
|
|
387
420
|
if (verifyIssues > 0)
|
|
388
421
|
issues.push(`verify reported ${verifyIssues} issue(s)`);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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)];
|
|
401
449
|
}
|
|
402
450
|
async function tools(args = []) {
|
|
403
451
|
const subcommand = args[0] || 'doctor';
|
|
@@ -456,6 +504,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
456
504
|
packageInfo.packageJson ? JSON.stringify(packageInfo.pkg?.devDependencies || {}) : ''
|
|
457
505
|
].join('\n'));
|
|
458
506
|
const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
|
|
507
|
+
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
459
508
|
const sqlScan = await scanSqlFiles(dir);
|
|
460
509
|
const rows = [
|
|
461
510
|
readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
|
|
@@ -485,8 +534,16 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
485
534
|
packageManager: detection.packageJson ? detection.packageManager : null,
|
|
486
535
|
instructionSurfaces,
|
|
487
536
|
projectSurfaces,
|
|
537
|
+
frameworkChecklist,
|
|
488
538
|
sqlScan,
|
|
489
539
|
riskNotes,
|
|
540
|
+
nextActions: toolsDoctorNextActions({
|
|
541
|
+
pack,
|
|
542
|
+
rows,
|
|
543
|
+
riskNotes,
|
|
544
|
+
frameworkChecklist,
|
|
545
|
+
commands: detection.packageJson ? detection.commands : {}
|
|
546
|
+
}),
|
|
490
547
|
routeRecommendations: {
|
|
491
548
|
dbSupabase: safety.hits.length || sqlScan.findings.length ? '$deep required before claiming safe' : '$deep when destructive or production mutation appears',
|
|
492
549
|
currentDocs: 'use current-docs proof only when version/freshness matters',
|
|
@@ -518,6 +575,13 @@ function printToolsDoctorReport(data) {
|
|
|
518
575
|
for (const surface of data.projectSurfaces)
|
|
519
576
|
console.log(`- ${surface}`);
|
|
520
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
|
+
}
|
|
521
585
|
if (data.sqlScan.filesScanned > 0) {
|
|
522
586
|
console.log('');
|
|
523
587
|
console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
|
|
@@ -531,6 +595,12 @@ function printToolsDoctorReport(data) {
|
|
|
531
595
|
for (const note of data.riskNotes)
|
|
532
596
|
console.log(`- ${note.level}: ${note.reason}`);
|
|
533
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
|
+
}
|
|
534
604
|
console.log('');
|
|
535
605
|
console.log('Route recommendations:');
|
|
536
606
|
console.log(`- DB/Supabase destructive or production write work: ${data.routeRecommendations.dbSupabase}`);
|
|
@@ -607,6 +677,53 @@ async function detectProjectToolSurfaces(dir, pkg = null) {
|
|
|
607
677
|
surfaces.push('.env file detected; values were not read');
|
|
608
678
|
return surfaces;
|
|
609
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
|
+
}
|
|
610
727
|
async function scanSqlFiles(rootDir, { maxFiles = 30, maxBytes = 200000, maxDepth = 5 } = {}) {
|
|
611
728
|
const root = path.resolve(rootDir || process.cwd());
|
|
612
729
|
const files = [];
|
|
@@ -1017,6 +1134,390 @@ async function readStdinTextIfAvailable() {
|
|
|
1017
1134
|
chunks.push(Buffer.from(chunk));
|
|
1018
1135
|
return Buffer.concat(chunks).toString('utf8').trim();
|
|
1019
1136
|
}
|
|
1137
|
+
async function ueye(args = []) {
|
|
1138
|
+
const subcommand = args[0] || 'help';
|
|
1139
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1140
|
+
return ueyeUsage();
|
|
1141
|
+
if (subcommand === 'capture')
|
|
1142
|
+
return ueyeCapture(args.slice(1));
|
|
1143
|
+
if (subcommand === 'compare')
|
|
1144
|
+
return ueyeCompare(args.slice(1));
|
|
1145
|
+
if (subcommand === 'report')
|
|
1146
|
+
return ueyeReport(args.slice(1));
|
|
1147
|
+
console.error(`unknown ueye command: ${subcommand}`);
|
|
1148
|
+
return ueyeUsage();
|
|
1149
|
+
}
|
|
1150
|
+
function ueyeUsage() {
|
|
1151
|
+
console.log(`yam ueye
|
|
1152
|
+
|
|
1153
|
+
Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/compare only when verified visual claims need real evidence.
|
|
1154
|
+
|
|
1155
|
+
Usage:
|
|
1156
|
+
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
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]
|
|
1159
|
+
|
|
1160
|
+
Notes:
|
|
1161
|
+
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
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.
|
|
1164
|
+
`);
|
|
1165
|
+
}
|
|
1166
|
+
async function ueyeCapture(args = []) {
|
|
1167
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1168
|
+
return ueyeUsage();
|
|
1169
|
+
const flags = parseSimpleFlags(args, new Set(['url', 'out', 'viewport', 'wait-until', 'timeout', 'full-page', 'json']));
|
|
1170
|
+
const url = String(flags.url || '');
|
|
1171
|
+
const out = String(flags.out || '');
|
|
1172
|
+
if (!url || !out) {
|
|
1173
|
+
console.error('usage: yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]');
|
|
1174
|
+
process.exitCode = 1;
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
const target = path.resolve(expandHome(out));
|
|
1178
|
+
const viewport = parseViewport(String(flags.viewport || '1440x900'));
|
|
1179
|
+
const timeout = Number(flags.timeout || 30000);
|
|
1180
|
+
const waitUntil = String(flags.wait_until || 'networkidle');
|
|
1181
|
+
let browser;
|
|
1182
|
+
try {
|
|
1183
|
+
const playwright = await loadOptionalPlaywright();
|
|
1184
|
+
const chromium = playwright?.chromium;
|
|
1185
|
+
if (!chromium)
|
|
1186
|
+
throw new Error('Playwright chromium launcher was not found');
|
|
1187
|
+
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
1188
|
+
browser = await chromium.launch({ headless: true });
|
|
1189
|
+
const page = await browser.newPage({ viewport });
|
|
1190
|
+
await page.goto(url, { waitUntil, timeout });
|
|
1191
|
+
await page.screenshot({ path: target, fullPage: Boolean(flags.full_page) });
|
|
1192
|
+
const info = await imageFileInfo(target);
|
|
1193
|
+
const result = {
|
|
1194
|
+
schema: 'yam.ueye-capture.v1',
|
|
1195
|
+
status: 'verified',
|
|
1196
|
+
url,
|
|
1197
|
+
out: target,
|
|
1198
|
+
viewport: `${viewport.width}x${viewport.height}`,
|
|
1199
|
+
full_page: Boolean(flags.full_page),
|
|
1200
|
+
sha256: info.sha256,
|
|
1201
|
+
dimensions: info.dimensions,
|
|
1202
|
+
visual_evidence: `browser screenshot captured: ${target} (${info.dimensions}, sha256:${info.sha256})`
|
|
1203
|
+
};
|
|
1204
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye capture');
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
const result = {
|
|
1208
|
+
schema: 'yam.ueye-capture.v1',
|
|
1209
|
+
status: 'blocked',
|
|
1210
|
+
url,
|
|
1211
|
+
out: target,
|
|
1212
|
+
reason: errorMessage(error),
|
|
1213
|
+
next_action: 'Install Playwright in the current project or provide a user/browser screenshot, then use `yam ueye compare`.',
|
|
1214
|
+
visual_cap: 'blocked'
|
|
1215
|
+
};
|
|
1216
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye capture');
|
|
1217
|
+
process.exitCode = 1;
|
|
1218
|
+
}
|
|
1219
|
+
finally {
|
|
1220
|
+
if (browser)
|
|
1221
|
+
await browser.close().catch(() => { });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async function ueyeCompare(args = []) {
|
|
1225
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1226
|
+
return ueyeUsage();
|
|
1227
|
+
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'json', 'reference-id', 'screenshot-id']));
|
|
1228
|
+
const reference = String(flags.reference || flags.ref || '');
|
|
1229
|
+
const actual = String(flags.actual || flags.screenshot || '');
|
|
1230
|
+
if (!reference || !actual) {
|
|
1231
|
+
console.error('usage: yam ueye compare --reference ref.png --actual screenshot.png [--json]');
|
|
1232
|
+
process.exitCode = 1;
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const referencePath = path.resolve(expandHome(reference));
|
|
1236
|
+
const actualPath = path.resolve(expandHome(actual));
|
|
1237
|
+
try {
|
|
1238
|
+
const referenceInfo = await imageFileInfo(referencePath);
|
|
1239
|
+
const actualInfo = await imageFileInfo(actualPath);
|
|
1240
|
+
if (referenceInfo.dimensions === 'unknown' || actualInfo.dimensions === 'unknown') {
|
|
1241
|
+
throw new Error('compare requires image files with readable PNG, JPEG, or GIF dimensions');
|
|
1242
|
+
}
|
|
1243
|
+
const exactMatch = referenceInfo.sha256 === actualInfo.sha256;
|
|
1244
|
+
const sameDimensions = referenceInfo.dimensions === actualInfo.dimensions;
|
|
1245
|
+
const comparisonResult = exactMatch ? 'matched' : 'different';
|
|
1246
|
+
const truthStatus = exactMatch ? 'verified' : 'partial';
|
|
1247
|
+
const provenance = buildUeyeVisualProvenance({
|
|
1248
|
+
source_kind: 'implementation_screenshot',
|
|
1249
|
+
source_path: actualPath,
|
|
1250
|
+
source_hash: actualInfo.sha256,
|
|
1251
|
+
reference_id: String(flags.reference_id || path.basename(referencePath)),
|
|
1252
|
+
screenshot_id: String(flags.screenshot_id || path.basename(actualPath)),
|
|
1253
|
+
local_only: true,
|
|
1254
|
+
redacted: false,
|
|
1255
|
+
operator_provided: false,
|
|
1256
|
+
comparison_result: comparisonResult,
|
|
1257
|
+
truth_status: truthStatus
|
|
1258
|
+
});
|
|
1259
|
+
const result = {
|
|
1260
|
+
schema: 'yam.ueye-compare.v1',
|
|
1261
|
+
status: truthStatus,
|
|
1262
|
+
comparison_result: comparisonResult,
|
|
1263
|
+
exact_match: exactMatch,
|
|
1264
|
+
same_dimensions: sameDimensions,
|
|
1265
|
+
reference: referenceInfo,
|
|
1266
|
+
actual: actualInfo,
|
|
1267
|
+
visual_evidence: `browser/local screenshot comparison executed: reference=${referencePath}, actual=${actualPath}, result=${comparisonResult}`,
|
|
1268
|
+
visual_provenance: provenance,
|
|
1269
|
+
proof_hint: `yam proof --route ueye --truth ${truthStatus} --visual "browser/local screenshot comparison executed: ${comparisonResult}" --visual-provenance '${JSON.stringify(provenance)}' --require-visual`
|
|
1270
|
+
};
|
|
1271
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye compare');
|
|
1272
|
+
}
|
|
1273
|
+
catch (error) {
|
|
1274
|
+
const result = {
|
|
1275
|
+
schema: 'yam.ueye-compare.v1',
|
|
1276
|
+
status: 'blocked',
|
|
1277
|
+
comparison_result: 'not-verified',
|
|
1278
|
+
reason: errorMessage(error),
|
|
1279
|
+
visual_cap: 'blocked'
|
|
1280
|
+
};
|
|
1281
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye compare');
|
|
1282
|
+
process.exitCode = 1;
|
|
1283
|
+
}
|
|
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
|
+
}
|
|
1400
|
+
function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
1401
|
+
const flags = {};
|
|
1402
|
+
const positionals = [];
|
|
1403
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1404
|
+
const arg = String(args[index] || '');
|
|
1405
|
+
if (!arg.startsWith('--')) {
|
|
1406
|
+
positionals.push(arg);
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
const [rawKey, inlineValue] = arg.includes('=') ? arg.split(/=(.*)/s, 2) : [arg, undefined];
|
|
1410
|
+
const key = rawKey.slice(2);
|
|
1411
|
+
if (allowed.size && !allowed.has(key))
|
|
1412
|
+
continue;
|
|
1413
|
+
const normalizedKey = key.replace(/-/g, '_');
|
|
1414
|
+
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
|
|
1415
|
+
flags[normalizedKey] = true;
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1419
|
+
if (inlineValue === undefined)
|
|
1420
|
+
index += 1;
|
|
1421
|
+
flags[normalizedKey] = value;
|
|
1422
|
+
}
|
|
1423
|
+
flags._ = positionals;
|
|
1424
|
+
return flags;
|
|
1425
|
+
}
|
|
1426
|
+
function parseViewport(value = '1440x900') {
|
|
1427
|
+
const match = String(value || '').match(/^(\d{2,5})x(\d{2,5})$/i);
|
|
1428
|
+
if (!match)
|
|
1429
|
+
return { width: 1440, height: 900 };
|
|
1430
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
1431
|
+
}
|
|
1432
|
+
async function loadOptionalPlaywright() {
|
|
1433
|
+
let projectError = '';
|
|
1434
|
+
try {
|
|
1435
|
+
const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
1436
|
+
return projectRequire('playwright');
|
|
1437
|
+
}
|
|
1438
|
+
catch (error) {
|
|
1439
|
+
projectError = errorMessage(error);
|
|
1440
|
+
// Fall through to the yam package context for local development or bundled installs.
|
|
1441
|
+
}
|
|
1442
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1443
|
+
try {
|
|
1444
|
+
return await dynamicImport('playwright');
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
throw new Error(`Playwright not found from current project or yam package context. Current project: ${projectError}; package context: ${errorMessage(error)}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
async function imageFileInfo(file) {
|
|
1451
|
+
const absolute = path.resolve(expandHome(file));
|
|
1452
|
+
const buffer = await fsp.readFile(absolute);
|
|
1453
|
+
const stat = await fsp.stat(absolute);
|
|
1454
|
+
return {
|
|
1455
|
+
path: absolute,
|
|
1456
|
+
sha256: crypto.createHash('sha256').update(buffer).digest('hex'),
|
|
1457
|
+
bytes: stat.size,
|
|
1458
|
+
dimensions: imageDimensions(buffer)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function imageDimensions(buffer) {
|
|
1462
|
+
const png = pngDimensions(buffer);
|
|
1463
|
+
if (png)
|
|
1464
|
+
return png;
|
|
1465
|
+
const jpeg = jpegDimensions(buffer);
|
|
1466
|
+
if (jpeg)
|
|
1467
|
+
return jpeg;
|
|
1468
|
+
const gif = gifDimensions(buffer);
|
|
1469
|
+
if (gif)
|
|
1470
|
+
return gif;
|
|
1471
|
+
return 'unknown';
|
|
1472
|
+
}
|
|
1473
|
+
function pngDimensions(buffer) {
|
|
1474
|
+
if (buffer.length < 24)
|
|
1475
|
+
return '';
|
|
1476
|
+
if (buffer.readUInt32BE(0) !== 0x89504e47 || buffer.readUInt32BE(4) !== 0x0d0a1a0a)
|
|
1477
|
+
return '';
|
|
1478
|
+
return `${buffer.readUInt32BE(16)}x${buffer.readUInt32BE(20)}`;
|
|
1479
|
+
}
|
|
1480
|
+
function gifDimensions(buffer) {
|
|
1481
|
+
if (buffer.length < 10)
|
|
1482
|
+
return '';
|
|
1483
|
+
const signature = buffer.toString('ascii', 0, 6);
|
|
1484
|
+
if (signature !== 'GIF87a' && signature !== 'GIF89a')
|
|
1485
|
+
return '';
|
|
1486
|
+
return `${buffer.readUInt16LE(6)}x${buffer.readUInt16LE(8)}`;
|
|
1487
|
+
}
|
|
1488
|
+
function jpegDimensions(buffer) {
|
|
1489
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8)
|
|
1490
|
+
return '';
|
|
1491
|
+
let offset = 2;
|
|
1492
|
+
while (offset < buffer.length) {
|
|
1493
|
+
if (buffer[offset] !== 0xff)
|
|
1494
|
+
return '';
|
|
1495
|
+
const marker = buffer[offset + 1];
|
|
1496
|
+
const length = buffer.readUInt16BE(offset + 2);
|
|
1497
|
+
if (marker >= 0xc0 && marker <= 0xc3 && offset + 8 < buffer.length) {
|
|
1498
|
+
return `${buffer.readUInt16BE(offset + 7)}x${buffer.readUInt16BE(offset + 5)}`;
|
|
1499
|
+
}
|
|
1500
|
+
offset += 2 + length;
|
|
1501
|
+
}
|
|
1502
|
+
return '';
|
|
1503
|
+
}
|
|
1504
|
+
function printJsonOrHuman(result, json = false, label = 'yam') {
|
|
1505
|
+
if (json) {
|
|
1506
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
console.log(label);
|
|
1510
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1511
|
+
if (key === 'schema')
|
|
1512
|
+
continue;
|
|
1513
|
+
if (typeof value === 'object' && value !== null) {
|
|
1514
|
+
console.log(`- ${key}: ${JSON.stringify(value)}`);
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1517
|
+
console.log(`- ${key}: ${value}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1020
1521
|
function detectDbSafetyText(text = '') {
|
|
1021
1522
|
return detectTrustDbSafetyText(text);
|
|
1022
1523
|
}
|
|
@@ -1057,9 +1558,11 @@ async function proof(args = []) {
|
|
|
1057
1558
|
printProofList('Evidence', summary.evidence);
|
|
1058
1559
|
printProofList('Visual evidence', summary.visual);
|
|
1059
1560
|
printProofList('Runtime evidence', summary.runtime);
|
|
1561
|
+
printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
|
|
1060
1562
|
printProofList('Visual provenance', summary.visualProvenance || []);
|
|
1061
1563
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
1062
1564
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
1565
|
+
printProofList('Media proof', summary.mediaProof || []);
|
|
1063
1566
|
printProofList('Changed surfaces', summary.changed);
|
|
1064
1567
|
printProofList('Skipped', summary.skipped);
|
|
1065
1568
|
printProofList('Blocked', summary.blocked);
|
|
@@ -1135,6 +1638,11 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1135
1638
|
evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
|
|
1136
1639
|
visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
|
|
1137
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
|
+
],
|
|
1138
1646
|
visualProvenance: [
|
|
1139
1647
|
...structuredEvidenceList(artifact.visualProvenance),
|
|
1140
1648
|
...structuredEvidenceList(parsed.flags.visual_provenance)
|
|
@@ -1147,6 +1655,10 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1147
1655
|
...structuredEvidenceList(artifact.rollbackHint),
|
|
1148
1656
|
...structuredEvidenceList(parsed.flags.rollback_hint)
|
|
1149
1657
|
],
|
|
1658
|
+
mediaProof: [
|
|
1659
|
+
...structuredEvidenceList(artifact.mediaProof),
|
|
1660
|
+
...structuredEvidenceList(parsed.flags.media_proof)
|
|
1661
|
+
],
|
|
1150
1662
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
1151
1663
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
1152
1664
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -1196,6 +1708,9 @@ function renderProofMarkdown(summary) {
|
|
|
1196
1708
|
'## Runtime evidence',
|
|
1197
1709
|
...renderProofMarkdownList(summary.runtime),
|
|
1198
1710
|
'',
|
|
1711
|
+
'## Runtime backend evidence',
|
|
1712
|
+
...renderProofMarkdownList(summary.runtimeBackendEvidence || []),
|
|
1713
|
+
'',
|
|
1199
1714
|
'## Visual provenance',
|
|
1200
1715
|
...renderProofMarkdownList(summary.visualProvenance || []),
|
|
1201
1716
|
'',
|
|
@@ -1205,6 +1720,9 @@ function renderProofMarkdown(summary) {
|
|
|
1205
1720
|
'## Rollback hint',
|
|
1206
1721
|
...renderProofMarkdownList(summary.rollbackHint || []),
|
|
1207
1722
|
'',
|
|
1723
|
+
'## Media proof',
|
|
1724
|
+
...renderProofMarkdownList(summary.mediaProof || []),
|
|
1725
|
+
'',
|
|
1208
1726
|
'## Changed surfaces',
|
|
1209
1727
|
...renderProofMarkdownList(summary.changed),
|
|
1210
1728
|
'',
|
|
@@ -1235,7 +1753,7 @@ function renderProofMarkdownList(values) {
|
|
|
1235
1753
|
function parseProofArgs(args = []) {
|
|
1236
1754
|
const flags = {};
|
|
1237
1755
|
const positionals = [];
|
|
1238
|
-
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']);
|
|
1239
1757
|
for (let index = 0; index < args.length; index += 1) {
|
|
1240
1758
|
const arg = args[index];
|
|
1241
1759
|
if (arg.startsWith('--')) {
|
|
@@ -1244,14 +1762,14 @@ function parseProofArgs(args = []) {
|
|
|
1244
1762
|
if (!aliases.has(key))
|
|
1245
1763
|
continue;
|
|
1246
1764
|
const normalizedKey = key.replace(/-/g, '_');
|
|
1247
|
-
if (key === 'json' || key.startsWith('require-')) {
|
|
1765
|
+
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
|
|
1248
1766
|
flags[normalizedKey] = true;
|
|
1249
1767
|
continue;
|
|
1250
1768
|
}
|
|
1251
1769
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1252
1770
|
if (inlineValue === undefined)
|
|
1253
1771
|
index += 1;
|
|
1254
|
-
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)) {
|
|
1255
1773
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1256
1774
|
}
|
|
1257
1775
|
else {
|
|
@@ -1290,9 +1808,11 @@ function emptyProofArtifact() {
|
|
|
1290
1808
|
evidence: [],
|
|
1291
1809
|
visual: [],
|
|
1292
1810
|
runtime: [],
|
|
1811
|
+
runtimeBackendEvidence: [],
|
|
1293
1812
|
visualProvenance: [],
|
|
1294
1813
|
missionEnvelope: [],
|
|
1295
1814
|
rollbackHint: [],
|
|
1815
|
+
mediaProof: [],
|
|
1296
1816
|
cleanup: '',
|
|
1297
1817
|
changed: [],
|
|
1298
1818
|
skipped: [],
|
|
@@ -1314,9 +1834,11 @@ async function parseProofJson(file, empty) {
|
|
|
1314
1834
|
evidence: arrayFlag(data.evidence),
|
|
1315
1835
|
visual: arrayFlag(data.visual || data.visualEvidence),
|
|
1316
1836
|
runtime: arrayFlag(data.runtime || data.runtimeEvidence),
|
|
1837
|
+
runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
|
|
1317
1838
|
visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
|
|
1318
1839
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
1319
1840
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
1841
|
+
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
1320
1842
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
1321
1843
|
changed: arrayFlag(data.changed || data.files),
|
|
1322
1844
|
skipped: arrayFlag(data.skipped),
|
|
@@ -1342,9 +1864,11 @@ async function parseProofMarkdown(file, empty) {
|
|
|
1342
1864
|
evidence: readProofList(text, 'Evidence'),
|
|
1343
1865
|
visual: readProofList(text, 'Visual evidence'),
|
|
1344
1866
|
runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
|
|
1867
|
+
runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
|
|
1345
1868
|
visualProvenance: readProofList(text, 'Visual provenance'),
|
|
1346
1869
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
1347
1870
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
1871
|
+
mediaProof: readProofList(text, 'Media proof'),
|
|
1348
1872
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
1349
1873
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
1350
1874
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -1392,6 +1916,18 @@ function arrayFlag(value) {
|
|
|
1392
1916
|
function structuredEvidenceList(value) {
|
|
1393
1917
|
return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
|
|
1394
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
|
+
}
|
|
1395
1931
|
function formatStructuredEvidence(value) {
|
|
1396
1932
|
if (!value)
|
|
1397
1933
|
return '';
|
|
@@ -1413,6 +1949,12 @@ function formatStructuredEvidence(value) {
|
|
|
1413
1949
|
if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
|
|
1414
1950
|
return JSON.stringify(buildRollbackHint(data));
|
|
1415
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
|
+
}
|
|
1416
1958
|
return JSON.stringify(data);
|
|
1417
1959
|
}
|
|
1418
1960
|
catch {
|
|
@@ -1421,7 +1963,7 @@ function formatStructuredEvidence(value) {
|
|
|
1421
1963
|
}
|
|
1422
1964
|
function hasProofEvidence(parsed) {
|
|
1423
1965
|
const flags = parsed.flags || {};
|
|
1424
|
-
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);
|
|
1425
1967
|
}
|
|
1426
1968
|
function printProofList(label, values) {
|
|
1427
1969
|
console.log(`- ${label}:`);
|
|
@@ -1675,7 +2217,8 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1675
2217
|
lint: null,
|
|
1676
2218
|
test: null,
|
|
1677
2219
|
build: null
|
|
1678
|
-
}
|
|
2220
|
+
},
|
|
2221
|
+
frameworkChecklist: null
|
|
1679
2222
|
};
|
|
1680
2223
|
if (!result.packageJson) {
|
|
1681
2224
|
if (!quiet) {
|
|
@@ -1692,6 +2235,7 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1692
2235
|
result.commands.lint = runCommand(result.packageManager, pickScript(scripts, ['lint']));
|
|
1693
2236
|
result.commands.test = runCommand(result.packageManager, pickScript(scripts, ['test', 'spec']));
|
|
1694
2237
|
result.commands.build = runCommand(result.packageManager, pickScript(scripts, ['build']));
|
|
2238
|
+
result.frameworkChecklist = await detectFrameworkChecklist(dir, pkg);
|
|
1695
2239
|
if (!quiet)
|
|
1696
2240
|
printDetection(result);
|
|
1697
2241
|
return result;
|
|
@@ -1709,6 +2253,13 @@ function printDetection(result) {
|
|
|
1709
2253
|
console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'Level 0 read/inspect; no command detected'}`);
|
|
1710
2254
|
console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
|
|
1711
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
|
+
}
|
|
1712
2263
|
}
|
|
1713
2264
|
function budget(routeArg = '') {
|
|
1714
2265
|
const normalized = normalizeRoute(routeArg);
|
|
@@ -2112,6 +2663,10 @@ async function main() {
|
|
|
2112
2663
|
return tools(process.argv.slice(3));
|
|
2113
2664
|
if (command === 'proof')
|
|
2114
2665
|
return proof(process.argv.slice(3));
|
|
2666
|
+
if (command === 'ueye')
|
|
2667
|
+
return ueye(process.argv.slice(3));
|
|
2668
|
+
if (command === 'media')
|
|
2669
|
+
return media(process.argv.slice(3));
|
|
2115
2670
|
if (command === 'release')
|
|
2116
2671
|
return release(process.argv.slice(3));
|
|
2117
2672
|
if (command === 'safety')
|
|
@@ -2135,7 +2690,7 @@ async function main() {
|
|
|
2135
2690
|
if (command === 'verify')
|
|
2136
2691
|
return verify();
|
|
2137
2692
|
if (command === 'doctor')
|
|
2138
|
-
return doctor();
|
|
2693
|
+
return doctor(process.argv.slice(3));
|
|
2139
2694
|
if (command === 'examples')
|
|
2140
2695
|
return examples();
|
|
2141
2696
|
if (command === 'path')
|