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