yam-flow 0.1.2 → 0.1.4
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/CHANGELOG.md +36 -0
- package/COMMANDS.md +31 -0
- package/README.md +30 -1
- package/ROADMAP.md +5 -5
- package/dist/bin/yam.js +855 -23
- package/dist/lib/trust-kernel.d.ts +41 -0
- package/dist/lib/trust-kernel.js +117 -0
- package/package.json +3 -2
- package/references/current-docs.md +3 -3
- package/references/doctor-scan.md +19 -0
- package/references/final-report.md +21 -0
- package/references/hook-lite.md +1 -1
- package/references/memory.md +1 -1
- package/references/mission.md +25 -0
- package/references/runtime-orchestration.md +25 -1
- package/references/tool-trust-layer.md +100 -6
- package/references/ueye-proof.md +54 -5
- package/references/ueye.md +24 -2
- package/skills/question/SKILL.md +1 -1
- package/skills/ueye/SKILL.md +16 -4
- package/src/bin/yam.ts +846 -21
- package/src/lib/trust-kernel.ts +147 -0
- package/templates/mission-plan.md +12 -0
- package/templates/runtime-proof.md +41 -2
- package/templates/tuning-log.md +11 -1
- package/templates/ueye-comparison.md +44 -0
- package/templates/ueye-review.md +44 -0
- package/templates/yam.project.md +2 -2
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,11 @@ 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]
|
|
144
|
+
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
145
|
+
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
146
|
+
yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
|
|
139
147
|
yam release report [--json]
|
|
140
148
|
yam safety [text...]
|
|
141
149
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
@@ -144,7 +152,7 @@ Usage:
|
|
|
144
152
|
yam tune-log [dir]
|
|
145
153
|
yam install
|
|
146
154
|
yam uninstall
|
|
147
|
-
yam doctor
|
|
155
|
+
yam doctor [--json]
|
|
148
156
|
yam examples
|
|
149
157
|
yam path
|
|
150
158
|
yam version
|
|
@@ -375,7 +383,33 @@ function failOrReport(label, issues, quiet = false) {
|
|
|
375
383
|
return issues.length;
|
|
376
384
|
}
|
|
377
385
|
|
|
378
|
-
async function doctor() {
|
|
386
|
+
async function doctor(args = []) {
|
|
387
|
+
const flags = parseSimpleFlags(args, new Set(['json']));
|
|
388
|
+
const report = await buildDoctorReport();
|
|
389
|
+
if (flags.json) {
|
|
390
|
+
console.log(JSON.stringify(report, null, 2));
|
|
391
|
+
if (!report.ok) process.exitCode = 1;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (report.ok) {
|
|
395
|
+
console.log('yam doctor: ok');
|
|
396
|
+
console.log('No hooks, automations, or global config are required.');
|
|
397
|
+
console.log('Skill install state is reported by `yam status`.');
|
|
398
|
+
console.log(`yam-lite hook: ${report.yamLiteHook} (optional)`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.log('yam doctor: issues');
|
|
403
|
+
for (const issue of report.issues) console.log(`- ${issue}`);
|
|
404
|
+
if (report.nextActions.length) {
|
|
405
|
+
console.log('');
|
|
406
|
+
console.log('Next actions:');
|
|
407
|
+
for (const action of report.nextActions) console.log(`- ${action}`);
|
|
408
|
+
}
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function buildDoctorReport() {
|
|
379
413
|
const issues = [];
|
|
380
414
|
for (const skill of SKILLS) {
|
|
381
415
|
if (!await exists(path.join(ROOT, 'skills', skill, 'SKILL.md'))) issues.push(`missing source skill: ${skill}`);
|
|
@@ -398,19 +432,98 @@ async function doctor() {
|
|
|
398
432
|
}
|
|
399
433
|
const verifyIssues = await verify({ quiet: true });
|
|
400
434
|
if (verifyIssues > 0) issues.push(`verify reported ${verifyIssues} issue(s)`);
|
|
435
|
+
const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
|
|
436
|
+
const nextActionDetails = doctorNextActionDetails(issues);
|
|
437
|
+
return {
|
|
438
|
+
schema: 'yam.doctor.v1',
|
|
439
|
+
generatedAt: new Date().toISOString(),
|
|
440
|
+
ok: issues.length === 0,
|
|
441
|
+
issues,
|
|
442
|
+
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
443
|
+
nextActionDetails,
|
|
444
|
+
yamLiteHook: hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'
|
|
445
|
+
};
|
|
446
|
+
}
|
|
401
447
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
448
|
+
function doctorNextActions(issues = []) {
|
|
449
|
+
return doctorNextActionDetails(issues).map((action) => action.next_action);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function doctorNextActionDetails(issues = []) {
|
|
453
|
+
const actions = [];
|
|
454
|
+
for (const issue of issues) {
|
|
455
|
+
if (/missing source skill/i.test(issue)) actions.push(nextActionDetail('restore-skill-source', 'warning', issue, 'run from a complete yam-flow checkout or reinstall the published package', 'npm install -g yam-flow@latest && yam install'));
|
|
456
|
+
else if (/missing truth matrix/i.test(issue)) actions.push(nextActionDetail('restore-truth-matrix', 'warning', issue, 'restore references/truth-matrix.md before publishing', 'git checkout -- references/truth-matrix.md'));
|
|
457
|
+
else if (/unexpected local hooks/i.test(issue)) actions.push(nextActionDetail('remove-project-hook', 'warning', issue, 'remove project hooks unless this repo is intentionally dogfooding them', 'inspect .codex/hooks.json and remove only intentional stale hooks'));
|
|
458
|
+
else if (/unexpected .*automation/i.test(issue)) actions.push(nextActionDetail('remove-stale-automation', 'warning', issue, 'remove stale yam/timeto automations before claiming clean install state', 'inspect ~/.codex/automations and remove stale entries manually'));
|
|
459
|
+
else if (/legacy|retired/i.test(issue)) actions.push(nextActionDetail('replace-old-skills', 'warning', issue, 'run `yam install` to replace old skill entries', 'yam install'));
|
|
460
|
+
else if (/verify reported/i.test(issue)) actions.push(nextActionDetail('run-verify', 'error', issue, 'run `npm run verify` and fix the reported package boundary or metadata issue', 'npm run verify'));
|
|
409
461
|
}
|
|
462
|
+
return uniqueNextActionDetails(actions);
|
|
463
|
+
}
|
|
410
464
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
465
|
+
function nextActionDetail(id, severity, reason, nextAction, command = '') {
|
|
466
|
+
const priority = priorityForSeverity(severity);
|
|
467
|
+
const ownerRoute = ownerRouteForAction(id, reason, command);
|
|
468
|
+
return {
|
|
469
|
+
id,
|
|
470
|
+
severity,
|
|
471
|
+
priority,
|
|
472
|
+
owner_route: ownerRoute,
|
|
473
|
+
reason,
|
|
474
|
+
next_action: nextAction,
|
|
475
|
+
command,
|
|
476
|
+
tool_intent: toolIntentForCommand(command),
|
|
477
|
+
fix_first: priority === 'P0' || priority === 'P1',
|
|
478
|
+
blocks_release: priority === 'P0',
|
|
479
|
+
truth_status: 'partial'
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function priorityForSeverity(severity = '') {
|
|
484
|
+
if (/error|danger|block/i.test(String(severity))) return 'P0';
|
|
485
|
+
if (/warn/i.test(String(severity))) return 'P1';
|
|
486
|
+
if (/info/i.test(String(severity))) return 'P2';
|
|
487
|
+
return 'P3';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function ownerRouteForAction(id = '', reason = '', command = '') {
|
|
491
|
+
const text = `${id} ${reason} ${command}`;
|
|
492
|
+
if (/visual|ueye|screenshot|browser/i.test(text)) return '$ueye';
|
|
493
|
+
if (/release|publish|package|registry|dist|version/i.test(text)) return '$deep';
|
|
494
|
+
if (/database|supabase|destructive|production|migration/i.test(text)) return '$deep';
|
|
495
|
+
if (/mission|subagent|team|lane/i.test(text)) return '$mission';
|
|
496
|
+
if (/research|docs|current/i.test(text)) return '$scout';
|
|
497
|
+
return '$quick';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function toolIntentForCommand(command = '') {
|
|
501
|
+
const text = String(command || '').toLowerCase();
|
|
502
|
+
if (!text) return 'read_only';
|
|
503
|
+
if (/(publish|registry|release|npm pack|npm publish)/i.test(text)) return 'publish';
|
|
504
|
+
if (/(rm |delete|drop|truncate|reset|migrate|push|deploy|write|commit|add |install)/i.test(text)) return 'destructive';
|
|
505
|
+
if (/(dev|server|tmux|playwright|browser|screenshot|runtime|port|pid)/i.test(text)) return 'runtime';
|
|
506
|
+
if (/(ueye|visual|image|screen)/i.test(text)) return 'visual';
|
|
507
|
+
if (/(build|typecheck|lint|test|verify|doctor|status|pack)/i.test(text)) return 'read_only';
|
|
508
|
+
return 'write';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function normalizeToolIntent(value = '') {
|
|
512
|
+
const text = String(value || '').toLowerCase().replace(/[-\s]/g, '_');
|
|
513
|
+
if (['read_only', 'write', 'destructive', 'runtime', 'visual', 'publish'].includes(text)) return text;
|
|
514
|
+
return 'read_only';
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function uniqueNextActionDetails(actions = []) {
|
|
518
|
+
const seen = new Set<string>();
|
|
519
|
+
const unique = [];
|
|
520
|
+
for (const action of actions) {
|
|
521
|
+
const key = `${action.id}:${action.next_action}`;
|
|
522
|
+
if (seen.has(key)) continue;
|
|
523
|
+
seen.add(key);
|
|
524
|
+
unique.push(action);
|
|
525
|
+
}
|
|
526
|
+
return unique;
|
|
414
527
|
}
|
|
415
528
|
|
|
416
529
|
async function tools(args = []) {
|
|
@@ -471,6 +584,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
471
584
|
packageInfo.packageJson ? JSON.stringify(packageInfo.pkg?.devDependencies || {}) : ''
|
|
472
585
|
].join('\n'));
|
|
473
586
|
const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
|
|
587
|
+
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
474
588
|
const sqlScan = await scanSqlFiles(dir);
|
|
475
589
|
const rows = [
|
|
476
590
|
readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
|
|
@@ -490,6 +604,13 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
490
604
|
...safety.hits,
|
|
491
605
|
...sqlScan.findings.map((finding) => ({ level: finding.level, reason: `${finding.file}: ${finding.reason}` }))
|
|
492
606
|
];
|
|
607
|
+
const nextActionDetails = toolsDoctorNextActionDetails({
|
|
608
|
+
pack,
|
|
609
|
+
rows,
|
|
610
|
+
riskNotes,
|
|
611
|
+
frameworkChecklist,
|
|
612
|
+
commands: detection.packageJson ? detection.commands : {}
|
|
613
|
+
});
|
|
493
614
|
return {
|
|
494
615
|
schemaVersion: 1,
|
|
495
616
|
generatedAt: new Date().toISOString(),
|
|
@@ -500,8 +621,11 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
500
621
|
packageManager: detection.packageJson ? detection.packageManager : null,
|
|
501
622
|
instructionSurfaces,
|
|
502
623
|
projectSurfaces,
|
|
624
|
+
frameworkChecklist,
|
|
503
625
|
sqlScan,
|
|
504
626
|
riskNotes,
|
|
627
|
+
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
628
|
+
nextActionDetails,
|
|
505
629
|
routeRecommendations: {
|
|
506
630
|
dbSupabase: safety.hits.length || sqlScan.findings.length ? '$deep required before claiming safe' : '$deep when destructive or production mutation appears',
|
|
507
631
|
currentDocs: 'use current-docs proof only when version/freshness matters',
|
|
@@ -534,6 +658,14 @@ function printToolsDoctorReport(data) {
|
|
|
534
658
|
for (const surface of data.projectSurfaces) console.log(`- ${surface}`);
|
|
535
659
|
}
|
|
536
660
|
|
|
661
|
+
if (data.frameworkChecklist?.detected) {
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log(`Framework checklist: ${data.frameworkChecklist.framework}`);
|
|
664
|
+
for (const check of data.frameworkChecklist.checks) {
|
|
665
|
+
console.log(`- ${check.id}: ${check.label} (${check.route})`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
537
669
|
if (data.sqlScan.filesScanned > 0) {
|
|
538
670
|
console.log('');
|
|
539
671
|
console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
|
|
@@ -548,6 +680,12 @@ function printToolsDoctorReport(data) {
|
|
|
548
680
|
for (const note of data.riskNotes) console.log(`- ${note.level}: ${note.reason}`);
|
|
549
681
|
}
|
|
550
682
|
|
|
683
|
+
if (data.nextActions?.length) {
|
|
684
|
+
console.log('');
|
|
685
|
+
console.log('Next actions:');
|
|
686
|
+
for (const action of data.nextActions) console.log(`- ${action}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
551
689
|
console.log('');
|
|
552
690
|
console.log('Route recommendations:');
|
|
553
691
|
console.log(`- DB/Supabase destructive or production write work: ${data.routeRecommendations.dbSupabase}`);
|
|
@@ -620,6 +758,60 @@ async function detectProjectToolSurfaces(dir, pkg = null) {
|
|
|
620
758
|
return surfaces;
|
|
621
759
|
}
|
|
622
760
|
|
|
761
|
+
async function detectFrameworkChecklist(dir, pkg = null) {
|
|
762
|
+
const deps = {
|
|
763
|
+
...pkg?.dependencies,
|
|
764
|
+
...pkg?.devDependencies
|
|
765
|
+
};
|
|
766
|
+
const depNames = Object.keys(deps || {});
|
|
767
|
+
const hasReact = depNames.includes('react');
|
|
768
|
+
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'));
|
|
769
|
+
const hasVite = depNames.includes('vite') || await exists(path.join(dir, 'vite.config.js')) || await exists(path.join(dir, 'vite.config.ts'));
|
|
770
|
+
const framework = hasNext ? 'next-app' : hasReact && hasVite ? 'react-vite' : hasReact ? 'react' : pkg ? 'node-package' : 'unknown';
|
|
771
|
+
const uiLike = hasReact || hasNext || hasVite || await exists(path.join(dir, 'app')) || await exists(path.join(dir, 'src', 'app')) || await exists(path.join(dir, 'pages'));
|
|
772
|
+
const checks = [];
|
|
773
|
+
if (uiLike) {
|
|
774
|
+
checks.push(
|
|
775
|
+
checklistRow('states', 'default/loading/error/empty states are reachable before claiming complete', '$ueye'),
|
|
776
|
+
checklistRow('responsive', 'mobile and desktop layouts avoid overlap and clipped text', '$ueye'),
|
|
777
|
+
checklistRow('accessibility', 'interactive controls have accessible labels and keyboard path', '$ueye'),
|
|
778
|
+
checklistRow('runtime', 'browser or in-app runtime evidence is recorded when claiming visual verified', '$ueye')
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
if (hasNext) {
|
|
782
|
+
checks.push(checklistRow('boundaries', 'server/client boundaries and async data states match the framework pattern', '$deep when risky'));
|
|
783
|
+
}
|
|
784
|
+
if (pkg) {
|
|
785
|
+
checks.push(checklistRow('package', 'run the smallest honest type/build/package check before publishing', '$quick or $deep'));
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
schema: 'yam.framework-checklist.v1',
|
|
789
|
+
detected: checks.length > 0,
|
|
790
|
+
uiLike,
|
|
791
|
+
framework,
|
|
792
|
+
routeHint: uiLike ? '$ueye for visual claims; $deep when runtime/data risk appears' : '$quick by default; deepen on risk',
|
|
793
|
+
checks
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function checklistRow(id, label, route) {
|
|
798
|
+
return { id, label, route };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function toolsDoctorNextActions(input) {
|
|
802
|
+
return toolsDoctorNextActionDetails(input).map((action) => action.next_action);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function toolsDoctorNextActionDetails({ pack, rows, riskNotes, frameworkChecklist, commands }) {
|
|
806
|
+
const actions = [];
|
|
807
|
+
if (!pack) actions.push(nextActionDetail('project-pack', 'info', 'project pack missing', 'create or refresh yam.project.md so route choices reuse local project context', 'yam init-project .'));
|
|
808
|
+
if (rows.some((row) => row.name === 'Yam skills' && row.status !== 'ready')) actions.push(nextActionDetail('install-skills', 'warning', 'skill install state is incomplete', 'run `yam install` and restart Codex before relying on installed skills', 'yam install'));
|
|
809
|
+
if (riskNotes.some((note) => note.level === 'danger')) actions.push(nextActionDetail('deep-safety', 'error', 'destructive database or production write signal detected', 'use $deep before claiming destructive database or production write safety', 'yam safety "<command or SQL>"'));
|
|
810
|
+
if (frameworkChecklist?.detected && !commands?.build && !commands?.typecheck) actions.push(nextActionDetail('verification-command', 'warning', 'build/typecheck command not detected', 'add or document a small build/typecheck command for implementation verification', 'yam pack .'));
|
|
811
|
+
if (frameworkChecklist?.uiLike) actions.push(nextActionDetail('ueye-evidence', 'info', 'UI-like project detected', 'for Ueye verified claims, record reference, actual screenshot, and comparison result', 'yam ueye report --reference ref.png --actual shot.png --json'));
|
|
812
|
+
return uniqueNextActionDetails(actions);
|
|
813
|
+
}
|
|
814
|
+
|
|
623
815
|
async function scanSqlFiles(rootDir, { maxFiles = 30, maxBytes = 200000, maxDepth = 5 } = {}) {
|
|
624
816
|
const root = path.resolve(rootDir || process.cwd());
|
|
625
817
|
const files = [];
|
|
@@ -949,10 +1141,23 @@ async function release(args = []) {
|
|
|
949
1141
|
console.log(`- ${check.id}: ${check.status} (${check.duration_ms}ms)`);
|
|
950
1142
|
if (check.note) console.log(` ${check.note}`);
|
|
951
1143
|
}
|
|
1144
|
+
if (report.provenance) {
|
|
1145
|
+
console.log(`- Git commit: ${report.provenance.git_commit || 'unknown'}`);
|
|
1146
|
+
console.log(`- Git dirty: ${report.provenance.git_dirty ? 'yes' : 'no'}`);
|
|
1147
|
+
console.log(`- Dist freshness: ${report.freshness.dist}`);
|
|
1148
|
+
}
|
|
1149
|
+
if (report.tarball) {
|
|
1150
|
+
console.log(`- Tarball: ${report.tarball.tarball_name || report.tarball.status}`);
|
|
1151
|
+
if (report.tarball.file_count !== undefined) console.log(`- Tarball files: ${report.tarball.file_count}`);
|
|
1152
|
+
}
|
|
952
1153
|
if (report.failed.length) {
|
|
953
1154
|
console.log('- Failed:');
|
|
954
1155
|
for (const id of report.failed) console.log(` - ${id}`);
|
|
955
1156
|
}
|
|
1157
|
+
if (report.next_actions?.length) {
|
|
1158
|
+
console.log('- Next actions:');
|
|
1159
|
+
for (const action of report.next_actions) console.log(` - ${action.next_action}`);
|
|
1160
|
+
}
|
|
956
1161
|
if (!report.ok) process.exitCode = 1;
|
|
957
1162
|
}
|
|
958
1163
|
|
|
@@ -967,6 +1172,10 @@ function runReleaseReport() {
|
|
|
967
1172
|
['dist_freshness', ['npm', ['run', 'dist:freshness']]]
|
|
968
1173
|
].map(([id, command]) => runReleaseCheck(id, command));
|
|
969
1174
|
const failed = checks.filter((check) => check.status !== 'passed').map((check) => check.id);
|
|
1175
|
+
const provenance = releaseProvenance(checks);
|
|
1176
|
+
const tarball = releaseTarballProvenance();
|
|
1177
|
+
const freshness = releaseFreshness(checks, provenance, tarball);
|
|
1178
|
+
const nextActions = releaseNextActions(checks, provenance, tarball);
|
|
970
1179
|
return {
|
|
971
1180
|
schema: 'yam.release-report.v1',
|
|
972
1181
|
generated_at: startedAt,
|
|
@@ -975,10 +1184,117 @@ function runReleaseReport() {
|
|
|
975
1184
|
ok: failed.length === 0,
|
|
976
1185
|
truth_status: failed.length === 0 ? 'verified' : 'blocked',
|
|
977
1186
|
checks,
|
|
978
|
-
failed
|
|
1187
|
+
failed,
|
|
1188
|
+
provenance,
|
|
1189
|
+
tarball,
|
|
1190
|
+
freshness,
|
|
1191
|
+
next_actions: nextActions
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function releaseProvenance(checks = []) {
|
|
1196
|
+
const gitCommit = runReadOnlyCommand('git', ['rev-parse', '--short', 'HEAD']);
|
|
1197
|
+
const gitStatus = runReadOnlyCommand('git', ['status', '--short']);
|
|
1198
|
+
const distChanged = runReadOnlyCommand('git', ['diff', '--name-only', '--', 'dist']);
|
|
1199
|
+
const packageFiles = checks.find((check) => check.id === 'package_boundary')?.status === 'passed' ? 'checked' : 'not-verified';
|
|
1200
|
+
return {
|
|
1201
|
+
schema: 'yam.release-provenance.v1',
|
|
1202
|
+
package_name: PACKAGE_JSON.name,
|
|
1203
|
+
version: VERSION,
|
|
1204
|
+
git_commit: gitCommit.trim(),
|
|
1205
|
+
git_dirty: Boolean(gitStatus.trim()),
|
|
1206
|
+
git_status_lines: gitStatus.trim().split(/\r?\n/).filter(Boolean).slice(0, 20),
|
|
1207
|
+
dist_changed_files: distChanged.trim().split(/\r?\n/).filter(Boolean).slice(0, 20),
|
|
1208
|
+
package_files: packageFiles,
|
|
1209
|
+
generated_at: new Date().toISOString(),
|
|
1210
|
+
truth_status: gitCommit.trim() && packageFiles === 'checked' ? 'partial' : 'assumed'
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function releaseTarballProvenance() {
|
|
1215
|
+
const result = spawnSync('npm', ['pack', '--json', '--dry-run', '--ignore-scripts'], {
|
|
1216
|
+
cwd: ROOT,
|
|
1217
|
+
encoding: 'utf8',
|
|
1218
|
+
env: { ...process.env, npm_config_cache: path.join(os.tmpdir(), 'yam-npm-cache') },
|
|
1219
|
+
timeout: 120000
|
|
1220
|
+
});
|
|
1221
|
+
if (result.status !== 0) {
|
|
1222
|
+
return {
|
|
1223
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1224
|
+
status: 'blocked',
|
|
1225
|
+
reason: summarizeCheckOutput([result.stdout, result.stderr, result.error?.message].filter(Boolean).join('\n')),
|
|
1226
|
+
truth_status: 'blocked'
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
try {
|
|
1230
|
+
const data = JSON.parse(String(result.stdout || '[]'))[0] || {};
|
|
1231
|
+
return {
|
|
1232
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1233
|
+
status: 'checked',
|
|
1234
|
+
tarball_name: String(data.filename || ''),
|
|
1235
|
+
package_size: Number(data.size || data.packageSize || 0),
|
|
1236
|
+
unpacked_size: Number(data.unpackedSize || 0),
|
|
1237
|
+
file_count: Array.isArray(data.files) ? data.files.length : Number(data.entryCount || 0),
|
|
1238
|
+
integrity: String(data.integrity || ''),
|
|
1239
|
+
shasum: String(data.shasum || ''),
|
|
1240
|
+
truth_status: data.filename ? 'verified' : 'partial'
|
|
1241
|
+
};
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
return {
|
|
1244
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1245
|
+
status: 'blocked',
|
|
1246
|
+
reason: `could not parse npm pack output: ${errorMessage(error)}`,
|
|
1247
|
+
truth_status: 'blocked'
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function releaseFreshness(checks = [], provenance: AnyRecord = {}, tarball: AnyRecord = {}) {
|
|
1253
|
+
const checkStatus = (id) => checks.find((check) => check.id === id)?.status || 'not-run';
|
|
1254
|
+
const dist = checkStatus('dist_freshness') === 'passed' && !provenance.dist_changed_files?.length ? 'fresh' : 'needs-check';
|
|
1255
|
+
const registry = checkStatus('registry_status') === 'passed' ? 'publishable-version' : 'not-verified';
|
|
1256
|
+
const boundary = checkStatus('package_boundary') === 'passed' ? 'checked' : 'not-verified';
|
|
1257
|
+
const smoke = checkStatus('cli_smoke') === 'passed' ? 'checked' : 'not-verified';
|
|
1258
|
+
const tarballStatus = tarball.status === 'checked' ? 'checked' : 'not-verified';
|
|
1259
|
+
return {
|
|
1260
|
+
schema: 'yam.release-freshness.v1',
|
|
1261
|
+
dist,
|
|
1262
|
+
registry,
|
|
1263
|
+
package_boundary: boundary,
|
|
1264
|
+
cli_smoke: smoke,
|
|
1265
|
+
tarball: tarballStatus,
|
|
1266
|
+
truth_status: [dist, registry, boundary, smoke, tarballStatus].every((value) => !/not-verified|needs-check/.test(value)) ? 'verified' : 'partial'
|
|
979
1267
|
};
|
|
980
1268
|
}
|
|
981
1269
|
|
|
1270
|
+
function releaseNextActions(checks = [], provenance: AnyRecord = {}, tarball: AnyRecord = {}) {
|
|
1271
|
+
const actions = [];
|
|
1272
|
+
for (const check of checks) {
|
|
1273
|
+
if (check.status === 'passed') continue;
|
|
1274
|
+
actions.push(nextActionDetail(`release-${check.id}`, check.status === 'blocked' ? 'error' : 'warning', check.note || `${check.id} did not pass`, `fix ${check.id} before publishing`, check.command));
|
|
1275
|
+
}
|
|
1276
|
+
if (provenance.git_dirty) {
|
|
1277
|
+
actions.push(nextActionDetail('release-git-dirty', 'warning', 'working tree has uncommitted changes', 'commit or intentionally keep local-only changes before tagging a release', 'git status --short'));
|
|
1278
|
+
}
|
|
1279
|
+
if (provenance.dist_changed_files?.length) {
|
|
1280
|
+
actions.push(nextActionDetail('release-dist-drift', 'warning', 'dist has unstaged or uncommitted differences', 'run build and commit generated dist only if this package publishes dist', 'npm run build'));
|
|
1281
|
+
}
|
|
1282
|
+
if (tarball.status !== 'checked') {
|
|
1283
|
+
actions.push(nextActionDetail('release-tarball-provenance', 'error', tarball.reason || 'tarball provenance was not produced', 'run npm pack dry-run before publishing', 'npm pack --dry-run'));
|
|
1284
|
+
}
|
|
1285
|
+
return uniqueNextActionDetails(actions);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function runReadOnlyCommand(command, args = []) {
|
|
1289
|
+
const result = spawnSync(command, args, {
|
|
1290
|
+
cwd: ROOT,
|
|
1291
|
+
encoding: 'utf8',
|
|
1292
|
+
timeout: 30000
|
|
1293
|
+
});
|
|
1294
|
+
if (result.status !== 0) return '';
|
|
1295
|
+
return String(result.stdout || '');
|
|
1296
|
+
}
|
|
1297
|
+
|
|
982
1298
|
function runReleaseCheck(id, commandSpec) {
|
|
983
1299
|
const [command, args] = commandSpec;
|
|
984
1300
|
const start = Date.now();
|
|
@@ -1019,6 +1335,7 @@ async function ueye(args = []) {
|
|
|
1019
1335
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return ueyeUsage();
|
|
1020
1336
|
if (subcommand === 'capture') return ueyeCapture(args.slice(1));
|
|
1021
1337
|
if (subcommand === 'compare') return ueyeCompare(args.slice(1));
|
|
1338
|
+
if (subcommand === 'report') return ueyeReport(args.slice(1));
|
|
1022
1339
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
1023
1340
|
return ueyeUsage();
|
|
1024
1341
|
}
|
|
@@ -1031,10 +1348,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
|
|
|
1031
1348
|
Usage:
|
|
1032
1349
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
1033
1350
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
1351
|
+
yam ueye report [--reference ref.png] [--actual screenshot.png] [--review-session-id id] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
|
|
1034
1352
|
|
|
1035
1353
|
Notes:
|
|
1036
1354
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
1037
1355
|
compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
|
|
1356
|
+
report produces a proof-ready Ueye visual run report and continuity/comparison record without requiring a new capture.
|
|
1038
1357
|
`);
|
|
1039
1358
|
}
|
|
1040
1359
|
|
|
@@ -1155,6 +1474,454 @@ async function ueyeCompare(args = []) {
|
|
|
1155
1474
|
}
|
|
1156
1475
|
}
|
|
1157
1476
|
|
|
1477
|
+
async function ueyeReport(args = []) {
|
|
1478
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
1479
|
+
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'json']));
|
|
1480
|
+
const reference = String(flags.reference || flags.ref || '');
|
|
1481
|
+
const actual = String(flags.actual || flags.screenshot || '');
|
|
1482
|
+
const referenceSources = [];
|
|
1483
|
+
const implementationSources = [];
|
|
1484
|
+
let comparisonResult = 'not-verified';
|
|
1485
|
+
let blockedReason = String(flags.blocked_reason || '');
|
|
1486
|
+
const reviewSessionId = String(flags.review_session_id || `ueye-${timestampId()}`);
|
|
1487
|
+
const previousReport = await readPreviousUeyeReport(flags.previous_report);
|
|
1488
|
+
|
|
1489
|
+
try {
|
|
1490
|
+
if (reference) {
|
|
1491
|
+
const info = await imageFileInfo(reference);
|
|
1492
|
+
referenceSources.push(buildUeyeVisualProvenance({
|
|
1493
|
+
source_kind: 'reference_image',
|
|
1494
|
+
source_path: info.path,
|
|
1495
|
+
source_hash: info.sha256,
|
|
1496
|
+
reference_id: String(flags.reference_id || path.basename(info.path)),
|
|
1497
|
+
local_only: true,
|
|
1498
|
+
operator_provided: true,
|
|
1499
|
+
comparison_result: 'reference-recorded',
|
|
1500
|
+
truth_status: 'partial'
|
|
1501
|
+
}));
|
|
1502
|
+
}
|
|
1503
|
+
if (actual) {
|
|
1504
|
+
const info = await imageFileInfo(actual);
|
|
1505
|
+
implementationSources.push(buildUeyeVisualProvenance({
|
|
1506
|
+
source_kind: 'implementation_screenshot',
|
|
1507
|
+
source_path: info.path,
|
|
1508
|
+
source_hash: info.sha256,
|
|
1509
|
+
reference_id: String(flags.reference_id || referenceSources[0]?.reference_id || ''),
|
|
1510
|
+
screenshot_id: String(flags.current_screenshot_id || flags.screenshot_id || path.basename(info.path)),
|
|
1511
|
+
local_only: true,
|
|
1512
|
+
operator_provided: false,
|
|
1513
|
+
comparison_result: 'implementation-recorded',
|
|
1514
|
+
truth_status: 'partial'
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
|
|
1518
|
+
comparisonResult = 'matched';
|
|
1519
|
+
} else if (reference && actual) {
|
|
1520
|
+
comparisonResult = 'different';
|
|
1521
|
+
}
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
blockedReason = errorMessage(error);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const report = buildUeyeRunReport({
|
|
1527
|
+
reference_sources: referenceSources,
|
|
1528
|
+
implementation_sources: implementationSources,
|
|
1529
|
+
comparison_result: comparisonResult,
|
|
1530
|
+
design_quality: String(flags.design_quality || 'not-checked'),
|
|
1531
|
+
blocked_reason: blockedReason,
|
|
1532
|
+
next_action: String(flags.next_visual_action || flags.next_action || '')
|
|
1533
|
+
});
|
|
1534
|
+
const comparisonReport = buildUeyeComparisonReport({
|
|
1535
|
+
reviewSessionId,
|
|
1536
|
+
previousReport,
|
|
1537
|
+
referenceSources,
|
|
1538
|
+
implementationSources,
|
|
1539
|
+
comparisonResult,
|
|
1540
|
+
designQuality: String(flags.design_quality || 'not-checked'),
|
|
1541
|
+
similar: arrayFlag(flags.similar),
|
|
1542
|
+
different: arrayFlag(flags.different),
|
|
1543
|
+
missing: arrayFlag(flags.missing),
|
|
1544
|
+
notes: arrayFlag(flags.comparison_notes),
|
|
1545
|
+
resolved: arrayFlag(flags.resolved),
|
|
1546
|
+
newFindings: arrayFlag(flags.new_finding),
|
|
1547
|
+
stillOpen: arrayFlag(flags.still_open),
|
|
1548
|
+
regressions: arrayFlag(flags.regression),
|
|
1549
|
+
previousScreenshotId: String(flags.previous_screenshot_id || previousReport?.screenshot_id || ''),
|
|
1550
|
+
currentScreenshotId: String(flags.current_screenshot_id || flags.screenshot_id || implementationSources[0]?.screenshot_id || ''),
|
|
1551
|
+
viewport: String(flags.viewport || ''),
|
|
1552
|
+
state: String(flags.state || 'unknown'),
|
|
1553
|
+
nextAction: report.next_action,
|
|
1554
|
+
truthStatus: report.truth_status
|
|
1555
|
+
});
|
|
1556
|
+
const result = {
|
|
1557
|
+
...report,
|
|
1558
|
+
review_session_id: reviewSessionId,
|
|
1559
|
+
tool_intent: 'visual',
|
|
1560
|
+
capture_backend: String(flags.capture_backend || 'not-recorded'),
|
|
1561
|
+
compare_backend: String(flags.compare_backend || 'local-file-hash'),
|
|
1562
|
+
continuity: comparisonReport.continuity,
|
|
1563
|
+
comparison_report: comparisonReport
|
|
1564
|
+
};
|
|
1565
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
|
|
1566
|
+
if (report.truth_status === 'blocked') process.exitCode = 1;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
async function readPreviousUeyeReport(file = '') {
|
|
1570
|
+
const target = String(file || '');
|
|
1571
|
+
if (!target) return null;
|
|
1572
|
+
try {
|
|
1573
|
+
const data = await readJson(path.resolve(expandHome(target)));
|
|
1574
|
+
return {
|
|
1575
|
+
path: path.resolve(expandHome(target)),
|
|
1576
|
+
review_session_id: data.review_session_id || data.comparison_report?.review_session_id || '',
|
|
1577
|
+
screenshot_id: data.comparison_report?.current_screenshot_id || data.current_screenshot_id || data.screenshot_id || '',
|
|
1578
|
+
truth_status: data.truth_status || data.comparison_report?.truth_status || 'partial',
|
|
1579
|
+
comparison_result: data.comparison_result || data.comparison_report?.comparison_result || 'not-verified'
|
|
1580
|
+
};
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
return {
|
|
1583
|
+
path: path.resolve(expandHome(target)),
|
|
1584
|
+
blocked_reason: errorMessage(error),
|
|
1585
|
+
truth_status: 'blocked',
|
|
1586
|
+
comparison_result: 'not-verified'
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function buildUeyeComparisonReport({
|
|
1592
|
+
reviewSessionId,
|
|
1593
|
+
previousReport,
|
|
1594
|
+
referenceSources,
|
|
1595
|
+
implementationSources,
|
|
1596
|
+
comparisonResult,
|
|
1597
|
+
designQuality,
|
|
1598
|
+
similar,
|
|
1599
|
+
different,
|
|
1600
|
+
missing,
|
|
1601
|
+
notes,
|
|
1602
|
+
resolved,
|
|
1603
|
+
newFindings,
|
|
1604
|
+
stillOpen,
|
|
1605
|
+
regressions,
|
|
1606
|
+
previousScreenshotId,
|
|
1607
|
+
currentScreenshotId,
|
|
1608
|
+
viewport,
|
|
1609
|
+
state,
|
|
1610
|
+
nextAction,
|
|
1611
|
+
truthStatus
|
|
1612
|
+
}) {
|
|
1613
|
+
const hasReference = referenceSources.length > 0;
|
|
1614
|
+
const hasImplementation = implementationSources.length > 0;
|
|
1615
|
+
const continuityTruth = previousReport?.blocked_reason ? 'blocked' : previousReport ? 'partial' : 'assumed';
|
|
1616
|
+
return {
|
|
1617
|
+
schema: 'yam.ueye-comparison-report.v1',
|
|
1618
|
+
review_session_id: reviewSessionId,
|
|
1619
|
+
previous_report: previousReport,
|
|
1620
|
+
continuity: {
|
|
1621
|
+
schema: 'yam.ueye-review-continuity.v1',
|
|
1622
|
+
current_review_session_id: reviewSessionId,
|
|
1623
|
+
previous_review_session_id: previousReport?.review_session_id || '',
|
|
1624
|
+
previous_report_path: previousReport?.path || '',
|
|
1625
|
+
previous_screenshot_id: previousScreenshotId || previousReport?.screenshot_id || '',
|
|
1626
|
+
current_screenshot_id: currentScreenshotId || '',
|
|
1627
|
+
carries_forward: Boolean(previousReport && !previousReport.blocked_reason),
|
|
1628
|
+
truth_status: continuityTruth
|
|
1629
|
+
},
|
|
1630
|
+
evidence_summary: {
|
|
1631
|
+
has_reference: hasReference,
|
|
1632
|
+
has_implementation_screenshot: hasImplementation,
|
|
1633
|
+
reference_count: referenceSources.length,
|
|
1634
|
+
implementation_count: implementationSources.length
|
|
1635
|
+
},
|
|
1636
|
+
comparison_result: comparisonResult,
|
|
1637
|
+
design_quality: designQuality,
|
|
1638
|
+
similar,
|
|
1639
|
+
different,
|
|
1640
|
+
missing,
|
|
1641
|
+
resolved,
|
|
1642
|
+
new_findings: newFindings,
|
|
1643
|
+
still_open: stillOpen,
|
|
1644
|
+
regressions,
|
|
1645
|
+
previous_screenshot_id: previousScreenshotId || previousReport?.screenshot_id || '',
|
|
1646
|
+
current_screenshot_id: currentScreenshotId || '',
|
|
1647
|
+
viewport,
|
|
1648
|
+
state,
|
|
1649
|
+
notes,
|
|
1650
|
+
next_action: nextAction || (truthStatus === 'verified' ? 'no action required' : 'capture or provide implementation screenshot, then compare again'),
|
|
1651
|
+
truth_status: truthStatus
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
async function media(args = []) {
|
|
1656
|
+
const subcommand = args[0] || 'help';
|
|
1657
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return mediaUsage();
|
|
1658
|
+
if (subcommand === 'proof') return mediaProof(args.slice(1));
|
|
1659
|
+
console.error(`unknown media command: ${subcommand}`);
|
|
1660
|
+
return mediaUsage();
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function mediaUsage() {
|
|
1664
|
+
console.log(`yam media
|
|
1665
|
+
|
|
1666
|
+
Opt-in media generation proof helpers. Generated media can support visual direction, but it cannot verify implemented UI without implementation screenshot evidence.
|
|
1667
|
+
|
|
1668
|
+
Usage:
|
|
1669
|
+
yam media proof [--tool name] [--requested] [--attempted] [--output file] [--wait-loop] [--blocked-reason text] [--json]
|
|
1670
|
+
`);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
async function mediaProof(args = []) {
|
|
1674
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return mediaUsage();
|
|
1675
|
+
const flags = parseSimpleFlags(args, new Set(['tool', 'requested', 'attempted', 'output', 'wait-loop', 'blocked-reason', 'next-action', 'json']));
|
|
1676
|
+
const output = String(flags.output || '');
|
|
1677
|
+
let outputHash = 'unknown';
|
|
1678
|
+
let outputPath = '';
|
|
1679
|
+
let blockedReason = String(flags.blocked_reason || '');
|
|
1680
|
+
if (output) {
|
|
1681
|
+
try {
|
|
1682
|
+
const info = await imageFileInfo(output);
|
|
1683
|
+
outputHash = info.sha256;
|
|
1684
|
+
outputPath = info.path;
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
blockedReason = blockedReason || errorMessage(error);
|
|
1687
|
+
outputPath = path.resolve(expandHome(output));
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const proof = buildMediaGenerationProof({
|
|
1691
|
+
tool_name: String(flags.tool || ''),
|
|
1692
|
+
generation_requested: Boolean(flags.requested),
|
|
1693
|
+
generation_attempted: Boolean(flags.attempted || output),
|
|
1694
|
+
output_path: outputPath,
|
|
1695
|
+
output_hash: outputHash,
|
|
1696
|
+
wait_loop_checked: Boolean(flags.wait_loop),
|
|
1697
|
+
blocked_reason: blockedReason,
|
|
1698
|
+
next_action: String(flags.next_action || '')
|
|
1699
|
+
});
|
|
1700
|
+
printJsonOrHuman(proof, Boolean(flags.json), 'Media proof');
|
|
1701
|
+
if (proof.truth_status === 'blocked') process.exitCode = 1;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async function runtime(args = []) {
|
|
1705
|
+
const subcommand = args[0] || 'help';
|
|
1706
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return runtimeUsage();
|
|
1707
|
+
if (subcommand === 'evidence') return runtimeEvidence(args.slice(1));
|
|
1708
|
+
console.error(`unknown runtime command: ${subcommand}`);
|
|
1709
|
+
return runtimeUsage();
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function runtimeUsage() {
|
|
1713
|
+
console.log(`yam runtime
|
|
1714
|
+
|
|
1715
|
+
Usage:
|
|
1716
|
+
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--evidence-id id] [--command text] [--pid n] [--port n] [--url URL] [--exit-code n] [--screenshot-id id] [--cleanup-checked] [--note text] [--json]
|
|
1717
|
+
|
|
1718
|
+
Notes:
|
|
1719
|
+
Records a small runtime evidence shape. It does not start, stop, or inspect processes by itself.
|
|
1720
|
+
`);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
async function runtimeEvidence(args = []) {
|
|
1724
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return runtimeUsage();
|
|
1725
|
+
const flags = parseSimpleFlags(args, new Set(['backend', 'claim', 'evidence-id', 'command', 'pid', 'port', 'url', 'exit-code', 'screenshot-id', 'started-at', 'stopped-at', 'cleanup-checked', 'note', 'truth', 'intent', 'json']));
|
|
1726
|
+
const evidence = buildRuntimeBackendEvidence({
|
|
1727
|
+
backend: flags.backend,
|
|
1728
|
+
claim: flags.claim,
|
|
1729
|
+
evidence_id: String(flags.evidence_id || `runtime-${timestampId()}`),
|
|
1730
|
+
command: String(flags.command || ''),
|
|
1731
|
+
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
1732
|
+
note: String(flags.note || ''),
|
|
1733
|
+
truth_status: isTruthStatus(flags.truth) ? flags.truth : undefined
|
|
1734
|
+
});
|
|
1735
|
+
const runtimeDetails = runtimeEvidenceDetails(flags);
|
|
1736
|
+
if (!isTruthStatus(flags.truth) && evidence.truth_status === 'verified' && !runtimeDetailsHasVerification(runtimeDetails)) {
|
|
1737
|
+
evidence.truth_status = 'partial';
|
|
1738
|
+
}
|
|
1739
|
+
const result = {
|
|
1740
|
+
...evidence,
|
|
1741
|
+
runtime_details: runtimeDetails,
|
|
1742
|
+
tool_intent: normalizeToolIntent(flags.intent || 'runtime'),
|
|
1743
|
+
next_action: runtimeEvidenceNextAction(evidence)
|
|
1744
|
+
};
|
|
1745
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Runtime evidence');
|
|
1746
|
+
if (result.truth_status === 'blocked' || result.truth_status === 'real_required_missing') process.exitCode = 1;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function runtimeEvidenceDetails(flags: AnyRecord = {}) {
|
|
1750
|
+
return {
|
|
1751
|
+
schema: 'yam.runtime-details.v1',
|
|
1752
|
+
pid: numberOrNull(flags.pid),
|
|
1753
|
+
port: numberOrNull(flags.port),
|
|
1754
|
+
url: String(flags.url || ''),
|
|
1755
|
+
exit_code: numberOrNull(flags.exit_code),
|
|
1756
|
+
screenshot_id: String(flags.screenshot_id || ''),
|
|
1757
|
+
started_at: String(flags.started_at || ''),
|
|
1758
|
+
stopped_at: String(flags.stopped_at || '')
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
function runtimeDetailsHasVerification(details: AnyRecord = {}) {
|
|
1763
|
+
return Boolean(details.url || details.screenshot_id || details.pid !== null || details.port !== null || details.exit_code !== null || details.started_at || details.stopped_at);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function runtimeEvidenceNextAction(evidence) {
|
|
1767
|
+
if (evidence.truth_status === 'proven' || evidence.truth_status === 'verified') return 'attach this evidence to `yam proof` when making a runtime claim';
|
|
1768
|
+
if (evidence.claim === 'cleanup_verified' && !evidence.cleanup_checked) return 'rerun with --cleanup-checked only after process exit or intentional persistence is confirmed';
|
|
1769
|
+
if (evidence.backend === 'none' || evidence.backend === 'unknown') return 'record the actual runtime backend before claiming runtime verification';
|
|
1770
|
+
return 'add command output, screenshot id, pid/session id, or cleanup proof before upgrading the claim';
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
async function mission(args = []) {
|
|
1774
|
+
const subcommand = args[0] || 'help';
|
|
1775
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return missionUsage();
|
|
1776
|
+
if (subcommand === 'queue' || subcommand === 'patch') return missionQueue(args.slice(1));
|
|
1777
|
+
console.error(`unknown mission command: ${subcommand}`);
|
|
1778
|
+
return missionUsage();
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function missionUsage() {
|
|
1782
|
+
console.log(`yam mission
|
|
1783
|
+
|
|
1784
|
+
Usage:
|
|
1785
|
+
yam mission queue [--lane-id id] [--agent-id id] [--status pending|applied|verified|blocked|reverted] [--scope text] [--changed file] [--depends-on lane] [--generated file] [--verification-hint text] [--rollback-hint text] [--before-check text] [--out file] [--truth status] [--json]
|
|
1786
|
+
|
|
1787
|
+
Notes:
|
|
1788
|
+
Produces a patch queue item for mission handoff/review. It does not run workers; persistence is opt-in with --out.
|
|
1789
|
+
`);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
async function missionQueue(args = []) {
|
|
1793
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return missionUsage();
|
|
1794
|
+
const flags = parseSimpleFlags(args, new Set(['lane-id', 'agent-id', 'status', 'scope', 'changed', 'generated', 'depends-on', 'verification-hint', 'rollback-hint', 'before-check', 'safe-revert-note', 'truth', 'intent', 'out', 'json']));
|
|
1795
|
+
const changedFiles = arrayFlag(flags.changed);
|
|
1796
|
+
const generatedFiles = arrayFlag(flags.generated);
|
|
1797
|
+
const laneId = String(flags.lane_id || flags.agent_id || `lane-${timestampId()}`);
|
|
1798
|
+
const status = normalizeMissionQueueStatus(flags.status || 'pending');
|
|
1799
|
+
const rollback = buildRollbackHint({
|
|
1800
|
+
touched_files: changedFiles,
|
|
1801
|
+
generated_files: generatedFiles,
|
|
1802
|
+
before_check: String(flags.before_check || ''),
|
|
1803
|
+
safe_revert_note: String(flags.safe_revert_note || flags.rollback_hint || '')
|
|
1804
|
+
});
|
|
1805
|
+
const envelope = buildMissionPatchEnvelope({
|
|
1806
|
+
agent_id: String(flags.agent_id || `agent-${timestampId()}`),
|
|
1807
|
+
assigned_scope: String(flags.scope || ''),
|
|
1808
|
+
changed_files: changedFiles,
|
|
1809
|
+
verification_hint: String(flags.verification_hint || ''),
|
|
1810
|
+
rollback_hint: rollback,
|
|
1811
|
+
truth_status: isTruthStatus(flags.truth) ? flags.truth : changedFiles.length ? 'partial' : 'assumed'
|
|
1812
|
+
});
|
|
1813
|
+
const item = {
|
|
1814
|
+
schema: 'yam.mission-patch-queue-item.v1',
|
|
1815
|
+
lane_id: laneId,
|
|
1816
|
+
status,
|
|
1817
|
+
depends_on: arrayFlag(flags.depends_on),
|
|
1818
|
+
tool_intent: normalizeToolIntent(flags.intent || (changedFiles.length || generatedFiles.length ? 'write' : 'read_only')),
|
|
1819
|
+
patch_envelope: envelope,
|
|
1820
|
+
next_action: missionQueueNextAction(envelope, status)
|
|
1821
|
+
};
|
|
1822
|
+
const result = {
|
|
1823
|
+
schema: 'yam.mission-patch-queue-lite.v1',
|
|
1824
|
+
generated_at: new Date().toISOString(),
|
|
1825
|
+
persistence: flags.out ? 'written' : 'not-persisted',
|
|
1826
|
+
out: flags.out ? path.resolve(expandHome(flags.out)) : '',
|
|
1827
|
+
items: [item],
|
|
1828
|
+
queue_depth: 1,
|
|
1829
|
+
next_action: item.next_action,
|
|
1830
|
+
truth_status: envelope.truth_status
|
|
1831
|
+
};
|
|
1832
|
+
if (flags.out) {
|
|
1833
|
+
const target = path.resolve(expandHome(flags.out));
|
|
1834
|
+
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
1835
|
+
await fsp.writeFile(target, `${JSON.stringify(result, null, 2)}\n`);
|
|
1836
|
+
}
|
|
1837
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Mission patch queue');
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function normalizeMissionQueueStatus(value = '') {
|
|
1841
|
+
const text = String(value || '').toLowerCase();
|
|
1842
|
+
if (['pending', 'applied', 'verified', 'blocked', 'reverted'].includes(text)) return text;
|
|
1843
|
+
return 'pending';
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
function missionQueueNextAction(envelope, status = 'pending') {
|
|
1847
|
+
if (status === 'blocked') return 'resolve or document the blocker before applying dependent mission lanes';
|
|
1848
|
+
if (status === 'reverted') return 'rerun the before-check or verification hint before continuing dependent work';
|
|
1849
|
+
if (!envelope.assigned_scope) return 'add --scope so the patch can be reviewed against an assigned boundary';
|
|
1850
|
+
if (!envelope.changed_files.length) return 'add at least one --changed file when this represents code-changing work';
|
|
1851
|
+
if (!envelope.verification_hint) return 'add --verification-hint so reviewers know the smallest honest check';
|
|
1852
|
+
if (!envelope.rollback_hint.safe_revert_note && !envelope.rollback_hint.before_check) return 'add rollback or before-check detail before using this for risky work';
|
|
1853
|
+
return 'attach this envelope to the mission proof summary and cross-check changed files';
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
async function benchmark(args = []) {
|
|
1857
|
+
const subcommand = args[0] || 'help';
|
|
1858
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return benchmarkUsage();
|
|
1859
|
+
if (subcommand === 'report') return benchmarkReport(args.slice(1));
|
|
1860
|
+
console.error(`unknown benchmark command: ${subcommand}`);
|
|
1861
|
+
return benchmarkUsage();
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function benchmarkUsage() {
|
|
1865
|
+
console.log(`yam benchmark
|
|
1866
|
+
|
|
1867
|
+
Usage:
|
|
1868
|
+
yam benchmark report [--label text] [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--next-action text] [--json]
|
|
1869
|
+
|
|
1870
|
+
Notes:
|
|
1871
|
+
Records a small optimization loop result. It does not run benchmarks by itself.
|
|
1872
|
+
`);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
async function benchmarkReport(args = []) {
|
|
1876
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return benchmarkUsage();
|
|
1877
|
+
const flags = parseSimpleFlags(args, new Set(['label', 'baseline', 'current', 'unit', 'target', 'next-action', 'rollback-hint', 'intent', 'json']));
|
|
1878
|
+
const baseline = numberOrNull(flags.baseline);
|
|
1879
|
+
const current = numberOrNull(flags.current);
|
|
1880
|
+
const target = String(flags.target || 'lower').toLowerCase() === 'higher' ? 'higher' : 'lower';
|
|
1881
|
+
const hasNumbers = baseline !== null && current !== null;
|
|
1882
|
+
const delta = hasNumbers ? current - baseline : null;
|
|
1883
|
+
const percent = hasNumbers && baseline !== 0 ? (delta / baseline) * 100 : null;
|
|
1884
|
+
const status = benchmarkStatus({ baseline, current, target });
|
|
1885
|
+
const result = {
|
|
1886
|
+
schema: 'yam.benchmark-report.v1',
|
|
1887
|
+
generated_at: new Date().toISOString(),
|
|
1888
|
+
label: String(flags.label || 'unnamed'),
|
|
1889
|
+
baseline,
|
|
1890
|
+
current,
|
|
1891
|
+
unit: String(flags.unit || ''),
|
|
1892
|
+
target,
|
|
1893
|
+
delta,
|
|
1894
|
+
percent,
|
|
1895
|
+
status,
|
|
1896
|
+
tool_intent: normalizeToolIntent(flags.intent || 'read_only'),
|
|
1897
|
+
rollback_hint: String(flags.rollback_hint || (status === 'regressed' ? 'inspect the optimization patch and revert the touched surface if the regression is confirmed' : 'no rollback action recorded')),
|
|
1898
|
+
next_action: String(flags.next_action || benchmarkNextAction(status)),
|
|
1899
|
+
truth_status: hasNumbers ? 'verified' : 'partial'
|
|
1900
|
+
};
|
|
1901
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Benchmark report');
|
|
1902
|
+
if (status === 'regressed') process.exitCode = 1;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function numberOrNull(value) {
|
|
1906
|
+
if (value === undefined || value === null || value === '') return null;
|
|
1907
|
+
const number = Number(value);
|
|
1908
|
+
return Number.isFinite(number) ? number : null;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
function benchmarkStatus({ baseline, current, target }) {
|
|
1912
|
+
if (baseline === null || current === null) return 'needs-baseline';
|
|
1913
|
+
if (current === baseline) return 'unchanged';
|
|
1914
|
+
if (target === 'higher') return current > baseline ? 'improved' : 'regressed';
|
|
1915
|
+
return current < baseline ? 'improved' : 'regressed';
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function benchmarkNextAction(status) {
|
|
1919
|
+
if (status === 'improved') return 'record the measurement source and keep the smallest relevant regression check';
|
|
1920
|
+
if (status === 'regressed') return 'treat this as a fix-first item before continuing planned optimization';
|
|
1921
|
+
if (status === 'unchanged') return 'keep the change only if it improves clarity or enables the next measured step';
|
|
1922
|
+
return 'capture baseline and current values before claiming optimization impact';
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1158
1925
|
function parseSimpleFlags(args = [], allowed = new Set<string>()) {
|
|
1159
1926
|
const flags: AnyRecord = {};
|
|
1160
1927
|
const positionals: string[] = [];
|
|
@@ -1168,13 +1935,17 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
|
|
|
1168
1935
|
const key = rawKey.slice(2);
|
|
1169
1936
|
if (allowed.size && !allowed.has(key)) continue;
|
|
1170
1937
|
const normalizedKey = key.replace(/-/g, '_');
|
|
1171
|
-
if (['json', 'full-page'].includes(key)) {
|
|
1938
|
+
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
|
|
1172
1939
|
flags[normalizedKey] = true;
|
|
1173
1940
|
continue;
|
|
1174
1941
|
}
|
|
1175
1942
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1176
1943
|
if (inlineValue === undefined) index += 1;
|
|
1177
|
-
flags[normalizedKey]
|
|
1944
|
+
if (flags[normalizedKey] !== undefined) {
|
|
1945
|
+
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1946
|
+
} else {
|
|
1947
|
+
flags[normalizedKey] = value;
|
|
1948
|
+
}
|
|
1178
1949
|
}
|
|
1179
1950
|
flags._ = positionals;
|
|
1180
1951
|
return flags;
|
|
@@ -1309,9 +2080,11 @@ async function proof(args = []) {
|
|
|
1309
2080
|
printProofList('Evidence', summary.evidence);
|
|
1310
2081
|
printProofList('Visual evidence', summary.visual);
|
|
1311
2082
|
printProofList('Runtime evidence', summary.runtime);
|
|
2083
|
+
printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
|
|
1312
2084
|
printProofList('Visual provenance', summary.visualProvenance || []);
|
|
1313
2085
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
1314
2086
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
2087
|
+
printProofList('Media proof', summary.mediaProof || []);
|
|
1315
2088
|
printProofList('Changed surfaces', summary.changed);
|
|
1316
2089
|
printProofList('Skipped', summary.skipped);
|
|
1317
2090
|
printProofList('Blocked', summary.blocked);
|
|
@@ -1386,6 +2159,11 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1386
2159
|
evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
|
|
1387
2160
|
visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
|
|
1388
2161
|
runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
|
|
2162
|
+
runtimeBackendEvidence: [
|
|
2163
|
+
...structuredEvidenceList(artifact.runtimeBackendEvidence),
|
|
2164
|
+
...structuredEvidenceList(parsed.flags.runtime_backend_evidence),
|
|
2165
|
+
...directRuntimeBackendEvidence(parsed.flags)
|
|
2166
|
+
],
|
|
1389
2167
|
visualProvenance: [
|
|
1390
2168
|
...structuredEvidenceList(artifact.visualProvenance),
|
|
1391
2169
|
...structuredEvidenceList(parsed.flags.visual_provenance)
|
|
@@ -1398,6 +2176,10 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1398
2176
|
...structuredEvidenceList(artifact.rollbackHint),
|
|
1399
2177
|
...structuredEvidenceList(parsed.flags.rollback_hint)
|
|
1400
2178
|
],
|
|
2179
|
+
mediaProof: [
|
|
2180
|
+
...structuredEvidenceList(artifact.mediaProof),
|
|
2181
|
+
...structuredEvidenceList(parsed.flags.media_proof)
|
|
2182
|
+
],
|
|
1401
2183
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
1402
2184
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
1403
2185
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -1448,6 +2230,9 @@ function renderProofMarkdown(summary) {
|
|
|
1448
2230
|
'## Runtime evidence',
|
|
1449
2231
|
...renderProofMarkdownList(summary.runtime),
|
|
1450
2232
|
'',
|
|
2233
|
+
'## Runtime backend evidence',
|
|
2234
|
+
...renderProofMarkdownList(summary.runtimeBackendEvidence || []),
|
|
2235
|
+
'',
|
|
1451
2236
|
'## Visual provenance',
|
|
1452
2237
|
...renderProofMarkdownList(summary.visualProvenance || []),
|
|
1453
2238
|
'',
|
|
@@ -1457,6 +2242,9 @@ function renderProofMarkdown(summary) {
|
|
|
1457
2242
|
'## Rollback hint',
|
|
1458
2243
|
...renderProofMarkdownList(summary.rollbackHint || []),
|
|
1459
2244
|
'',
|
|
2245
|
+
'## Media proof',
|
|
2246
|
+
...renderProofMarkdownList(summary.mediaProof || []),
|
|
2247
|
+
'',
|
|
1460
2248
|
'## Changed surfaces',
|
|
1461
2249
|
...renderProofMarkdownList(summary.changed),
|
|
1462
2250
|
'',
|
|
@@ -1489,7 +2277,7 @@ function renderProofMarkdownList(values) {
|
|
|
1489
2277
|
function parseProofArgs(args = []) {
|
|
1490
2278
|
const flags: AnyRecord = {};
|
|
1491
2279
|
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']);
|
|
2280
|
+
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
2281
|
for (let index = 0; index < args.length; index += 1) {
|
|
1494
2282
|
const arg = args[index];
|
|
1495
2283
|
if (arg.startsWith('--')) {
|
|
@@ -1497,13 +2285,13 @@ function parseProofArgs(args = []) {
|
|
|
1497
2285
|
const key = rawKey.slice(2);
|
|
1498
2286
|
if (!aliases.has(key)) continue;
|
|
1499
2287
|
const normalizedKey = key.replace(/-/g, '_');
|
|
1500
|
-
if (key === 'json' || key.startsWith('require-')) {
|
|
2288
|
+
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
|
|
1501
2289
|
flags[normalizedKey] = true;
|
|
1502
2290
|
continue;
|
|
1503
2291
|
}
|
|
1504
2292
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1505
2293
|
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)) {
|
|
2294
|
+
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
2295
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1508
2296
|
} else {
|
|
1509
2297
|
flags[normalizedKey] = value;
|
|
@@ -1541,9 +2329,11 @@ function emptyProofArtifact() {
|
|
|
1541
2329
|
evidence: [],
|
|
1542
2330
|
visual: [],
|
|
1543
2331
|
runtime: [],
|
|
2332
|
+
runtimeBackendEvidence: [],
|
|
1544
2333
|
visualProvenance: [],
|
|
1545
2334
|
missionEnvelope: [],
|
|
1546
2335
|
rollbackHint: [],
|
|
2336
|
+
mediaProof: [],
|
|
1547
2337
|
cleanup: '',
|
|
1548
2338
|
changed: [],
|
|
1549
2339
|
skipped: [],
|
|
@@ -1566,9 +2356,11 @@ async function parseProofJson(file, empty) {
|
|
|
1566
2356
|
evidence: arrayFlag(data.evidence),
|
|
1567
2357
|
visual: arrayFlag(data.visual || data.visualEvidence),
|
|
1568
2358
|
runtime: arrayFlag(data.runtime || data.runtimeEvidence),
|
|
2359
|
+
runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
|
|
1569
2360
|
visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
|
|
1570
2361
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
1571
2362
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
2363
|
+
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
1572
2364
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
1573
2365
|
changed: arrayFlag(data.changed || data.files),
|
|
1574
2366
|
skipped: arrayFlag(data.skipped),
|
|
@@ -1594,9 +2386,11 @@ async function parseProofMarkdown(file, empty) {
|
|
|
1594
2386
|
evidence: readProofList(text, 'Evidence'),
|
|
1595
2387
|
visual: readProofList(text, 'Visual evidence'),
|
|
1596
2388
|
runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
|
|
2389
|
+
runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
|
|
1597
2390
|
visualProvenance: readProofList(text, 'Visual provenance'),
|
|
1598
2391
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
1599
2392
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
2393
|
+
mediaProof: readProofList(text, 'Media proof'),
|
|
1600
2394
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
1601
2395
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
1602
2396
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -1647,6 +2441,18 @@ function structuredEvidenceList(value) {
|
|
|
1647
2441
|
return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
|
|
1648
2442
|
}
|
|
1649
2443
|
|
|
2444
|
+
function directRuntimeBackendEvidence(flags) {
|
|
2445
|
+
if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked) return [];
|
|
2446
|
+
return [JSON.stringify(buildRuntimeBackendEvidence({
|
|
2447
|
+
backend: flags.runtime_backend,
|
|
2448
|
+
claim: flags.runtime_claim,
|
|
2449
|
+
evidence_id: flags.runtime_evidence_id,
|
|
2450
|
+
command: flags.runtime_command,
|
|
2451
|
+
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2452
|
+
note: flags.runtime_note
|
|
2453
|
+
}))];
|
|
2454
|
+
}
|
|
2455
|
+
|
|
1650
2456
|
function formatStructuredEvidence(value) {
|
|
1651
2457
|
if (!value) return '';
|
|
1652
2458
|
if (typeof value === 'object') return JSON.stringify(value);
|
|
@@ -1664,6 +2470,12 @@ function formatStructuredEvidence(value) {
|
|
|
1664
2470
|
if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
|
|
1665
2471
|
return JSON.stringify(buildRollbackHint(data));
|
|
1666
2472
|
}
|
|
2473
|
+
if (data.schema === 'yam.runtime-backend-evidence.v1' || data.backend || data.evidence_id) {
|
|
2474
|
+
return JSON.stringify(buildRuntimeBackendEvidence(data));
|
|
2475
|
+
}
|
|
2476
|
+
if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
|
|
2477
|
+
return JSON.stringify(buildMediaGenerationProof(data));
|
|
2478
|
+
}
|
|
1667
2479
|
return JSON.stringify(data);
|
|
1668
2480
|
} catch {
|
|
1669
2481
|
return text;
|
|
@@ -1672,7 +2484,7 @@ function formatStructuredEvidence(value) {
|
|
|
1672
2484
|
|
|
1673
2485
|
function hasProofEvidence(parsed) {
|
|
1674
2486
|
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);
|
|
2487
|
+
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
2488
|
}
|
|
1677
2489
|
|
|
1678
2490
|
function printProofList(label, values) {
|
|
@@ -1924,7 +2736,8 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1924
2736
|
lint: null,
|
|
1925
2737
|
test: null,
|
|
1926
2738
|
build: null
|
|
1927
|
-
}
|
|
2739
|
+
},
|
|
2740
|
+
frameworkChecklist: null
|
|
1928
2741
|
};
|
|
1929
2742
|
if (!result.packageJson) {
|
|
1930
2743
|
if (!quiet) {
|
|
@@ -1942,6 +2755,7 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1942
2755
|
result.commands.lint = runCommand(result.packageManager, pickScript(scripts, ['lint']));
|
|
1943
2756
|
result.commands.test = runCommand(result.packageManager, pickScript(scripts, ['test', 'spec']));
|
|
1944
2757
|
result.commands.build = runCommand(result.packageManager, pickScript(scripts, ['build']));
|
|
2758
|
+
result.frameworkChecklist = await detectFrameworkChecklist(dir, pkg);
|
|
1945
2759
|
|
|
1946
2760
|
if (!quiet) printDetection(result);
|
|
1947
2761
|
return result;
|
|
@@ -1960,6 +2774,13 @@ function printDetection(result) {
|
|
|
1960
2774
|
console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'Level 0 read/inspect; no command detected'}`);
|
|
1961
2775
|
console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
|
|
1962
2776
|
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}`}`);
|
|
2777
|
+
if (result.frameworkChecklist?.detected) {
|
|
2778
|
+
console.log('');
|
|
2779
|
+
console.log(`Framework checklist: ${result.frameworkChecklist.framework}`);
|
|
2780
|
+
for (const check of result.frameworkChecklist.checks) {
|
|
2781
|
+
console.log(`- ${check.id}: ${check.label} (${check.route})`);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
1963
2784
|
}
|
|
1964
2785
|
|
|
1965
2786
|
function budget(routeArg = '') {
|
|
@@ -2365,6 +3186,10 @@ async function main() {
|
|
|
2365
3186
|
if (command === 'tools') return tools(process.argv.slice(3));
|
|
2366
3187
|
if (command === 'proof') return proof(process.argv.slice(3));
|
|
2367
3188
|
if (command === 'ueye') return ueye(process.argv.slice(3));
|
|
3189
|
+
if (command === 'media') return media(process.argv.slice(3));
|
|
3190
|
+
if (command === 'runtime') return runtime(process.argv.slice(3));
|
|
3191
|
+
if (command === 'mission') return mission(process.argv.slice(3));
|
|
3192
|
+
if (command === 'benchmark') return benchmark(process.argv.slice(3));
|
|
2368
3193
|
if (command === 'release') return release(process.argv.slice(3));
|
|
2369
3194
|
if (command === 'safety') return safety(process.argv.slice(3));
|
|
2370
3195
|
if (command === 'memory') return memory(process.argv.slice(3));
|
|
@@ -2378,7 +3203,7 @@ async function main() {
|
|
|
2378
3203
|
}
|
|
2379
3204
|
if (command === 'list') return list();
|
|
2380
3205
|
if (command === 'verify') return verify();
|
|
2381
|
-
if (command === 'doctor') return doctor();
|
|
3206
|
+
if (command === 'doctor') return doctor(process.argv.slice(3));
|
|
2382
3207
|
if (command === 'examples') return examples();
|
|
2383
3208
|
if (command === 'path') return showPath();
|
|
2384
3209
|
if (command === 'init-project') return initProject(process.argv[3]);
|