yam-flow 0.1.5 → 0.1.7
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/AGENTS.md +7 -4
- package/CHANGELOG.md +45 -0
- package/COMMANDS.md +12 -3
- package/DECISIONS.md +28 -5
- package/README.md +29 -5
- package/ROADMAP.md +2 -2
- package/dist/bin/yam.js +607 -20
- package/dist/lib/trust-kernel.d.ts +38 -1
- package/dist/lib/trust-kernel.js +153 -7
- package/package.json +2 -2
- package/references/cleanup-scan.md +33 -0
- package/references/final-report.md +6 -1
- package/references/risk-escalation.md +4 -0
- package/references/runtime-orchestration.md +7 -0
- package/references/scout.md +9 -3
- package/references/tool-trust-layer.md +14 -2
- package/references/trust-kernel.md +3 -0
- package/references/truth-matrix.md +4 -0
- package/references/ueye-proof.md +51 -1
- package/references/ueye.md +27 -3
- package/skills/scout/SKILL.md +19 -9
- package/skills/ueye/SKILL.md +20 -3
- package/src/bin/yam.ts +603 -17
- package/src/lib/trust-kernel.ts +199 -7
- package/templates/ueye-comparison.md +42 -0
- package/templates/ueye-review.md +42 -0
- package/templates/yam.project.md +14 -0
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, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
|
|
11
|
+
import { TRUTH_STATUSES, buildMediaGenerationProof, buildMissionPatchEnvelope, buildRollbackHint, buildRuntimeBackendEvidence, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, 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
|
}
|
|
@@ -115,6 +115,8 @@ Usage:
|
|
|
115
115
|
yam verify
|
|
116
116
|
yam detect [dir]
|
|
117
117
|
yam pack [dir]
|
|
118
|
+
yam context pressure [dir] [--json]
|
|
119
|
+
yam cleanup scan [dir] [--json]
|
|
118
120
|
yam budget [route]
|
|
119
121
|
yam measure <route> [--files n] [--commands n] [--report-lines n] [--seconds n]
|
|
120
122
|
yam tools doctor [dir]
|
|
@@ -122,6 +124,7 @@ Usage:
|
|
|
122
124
|
yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
|
|
123
125
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
124
126
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
127
|
+
yam ueye preflight [dir] [--json]
|
|
125
128
|
yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
|
|
126
129
|
yam media proof [--requested] [--attempted] [--output file] [--json]
|
|
127
130
|
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
@@ -302,6 +305,7 @@ async function verify({ quiet = false } = {}) {
|
|
|
302
305
|
'ui-quality.md',
|
|
303
306
|
'question.md',
|
|
304
307
|
'mission.md',
|
|
308
|
+
'cleanup-scan.md',
|
|
305
309
|
'doctor-scan.md',
|
|
306
310
|
'scout.md',
|
|
307
311
|
'runtime-orchestration.md',
|
|
@@ -529,6 +533,297 @@ function uniqueNextActionDetails(actions = []) {
|
|
|
529
533
|
}
|
|
530
534
|
return unique;
|
|
531
535
|
}
|
|
536
|
+
async function context(args = []) {
|
|
537
|
+
const subcommand = args[0] || 'help';
|
|
538
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
539
|
+
return contextUsage();
|
|
540
|
+
if (subcommand === 'pressure')
|
|
541
|
+
return contextPressure(args.slice(1));
|
|
542
|
+
console.error(`unknown context command: ${subcommand}`);
|
|
543
|
+
return contextUsage();
|
|
544
|
+
}
|
|
545
|
+
function contextUsage() {
|
|
546
|
+
console.log(`yam context
|
|
547
|
+
|
|
548
|
+
Usage:
|
|
549
|
+
yam context pressure [dir] [--json]
|
|
550
|
+
|
|
551
|
+
Notes:
|
|
552
|
+
Read-only context pressure scan. It explains when to summarize, refresh the project pack, narrow scope, or deepen the route.
|
|
553
|
+
`);
|
|
554
|
+
}
|
|
555
|
+
async function contextPressure(args = []) {
|
|
556
|
+
const parsed = parseDirJsonArgs(args);
|
|
557
|
+
const report = await buildContextPressureReport(parsed.dir);
|
|
558
|
+
if (parsed.json) {
|
|
559
|
+
console.log(JSON.stringify(report, null, 2));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
printContextPressureReport(report);
|
|
563
|
+
}
|
|
564
|
+
async function buildContextPressureReport(targetDir = process.cwd()) {
|
|
565
|
+
const dir = path.resolve(expandHome(targetDir || process.cwd()));
|
|
566
|
+
const pack = await findProjectPack(dir);
|
|
567
|
+
const memorySummary = path.join(dir, '.yam', 'memory', 'summary.md');
|
|
568
|
+
const instructionSurfaces = await findInstructionSurfaces(dir);
|
|
569
|
+
const detection = await detectProject(dir, { quiet: true });
|
|
570
|
+
const gitStatus = runReadOnlyCommandIn(dir, 'git', ['status', '--short']);
|
|
571
|
+
const dirtyFiles = gitStatus.trim().split(/\r?\n/).filter(Boolean);
|
|
572
|
+
const signals = {
|
|
573
|
+
project_pack: await projectPackPressureSignal(pack),
|
|
574
|
+
memory_summary: {
|
|
575
|
+
present: await exists(memorySummary),
|
|
576
|
+
path: memorySummary
|
|
577
|
+
},
|
|
578
|
+
git: {
|
|
579
|
+
dirty_file_count: dirtyFiles.length,
|
|
580
|
+
dirty_files_sample: dirtyFiles.slice(0, 12)
|
|
581
|
+
},
|
|
582
|
+
package_scripts: detection.packageJson ? detection.commands : {},
|
|
583
|
+
instruction_surfaces: instructionSurfaces
|
|
584
|
+
};
|
|
585
|
+
const reasons = [];
|
|
586
|
+
let score = 0;
|
|
587
|
+
if (!signals.project_pack.present) {
|
|
588
|
+
score += 3;
|
|
589
|
+
reasons.push('project pack is missing, so direction may need rediscovery');
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
if (signals.project_pack.age_days > PACK_STALE_DAYS) {
|
|
593
|
+
score += 2;
|
|
594
|
+
reasons.push(`project pack is ${signals.project_pack.age_days} days old`);
|
|
595
|
+
}
|
|
596
|
+
if (signals.project_pack.words < 80) {
|
|
597
|
+
score += 1;
|
|
598
|
+
reasons.push('project pack is very short, so local direction may be thin');
|
|
599
|
+
}
|
|
600
|
+
if (signals.project_pack.words > 1200) {
|
|
601
|
+
score += 2;
|
|
602
|
+
reasons.push('project pack is long, so reuse may become expensive');
|
|
603
|
+
}
|
|
604
|
+
if (signals.project_pack.missing_sections?.length) {
|
|
605
|
+
score += 2;
|
|
606
|
+
reasons.push(`project pack is missing sections: ${signals.project_pack.missing_sections.join(', ')}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (!signals.memory_summary.present) {
|
|
610
|
+
score += 1;
|
|
611
|
+
reasons.push('project memory summary is missing');
|
|
612
|
+
}
|
|
613
|
+
if (dirtyFiles.length >= 12) {
|
|
614
|
+
score += 3;
|
|
615
|
+
reasons.push(`${dirtyFiles.length} dirty files increase review and reporting pressure`);
|
|
616
|
+
}
|
|
617
|
+
else if (dirtyFiles.length >= 4) {
|
|
618
|
+
score += 2;
|
|
619
|
+
reasons.push(`${dirtyFiles.length} dirty files suggest the scope is widening`);
|
|
620
|
+
}
|
|
621
|
+
else if (dirtyFiles.length > 0) {
|
|
622
|
+
score += 1;
|
|
623
|
+
reasons.push(`${dirtyFiles.length} dirty file(s) should be kept in the final handoff`);
|
|
624
|
+
}
|
|
625
|
+
if (instructionSurfaces.issues.length) {
|
|
626
|
+
score += 3;
|
|
627
|
+
reasons.push('active hook or instruction issue can change route behavior');
|
|
628
|
+
}
|
|
629
|
+
if (instructionSurfaces.warnings.length >= 2) {
|
|
630
|
+
score += 1;
|
|
631
|
+
reasons.push('multiple instruction surfaces can make guidance harder to predict');
|
|
632
|
+
}
|
|
633
|
+
if (detection.packageJson && !detection.commands.build && !detection.commands.typecheck) {
|
|
634
|
+
score += 1;
|
|
635
|
+
reasons.push('no build/typecheck script was detected for quick implementation verification');
|
|
636
|
+
}
|
|
637
|
+
const pressure = score >= 7 ? 'high' : score >= 3 ? 'medium' : 'low';
|
|
638
|
+
return {
|
|
639
|
+
schema: 'yam.context-pressure.v1',
|
|
640
|
+
generated_at: new Date().toISOString(),
|
|
641
|
+
project: dir,
|
|
642
|
+
pressure,
|
|
643
|
+
score,
|
|
644
|
+
why_pressure_is_rising: reasons,
|
|
645
|
+
signals,
|
|
646
|
+
beginner_insight: contextPressureInsight(pressure),
|
|
647
|
+
recommended_next_action: pressure === 'high'
|
|
648
|
+
? 'pause and narrow scope, refresh yam.project.md or memory summary, then choose the route deliberately'
|
|
649
|
+
: pressure === 'medium'
|
|
650
|
+
? 'keep the task scoped and summarize important changes before expanding'
|
|
651
|
+
: 'continue with the current route and the smallest honest verification',
|
|
652
|
+
route_hint: pressure === 'high' ? '$deep or $mission when risk is real; otherwise split the task' : pressure === 'medium' ? '$quick with explicit boundaries, or $deep if verification claims grow' : '$quick or current route',
|
|
653
|
+
truth_status: 'partial'
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async function projectPackPressureSignal(pack) {
|
|
657
|
+
if (!pack)
|
|
658
|
+
return { present: false, path: '', age_days: null, words: 0, size_bytes: 0, missing_sections: REQUIRED_PACK_SECTIONS };
|
|
659
|
+
const text = await readText(pack).catch(() => '');
|
|
660
|
+
const stat = await fsp.stat(pack).catch(() => null);
|
|
661
|
+
return {
|
|
662
|
+
present: true,
|
|
663
|
+
path: pack,
|
|
664
|
+
age_days: stat ? Math.floor((Date.now() - stat.mtimeMs) / 86400000) : null,
|
|
665
|
+
words: countWords(text),
|
|
666
|
+
size_bytes: stat?.size || 0,
|
|
667
|
+
missing_sections: REQUIRED_PACK_SECTIONS.filter((section) => !hasHeading(text, section))
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function contextPressureInsight(pressure) {
|
|
671
|
+
if (pressure === 'high')
|
|
672
|
+
return 'The project has enough moving pieces that a non-specialist may lose track of what changed, what was checked, or which instruction surface is steering the agent.';
|
|
673
|
+
if (pressure === 'medium')
|
|
674
|
+
return 'The task is still manageable, but scope or stale context may start causing repeated reading and weaker final explanations.';
|
|
675
|
+
return 'The current context looks small enough to keep momentum without heavy proof, as long as claims stay modest.';
|
|
676
|
+
}
|
|
677
|
+
function printContextPressureReport(report) {
|
|
678
|
+
console.log('yam context pressure');
|
|
679
|
+
console.log(`Project: ${report.project}`);
|
|
680
|
+
console.log(`Pressure: ${report.pressure} (score ${report.score})`);
|
|
681
|
+
console.log(`Insight: ${report.beginner_insight}`);
|
|
682
|
+
if (report.why_pressure_is_rising.length) {
|
|
683
|
+
console.log('');
|
|
684
|
+
console.log('Why pressure is rising:');
|
|
685
|
+
for (const reason of report.why_pressure_is_rising)
|
|
686
|
+
console.log(`- ${reason}`);
|
|
687
|
+
}
|
|
688
|
+
console.log('');
|
|
689
|
+
console.log(`Next: ${report.recommended_next_action}`);
|
|
690
|
+
console.log(`Route hint: ${report.route_hint}`);
|
|
691
|
+
}
|
|
692
|
+
async function cleanup(args = []) {
|
|
693
|
+
const subcommand = args[0] || 'help';
|
|
694
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
695
|
+
return cleanupUsage();
|
|
696
|
+
if (subcommand === 'scan')
|
|
697
|
+
return cleanupScan(args.slice(1));
|
|
698
|
+
console.error(`unknown cleanup command: ${subcommand}`);
|
|
699
|
+
return cleanupUsage();
|
|
700
|
+
}
|
|
701
|
+
function cleanupUsage() {
|
|
702
|
+
console.log(`yam cleanup
|
|
703
|
+
|
|
704
|
+
Usage:
|
|
705
|
+
yam cleanup scan [dir] [--json]
|
|
706
|
+
|
|
707
|
+
Notes:
|
|
708
|
+
Read-only cleanup scan. It never deletes files; it only reports confusing surfaces and safe next actions.
|
|
709
|
+
`);
|
|
710
|
+
}
|
|
711
|
+
async function cleanupScan(args = []) {
|
|
712
|
+
const parsed = parseDirJsonArgs(args);
|
|
713
|
+
const report = await buildCleanupScanReport(parsed.dir);
|
|
714
|
+
if (parsed.json) {
|
|
715
|
+
console.log(JSON.stringify(report, null, 2));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
printCleanupScanReport(report);
|
|
719
|
+
}
|
|
720
|
+
async function buildCleanupScanReport(targetDir = process.cwd()) {
|
|
721
|
+
const dir = path.resolve(expandHome(targetDir || process.cwd()));
|
|
722
|
+
const findings = [];
|
|
723
|
+
const instructionSurfaces = await findInstructionSurfaces(dir);
|
|
724
|
+
for (const found of instructionSurfaces.found) {
|
|
725
|
+
const issue = instructionSurfaces.issues.find((item) => item.startsWith(`${found}:`));
|
|
726
|
+
const warning = instructionSurfaces.warnings.find((item) => item.startsWith(`${found}:`));
|
|
727
|
+
findings.push(cleanupFinding(issue ? 'high' : 'medium', found, issue || warning || 'instruction surface detected', 'inspect the file and keep only the instruction surface you intend to use'));
|
|
728
|
+
}
|
|
729
|
+
const projectAgents = path.join(dir, '.agents');
|
|
730
|
+
if (await exists(projectAgents)) {
|
|
731
|
+
findings.push(cleanupFinding('medium', '.agents', 'project-local skills can override or duplicate user-level skills', 'review project-local skills before assuming global yam-flow behavior'));
|
|
732
|
+
}
|
|
733
|
+
for (const hook of [path.join(dir, '.codex', 'hooks.json'), path.join(os.homedir(), '.codex', 'hooks.json')]) {
|
|
734
|
+
if (await exists(hook))
|
|
735
|
+
findings.push(cleanupFinding('high', hook, 'active hooks can add hidden latency or route pressure', 'disable or edit only after user approval and backup'));
|
|
736
|
+
}
|
|
737
|
+
for (const skill of [...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
|
|
738
|
+
if (await exists(path.join(DEST, skill, 'SKILL.md')))
|
|
739
|
+
findings.push(cleanupFinding('medium', `installed skill: ${skill}`, 'old skill names can confuse route selection', 'run the explicit current installer or remove only with user approval'));
|
|
740
|
+
}
|
|
741
|
+
const legacyPackageHits = await scanLimitedTextFiles(dir, ['package.json', 'README.md', 'COMMANDS.md', 'CHANGELOG.md'], /\b(yam-harness|yam-codex)\b/i);
|
|
742
|
+
for (const hit of legacyPackageHits) {
|
|
743
|
+
findings.push(cleanupFinding('low', hit.file, `legacy package name mention: ${hit.match}`, 'update public text only if it confuses install guidance'));
|
|
744
|
+
}
|
|
745
|
+
for (const artifact of await staleYamArtifacts(dir)) {
|
|
746
|
+
findings.push(cleanupFinding('low', artifact.file, artifact.reason, 'archive or refresh only if it misleads the current work'));
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
schema: 'yam.cleanup-scan.v1',
|
|
750
|
+
generated_at: new Date().toISOString(),
|
|
751
|
+
project: dir,
|
|
752
|
+
destructive: false,
|
|
753
|
+
findings,
|
|
754
|
+
risk_level: overallCleanupRisk(findings),
|
|
755
|
+
safe_next_action: findings.length ? 'review findings and make explicit user-approved cleanup decisions' : 'no cleanup action needed',
|
|
756
|
+
truth_status: 'partial'
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function cleanupFinding(riskLevel, surface, whyItMatters, safeNextAction) {
|
|
760
|
+
return {
|
|
761
|
+
risk_level: riskLevel,
|
|
762
|
+
surface,
|
|
763
|
+
why_it_matters: whyItMatters,
|
|
764
|
+
safe_next_action: safeNextAction,
|
|
765
|
+
destructive: false,
|
|
766
|
+
truth_status: 'partial'
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function overallCleanupRisk(findings = []) {
|
|
770
|
+
if (findings.some((item) => item.risk_level === 'high'))
|
|
771
|
+
return 'high';
|
|
772
|
+
if (findings.some((item) => item.risk_level === 'medium'))
|
|
773
|
+
return 'medium';
|
|
774
|
+
return 'low';
|
|
775
|
+
}
|
|
776
|
+
async function scanLimitedTextFiles(dir, relativeFiles = [], pattern = /$^/) {
|
|
777
|
+
const hits = [];
|
|
778
|
+
for (const relative of relativeFiles) {
|
|
779
|
+
const file = path.join(dir, relative);
|
|
780
|
+
if (!await exists(file))
|
|
781
|
+
continue;
|
|
782
|
+
const text = await readText(file).catch(() => '');
|
|
783
|
+
const match = text.match(pattern);
|
|
784
|
+
if (match)
|
|
785
|
+
hits.push({ file: relative, match: match[0] });
|
|
786
|
+
}
|
|
787
|
+
return hits;
|
|
788
|
+
}
|
|
789
|
+
async function staleYamArtifacts(dir) {
|
|
790
|
+
const candidates = [
|
|
791
|
+
path.join(dir, '.yam', 'proof.json'),
|
|
792
|
+
path.join(dir, '.yam', 'proof.md'),
|
|
793
|
+
path.join(dir, '.yam', 'runtime-proof.md'),
|
|
794
|
+
path.join(dir, '.yam', 'ueye-report.json')
|
|
795
|
+
];
|
|
796
|
+
const artifacts = [];
|
|
797
|
+
for (const file of candidates) {
|
|
798
|
+
if (!await exists(file))
|
|
799
|
+
continue;
|
|
800
|
+
const stat = await fsp.stat(file).catch(() => null);
|
|
801
|
+
if (stat && Date.now() - stat.mtimeMs > 14 * 86400000) {
|
|
802
|
+
artifacts.push({ file: path.relative(dir, file), reason: 'old proof/runtime artifact may be mistaken for current evidence' });
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return artifacts;
|
|
806
|
+
}
|
|
807
|
+
function printCleanupScanReport(report) {
|
|
808
|
+
console.log('yam cleanup scan');
|
|
809
|
+
console.log(`Project: ${report.project}`);
|
|
810
|
+
console.log(`Risk level: ${report.risk_level}`);
|
|
811
|
+
console.log('Destructive: false');
|
|
812
|
+
if (!report.findings.length) {
|
|
813
|
+
console.log('No confusing cleanup surfaces found.');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
for (const finding of report.findings) {
|
|
817
|
+
console.log(`- ${finding.risk_level}: ${finding.surface}`);
|
|
818
|
+
console.log(` why: ${finding.why_it_matters}`);
|
|
819
|
+
console.log(` next: ${finding.safe_next_action}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function parseDirJsonArgs(args = []) {
|
|
823
|
+
const flags = parseSimpleFlags(args, new Set(['json']));
|
|
824
|
+
const dir = flags._?.find(looksLikeDirectoryArg) || process.cwd();
|
|
825
|
+
return { dir, json: Boolean(flags.json) };
|
|
826
|
+
}
|
|
532
827
|
async function tools(args = []) {
|
|
533
828
|
const subcommand = args[0] || 'doctor';
|
|
534
829
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
@@ -588,6 +883,8 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
588
883
|
const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
|
|
589
884
|
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
590
885
|
const sqlScan = await scanSqlFiles(dir);
|
|
886
|
+
const contextPressure = await buildContextPressureReport(dir);
|
|
887
|
+
const realProbe = await buildRealProbeReport(dir, packageInfo.pkg, detection);
|
|
591
888
|
const rows = [
|
|
592
889
|
readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
|
|
593
890
|
readinessRow('Yam skills', await status({ quiet: true }) === 0 ? 'ready' : 'missing', DEST),
|
|
@@ -624,6 +921,8 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
624
921
|
instructionSurfaces,
|
|
625
922
|
projectSurfaces,
|
|
626
923
|
frameworkChecklist,
|
|
924
|
+
contextPressure,
|
|
925
|
+
realProbe,
|
|
627
926
|
sqlScan,
|
|
628
927
|
riskNotes,
|
|
629
928
|
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
@@ -666,6 +965,16 @@ function printToolsDoctorReport(data) {
|
|
|
666
965
|
console.log(`- ${check.id}: ${check.label} (${check.route})`);
|
|
667
966
|
}
|
|
668
967
|
}
|
|
968
|
+
if (data.contextPressure) {
|
|
969
|
+
console.log('');
|
|
970
|
+
console.log(`Context pressure: ${data.contextPressure.pressure} (${data.contextPressure.recommended_next_action})`);
|
|
971
|
+
}
|
|
972
|
+
if (data.realProbe?.probes?.length) {
|
|
973
|
+
console.log('');
|
|
974
|
+
console.log(`Real probe: ${data.realProbe.readiness_truth_status}`);
|
|
975
|
+
for (const probe of data.realProbe.probes)
|
|
976
|
+
console.log(`- ${probe.id}: ${probe.status} (${probe.truth_status})`);
|
|
977
|
+
}
|
|
669
978
|
if (data.sqlScan.filesScanned > 0) {
|
|
670
979
|
console.log('');
|
|
671
980
|
console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
|
|
@@ -794,6 +1103,62 @@ async function detectFrameworkChecklist(dir, pkg = null) {
|
|
|
794
1103
|
function checklistRow(id, label, route) {
|
|
795
1104
|
return { id, label, route };
|
|
796
1105
|
}
|
|
1106
|
+
async function buildRealProbeReport(dir = process.cwd(), pkg = null, detection = {}) {
|
|
1107
|
+
const probes = [];
|
|
1108
|
+
probes.push(realProbeRow('node_version', 'observed', process.version, 'verified'));
|
|
1109
|
+
const npmVersion = runReadOnlyCommandIn(dir, 'npm', ['--version']).trim();
|
|
1110
|
+
probes.push(realProbeRow('npm_version', npmVersion ? 'observed' : 'not_found', npmVersion || 'npm not found or not runnable', npmVersion ? 'verified' : 'partial'));
|
|
1111
|
+
probes.push(realProbeRow('yam_binary', 'observed', `${process.argv[1] || 'unknown'} @ ${VERSION}`, 'verified'));
|
|
1112
|
+
const scripts = detection.packageJson ? detection.commands : {};
|
|
1113
|
+
probes.push(realProbeRow('package_scripts', Object.values(scripts).some(Boolean) ? 'observed' : 'not_found', scripts, Object.values(scripts).some(Boolean) ? 'verified' : 'partial'));
|
|
1114
|
+
const playwrightPath = await localPackagePath(dir, 'playwright');
|
|
1115
|
+
probes.push(realProbeRow('playwright_package', playwrightPath ? 'observed' : 'not_found', playwrightPath || 'playwright package not found in project or yam package context', playwrightPath ? 'verified' : 'partial'));
|
|
1116
|
+
const browserCache = await pluginCacheHas('openai-bundled/browser');
|
|
1117
|
+
probes.push(realProbeRow('in_app_browser_plugin_cache', browserCache ? 'observed' : 'not_found', browserCache ? 'plugin cache present' : 'plugin cache not found locally', browserCache ? 'partial' : 'partial'));
|
|
1118
|
+
const tmux = await findExecutable('tmux');
|
|
1119
|
+
const zellij = await findExecutable('zellij');
|
|
1120
|
+
probes.push(realProbeRow('tmux', tmux ? 'observed' : 'not_found', tmux || 'not found on PATH', tmux ? 'verified' : 'partial'));
|
|
1121
|
+
probes.push(realProbeRow('zellij', zellij ? 'observed' : 'not_found', zellij || 'not found on PATH', zellij ? 'verified' : 'partial'));
|
|
1122
|
+
const gitInside = runReadOnlyCommandIn(dir, 'git', ['rev-parse', '--is-inside-work-tree']).trim();
|
|
1123
|
+
const gitDirty = runReadOnlyCommandIn(dir, 'git', ['status', '--short']).trim();
|
|
1124
|
+
probes.push(realProbeRow('git_state', gitInside === 'true' ? 'observed' : 'not_found', {
|
|
1125
|
+
inside_work_tree: gitInside === 'true',
|
|
1126
|
+
dirty_file_count: gitDirty ? gitDirty.split(/\r?\n/).length : 0
|
|
1127
|
+
}, gitInside === 'true' ? 'verified' : 'partial'));
|
|
1128
|
+
return {
|
|
1129
|
+
schema: 'yam.real-probe.v1',
|
|
1130
|
+
generated_at: new Date().toISOString(),
|
|
1131
|
+
project: path.resolve(dir),
|
|
1132
|
+
probes,
|
|
1133
|
+
readiness_truth_status: probes.some((probe) => probe.status === 'blocked') ? 'blocked' : 'partial',
|
|
1134
|
+
note: 'read-only local availability probe; no browser, server, database, or process was started'
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function realProbeRow(id, status, evidence, truthStatus = 'partial') {
|
|
1138
|
+
return {
|
|
1139
|
+
id,
|
|
1140
|
+
status,
|
|
1141
|
+
evidence,
|
|
1142
|
+
truth_status: truthStatus
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
async function localPackagePath(dir, packageName) {
|
|
1146
|
+
const candidates = [
|
|
1147
|
+
path.join(dir, 'node_modules', packageName, 'package.json'),
|
|
1148
|
+
path.join(ROOT, 'node_modules', packageName, 'package.json')
|
|
1149
|
+
];
|
|
1150
|
+
for (const candidate of candidates) {
|
|
1151
|
+
if (await exists(candidate))
|
|
1152
|
+
return candidate;
|
|
1153
|
+
}
|
|
1154
|
+
try {
|
|
1155
|
+
const projectRequire = createRequire(path.join(dir, 'package.json'));
|
|
1156
|
+
return projectRequire.resolve(`${packageName}/package.json`);
|
|
1157
|
+
}
|
|
1158
|
+
catch {
|
|
1159
|
+
return '';
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
797
1162
|
function toolsDoctorNextActions(input) {
|
|
798
1163
|
return toolsDoctorNextActionDetails(input).map((action) => action.next_action);
|
|
799
1164
|
}
|
|
@@ -1171,6 +1536,13 @@ async function release(args = []) {
|
|
|
1171
1536
|
for (const id of report.failed)
|
|
1172
1537
|
console.log(` - ${id}`);
|
|
1173
1538
|
}
|
|
1539
|
+
if (report.publishBlockerEvidence?.length) {
|
|
1540
|
+
console.log('- Publish blockers:');
|
|
1541
|
+
for (const blocker of report.publishBlockerEvidence) {
|
|
1542
|
+
console.log(` - ${blocker.blocker_kind}: ${blocker.likely_cause}`);
|
|
1543
|
+
console.log(` next: ${blocker.safe_next_action}`);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1174
1546
|
if (report.next_actions?.length) {
|
|
1175
1547
|
console.log('- Next actions:');
|
|
1176
1548
|
for (const action of report.next_actions)
|
|
@@ -1193,7 +1565,8 @@ function runReleaseReport() {
|
|
|
1193
1565
|
const provenance = releaseProvenance(checks);
|
|
1194
1566
|
const tarball = releaseTarballProvenance();
|
|
1195
1567
|
const freshness = releaseFreshness(checks, provenance, tarball);
|
|
1196
|
-
const
|
|
1568
|
+
const publishBlockerEvidence = publishBlockerEvidenceFromRelease(checks, tarball);
|
|
1569
|
+
const nextActions = releaseNextActions(checks, provenance, tarball, publishBlockerEvidence);
|
|
1197
1570
|
return {
|
|
1198
1571
|
schema: 'yam.release-report.v1',
|
|
1199
1572
|
generated_at: startedAt,
|
|
@@ -1206,6 +1579,7 @@ function runReleaseReport() {
|
|
|
1206
1579
|
provenance,
|
|
1207
1580
|
tarball,
|
|
1208
1581
|
freshness,
|
|
1582
|
+
publishBlockerEvidence,
|
|
1209
1583
|
next_actions: nextActions
|
|
1210
1584
|
};
|
|
1211
1585
|
}
|
|
@@ -1282,7 +1656,7 @@ function releaseFreshness(checks = [], provenance = {}, tarball = {}) {
|
|
|
1282
1656
|
truth_status: [dist, registry, boundary, smoke, tarballStatus].every((value) => !/not-verified|needs-check/.test(value)) ? 'verified' : 'partial'
|
|
1283
1657
|
};
|
|
1284
1658
|
}
|
|
1285
|
-
function releaseNextActions(checks = [], provenance = {}, tarball = {}) {
|
|
1659
|
+
function releaseNextActions(checks = [], provenance = {}, tarball = {}, publishBlockers = []) {
|
|
1286
1660
|
const actions = [];
|
|
1287
1661
|
for (const check of checks) {
|
|
1288
1662
|
if (check.status === 'passed')
|
|
@@ -1298,8 +1672,70 @@ function releaseNextActions(checks = [], provenance = {}, tarball = {}) {
|
|
|
1298
1672
|
if (tarball.status !== 'checked') {
|
|
1299
1673
|
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
1674
|
}
|
|
1675
|
+
for (const blocker of publishBlockers) {
|
|
1676
|
+
actions.push(nextActionDetail(`publish-${blocker.blocker_kind}`, blocker.truth_status === 'blocked' ? 'error' : 'warning', blocker.likely_cause, blocker.safe_next_action, 'npm publish'));
|
|
1677
|
+
}
|
|
1301
1678
|
return uniqueNextActionDetails(actions);
|
|
1302
1679
|
}
|
|
1680
|
+
function publishBlockerEvidenceFromRelease(checks = [], tarball = {}) {
|
|
1681
|
+
const texts = [
|
|
1682
|
+
...checks.map((check) => `${check.id}\n${check.note || ''}`),
|
|
1683
|
+
tarball.reason || ''
|
|
1684
|
+
].filter(Boolean);
|
|
1685
|
+
const blockers = [];
|
|
1686
|
+
for (const text of texts) {
|
|
1687
|
+
const blocker = classifyPublishBlockerText(text);
|
|
1688
|
+
if (blocker)
|
|
1689
|
+
blockers.push(blocker);
|
|
1690
|
+
}
|
|
1691
|
+
const seen = new Set();
|
|
1692
|
+
return blockers.filter((blocker) => {
|
|
1693
|
+
if (seen.has(blocker.blocker_kind))
|
|
1694
|
+
return false;
|
|
1695
|
+
seen.add(blocker.blocker_kind);
|
|
1696
|
+
return true;
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
function classifyPublishBlockerText(text = '') {
|
|
1700
|
+
const value = String(text || '');
|
|
1701
|
+
if (!value.trim())
|
|
1702
|
+
return null;
|
|
1703
|
+
const excerpt = summarizeCheckOutput(value);
|
|
1704
|
+
if (/\b(ENEEDAUTH|E401|Unauthorized|whoami)\b/i.test(value)) {
|
|
1705
|
+
return publishBlocker('auth_required', excerpt, 'npm is not logged in for this shell or token', 'run `npm login` or refresh the npm token, then rerun `npm whoami`');
|
|
1706
|
+
}
|
|
1707
|
+
if (/\b(OTP|one-time password|two-factor|2FA)\b/i.test(value)) {
|
|
1708
|
+
return publishBlocker('otp_required', excerpt, 'npm publish requires a current two-factor authentication code', 'rerun publish with `npm publish --otp=123456` using the current authenticator code');
|
|
1709
|
+
}
|
|
1710
|
+
if (/\bE403\b|403\s+Forbidden|Forbidden\s+-\s+PUT|npm ERR!.*Forbidden/i.test(value)) {
|
|
1711
|
+
return publishBlocker('permission_forbidden', excerpt, 'the npm account lacks publish permission or the package policy rejected the request', 'confirm package ownership with `npm owner ls <package>` and publish with the owning account');
|
|
1712
|
+
}
|
|
1713
|
+
if (/\bE404\b|Not Found - PUT/i.test(value)) {
|
|
1714
|
+
return publishBlocker('package_or_permission_not_found', excerpt, 'npm could not publish this package name from the current account or registry context', 'check `npm config get registry`, `npm owner ls <package>`, and package name access');
|
|
1715
|
+
}
|
|
1716
|
+
if (/previously published|cannot publish over|already exists|You cannot publish over/i.test(value)) {
|
|
1717
|
+
return publishBlocker('version_already_published', excerpt, 'npm versions are immutable once published', 'bump package.json to the next patch version before publishing');
|
|
1718
|
+
}
|
|
1719
|
+
if (/(EACCES|EPERM|permission denied|cache|_cacache)/i.test(value)) {
|
|
1720
|
+
return publishBlocker('npm_cache_permission', excerpt, 'npm cache or filesystem permissions are blocking the command', 'retry with a user-owned cache such as `npm_config_cache=/private/tmp/yam-npm-cache npm publish`');
|
|
1721
|
+
}
|
|
1722
|
+
if (/(ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|registry unreachable|network timeout)/i.test(value)) {
|
|
1723
|
+
return publishBlocker('registry_unreachable', excerpt, 'the npm registry or network path was unreachable', 'wait, check network/VPN, then retry the read-only registry check first');
|
|
1724
|
+
}
|
|
1725
|
+
if (/(tarball|package boundary|files list|npm pack)/i.test(value)) {
|
|
1726
|
+
return publishBlocker('tarball_or_boundary_failure', excerpt, 'the package contents or dry-run tarball check failed', 'run `npm run package-boundary:check` and `npm pack --dry-run` before publishing');
|
|
1727
|
+
}
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
function publishBlocker(kind, excerpt, likelyCause, safeNextAction) {
|
|
1731
|
+
return {
|
|
1732
|
+
blocker_kind: kind,
|
|
1733
|
+
native_error_excerpt: excerpt,
|
|
1734
|
+
likely_cause: likelyCause,
|
|
1735
|
+
safe_next_action: safeNextAction,
|
|
1736
|
+
truth_status: 'partial'
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1303
1739
|
function runReadOnlyCommand(command, args = []) {
|
|
1304
1740
|
const result = spawnSync(command, args, {
|
|
1305
1741
|
cwd: ROOT,
|
|
@@ -1310,6 +1746,16 @@ function runReadOnlyCommand(command, args = []) {
|
|
|
1310
1746
|
return '';
|
|
1311
1747
|
return String(result.stdout || '');
|
|
1312
1748
|
}
|
|
1749
|
+
function runReadOnlyCommandIn(cwd, command, args = []) {
|
|
1750
|
+
const result = spawnSync(command, args, {
|
|
1751
|
+
cwd,
|
|
1752
|
+
encoding: 'utf8',
|
|
1753
|
+
timeout: 30000
|
|
1754
|
+
});
|
|
1755
|
+
if (result.status !== 0)
|
|
1756
|
+
return '';
|
|
1757
|
+
return String(result.stdout || '');
|
|
1758
|
+
}
|
|
1313
1759
|
function runReleaseCheck(id, commandSpec) {
|
|
1314
1760
|
const [command, args] = commandSpec;
|
|
1315
1761
|
const start = Date.now();
|
|
@@ -1353,6 +1799,8 @@ async function ueye(args = []) {
|
|
|
1353
1799
|
return ueyeCapture(args.slice(1));
|
|
1354
1800
|
if (subcommand === 'compare')
|
|
1355
1801
|
return ueyeCompare(args.slice(1));
|
|
1802
|
+
if (subcommand === 'preflight')
|
|
1803
|
+
return ueyePreflight(args.slice(1));
|
|
1356
1804
|
if (subcommand === 'report')
|
|
1357
1805
|
return ueyeReport(args.slice(1));
|
|
1358
1806
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
@@ -1366,14 +1814,83 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
|
|
|
1366
1814
|
Usage:
|
|
1367
1815
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
1368
1816
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
1369
|
-
yam ueye
|
|
1817
|
+
yam ueye preflight [dir] [--json]
|
|
1818
|
+
yam ueye report [--reference ref.png] [--actual screenshot.png] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--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]
|
|
1370
1819
|
|
|
1371
1820
|
Notes:
|
|
1372
1821
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
1373
1822
|
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.
|
|
1823
|
+
report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
|
|
1375
1824
|
`);
|
|
1376
1825
|
}
|
|
1826
|
+
async function ueyePreflight(args = []) {
|
|
1827
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1828
|
+
return ueyeUsage();
|
|
1829
|
+
const parsed = parseDirJsonArgs(args);
|
|
1830
|
+
const report = await buildUeyePreflightReport(parsed.dir);
|
|
1831
|
+
if (parsed.json) {
|
|
1832
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
console.log('Ueye preflight');
|
|
1836
|
+
console.log(`Project: ${report.project}`);
|
|
1837
|
+
console.log(`Preflight id: ${report.preflight_id}`);
|
|
1838
|
+
console.log(`Truth status: ${report.truth_status}`);
|
|
1839
|
+
for (const item of report.checklist)
|
|
1840
|
+
console.log(`- ${item.status}: ${item.item} (${item.next_action})`);
|
|
1841
|
+
}
|
|
1842
|
+
async function buildUeyePreflightReport(targetDir = process.cwd()) {
|
|
1843
|
+
const dir = path.resolve(expandHome(targetDir || process.cwd()));
|
|
1844
|
+
const packageInfo = await projectPackageInfo(dir);
|
|
1845
|
+
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
1846
|
+
const pack = await findProjectPack(dir);
|
|
1847
|
+
const playwrightPath = await localPackagePath(dir, 'playwright');
|
|
1848
|
+
const browserCache = await pluginCacheHas('openai-bundled/browser');
|
|
1849
|
+
const uiPaths = await existingRelativePaths(dir, ['app', 'src/app', 'pages', 'src/pages', 'components', 'src/components', 'styles', 'src/styles']);
|
|
1850
|
+
const screenshotAvailability = playwrightPath ? 'automated_capture_possible' : browserCache ? 'in_app_or_user_screenshot_needed' : 'user_screenshot_needed';
|
|
1851
|
+
const p0p1Risks = [];
|
|
1852
|
+
if (!frameworkChecklist.uiLike)
|
|
1853
|
+
p0p1Risks.push('target UI surface is not detected yet');
|
|
1854
|
+
if (!browserCache && !playwrightPath)
|
|
1855
|
+
p0p1Risks.push('no local capture surface was observed');
|
|
1856
|
+
if (!pack)
|
|
1857
|
+
p0p1Risks.push('project direction pack is missing');
|
|
1858
|
+
return {
|
|
1859
|
+
schema: 'yam.ueye-preflight.v1',
|
|
1860
|
+
preflight_id: `ueye-preflight-${timestampId()}`,
|
|
1861
|
+
generated_at: new Date().toISOString(),
|
|
1862
|
+
project: dir,
|
|
1863
|
+
detected_framework: frameworkChecklist.framework,
|
|
1864
|
+
ui_paths: uiPaths,
|
|
1865
|
+
reference_source_needed: true,
|
|
1866
|
+
target_states_needed: ['default', 'loading', 'error', 'empty', 'mobile'],
|
|
1867
|
+
mobile_responsive_check_needed: true,
|
|
1868
|
+
contrast_cta_accessibility_risk: frameworkChecklist.uiLike ? 'check before done claim' : 'unknown until UI target is identified',
|
|
1869
|
+
p0_p1_risk_candidates: p0p1Risks,
|
|
1870
|
+
screenshot_capture_availability: screenshotAvailability,
|
|
1871
|
+
preferred_surface: 'in-app-browser',
|
|
1872
|
+
checklist: [
|
|
1873
|
+
preflightItem('reference', 'reference source selected or user confirms no reference', 'needed', 'record reference path, screenshot, or text target before claiming design fidelity'),
|
|
1874
|
+
preflightItem('states', 'primary states are listed', 'needed', 'prepare default/loading/error/empty/mobile states when the UI supports them'),
|
|
1875
|
+
preflightItem('mobile', 'mobile/responsive pass planned', 'needed', 'capture or inspect a narrow viewport before verified visual claims'),
|
|
1876
|
+
preflightItem('contrast_cta_accessibility', 'CTA, contrast, and accessible labels reviewed', 'needed', 'mark P0/P1 risks before saying done'),
|
|
1877
|
+
preflightItem('capture', 'screenshot/capture surface available', screenshotAvailability === 'automated_capture_possible' ? 'observed' : 'partial', 'use in-app browser or user-provided screenshot if automation is unavailable')
|
|
1878
|
+
],
|
|
1879
|
+
completion_cap: 'preflight cannot prove visual quality without implementation screenshot and comparison evidence',
|
|
1880
|
+
truth_status: 'partial'
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
function preflightItem(id, item, status, nextAction) {
|
|
1884
|
+
return { id, item, status, next_action: nextAction, truth_status: status === 'observed' ? 'verified' : 'partial' };
|
|
1885
|
+
}
|
|
1886
|
+
async function existingRelativePaths(dir, relativePaths = []) {
|
|
1887
|
+
const found = [];
|
|
1888
|
+
for (const relative of relativePaths) {
|
|
1889
|
+
if (await exists(path.join(dir, relative)))
|
|
1890
|
+
found.push(relative);
|
|
1891
|
+
}
|
|
1892
|
+
return found;
|
|
1893
|
+
}
|
|
1377
1894
|
async function ueyeCapture(args = []) {
|
|
1378
1895
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1379
1896
|
return ueyeUsage();
|
|
@@ -1539,7 +2056,7 @@ async function ueyeCompare(args = []) {
|
|
|
1539
2056
|
async function ueyeReport(args = []) {
|
|
1540
2057
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1541
2058
|
return ueyeUsage();
|
|
1542
|
-
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', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'json']));
|
|
2059
|
+
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', 'preflight-id', 'p0-risk', 'quality-gate-note', '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', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
|
|
1543
2060
|
const reference = String(flags.reference || flags.ref || '');
|
|
1544
2061
|
const actual = String(flags.actual || flags.screenshot || '');
|
|
1545
2062
|
const referenceSources = [];
|
|
@@ -1604,7 +2121,10 @@ async function ueyeReport(args = []) {
|
|
|
1604
2121
|
truth_status: 'partial'
|
|
1605
2122
|
}));
|
|
1606
2123
|
}
|
|
1607
|
-
if (
|
|
2124
|
+
if (flags.comparison_result) {
|
|
2125
|
+
comparisonResult = normalizeUeyeComparisonResult(flags.comparison_result);
|
|
2126
|
+
}
|
|
2127
|
+
else if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
|
|
1608
2128
|
comparisonResult = 'matched';
|
|
1609
2129
|
}
|
|
1610
2130
|
else if (reference && actual) {
|
|
@@ -1624,6 +2144,22 @@ async function ueyeReport(args = []) {
|
|
|
1624
2144
|
},
|
|
1625
2145
|
comparison_result: comparisonResult,
|
|
1626
2146
|
design_quality: String(flags.design_quality || 'not-checked'),
|
|
2147
|
+
design_completion_gate: {
|
|
2148
|
+
mode: flags.strict || flags.gate_mode === 'strict' ? 'strict' : 'fast',
|
|
2149
|
+
completion_claim: flags.completion_claim || flags.completion_status || 'draft',
|
|
2150
|
+
design_score: numberOrNull(flags.design_score),
|
|
2151
|
+
min_design_score: numberOrNull(flags.min_design_score) ?? 8,
|
|
2152
|
+
p0: [...arrayFlag(flags.p0), ...arrayFlag(flags.open_p0), ...arrayFlag(flags.p0_risk)],
|
|
2153
|
+
p1: [...arrayFlag(flags.p1), ...arrayFlag(flags.open_p1)],
|
|
2154
|
+
states_checked: Boolean(flags.states_checked),
|
|
2155
|
+
mobile_checked: Boolean(flags.mobile_checked),
|
|
2156
|
+
responsive_checked: Boolean(flags.responsive_checked),
|
|
2157
|
+
contrast_checked: Boolean(flags.contrast_checked),
|
|
2158
|
+
accessibility_checked: Boolean(flags.accessibility_checked),
|
|
2159
|
+
cta_checked: Boolean(flags.cta_checked),
|
|
2160
|
+
direction_locked: Boolean(flags.direction_locked),
|
|
2161
|
+
reference_read: Boolean(flags.reference_read)
|
|
2162
|
+
},
|
|
1627
2163
|
blocked_reason: blockedReason,
|
|
1628
2164
|
next_action: String(flags.next_visual_action || flags.next_action || '')
|
|
1629
2165
|
});
|
|
@@ -1633,6 +2169,7 @@ async function ueyeReport(args = []) {
|
|
|
1633
2169
|
referenceSources,
|
|
1634
2170
|
implementationSources,
|
|
1635
2171
|
surfaceContext: report.surface_context,
|
|
2172
|
+
designCompletionGate: report.design_completion_gate,
|
|
1636
2173
|
comparisonResult,
|
|
1637
2174
|
designQuality: String(flags.design_quality || 'not-checked'),
|
|
1638
2175
|
similar: arrayFlag(flags.similar),
|
|
@@ -1653,6 +2190,12 @@ async function ueyeReport(args = []) {
|
|
|
1653
2190
|
const result = {
|
|
1654
2191
|
...report,
|
|
1655
2192
|
review_session_id: reviewSessionId,
|
|
2193
|
+
preflight: {
|
|
2194
|
+
preflight_id: String(flags.preflight_id || ''),
|
|
2195
|
+
p0_risks: arrayFlag(flags.p0_risk),
|
|
2196
|
+
quality_gate_notes: arrayFlag(flags.quality_gate_note),
|
|
2197
|
+
truth_status: flags.preflight_id || flags.p0_risk || flags.quality_gate_note ? 'partial' : 'assumed'
|
|
2198
|
+
},
|
|
1656
2199
|
tool_intent: 'visual',
|
|
1657
2200
|
capture_backend: String(flags.capture_backend || 'not-recorded'),
|
|
1658
2201
|
compare_backend: String(flags.compare_backend || 'local-file-hash'),
|
|
@@ -1686,7 +2229,7 @@ async function readPreviousUeyeReport(file = '') {
|
|
|
1686
2229
|
};
|
|
1687
2230
|
}
|
|
1688
2231
|
}
|
|
1689
|
-
function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceSources, implementationSources, surfaceContext, comparisonResult, designQuality, similar, different, missing, notes, resolved, newFindings, stillOpen, regressions, previousScreenshotId, currentScreenshotId, viewport, state, nextAction, truthStatus }) {
|
|
2232
|
+
function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceSources, implementationSources, surfaceContext, designCompletionGate, comparisonResult, designQuality, similar, different, missing, notes, resolved, newFindings, stillOpen, regressions, previousScreenshotId, currentScreenshotId, viewport, state, nextAction, truthStatus }) {
|
|
1690
2233
|
const hasReference = referenceSources.length > 0;
|
|
1691
2234
|
const hasImplementation = implementationSources.length > 0;
|
|
1692
2235
|
const continuityTruth = previousReport?.blocked_reason ? 'blocked' : previousReport ? 'partial' : 'assumed';
|
|
@@ -1711,6 +2254,7 @@ function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceS
|
|
|
1711
2254
|
implementation_count: implementationSources.length
|
|
1712
2255
|
},
|
|
1713
2256
|
surface_context: surfaceContext || buildUeyeSurfaceContext(),
|
|
2257
|
+
design_completion_gate: designCompletionGate || null,
|
|
1714
2258
|
comparison_result: comparisonResult,
|
|
1715
2259
|
design_quality: designQuality,
|
|
1716
2260
|
similar,
|
|
@@ -1729,6 +2273,12 @@ function buildUeyeComparisonReport({ reviewSessionId, previousReport, referenceS
|
|
|
1729
2273
|
truth_status: truthStatus
|
|
1730
2274
|
};
|
|
1731
2275
|
}
|
|
2276
|
+
function normalizeUeyeComparisonResult(value = '') {
|
|
2277
|
+
const text = String(value || '').toLowerCase();
|
|
2278
|
+
if (['matched', 'similar', 'different', 'not-verified', 'not-applicable'].includes(text))
|
|
2279
|
+
return text;
|
|
2280
|
+
return 'not-verified';
|
|
2281
|
+
}
|
|
1732
2282
|
async function media(args = []) {
|
|
1733
2283
|
const subcommand = args[0] || 'help';
|
|
1734
2284
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
@@ -1793,7 +2343,7 @@ function runtimeUsage() {
|
|
|
1793
2343
|
console.log(`yam runtime
|
|
1794
2344
|
|
|
1795
2345
|
Usage:
|
|
1796
|
-
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]
|
|
2346
|
+
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] [--started-at time] [--stopped-at time] [--cleanup-method text] [--cleanup-observed] [--left-running-intentionally] [--cleanup-checked] [--note text] [--json]
|
|
1797
2347
|
|
|
1798
2348
|
Notes:
|
|
1799
2349
|
Records a small runtime evidence shape. It does not start, stop, or inspect processes by itself.
|
|
@@ -1802,13 +2352,21 @@ Notes:
|
|
|
1802
2352
|
async function runtimeEvidence(args = []) {
|
|
1803
2353
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1804
2354
|
return runtimeUsage();
|
|
1805
|
-
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']));
|
|
2355
|
+
const flags = parseSimpleFlags(args, new Set(['backend', 'claim', 'evidence-id', 'command', 'pid', 'port', 'url', 'exit-code', 'screenshot-id', 'started-at', 'stopped-at', 'cleanup-method', 'cleanup-observed', 'left-running-intentionally', 'cleanup-checked', 'note', 'truth', 'intent', 'json']));
|
|
1806
2356
|
const evidence = buildRuntimeBackendEvidence({
|
|
1807
2357
|
backend: flags.backend,
|
|
1808
2358
|
claim: flags.claim,
|
|
1809
2359
|
evidence_id: String(flags.evidence_id || `runtime-${timestampId()}`),
|
|
1810
2360
|
command: String(flags.command || ''),
|
|
1811
2361
|
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2362
|
+
started_at: String(flags.started_at || ''),
|
|
2363
|
+
stopped_at: String(flags.stopped_at || ''),
|
|
2364
|
+
exit_code: numberOrNull(flags.exit_code),
|
|
2365
|
+
pid: numberOrNull(flags.pid),
|
|
2366
|
+
port: numberOrNull(flags.port),
|
|
2367
|
+
cleanup_method: String(flags.cleanup_method || ''),
|
|
2368
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
2369
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally),
|
|
1812
2370
|
note: String(flags.note || ''),
|
|
1813
2371
|
truth_status: isTruthStatus(flags.truth) ? flags.truth : undefined
|
|
1814
2372
|
});
|
|
@@ -1835,17 +2393,20 @@ function runtimeEvidenceDetails(flags = {}) {
|
|
|
1835
2393
|
exit_code: numberOrNull(flags.exit_code),
|
|
1836
2394
|
screenshot_id: String(flags.screenshot_id || ''),
|
|
1837
2395
|
started_at: String(flags.started_at || ''),
|
|
1838
|
-
stopped_at: String(flags.stopped_at || '')
|
|
2396
|
+
stopped_at: String(flags.stopped_at || ''),
|
|
2397
|
+
cleanup_method: String(flags.cleanup_method || ''),
|
|
2398
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
2399
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally)
|
|
1839
2400
|
};
|
|
1840
2401
|
}
|
|
1841
2402
|
function runtimeDetailsHasVerification(details = {}) {
|
|
1842
|
-
return Boolean(details.url || details.screenshot_id || details.pid !== null || details.port !== null || details.exit_code !== null || details.started_at || details.stopped_at);
|
|
2403
|
+
return Boolean(details.url || details.screenshot_id || details.pid !== null || details.port !== null || details.exit_code !== null || details.started_at || details.stopped_at || details.cleanup_method || details.cleanup_observed || details.left_running_intentionally);
|
|
1843
2404
|
}
|
|
1844
2405
|
function runtimeEvidenceNextAction(evidence) {
|
|
1845
2406
|
if (evidence.truth_status === 'proven' || evidence.truth_status === 'verified')
|
|
1846
2407
|
return 'attach this evidence to `yam proof` when making a runtime claim';
|
|
1847
|
-
if (evidence.claim === 'cleanup_verified' && !evidence.
|
|
1848
|
-
return 'rerun with --cleanup-
|
|
2408
|
+
if (evidence.claim === 'cleanup_verified' && !evidence.cleanup_observed && !evidence.left_running_intentionally)
|
|
2409
|
+
return 'rerun with --cleanup-observed plus exit/closure evidence, or record --left-running-intentionally';
|
|
1849
2410
|
if (evidence.backend === 'none' || evidence.backend === 'unknown')
|
|
1850
2411
|
return 'record the actual runtime backend before claiming runtime verification';
|
|
1851
2412
|
return 'add command output, screenshot id, pid/session id, or cleanup proof before upgrading the claim';
|
|
@@ -2026,7 +2587,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
|
2026
2587
|
if (allowed.size && !allowed.has(key))
|
|
2027
2588
|
continue;
|
|
2028
2589
|
const normalizedKey = key.replace(/-/g, '_');
|
|
2029
|
-
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
|
|
2590
|
+
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'cleanup-observed', 'left-running-intentionally', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read'].includes(key)) {
|
|
2030
2591
|
flags[normalizedKey] = true;
|
|
2031
2592
|
continue;
|
|
2032
2593
|
}
|
|
@@ -2183,6 +2744,7 @@ async function proof(args = []) {
|
|
|
2183
2744
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
2184
2745
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
2185
2746
|
printProofList('Media proof', summary.mediaProof || []);
|
|
2747
|
+
printProofList('Design completion', summary.designCompletion || []);
|
|
2186
2748
|
printProofList('Changed surfaces', summary.changed);
|
|
2187
2749
|
printProofList('Skipped', summary.skipped);
|
|
2188
2750
|
printProofList('Blocked', summary.blocked);
|
|
@@ -2279,6 +2841,10 @@ function buildProofSummary(parsed, artifact) {
|
|
|
2279
2841
|
...structuredEvidenceList(artifact.mediaProof),
|
|
2280
2842
|
...structuredEvidenceList(parsed.flags.media_proof)
|
|
2281
2843
|
],
|
|
2844
|
+
designCompletion: [
|
|
2845
|
+
...structuredEvidenceList(artifact.designCompletion),
|
|
2846
|
+
...structuredEvidenceList(parsed.flags.design_completion)
|
|
2847
|
+
],
|
|
2282
2848
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
2283
2849
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
2284
2850
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -2343,6 +2909,9 @@ function renderProofMarkdown(summary) {
|
|
|
2343
2909
|
'## Media proof',
|
|
2344
2910
|
...renderProofMarkdownList(summary.mediaProof || []),
|
|
2345
2911
|
'',
|
|
2912
|
+
'## Design completion',
|
|
2913
|
+
...renderProofMarkdownList(summary.designCompletion || []),
|
|
2914
|
+
'',
|
|
2346
2915
|
'## Changed surfaces',
|
|
2347
2916
|
...renderProofMarkdownList(summary.changed),
|
|
2348
2917
|
'',
|
|
@@ -2373,7 +2942,7 @@ function renderProofMarkdownList(values) {
|
|
|
2373
2942
|
function parseProofArgs(args = []) {
|
|
2374
2943
|
const flags = {};
|
|
2375
2944
|
const positionals = [];
|
|
2376
|
-
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']);
|
|
2945
|
+
const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'runtime-pid', 'runtime-port', 'runtime-exit-code', 'runtime-started-at', 'runtime-stopped-at', 'runtime-cleanup-method', 'cleanup-observed', 'left-running-intentionally', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
|
|
2377
2946
|
for (let index = 0; index < args.length; index += 1) {
|
|
2378
2947
|
const arg = args[index];
|
|
2379
2948
|
if (arg.startsWith('--')) {
|
|
@@ -2382,14 +2951,14 @@ function parseProofArgs(args = []) {
|
|
|
2382
2951
|
if (!aliases.has(key))
|
|
2383
2952
|
continue;
|
|
2384
2953
|
const normalizedKey = key.replace(/-/g, '_');
|
|
2385
|
-
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
|
|
2954
|
+
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked' || key === 'cleanup-observed' || key === 'left-running-intentionally') {
|
|
2386
2955
|
flags[normalizedKey] = true;
|
|
2387
2956
|
continue;
|
|
2388
2957
|
}
|
|
2389
2958
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
2390
2959
|
if (inlineValue === undefined)
|
|
2391
2960
|
index += 1;
|
|
2392
|
-
if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
2961
|
+
if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
2393
2962
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
2394
2963
|
}
|
|
2395
2964
|
else {
|
|
@@ -2433,6 +3002,7 @@ function emptyProofArtifact() {
|
|
|
2433
3002
|
missionEnvelope: [],
|
|
2434
3003
|
rollbackHint: [],
|
|
2435
3004
|
mediaProof: [],
|
|
3005
|
+
designCompletion: [],
|
|
2436
3006
|
cleanup: '',
|
|
2437
3007
|
changed: [],
|
|
2438
3008
|
skipped: [],
|
|
@@ -2459,6 +3029,7 @@ async function parseProofJson(file, empty) {
|
|
|
2459
3029
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
2460
3030
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
2461
3031
|
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
3032
|
+
designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
|
|
2462
3033
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
2463
3034
|
changed: arrayFlag(data.changed || data.files),
|
|
2464
3035
|
skipped: arrayFlag(data.skipped),
|
|
@@ -2489,6 +3060,7 @@ async function parseProofMarkdown(file, empty) {
|
|
|
2489
3060
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
2490
3061
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
2491
3062
|
mediaProof: readProofList(text, 'Media proof'),
|
|
3063
|
+
designCompletion: readProofList(text, 'Design completion'),
|
|
2492
3064
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
2493
3065
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
2494
3066
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -2537,13 +3109,21 @@ function structuredEvidenceList(value) {
|
|
|
2537
3109
|
return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
|
|
2538
3110
|
}
|
|
2539
3111
|
function directRuntimeBackendEvidence(flags) {
|
|
2540
|
-
if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked)
|
|
3112
|
+
if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked && !flags.cleanup_observed && !flags.left_running_intentionally)
|
|
2541
3113
|
return [];
|
|
2542
3114
|
return [JSON.stringify(buildRuntimeBackendEvidence({
|
|
2543
3115
|
backend: flags.runtime_backend,
|
|
2544
3116
|
claim: flags.runtime_claim,
|
|
2545
3117
|
evidence_id: flags.runtime_evidence_id,
|
|
2546
3118
|
command: flags.runtime_command,
|
|
3119
|
+
pid: numberOrNull(flags.runtime_pid),
|
|
3120
|
+
port: numberOrNull(flags.runtime_port),
|
|
3121
|
+
exit_code: numberOrNull(flags.runtime_exit_code),
|
|
3122
|
+
started_at: String(flags.runtime_started_at || ''),
|
|
3123
|
+
stopped_at: String(flags.runtime_stopped_at || ''),
|
|
3124
|
+
cleanup_method: String(flags.runtime_cleanup_method || ''),
|
|
3125
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
3126
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally),
|
|
2547
3127
|
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2548
3128
|
note: flags.runtime_note
|
|
2549
3129
|
}))];
|
|
@@ -2575,6 +3155,9 @@ function formatStructuredEvidence(value) {
|
|
|
2575
3155
|
if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
|
|
2576
3156
|
return JSON.stringify(buildMediaGenerationProof(data));
|
|
2577
3157
|
}
|
|
3158
|
+
if (data.schema === 'yam.ueye-design-completion-gate.v1' || data.completion_claim || data.ready_to_claim_done !== undefined) {
|
|
3159
|
+
return JSON.stringify(buildUeyeDesignCompletionGate(data));
|
|
3160
|
+
}
|
|
2578
3161
|
return JSON.stringify(data);
|
|
2579
3162
|
}
|
|
2580
3163
|
catch {
|
|
@@ -2583,7 +3166,7 @@ function formatStructuredEvidence(value) {
|
|
|
2583
3166
|
}
|
|
2584
3167
|
function hasProofEvidence(parsed) {
|
|
2585
3168
|
const flags = parsed.flags || {};
|
|
2586
|
-
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);
|
|
3169
|
+
return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'media_proof', 'design_completion', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup || flags.runtime_backend || flags.runtime_claim);
|
|
2587
3170
|
}
|
|
2588
3171
|
function printProofList(label, values) {
|
|
2589
3172
|
console.log(`- ${label}:`);
|
|
@@ -3275,6 +3858,10 @@ async function main() {
|
|
|
3275
3858
|
return detectProject(process.argv[3]);
|
|
3276
3859
|
if (command === 'pack')
|
|
3277
3860
|
return inspectProjectPack(process.argv[3]);
|
|
3861
|
+
if (command === 'context')
|
|
3862
|
+
return context(process.argv.slice(3));
|
|
3863
|
+
if (command === 'cleanup')
|
|
3864
|
+
return cleanup(process.argv.slice(3));
|
|
3278
3865
|
if (command === 'budget')
|
|
3279
3866
|
return budget(process.argv[3]);
|
|
3280
3867
|
if (command === 'measure')
|