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 +4 -0
- package/README.md +14 -0
- package/dist/bin/yam.js +306 -22
- package/dist/lib/trust-kernel.d.ts +41 -0
- package/dist/lib/trust-kernel.js +117 -0
- package/package.json +1 -1
- package/references/ueye-proof.md +20 -0
- package/skills/ueye/SKILL.md +7 -0
- package/src/bin/yam.ts +299 -20
- package/src/lib/trust-kernel.ts +147 -0
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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]);
|