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/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,11 @@ 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]
|
|
127
|
+
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
128
|
+
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
129
|
+
yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
|
|
125
130
|
yam release report [--json]
|
|
126
131
|
yam safety [text...]
|
|
127
132
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
@@ -130,7 +135,7 @@ Usage:
|
|
|
130
135
|
yam tune-log [dir]
|
|
131
136
|
yam install
|
|
132
137
|
yam uninstall
|
|
133
|
-
yam doctor
|
|
138
|
+
yam doctor [--json]
|
|
134
139
|
yam examples
|
|
135
140
|
yam path
|
|
136
141
|
yam version
|
|
@@ -359,7 +364,34 @@ function failOrReport(label, issues, quiet = false) {
|
|
|
359
364
|
process.exitCode = 1;
|
|
360
365
|
return issues.length;
|
|
361
366
|
}
|
|
362
|
-
async function doctor() {
|
|
367
|
+
async function doctor(args = []) {
|
|
368
|
+
const flags = parseSimpleFlags(args, new Set(['json']));
|
|
369
|
+
const report = await buildDoctorReport();
|
|
370
|
+
if (flags.json) {
|
|
371
|
+
console.log(JSON.stringify(report, null, 2));
|
|
372
|
+
if (!report.ok)
|
|
373
|
+
process.exitCode = 1;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (report.ok) {
|
|
377
|
+
console.log('yam doctor: ok');
|
|
378
|
+
console.log('No hooks, automations, or global config are required.');
|
|
379
|
+
console.log('Skill install state is reported by `yam status`.');
|
|
380
|
+
console.log(`yam-lite hook: ${report.yamLiteHook} (optional)`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
console.log('yam doctor: issues');
|
|
384
|
+
for (const issue of report.issues)
|
|
385
|
+
console.log(`- ${issue}`);
|
|
386
|
+
if (report.nextActions.length) {
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log('Next actions:');
|
|
389
|
+
for (const action of report.nextActions)
|
|
390
|
+
console.log(`- ${action}`);
|
|
391
|
+
}
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
}
|
|
394
|
+
async function buildDoctorReport() {
|
|
363
395
|
const issues = [];
|
|
364
396
|
for (const skill of SKILLS) {
|
|
365
397
|
if (!await exists(path.join(ROOT, 'skills', skill, 'SKILL.md')))
|
|
@@ -390,18 +422,112 @@ async function doctor() {
|
|
|
390
422
|
const verifyIssues = await verify({ quiet: true });
|
|
391
423
|
if (verifyIssues > 0)
|
|
392
424
|
issues.push(`verify reported ${verifyIssues} issue(s)`);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
425
|
+
const globalHook = await readJsonOrDefault(path.join(os.homedir(), '.codex', 'hooks.json'), {});
|
|
426
|
+
const nextActionDetails = doctorNextActionDetails(issues);
|
|
427
|
+
return {
|
|
428
|
+
schema: 'yam.doctor.v1',
|
|
429
|
+
generatedAt: new Date().toISOString(),
|
|
430
|
+
ok: issues.length === 0,
|
|
431
|
+
issues,
|
|
432
|
+
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
433
|
+
nextActionDetails,
|
|
434
|
+
yamLiteHook: hookConfigHasYamLite(globalHook) ? 'enabled' : 'disabled'
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function doctorNextActions(issues = []) {
|
|
438
|
+
return doctorNextActionDetails(issues).map((action) => action.next_action);
|
|
439
|
+
}
|
|
440
|
+
function doctorNextActionDetails(issues = []) {
|
|
441
|
+
const actions = [];
|
|
442
|
+
for (const issue of issues) {
|
|
443
|
+
if (/missing source skill/i.test(issue))
|
|
444
|
+
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'));
|
|
445
|
+
else if (/missing truth matrix/i.test(issue))
|
|
446
|
+
actions.push(nextActionDetail('restore-truth-matrix', 'warning', issue, 'restore references/truth-matrix.md before publishing', 'git checkout -- references/truth-matrix.md'));
|
|
447
|
+
else if (/unexpected local hooks/i.test(issue))
|
|
448
|
+
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'));
|
|
449
|
+
else if (/unexpected .*automation/i.test(issue))
|
|
450
|
+
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'));
|
|
451
|
+
else if (/legacy|retired/i.test(issue))
|
|
452
|
+
actions.push(nextActionDetail('replace-old-skills', 'warning', issue, 'run `yam install` to replace old skill entries', 'yam install'));
|
|
453
|
+
else if (/verify reported/i.test(issue))
|
|
454
|
+
actions.push(nextActionDetail('run-verify', 'error', issue, 'run `npm run verify` and fix the reported package boundary or metadata issue', 'npm run verify'));
|
|
455
|
+
}
|
|
456
|
+
return uniqueNextActionDetails(actions);
|
|
457
|
+
}
|
|
458
|
+
function nextActionDetail(id, severity, reason, nextAction, command = '') {
|
|
459
|
+
const priority = priorityForSeverity(severity);
|
|
460
|
+
const ownerRoute = ownerRouteForAction(id, reason, command);
|
|
461
|
+
return {
|
|
462
|
+
id,
|
|
463
|
+
severity,
|
|
464
|
+
priority,
|
|
465
|
+
owner_route: ownerRoute,
|
|
466
|
+
reason,
|
|
467
|
+
next_action: nextAction,
|
|
468
|
+
command,
|
|
469
|
+
tool_intent: toolIntentForCommand(command),
|
|
470
|
+
fix_first: priority === 'P0' || priority === 'P1',
|
|
471
|
+
blocks_release: priority === 'P0',
|
|
472
|
+
truth_status: 'partial'
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function priorityForSeverity(severity = '') {
|
|
476
|
+
if (/error|danger|block/i.test(String(severity)))
|
|
477
|
+
return 'P0';
|
|
478
|
+
if (/warn/i.test(String(severity)))
|
|
479
|
+
return 'P1';
|
|
480
|
+
if (/info/i.test(String(severity)))
|
|
481
|
+
return 'P2';
|
|
482
|
+
return 'P3';
|
|
483
|
+
}
|
|
484
|
+
function ownerRouteForAction(id = '', reason = '', command = '') {
|
|
485
|
+
const text = `${id} ${reason} ${command}`;
|
|
486
|
+
if (/visual|ueye|screenshot|browser/i.test(text))
|
|
487
|
+
return '$ueye';
|
|
488
|
+
if (/release|publish|package|registry|dist|version/i.test(text))
|
|
489
|
+
return '$deep';
|
|
490
|
+
if (/database|supabase|destructive|production|migration/i.test(text))
|
|
491
|
+
return '$deep';
|
|
492
|
+
if (/mission|subagent|team|lane/i.test(text))
|
|
493
|
+
return '$mission';
|
|
494
|
+
if (/research|docs|current/i.test(text))
|
|
495
|
+
return '$scout';
|
|
496
|
+
return '$quick';
|
|
497
|
+
}
|
|
498
|
+
function toolIntentForCommand(command = '') {
|
|
499
|
+
const text = String(command || '').toLowerCase();
|
|
500
|
+
if (!text)
|
|
501
|
+
return 'read_only';
|
|
502
|
+
if (/(publish|registry|release|npm pack|npm publish)/i.test(text))
|
|
503
|
+
return 'publish';
|
|
504
|
+
if (/(rm |delete|drop|truncate|reset|migrate|push|deploy|write|commit|add |install)/i.test(text))
|
|
505
|
+
return 'destructive';
|
|
506
|
+
if (/(dev|server|tmux|playwright|browser|screenshot|runtime|port|pid)/i.test(text))
|
|
507
|
+
return 'runtime';
|
|
508
|
+
if (/(ueye|visual|image|screen)/i.test(text))
|
|
509
|
+
return 'visual';
|
|
510
|
+
if (/(build|typecheck|lint|test|verify|doctor|status|pack)/i.test(text))
|
|
511
|
+
return 'read_only';
|
|
512
|
+
return 'write';
|
|
513
|
+
}
|
|
514
|
+
function normalizeToolIntent(value = '') {
|
|
515
|
+
const text = String(value || '').toLowerCase().replace(/[-\s]/g, '_');
|
|
516
|
+
if (['read_only', 'write', 'destructive', 'runtime', 'visual', 'publish'].includes(text))
|
|
517
|
+
return text;
|
|
518
|
+
return 'read_only';
|
|
519
|
+
}
|
|
520
|
+
function uniqueNextActionDetails(actions = []) {
|
|
521
|
+
const seen = new Set();
|
|
522
|
+
const unique = [];
|
|
523
|
+
for (const action of actions) {
|
|
524
|
+
const key = `${action.id}:${action.next_action}`;
|
|
525
|
+
if (seen.has(key))
|
|
526
|
+
continue;
|
|
527
|
+
seen.add(key);
|
|
528
|
+
unique.push(action);
|
|
400
529
|
}
|
|
401
|
-
|
|
402
|
-
for (const issue of issues)
|
|
403
|
-
console.log(`- ${issue}`);
|
|
404
|
-
process.exitCode = 1;
|
|
530
|
+
return unique;
|
|
405
531
|
}
|
|
406
532
|
async function tools(args = []) {
|
|
407
533
|
const subcommand = args[0] || 'doctor';
|
|
@@ -460,6 +586,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
460
586
|
packageInfo.packageJson ? JSON.stringify(packageInfo.pkg?.devDependencies || {}) : ''
|
|
461
587
|
].join('\n'));
|
|
462
588
|
const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
|
|
589
|
+
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
463
590
|
const sqlScan = await scanSqlFiles(dir);
|
|
464
591
|
const rows = [
|
|
465
592
|
readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
|
|
@@ -479,6 +606,13 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
479
606
|
...safety.hits,
|
|
480
607
|
...sqlScan.findings.map((finding) => ({ level: finding.level, reason: `${finding.file}: ${finding.reason}` }))
|
|
481
608
|
];
|
|
609
|
+
const nextActionDetails = toolsDoctorNextActionDetails({
|
|
610
|
+
pack,
|
|
611
|
+
rows,
|
|
612
|
+
riskNotes,
|
|
613
|
+
frameworkChecklist,
|
|
614
|
+
commands: detection.packageJson ? detection.commands : {}
|
|
615
|
+
});
|
|
482
616
|
return {
|
|
483
617
|
schemaVersion: 1,
|
|
484
618
|
generatedAt: new Date().toISOString(),
|
|
@@ -489,8 +623,11 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
489
623
|
packageManager: detection.packageJson ? detection.packageManager : null,
|
|
490
624
|
instructionSurfaces,
|
|
491
625
|
projectSurfaces,
|
|
626
|
+
frameworkChecklist,
|
|
492
627
|
sqlScan,
|
|
493
628
|
riskNotes,
|
|
629
|
+
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
630
|
+
nextActionDetails,
|
|
494
631
|
routeRecommendations: {
|
|
495
632
|
dbSupabase: safety.hits.length || sqlScan.findings.length ? '$deep required before claiming safe' : '$deep when destructive or production mutation appears',
|
|
496
633
|
currentDocs: 'use current-docs proof only when version/freshness matters',
|
|
@@ -522,6 +659,13 @@ function printToolsDoctorReport(data) {
|
|
|
522
659
|
for (const surface of data.projectSurfaces)
|
|
523
660
|
console.log(`- ${surface}`);
|
|
524
661
|
}
|
|
662
|
+
if (data.frameworkChecklist?.detected) {
|
|
663
|
+
console.log('');
|
|
664
|
+
console.log(`Framework checklist: ${data.frameworkChecklist.framework}`);
|
|
665
|
+
for (const check of data.frameworkChecklist.checks) {
|
|
666
|
+
console.log(`- ${check.id}: ${check.label} (${check.route})`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
525
669
|
if (data.sqlScan.filesScanned > 0) {
|
|
526
670
|
console.log('');
|
|
527
671
|
console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
|
|
@@ -535,6 +679,12 @@ function printToolsDoctorReport(data) {
|
|
|
535
679
|
for (const note of data.riskNotes)
|
|
536
680
|
console.log(`- ${note.level}: ${note.reason}`);
|
|
537
681
|
}
|
|
682
|
+
if (data.nextActions?.length) {
|
|
683
|
+
console.log('');
|
|
684
|
+
console.log('Next actions:');
|
|
685
|
+
for (const action of data.nextActions)
|
|
686
|
+
console.log(`- ${action}`);
|
|
687
|
+
}
|
|
538
688
|
console.log('');
|
|
539
689
|
console.log('Route recommendations:');
|
|
540
690
|
console.log(`- DB/Supabase destructive or production write work: ${data.routeRecommendations.dbSupabase}`);
|
|
@@ -611,6 +761,56 @@ async function detectProjectToolSurfaces(dir, pkg = null) {
|
|
|
611
761
|
surfaces.push('.env file detected; values were not read');
|
|
612
762
|
return surfaces;
|
|
613
763
|
}
|
|
764
|
+
async function detectFrameworkChecklist(dir, pkg = null) {
|
|
765
|
+
const deps = {
|
|
766
|
+
...pkg?.dependencies,
|
|
767
|
+
...pkg?.devDependencies
|
|
768
|
+
};
|
|
769
|
+
const depNames = Object.keys(deps || {});
|
|
770
|
+
const hasReact = depNames.includes('react');
|
|
771
|
+
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'));
|
|
772
|
+
const hasVite = depNames.includes('vite') || await exists(path.join(dir, 'vite.config.js')) || await exists(path.join(dir, 'vite.config.ts'));
|
|
773
|
+
const framework = hasNext ? 'next-app' : hasReact && hasVite ? 'react-vite' : hasReact ? 'react' : pkg ? 'node-package' : 'unknown';
|
|
774
|
+
const uiLike = hasReact || hasNext || hasVite || await exists(path.join(dir, 'app')) || await exists(path.join(dir, 'src', 'app')) || await exists(path.join(dir, 'pages'));
|
|
775
|
+
const checks = [];
|
|
776
|
+
if (uiLike) {
|
|
777
|
+
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'));
|
|
778
|
+
}
|
|
779
|
+
if (hasNext) {
|
|
780
|
+
checks.push(checklistRow('boundaries', 'server/client boundaries and async data states match the framework pattern', '$deep when risky'));
|
|
781
|
+
}
|
|
782
|
+
if (pkg) {
|
|
783
|
+
checks.push(checklistRow('package', 'run the smallest honest type/build/package check before publishing', '$quick or $deep'));
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
schema: 'yam.framework-checklist.v1',
|
|
787
|
+
detected: checks.length > 0,
|
|
788
|
+
uiLike,
|
|
789
|
+
framework,
|
|
790
|
+
routeHint: uiLike ? '$ueye for visual claims; $deep when runtime/data risk appears' : '$quick by default; deepen on risk',
|
|
791
|
+
checks
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function checklistRow(id, label, route) {
|
|
795
|
+
return { id, label, route };
|
|
796
|
+
}
|
|
797
|
+
function toolsDoctorNextActions(input) {
|
|
798
|
+
return toolsDoctorNextActionDetails(input).map((action) => action.next_action);
|
|
799
|
+
}
|
|
800
|
+
function toolsDoctorNextActionDetails({ pack, rows, riskNotes, frameworkChecklist, commands }) {
|
|
801
|
+
const actions = [];
|
|
802
|
+
if (!pack)
|
|
803
|
+
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 .'));
|
|
804
|
+
if (rows.some((row) => row.name === 'Yam skills' && row.status !== 'ready'))
|
|
805
|
+
actions.push(nextActionDetail('install-skills', 'warning', 'skill install state is incomplete', 'run `yam install` and restart Codex before relying on installed skills', 'yam install'));
|
|
806
|
+
if (riskNotes.some((note) => note.level === 'danger'))
|
|
807
|
+
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>"'));
|
|
808
|
+
if (frameworkChecklist?.detected && !commands?.build && !commands?.typecheck)
|
|
809
|
+
actions.push(nextActionDetail('verification-command', 'warning', 'build/typecheck command not detected', 'add or document a small build/typecheck command for implementation verification', 'yam pack .'));
|
|
810
|
+
if (frameworkChecklist?.uiLike)
|
|
811
|
+
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
|
+
}
|
|
614
814
|
async function scanSqlFiles(rootDir, { maxFiles = 30, maxBytes = 200000, maxDepth = 5 } = {}) {
|
|
615
815
|
const root = path.resolve(rootDir || process.cwd());
|
|
616
816
|
const files = [];
|
|
@@ -956,11 +1156,26 @@ async function release(args = []) {
|
|
|
956
1156
|
if (check.note)
|
|
957
1157
|
console.log(` ${check.note}`);
|
|
958
1158
|
}
|
|
1159
|
+
if (report.provenance) {
|
|
1160
|
+
console.log(`- Git commit: ${report.provenance.git_commit || 'unknown'}`);
|
|
1161
|
+
console.log(`- Git dirty: ${report.provenance.git_dirty ? 'yes' : 'no'}`);
|
|
1162
|
+
console.log(`- Dist freshness: ${report.freshness.dist}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (report.tarball) {
|
|
1165
|
+
console.log(`- Tarball: ${report.tarball.tarball_name || report.tarball.status}`);
|
|
1166
|
+
if (report.tarball.file_count !== undefined)
|
|
1167
|
+
console.log(`- Tarball files: ${report.tarball.file_count}`);
|
|
1168
|
+
}
|
|
959
1169
|
if (report.failed.length) {
|
|
960
1170
|
console.log('- Failed:');
|
|
961
1171
|
for (const id of report.failed)
|
|
962
1172
|
console.log(` - ${id}`);
|
|
963
1173
|
}
|
|
1174
|
+
if (report.next_actions?.length) {
|
|
1175
|
+
console.log('- Next actions:');
|
|
1176
|
+
for (const action of report.next_actions)
|
|
1177
|
+
console.log(` - ${action.next_action}`);
|
|
1178
|
+
}
|
|
964
1179
|
if (!report.ok)
|
|
965
1180
|
process.exitCode = 1;
|
|
966
1181
|
}
|
|
@@ -975,6 +1190,10 @@ function runReleaseReport() {
|
|
|
975
1190
|
['dist_freshness', ['npm', ['run', 'dist:freshness']]]
|
|
976
1191
|
].map(([id, command]) => runReleaseCheck(id, command));
|
|
977
1192
|
const failed = checks.filter((check) => check.status !== 'passed').map((check) => check.id);
|
|
1193
|
+
const provenance = releaseProvenance(checks);
|
|
1194
|
+
const tarball = releaseTarballProvenance();
|
|
1195
|
+
const freshness = releaseFreshness(checks, provenance, tarball);
|
|
1196
|
+
const nextActions = releaseNextActions(checks, provenance, tarball);
|
|
978
1197
|
return {
|
|
979
1198
|
schema: 'yam.release-report.v1',
|
|
980
1199
|
generated_at: startedAt,
|
|
@@ -983,9 +1202,114 @@ function runReleaseReport() {
|
|
|
983
1202
|
ok: failed.length === 0,
|
|
984
1203
|
truth_status: failed.length === 0 ? 'verified' : 'blocked',
|
|
985
1204
|
checks,
|
|
986
|
-
failed
|
|
1205
|
+
failed,
|
|
1206
|
+
provenance,
|
|
1207
|
+
tarball,
|
|
1208
|
+
freshness,
|
|
1209
|
+
next_actions: nextActions
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
function releaseProvenance(checks = []) {
|
|
1213
|
+
const gitCommit = runReadOnlyCommand('git', ['rev-parse', '--short', 'HEAD']);
|
|
1214
|
+
const gitStatus = runReadOnlyCommand('git', ['status', '--short']);
|
|
1215
|
+
const distChanged = runReadOnlyCommand('git', ['diff', '--name-only', '--', 'dist']);
|
|
1216
|
+
const packageFiles = checks.find((check) => check.id === 'package_boundary')?.status === 'passed' ? 'checked' : 'not-verified';
|
|
1217
|
+
return {
|
|
1218
|
+
schema: 'yam.release-provenance.v1',
|
|
1219
|
+
package_name: PACKAGE_JSON.name,
|
|
1220
|
+
version: VERSION,
|
|
1221
|
+
git_commit: gitCommit.trim(),
|
|
1222
|
+
git_dirty: Boolean(gitStatus.trim()),
|
|
1223
|
+
git_status_lines: gitStatus.trim().split(/\r?\n/).filter(Boolean).slice(0, 20),
|
|
1224
|
+
dist_changed_files: distChanged.trim().split(/\r?\n/).filter(Boolean).slice(0, 20),
|
|
1225
|
+
package_files: packageFiles,
|
|
1226
|
+
generated_at: new Date().toISOString(),
|
|
1227
|
+
truth_status: gitCommit.trim() && packageFiles === 'checked' ? 'partial' : 'assumed'
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
function releaseTarballProvenance() {
|
|
1231
|
+
const result = spawnSync('npm', ['pack', '--json', '--dry-run', '--ignore-scripts'], {
|
|
1232
|
+
cwd: ROOT,
|
|
1233
|
+
encoding: 'utf8',
|
|
1234
|
+
env: { ...process.env, npm_config_cache: path.join(os.tmpdir(), 'yam-npm-cache') },
|
|
1235
|
+
timeout: 120000
|
|
1236
|
+
});
|
|
1237
|
+
if (result.status !== 0) {
|
|
1238
|
+
return {
|
|
1239
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1240
|
+
status: 'blocked',
|
|
1241
|
+
reason: summarizeCheckOutput([result.stdout, result.stderr, result.error?.message].filter(Boolean).join('\n')),
|
|
1242
|
+
truth_status: 'blocked'
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
const data = JSON.parse(String(result.stdout || '[]'))[0] || {};
|
|
1247
|
+
return {
|
|
1248
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1249
|
+
status: 'checked',
|
|
1250
|
+
tarball_name: String(data.filename || ''),
|
|
1251
|
+
package_size: Number(data.size || data.packageSize || 0),
|
|
1252
|
+
unpacked_size: Number(data.unpackedSize || 0),
|
|
1253
|
+
file_count: Array.isArray(data.files) ? data.files.length : Number(data.entryCount || 0),
|
|
1254
|
+
integrity: String(data.integrity || ''),
|
|
1255
|
+
shasum: String(data.shasum || ''),
|
|
1256
|
+
truth_status: data.filename ? 'verified' : 'partial'
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
catch (error) {
|
|
1260
|
+
return {
|
|
1261
|
+
schema: 'yam.release-tarball-provenance.v1',
|
|
1262
|
+
status: 'blocked',
|
|
1263
|
+
reason: `could not parse npm pack output: ${errorMessage(error)}`,
|
|
1264
|
+
truth_status: 'blocked'
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function releaseFreshness(checks = [], provenance = {}, tarball = {}) {
|
|
1269
|
+
const checkStatus = (id) => checks.find((check) => check.id === id)?.status || 'not-run';
|
|
1270
|
+
const dist = checkStatus('dist_freshness') === 'passed' && !provenance.dist_changed_files?.length ? 'fresh' : 'needs-check';
|
|
1271
|
+
const registry = checkStatus('registry_status') === 'passed' ? 'publishable-version' : 'not-verified';
|
|
1272
|
+
const boundary = checkStatus('package_boundary') === 'passed' ? 'checked' : 'not-verified';
|
|
1273
|
+
const smoke = checkStatus('cli_smoke') === 'passed' ? 'checked' : 'not-verified';
|
|
1274
|
+
const tarballStatus = tarball.status === 'checked' ? 'checked' : 'not-verified';
|
|
1275
|
+
return {
|
|
1276
|
+
schema: 'yam.release-freshness.v1',
|
|
1277
|
+
dist,
|
|
1278
|
+
registry,
|
|
1279
|
+
package_boundary: boundary,
|
|
1280
|
+
cli_smoke: smoke,
|
|
1281
|
+
tarball: tarballStatus,
|
|
1282
|
+
truth_status: [dist, registry, boundary, smoke, tarballStatus].every((value) => !/not-verified|needs-check/.test(value)) ? 'verified' : 'partial'
|
|
987
1283
|
};
|
|
988
1284
|
}
|
|
1285
|
+
function releaseNextActions(checks = [], provenance = {}, tarball = {}) {
|
|
1286
|
+
const actions = [];
|
|
1287
|
+
for (const check of checks) {
|
|
1288
|
+
if (check.status === 'passed')
|
|
1289
|
+
continue;
|
|
1290
|
+
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));
|
|
1291
|
+
}
|
|
1292
|
+
if (provenance.git_dirty) {
|
|
1293
|
+
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'));
|
|
1294
|
+
}
|
|
1295
|
+
if (provenance.dist_changed_files?.length) {
|
|
1296
|
+
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'));
|
|
1297
|
+
}
|
|
1298
|
+
if (tarball.status !== 'checked') {
|
|
1299
|
+
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'));
|
|
1300
|
+
}
|
|
1301
|
+
return uniqueNextActionDetails(actions);
|
|
1302
|
+
}
|
|
1303
|
+
function runReadOnlyCommand(command, args = []) {
|
|
1304
|
+
const result = spawnSync(command, args, {
|
|
1305
|
+
cwd: ROOT,
|
|
1306
|
+
encoding: 'utf8',
|
|
1307
|
+
timeout: 30000
|
|
1308
|
+
});
|
|
1309
|
+
if (result.status !== 0)
|
|
1310
|
+
return '';
|
|
1311
|
+
return String(result.stdout || '');
|
|
1312
|
+
}
|
|
989
1313
|
function runReleaseCheck(id, commandSpec) {
|
|
990
1314
|
const [command, args] = commandSpec;
|
|
991
1315
|
const start = Date.now();
|
|
@@ -1029,6 +1353,8 @@ async function ueye(args = []) {
|
|
|
1029
1353
|
return ueyeCapture(args.slice(1));
|
|
1030
1354
|
if (subcommand === 'compare')
|
|
1031
1355
|
return ueyeCompare(args.slice(1));
|
|
1356
|
+
if (subcommand === 'report')
|
|
1357
|
+
return ueyeReport(args.slice(1));
|
|
1032
1358
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
1033
1359
|
return ueyeUsage();
|
|
1034
1360
|
}
|
|
@@ -1040,10 +1366,12 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
|
|
|
1040
1366
|
Usage:
|
|
1041
1367
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
1042
1368
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
1369
|
+
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]
|
|
1043
1370
|
|
|
1044
1371
|
Notes:
|
|
1045
1372
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
1046
1373
|
compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
|
|
1374
|
+
report produces a proof-ready Ueye visual run report and continuity/comparison record without requiring a new capture.
|
|
1047
1375
|
`);
|
|
1048
1376
|
}
|
|
1049
1377
|
async function ueyeCapture(args = []) {
|
|
@@ -1165,6 +1493,447 @@ async function ueyeCompare(args = []) {
|
|
|
1165
1493
|
process.exitCode = 1;
|
|
1166
1494
|
}
|
|
1167
1495
|
}
|
|
1496
|
+
async function ueyeReport(args = []) {
|
|
1497
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1498
|
+
return ueyeUsage();
|
|
1499
|
+
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']));
|
|
1500
|
+
const reference = String(flags.reference || flags.ref || '');
|
|
1501
|
+
const actual = String(flags.actual || flags.screenshot || '');
|
|
1502
|
+
const referenceSources = [];
|
|
1503
|
+
const implementationSources = [];
|
|
1504
|
+
let comparisonResult = 'not-verified';
|
|
1505
|
+
let blockedReason = String(flags.blocked_reason || '');
|
|
1506
|
+
const reviewSessionId = String(flags.review_session_id || `ueye-${timestampId()}`);
|
|
1507
|
+
const previousReport = await readPreviousUeyeReport(flags.previous_report);
|
|
1508
|
+
try {
|
|
1509
|
+
if (reference) {
|
|
1510
|
+
const info = await imageFileInfo(reference);
|
|
1511
|
+
referenceSources.push(buildUeyeVisualProvenance({
|
|
1512
|
+
source_kind: 'reference_image',
|
|
1513
|
+
source_path: info.path,
|
|
1514
|
+
source_hash: info.sha256,
|
|
1515
|
+
reference_id: String(flags.reference_id || path.basename(info.path)),
|
|
1516
|
+
local_only: true,
|
|
1517
|
+
operator_provided: true,
|
|
1518
|
+
comparison_result: 'reference-recorded',
|
|
1519
|
+
truth_status: 'partial'
|
|
1520
|
+
}));
|
|
1521
|
+
}
|
|
1522
|
+
if (actual) {
|
|
1523
|
+
const info = await imageFileInfo(actual);
|
|
1524
|
+
implementationSources.push(buildUeyeVisualProvenance({
|
|
1525
|
+
source_kind: 'implementation_screenshot',
|
|
1526
|
+
source_path: info.path,
|
|
1527
|
+
source_hash: info.sha256,
|
|
1528
|
+
reference_id: String(flags.reference_id || referenceSources[0]?.reference_id || ''),
|
|
1529
|
+
screenshot_id: String(flags.current_screenshot_id || flags.screenshot_id || path.basename(info.path)),
|
|
1530
|
+
local_only: true,
|
|
1531
|
+
operator_provided: false,
|
|
1532
|
+
comparison_result: 'implementation-recorded',
|
|
1533
|
+
truth_status: 'partial'
|
|
1534
|
+
}));
|
|
1535
|
+
}
|
|
1536
|
+
if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
|
|
1537
|
+
comparisonResult = 'matched';
|
|
1538
|
+
}
|
|
1539
|
+
else if (reference && actual) {
|
|
1540
|
+
comparisonResult = 'different';
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
blockedReason = errorMessage(error);
|
|
1545
|
+
}
|
|
1546
|
+
const report = buildUeyeRunReport({
|
|
1547
|
+
reference_sources: referenceSources,
|
|
1548
|
+
implementation_sources: implementationSources,
|
|
1549
|
+
comparison_result: comparisonResult,
|
|
1550
|
+
design_quality: String(flags.design_quality || 'not-checked'),
|
|
1551
|
+
blocked_reason: blockedReason,
|
|
1552
|
+
next_action: String(flags.next_visual_action || flags.next_action || '')
|
|
1553
|
+
});
|
|
1554
|
+
const comparisonReport = buildUeyeComparisonReport({
|
|
1555
|
+
reviewSessionId,
|
|
1556
|
+
previousReport,
|
|
1557
|
+
referenceSources,
|
|
1558
|
+
implementationSources,
|
|
1559
|
+
comparisonResult,
|
|
1560
|
+
designQuality: String(flags.design_quality || 'not-checked'),
|
|
1561
|
+
similar: arrayFlag(flags.similar),
|
|
1562
|
+
different: arrayFlag(flags.different),
|
|
1563
|
+
missing: arrayFlag(flags.missing),
|
|
1564
|
+
notes: arrayFlag(flags.comparison_notes),
|
|
1565
|
+
resolved: arrayFlag(flags.resolved),
|
|
1566
|
+
newFindings: arrayFlag(flags.new_finding),
|
|
1567
|
+
stillOpen: arrayFlag(flags.still_open),
|
|
1568
|
+
regressions: arrayFlag(flags.regression),
|
|
1569
|
+
previousScreenshotId: String(flags.previous_screenshot_id || previousReport?.screenshot_id || ''),
|
|
1570
|
+
currentScreenshotId: String(flags.current_screenshot_id || flags.screenshot_id || implementationSources[0]?.screenshot_id || ''),
|
|
1571
|
+
viewport: String(flags.viewport || ''),
|
|
1572
|
+
state: String(flags.state || 'unknown'),
|
|
1573
|
+
nextAction: report.next_action,
|
|
1574
|
+
truthStatus: report.truth_status
|
|
1575
|
+
});
|
|
1576
|
+
const result = {
|
|
1577
|
+
...report,
|
|
1578
|
+
review_session_id: reviewSessionId,
|
|
1579
|
+
tool_intent: 'visual',
|
|
1580
|
+
capture_backend: String(flags.capture_backend || 'not-recorded'),
|
|
1581
|
+
compare_backend: String(flags.compare_backend || 'local-file-hash'),
|
|
1582
|
+
continuity: comparisonReport.continuity,
|
|
1583
|
+
comparison_report: comparisonReport
|
|
1584
|
+
};
|
|
1585
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
|
|
1586
|
+
if (report.truth_status === 'blocked')
|
|
1587
|
+
process.exitCode = 1;
|
|
1588
|
+
}
|
|
1589
|
+
async function readPreviousUeyeReport(file = '') {
|
|
1590
|
+
const target = String(file || '');
|
|
1591
|
+
if (!target)
|
|
1592
|
+
return null;
|
|
1593
|
+
try {
|
|
1594
|
+
const data = await readJson(path.resolve(expandHome(target)));
|
|
1595
|
+
return {
|
|
1596
|
+
path: path.resolve(expandHome(target)),
|
|
1597
|
+
review_session_id: data.review_session_id || data.comparison_report?.review_session_id || '',
|
|
1598
|
+
screenshot_id: data.comparison_report?.current_screenshot_id || data.current_screenshot_id || data.screenshot_id || '',
|
|
1599
|
+
truth_status: data.truth_status || data.comparison_report?.truth_status || 'partial',
|
|
1600
|
+
comparison_result: data.comparison_result || data.comparison_report?.comparison_result || 'not-verified'
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
catch (error) {
|
|
1604
|
+
return {
|
|
1605
|
+
path: path.resolve(expandHome(target)),
|
|
1606
|
+
blocked_reason: errorMessage(error),
|
|
1607
|
+
truth_status: 'blocked',
|
|
1608
|
+
comparison_result: 'not-verified'
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceSources, implementationSources, comparisonResult, designQuality, similar, different, missing, notes, resolved, newFindings, stillOpen, regressions, previousScreenshotId, currentScreenshotId, viewport, state, nextAction, truthStatus }) {
|
|
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
|
+
async function media(args = []) {
|
|
1655
|
+
const subcommand = args[0] || 'help';
|
|
1656
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1657
|
+
return mediaUsage();
|
|
1658
|
+
if (subcommand === 'proof')
|
|
1659
|
+
return mediaProof(args.slice(1));
|
|
1660
|
+
console.error(`unknown media command: ${subcommand}`);
|
|
1661
|
+
return mediaUsage();
|
|
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
|
+
async function mediaProof(args = []) {
|
|
1673
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1674
|
+
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
|
+
}
|
|
1686
|
+
catch (error) {
|
|
1687
|
+
blockedReason = blockedReason || errorMessage(error);
|
|
1688
|
+
outputPath = path.resolve(expandHome(output));
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const proof = buildMediaGenerationProof({
|
|
1692
|
+
tool_name: String(flags.tool || ''),
|
|
1693
|
+
generation_requested: Boolean(flags.requested),
|
|
1694
|
+
generation_attempted: Boolean(flags.attempted || output),
|
|
1695
|
+
output_path: outputPath,
|
|
1696
|
+
output_hash: outputHash,
|
|
1697
|
+
wait_loop_checked: Boolean(flags.wait_loop),
|
|
1698
|
+
blocked_reason: blockedReason,
|
|
1699
|
+
next_action: String(flags.next_action || '')
|
|
1700
|
+
});
|
|
1701
|
+
printJsonOrHuman(proof, Boolean(flags.json), 'Media proof');
|
|
1702
|
+
if (proof.truth_status === 'blocked')
|
|
1703
|
+
process.exitCode = 1;
|
|
1704
|
+
}
|
|
1705
|
+
async function runtime(args = []) {
|
|
1706
|
+
const subcommand = args[0] || 'help';
|
|
1707
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1708
|
+
return runtimeUsage();
|
|
1709
|
+
if (subcommand === 'evidence')
|
|
1710
|
+
return runtimeEvidence(args.slice(1));
|
|
1711
|
+
console.error(`unknown runtime command: ${subcommand}`);
|
|
1712
|
+
return runtimeUsage();
|
|
1713
|
+
}
|
|
1714
|
+
function runtimeUsage() {
|
|
1715
|
+
console.log(`yam runtime
|
|
1716
|
+
|
|
1717
|
+
Usage:
|
|
1718
|
+
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]
|
|
1719
|
+
|
|
1720
|
+
Notes:
|
|
1721
|
+
Records a small runtime evidence shape. It does not start, stop, or inspect processes by itself.
|
|
1722
|
+
`);
|
|
1723
|
+
}
|
|
1724
|
+
async function runtimeEvidence(args = []) {
|
|
1725
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1726
|
+
return runtimeUsage();
|
|
1727
|
+
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']));
|
|
1728
|
+
const evidence = buildRuntimeBackendEvidence({
|
|
1729
|
+
backend: flags.backend,
|
|
1730
|
+
claim: flags.claim,
|
|
1731
|
+
evidence_id: String(flags.evidence_id || `runtime-${timestampId()}`),
|
|
1732
|
+
command: String(flags.command || ''),
|
|
1733
|
+
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
1734
|
+
note: String(flags.note || ''),
|
|
1735
|
+
truth_status: isTruthStatus(flags.truth) ? flags.truth : undefined
|
|
1736
|
+
});
|
|
1737
|
+
const runtimeDetails = runtimeEvidenceDetails(flags);
|
|
1738
|
+
if (!isTruthStatus(flags.truth) && evidence.truth_status === 'verified' && !runtimeDetailsHasVerification(runtimeDetails)) {
|
|
1739
|
+
evidence.truth_status = 'partial';
|
|
1740
|
+
}
|
|
1741
|
+
const result = {
|
|
1742
|
+
...evidence,
|
|
1743
|
+
runtime_details: runtimeDetails,
|
|
1744
|
+
tool_intent: normalizeToolIntent(flags.intent || 'runtime'),
|
|
1745
|
+
next_action: runtimeEvidenceNextAction(evidence)
|
|
1746
|
+
};
|
|
1747
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Runtime evidence');
|
|
1748
|
+
if (result.truth_status === 'blocked' || result.truth_status === 'real_required_missing')
|
|
1749
|
+
process.exitCode = 1;
|
|
1750
|
+
}
|
|
1751
|
+
function runtimeEvidenceDetails(flags = {}) {
|
|
1752
|
+
return {
|
|
1753
|
+
schema: 'yam.runtime-details.v1',
|
|
1754
|
+
pid: numberOrNull(flags.pid),
|
|
1755
|
+
port: numberOrNull(flags.port),
|
|
1756
|
+
url: String(flags.url || ''),
|
|
1757
|
+
exit_code: numberOrNull(flags.exit_code),
|
|
1758
|
+
screenshot_id: String(flags.screenshot_id || ''),
|
|
1759
|
+
started_at: String(flags.started_at || ''),
|
|
1760
|
+
stopped_at: String(flags.stopped_at || '')
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function runtimeDetailsHasVerification(details = {}) {
|
|
1764
|
+
return Boolean(details.url || details.screenshot_id || details.pid !== null || details.port !== null || details.exit_code !== null || details.started_at || details.stopped_at);
|
|
1765
|
+
}
|
|
1766
|
+
function runtimeEvidenceNextAction(evidence) {
|
|
1767
|
+
if (evidence.truth_status === 'proven' || evidence.truth_status === 'verified')
|
|
1768
|
+
return 'attach this evidence to `yam proof` when making a runtime claim';
|
|
1769
|
+
if (evidence.claim === 'cleanup_verified' && !evidence.cleanup_checked)
|
|
1770
|
+
return 'rerun with --cleanup-checked only after process exit or intentional persistence is confirmed';
|
|
1771
|
+
if (evidence.backend === 'none' || evidence.backend === 'unknown')
|
|
1772
|
+
return 'record the actual runtime backend before claiming runtime verification';
|
|
1773
|
+
return 'add command output, screenshot id, pid/session id, or cleanup proof before upgrading the claim';
|
|
1774
|
+
}
|
|
1775
|
+
async function mission(args = []) {
|
|
1776
|
+
const subcommand = args[0] || 'help';
|
|
1777
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1778
|
+
return missionUsage();
|
|
1779
|
+
if (subcommand === 'queue' || subcommand === 'patch')
|
|
1780
|
+
return missionQueue(args.slice(1));
|
|
1781
|
+
console.error(`unknown mission command: ${subcommand}`);
|
|
1782
|
+
return missionUsage();
|
|
1783
|
+
}
|
|
1784
|
+
function missionUsage() {
|
|
1785
|
+
console.log(`yam mission
|
|
1786
|
+
|
|
1787
|
+
Usage:
|
|
1788
|
+
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]
|
|
1789
|
+
|
|
1790
|
+
Notes:
|
|
1791
|
+
Produces a patch queue item for mission handoff/review. It does not run workers; persistence is opt-in with --out.
|
|
1792
|
+
`);
|
|
1793
|
+
}
|
|
1794
|
+
async function missionQueue(args = []) {
|
|
1795
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1796
|
+
return missionUsage();
|
|
1797
|
+
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']));
|
|
1798
|
+
const changedFiles = arrayFlag(flags.changed);
|
|
1799
|
+
const generatedFiles = arrayFlag(flags.generated);
|
|
1800
|
+
const laneId = String(flags.lane_id || flags.agent_id || `lane-${timestampId()}`);
|
|
1801
|
+
const status = normalizeMissionQueueStatus(flags.status || 'pending');
|
|
1802
|
+
const rollback = buildRollbackHint({
|
|
1803
|
+
touched_files: changedFiles,
|
|
1804
|
+
generated_files: generatedFiles,
|
|
1805
|
+
before_check: String(flags.before_check || ''),
|
|
1806
|
+
safe_revert_note: String(flags.safe_revert_note || flags.rollback_hint || '')
|
|
1807
|
+
});
|
|
1808
|
+
const envelope = buildMissionPatchEnvelope({
|
|
1809
|
+
agent_id: String(flags.agent_id || `agent-${timestampId()}`),
|
|
1810
|
+
assigned_scope: String(flags.scope || ''),
|
|
1811
|
+
changed_files: changedFiles,
|
|
1812
|
+
verification_hint: String(flags.verification_hint || ''),
|
|
1813
|
+
rollback_hint: rollback,
|
|
1814
|
+
truth_status: isTruthStatus(flags.truth) ? flags.truth : changedFiles.length ? 'partial' : 'assumed'
|
|
1815
|
+
});
|
|
1816
|
+
const item = {
|
|
1817
|
+
schema: 'yam.mission-patch-queue-item.v1',
|
|
1818
|
+
lane_id: laneId,
|
|
1819
|
+
status,
|
|
1820
|
+
depends_on: arrayFlag(flags.depends_on),
|
|
1821
|
+
tool_intent: normalizeToolIntent(flags.intent || (changedFiles.length || generatedFiles.length ? 'write' : 'read_only')),
|
|
1822
|
+
patch_envelope: envelope,
|
|
1823
|
+
next_action: missionQueueNextAction(envelope, status)
|
|
1824
|
+
};
|
|
1825
|
+
const result = {
|
|
1826
|
+
schema: 'yam.mission-patch-queue-lite.v1',
|
|
1827
|
+
generated_at: new Date().toISOString(),
|
|
1828
|
+
persistence: flags.out ? 'written' : 'not-persisted',
|
|
1829
|
+
out: flags.out ? path.resolve(expandHome(flags.out)) : '',
|
|
1830
|
+
items: [item],
|
|
1831
|
+
queue_depth: 1,
|
|
1832
|
+
next_action: item.next_action,
|
|
1833
|
+
truth_status: envelope.truth_status
|
|
1834
|
+
};
|
|
1835
|
+
if (flags.out) {
|
|
1836
|
+
const target = path.resolve(expandHome(flags.out));
|
|
1837
|
+
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
1838
|
+
await fsp.writeFile(target, `${JSON.stringify(result, null, 2)}\n`);
|
|
1839
|
+
}
|
|
1840
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Mission patch queue');
|
|
1841
|
+
}
|
|
1842
|
+
function normalizeMissionQueueStatus(value = '') {
|
|
1843
|
+
const text = String(value || '').toLowerCase();
|
|
1844
|
+
if (['pending', 'applied', 'verified', 'blocked', 'reverted'].includes(text))
|
|
1845
|
+
return text;
|
|
1846
|
+
return 'pending';
|
|
1847
|
+
}
|
|
1848
|
+
function missionQueueNextAction(envelope, status = 'pending') {
|
|
1849
|
+
if (status === 'blocked')
|
|
1850
|
+
return 'resolve or document the blocker before applying dependent mission lanes';
|
|
1851
|
+
if (status === 'reverted')
|
|
1852
|
+
return 'rerun the before-check or verification hint before continuing dependent work';
|
|
1853
|
+
if (!envelope.assigned_scope)
|
|
1854
|
+
return 'add --scope so the patch can be reviewed against an assigned boundary';
|
|
1855
|
+
if (!envelope.changed_files.length)
|
|
1856
|
+
return 'add at least one --changed file when this represents code-changing work';
|
|
1857
|
+
if (!envelope.verification_hint)
|
|
1858
|
+
return 'add --verification-hint so reviewers know the smallest honest check';
|
|
1859
|
+
if (!envelope.rollback_hint.safe_revert_note && !envelope.rollback_hint.before_check)
|
|
1860
|
+
return 'add rollback or before-check detail before using this for risky work';
|
|
1861
|
+
return 'attach this envelope to the mission proof summary and cross-check changed files';
|
|
1862
|
+
}
|
|
1863
|
+
async function benchmark(args = []) {
|
|
1864
|
+
const subcommand = args[0] || 'help';
|
|
1865
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1866
|
+
return benchmarkUsage();
|
|
1867
|
+
if (subcommand === 'report')
|
|
1868
|
+
return benchmarkReport(args.slice(1));
|
|
1869
|
+
console.error(`unknown benchmark command: ${subcommand}`);
|
|
1870
|
+
return benchmarkUsage();
|
|
1871
|
+
}
|
|
1872
|
+
function benchmarkUsage() {
|
|
1873
|
+
console.log(`yam benchmark
|
|
1874
|
+
|
|
1875
|
+
Usage:
|
|
1876
|
+
yam benchmark report [--label text] [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--next-action text] [--json]
|
|
1877
|
+
|
|
1878
|
+
Notes:
|
|
1879
|
+
Records a small optimization loop result. It does not run benchmarks by itself.
|
|
1880
|
+
`);
|
|
1881
|
+
}
|
|
1882
|
+
async function benchmarkReport(args = []) {
|
|
1883
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1884
|
+
return benchmarkUsage();
|
|
1885
|
+
const flags = parseSimpleFlags(args, new Set(['label', 'baseline', 'current', 'unit', 'target', 'next-action', 'rollback-hint', 'intent', 'json']));
|
|
1886
|
+
const baseline = numberOrNull(flags.baseline);
|
|
1887
|
+
const current = numberOrNull(flags.current);
|
|
1888
|
+
const target = String(flags.target || 'lower').toLowerCase() === 'higher' ? 'higher' : 'lower';
|
|
1889
|
+
const hasNumbers = baseline !== null && current !== null;
|
|
1890
|
+
const delta = hasNumbers ? current - baseline : null;
|
|
1891
|
+
const percent = hasNumbers && baseline !== 0 ? (delta / baseline) * 100 : null;
|
|
1892
|
+
const status = benchmarkStatus({ baseline, current, target });
|
|
1893
|
+
const result = {
|
|
1894
|
+
schema: 'yam.benchmark-report.v1',
|
|
1895
|
+
generated_at: new Date().toISOString(),
|
|
1896
|
+
label: String(flags.label || 'unnamed'),
|
|
1897
|
+
baseline,
|
|
1898
|
+
current,
|
|
1899
|
+
unit: String(flags.unit || ''),
|
|
1900
|
+
target,
|
|
1901
|
+
delta,
|
|
1902
|
+
percent,
|
|
1903
|
+
status,
|
|
1904
|
+
tool_intent: normalizeToolIntent(flags.intent || 'read_only'),
|
|
1905
|
+
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')),
|
|
1906
|
+
next_action: String(flags.next_action || benchmarkNextAction(status)),
|
|
1907
|
+
truth_status: hasNumbers ? 'verified' : 'partial'
|
|
1908
|
+
};
|
|
1909
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Benchmark report');
|
|
1910
|
+
if (status === 'regressed')
|
|
1911
|
+
process.exitCode = 1;
|
|
1912
|
+
}
|
|
1913
|
+
function numberOrNull(value) {
|
|
1914
|
+
if (value === undefined || value === null || value === '')
|
|
1915
|
+
return null;
|
|
1916
|
+
const number = Number(value);
|
|
1917
|
+
return Number.isFinite(number) ? number : null;
|
|
1918
|
+
}
|
|
1919
|
+
function benchmarkStatus({ baseline, current, target }) {
|
|
1920
|
+
if (baseline === null || current === null)
|
|
1921
|
+
return 'needs-baseline';
|
|
1922
|
+
if (current === baseline)
|
|
1923
|
+
return 'unchanged';
|
|
1924
|
+
if (target === 'higher')
|
|
1925
|
+
return current > baseline ? 'improved' : 'regressed';
|
|
1926
|
+
return current < baseline ? 'improved' : 'regressed';
|
|
1927
|
+
}
|
|
1928
|
+
function benchmarkNextAction(status) {
|
|
1929
|
+
if (status === 'improved')
|
|
1930
|
+
return 'record the measurement source and keep the smallest relevant regression check';
|
|
1931
|
+
if (status === 'regressed')
|
|
1932
|
+
return 'treat this as a fix-first item before continuing planned optimization';
|
|
1933
|
+
if (status === 'unchanged')
|
|
1934
|
+
return 'keep the change only if it improves clarity or enables the next measured step';
|
|
1935
|
+
return 'capture baseline and current values before claiming optimization impact';
|
|
1936
|
+
}
|
|
1168
1937
|
function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
1169
1938
|
const flags = {};
|
|
1170
1939
|
const positionals = [];
|
|
@@ -1179,14 +1948,19 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
|
1179
1948
|
if (allowed.size && !allowed.has(key))
|
|
1180
1949
|
continue;
|
|
1181
1950
|
const normalizedKey = key.replace(/-/g, '_');
|
|
1182
|
-
if (['json', 'full-page'].includes(key)) {
|
|
1951
|
+
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
|
|
1183
1952
|
flags[normalizedKey] = true;
|
|
1184
1953
|
continue;
|
|
1185
1954
|
}
|
|
1186
1955
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1187
1956
|
if (inlineValue === undefined)
|
|
1188
1957
|
index += 1;
|
|
1189
|
-
flags[normalizedKey]
|
|
1958
|
+
if (flags[normalizedKey] !== undefined) {
|
|
1959
|
+
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1960
|
+
}
|
|
1961
|
+
else {
|
|
1962
|
+
flags[normalizedKey] = value;
|
|
1963
|
+
}
|
|
1190
1964
|
}
|
|
1191
1965
|
flags._ = positionals;
|
|
1192
1966
|
return flags;
|
|
@@ -1326,9 +2100,11 @@ async function proof(args = []) {
|
|
|
1326
2100
|
printProofList('Evidence', summary.evidence);
|
|
1327
2101
|
printProofList('Visual evidence', summary.visual);
|
|
1328
2102
|
printProofList('Runtime evidence', summary.runtime);
|
|
2103
|
+
printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
|
|
1329
2104
|
printProofList('Visual provenance', summary.visualProvenance || []);
|
|
1330
2105
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
1331
2106
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
2107
|
+
printProofList('Media proof', summary.mediaProof || []);
|
|
1332
2108
|
printProofList('Changed surfaces', summary.changed);
|
|
1333
2109
|
printProofList('Skipped', summary.skipped);
|
|
1334
2110
|
printProofList('Blocked', summary.blocked);
|
|
@@ -1404,6 +2180,11 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1404
2180
|
evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
|
|
1405
2181
|
visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
|
|
1406
2182
|
runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
|
|
2183
|
+
runtimeBackendEvidence: [
|
|
2184
|
+
...structuredEvidenceList(artifact.runtimeBackendEvidence),
|
|
2185
|
+
...structuredEvidenceList(parsed.flags.runtime_backend_evidence),
|
|
2186
|
+
...directRuntimeBackendEvidence(parsed.flags)
|
|
2187
|
+
],
|
|
1407
2188
|
visualProvenance: [
|
|
1408
2189
|
...structuredEvidenceList(artifact.visualProvenance),
|
|
1409
2190
|
...structuredEvidenceList(parsed.flags.visual_provenance)
|
|
@@ -1416,6 +2197,10 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1416
2197
|
...structuredEvidenceList(artifact.rollbackHint),
|
|
1417
2198
|
...structuredEvidenceList(parsed.flags.rollback_hint)
|
|
1418
2199
|
],
|
|
2200
|
+
mediaProof: [
|
|
2201
|
+
...structuredEvidenceList(artifact.mediaProof),
|
|
2202
|
+
...structuredEvidenceList(parsed.flags.media_proof)
|
|
2203
|
+
],
|
|
1419
2204
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
1420
2205
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
1421
2206
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -1465,6 +2250,9 @@ function renderProofMarkdown(summary) {
|
|
|
1465
2250
|
'## Runtime evidence',
|
|
1466
2251
|
...renderProofMarkdownList(summary.runtime),
|
|
1467
2252
|
'',
|
|
2253
|
+
'## Runtime backend evidence',
|
|
2254
|
+
...renderProofMarkdownList(summary.runtimeBackendEvidence || []),
|
|
2255
|
+
'',
|
|
1468
2256
|
'## Visual provenance',
|
|
1469
2257
|
...renderProofMarkdownList(summary.visualProvenance || []),
|
|
1470
2258
|
'',
|
|
@@ -1474,6 +2262,9 @@ function renderProofMarkdown(summary) {
|
|
|
1474
2262
|
'## Rollback hint',
|
|
1475
2263
|
...renderProofMarkdownList(summary.rollbackHint || []),
|
|
1476
2264
|
'',
|
|
2265
|
+
'## Media proof',
|
|
2266
|
+
...renderProofMarkdownList(summary.mediaProof || []),
|
|
2267
|
+
'',
|
|
1477
2268
|
'## Changed surfaces',
|
|
1478
2269
|
...renderProofMarkdownList(summary.changed),
|
|
1479
2270
|
'',
|
|
@@ -1504,7 +2295,7 @@ function renderProofMarkdownList(values) {
|
|
|
1504
2295
|
function parseProofArgs(args = []) {
|
|
1505
2296
|
const flags = {};
|
|
1506
2297
|
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']);
|
|
2298
|
+
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
2299
|
for (let index = 0; index < args.length; index += 1) {
|
|
1509
2300
|
const arg = args[index];
|
|
1510
2301
|
if (arg.startsWith('--')) {
|
|
@@ -1513,14 +2304,14 @@ function parseProofArgs(args = []) {
|
|
|
1513
2304
|
if (!aliases.has(key))
|
|
1514
2305
|
continue;
|
|
1515
2306
|
const normalizedKey = key.replace(/-/g, '_');
|
|
1516
|
-
if (key === 'json' || key.startsWith('require-')) {
|
|
2307
|
+
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
|
|
1517
2308
|
flags[normalizedKey] = true;
|
|
1518
2309
|
continue;
|
|
1519
2310
|
}
|
|
1520
2311
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1521
2312
|
if (inlineValue === undefined)
|
|
1522
2313
|
index += 1;
|
|
1523
|
-
if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
2314
|
+
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
2315
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1525
2316
|
}
|
|
1526
2317
|
else {
|
|
@@ -1559,9 +2350,11 @@ function emptyProofArtifact() {
|
|
|
1559
2350
|
evidence: [],
|
|
1560
2351
|
visual: [],
|
|
1561
2352
|
runtime: [],
|
|
2353
|
+
runtimeBackendEvidence: [],
|
|
1562
2354
|
visualProvenance: [],
|
|
1563
2355
|
missionEnvelope: [],
|
|
1564
2356
|
rollbackHint: [],
|
|
2357
|
+
mediaProof: [],
|
|
1565
2358
|
cleanup: '',
|
|
1566
2359
|
changed: [],
|
|
1567
2360
|
skipped: [],
|
|
@@ -1583,9 +2376,11 @@ async function parseProofJson(file, empty) {
|
|
|
1583
2376
|
evidence: arrayFlag(data.evidence),
|
|
1584
2377
|
visual: arrayFlag(data.visual || data.visualEvidence),
|
|
1585
2378
|
runtime: arrayFlag(data.runtime || data.runtimeEvidence),
|
|
2379
|
+
runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
|
|
1586
2380
|
visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
|
|
1587
2381
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
1588
2382
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
2383
|
+
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
1589
2384
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
1590
2385
|
changed: arrayFlag(data.changed || data.files),
|
|
1591
2386
|
skipped: arrayFlag(data.skipped),
|
|
@@ -1611,9 +2406,11 @@ async function parseProofMarkdown(file, empty) {
|
|
|
1611
2406
|
evidence: readProofList(text, 'Evidence'),
|
|
1612
2407
|
visual: readProofList(text, 'Visual evidence'),
|
|
1613
2408
|
runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
|
|
2409
|
+
runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
|
|
1614
2410
|
visualProvenance: readProofList(text, 'Visual provenance'),
|
|
1615
2411
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
1616
2412
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
2413
|
+
mediaProof: readProofList(text, 'Media proof'),
|
|
1617
2414
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
1618
2415
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
1619
2416
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -1661,6 +2458,18 @@ function arrayFlag(value) {
|
|
|
1661
2458
|
function structuredEvidenceList(value) {
|
|
1662
2459
|
return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
|
|
1663
2460
|
}
|
|
2461
|
+
function directRuntimeBackendEvidence(flags) {
|
|
2462
|
+
if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked)
|
|
2463
|
+
return [];
|
|
2464
|
+
return [JSON.stringify(buildRuntimeBackendEvidence({
|
|
2465
|
+
backend: flags.runtime_backend,
|
|
2466
|
+
claim: flags.runtime_claim,
|
|
2467
|
+
evidence_id: flags.runtime_evidence_id,
|
|
2468
|
+
command: flags.runtime_command,
|
|
2469
|
+
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2470
|
+
note: flags.runtime_note
|
|
2471
|
+
}))];
|
|
2472
|
+
}
|
|
1664
2473
|
function formatStructuredEvidence(value) {
|
|
1665
2474
|
if (!value)
|
|
1666
2475
|
return '';
|
|
@@ -1682,6 +2491,12 @@ function formatStructuredEvidence(value) {
|
|
|
1682
2491
|
if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
|
|
1683
2492
|
return JSON.stringify(buildRollbackHint(data));
|
|
1684
2493
|
}
|
|
2494
|
+
if (data.schema === 'yam.runtime-backend-evidence.v1' || data.backend || data.evidence_id) {
|
|
2495
|
+
return JSON.stringify(buildRuntimeBackendEvidence(data));
|
|
2496
|
+
}
|
|
2497
|
+
if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
|
|
2498
|
+
return JSON.stringify(buildMediaGenerationProof(data));
|
|
2499
|
+
}
|
|
1685
2500
|
return JSON.stringify(data);
|
|
1686
2501
|
}
|
|
1687
2502
|
catch {
|
|
@@ -1690,7 +2505,7 @@ function formatStructuredEvidence(value) {
|
|
|
1690
2505
|
}
|
|
1691
2506
|
function hasProofEvidence(parsed) {
|
|
1692
2507
|
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);
|
|
2508
|
+
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
2509
|
}
|
|
1695
2510
|
function printProofList(label, values) {
|
|
1696
2511
|
console.log(`- ${label}:`);
|
|
@@ -1944,7 +2759,8 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1944
2759
|
lint: null,
|
|
1945
2760
|
test: null,
|
|
1946
2761
|
build: null
|
|
1947
|
-
}
|
|
2762
|
+
},
|
|
2763
|
+
frameworkChecklist: null
|
|
1948
2764
|
};
|
|
1949
2765
|
if (!result.packageJson) {
|
|
1950
2766
|
if (!quiet) {
|
|
@@ -1961,6 +2777,7 @@ async function detectProject(targetDir = process.cwd(), { quiet = false } = {})
|
|
|
1961
2777
|
result.commands.lint = runCommand(result.packageManager, pickScript(scripts, ['lint']));
|
|
1962
2778
|
result.commands.test = runCommand(result.packageManager, pickScript(scripts, ['test', 'spec']));
|
|
1963
2779
|
result.commands.build = runCommand(result.packageManager, pickScript(scripts, ['build']));
|
|
2780
|
+
result.frameworkChecklist = await detectFrameworkChecklist(dir, pkg);
|
|
1964
2781
|
if (!quiet)
|
|
1965
2782
|
printDetection(result);
|
|
1966
2783
|
return result;
|
|
@@ -1978,6 +2795,13 @@ function printDetection(result) {
|
|
|
1978
2795
|
console.log(`- $quick: ${result.commands.typecheck || result.commands.lint || result.commands.test || result.commands.build || 'Level 0 read/inspect; no command detected'}`);
|
|
1979
2796
|
console.log(`- $ueye: ${result.commands.typecheck || result.commands.build || 'Browser/screenshot check; no command detected'}`);
|
|
1980
2797
|
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}`}`);
|
|
2798
|
+
if (result.frameworkChecklist?.detected) {
|
|
2799
|
+
console.log('');
|
|
2800
|
+
console.log(`Framework checklist: ${result.frameworkChecklist.framework}`);
|
|
2801
|
+
for (const check of result.frameworkChecklist.checks) {
|
|
2802
|
+
console.log(`- ${check.id}: ${check.label} (${check.route})`);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
1981
2805
|
}
|
|
1982
2806
|
function budget(routeArg = '') {
|
|
1983
2807
|
const normalized = normalizeRoute(routeArg);
|
|
@@ -2383,6 +3207,14 @@ async function main() {
|
|
|
2383
3207
|
return proof(process.argv.slice(3));
|
|
2384
3208
|
if (command === 'ueye')
|
|
2385
3209
|
return ueye(process.argv.slice(3));
|
|
3210
|
+
if (command === 'media')
|
|
3211
|
+
return media(process.argv.slice(3));
|
|
3212
|
+
if (command === 'runtime')
|
|
3213
|
+
return runtime(process.argv.slice(3));
|
|
3214
|
+
if (command === 'mission')
|
|
3215
|
+
return mission(process.argv.slice(3));
|
|
3216
|
+
if (command === 'benchmark')
|
|
3217
|
+
return benchmark(process.argv.slice(3));
|
|
2386
3218
|
if (command === 'release')
|
|
2387
3219
|
return release(process.argv.slice(3));
|
|
2388
3220
|
if (command === 'safety')
|
|
@@ -2406,7 +3238,7 @@ async function main() {
|
|
|
2406
3238
|
if (command === 'verify')
|
|
2407
3239
|
return verify();
|
|
2408
3240
|
if (command === 'doctor')
|
|
2409
|
-
return doctor();
|
|
3241
|
+
return doctor(process.argv.slice(3));
|
|
2410
3242
|
if (command === 'examples')
|
|
2411
3243
|
return examples();
|
|
2412
3244
|
if (command === 'path')
|