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/src/bin/yam.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
buildMissionPatchEnvelope,
|
|
15
15
|
buildRollbackHint,
|
|
16
16
|
buildRuntimeBackendEvidence,
|
|
17
|
+
buildUeyeDesignCompletionGate,
|
|
17
18
|
buildUeyeVisualProvenance,
|
|
18
19
|
buildUeyeRunReport,
|
|
19
20
|
buildUeyeSurfaceContext,
|
|
@@ -133,6 +134,8 @@ Usage:
|
|
|
133
134
|
yam verify
|
|
134
135
|
yam detect [dir]
|
|
135
136
|
yam pack [dir]
|
|
137
|
+
yam context pressure [dir] [--json]
|
|
138
|
+
yam cleanup scan [dir] [--json]
|
|
136
139
|
yam budget [route]
|
|
137
140
|
yam measure <route> [--files n] [--commands n] [--report-lines n] [--seconds n]
|
|
138
141
|
yam tools doctor [dir]
|
|
@@ -140,6 +143,7 @@ Usage:
|
|
|
140
143
|
yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
|
|
141
144
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
142
145
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
146
|
+
yam ueye preflight [dir] [--json]
|
|
143
147
|
yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
|
|
144
148
|
yam media proof [--requested] [--attempted] [--output file] [--json]
|
|
145
149
|
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
@@ -326,6 +330,7 @@ async function verify({ quiet = false } = {}) {
|
|
|
326
330
|
'ui-quality.md',
|
|
327
331
|
'question.md',
|
|
328
332
|
'mission.md',
|
|
333
|
+
'cleanup-scan.md',
|
|
329
334
|
'doctor-scan.md',
|
|
330
335
|
'scout.md',
|
|
331
336
|
'runtime-orchestration.md',
|
|
@@ -527,6 +532,298 @@ function uniqueNextActionDetails(actions = []) {
|
|
|
527
532
|
return unique;
|
|
528
533
|
}
|
|
529
534
|
|
|
535
|
+
async function context(args = []) {
|
|
536
|
+
const subcommand = args[0] || 'help';
|
|
537
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return contextUsage();
|
|
538
|
+
if (subcommand === 'pressure') return contextPressure(args.slice(1));
|
|
539
|
+
console.error(`unknown context command: ${subcommand}`);
|
|
540
|
+
return contextUsage();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function contextUsage() {
|
|
544
|
+
console.log(`yam context
|
|
545
|
+
|
|
546
|
+
Usage:
|
|
547
|
+
yam context pressure [dir] [--json]
|
|
548
|
+
|
|
549
|
+
Notes:
|
|
550
|
+
Read-only context pressure scan. It explains when to summarize, refresh the project pack, narrow scope, or deepen the route.
|
|
551
|
+
`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function contextPressure(args = []) {
|
|
555
|
+
const parsed = parseDirJsonArgs(args);
|
|
556
|
+
const report = await buildContextPressureReport(parsed.dir);
|
|
557
|
+
if (parsed.json) {
|
|
558
|
+
console.log(JSON.stringify(report, null, 2));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
printContextPressureReport(report);
|
|
562
|
+
}
|
|
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: AnyRecord = {
|
|
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
|
+
|
|
588
|
+
if (!signals.project_pack.present) {
|
|
589
|
+
score += 3;
|
|
590
|
+
reasons.push('project pack is missing, so direction may need rediscovery');
|
|
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
|
+
} else if (dirtyFiles.length >= 4) {
|
|
617
|
+
score += 2;
|
|
618
|
+
reasons.push(`${dirtyFiles.length} dirty files suggest the scope is widening`);
|
|
619
|
+
} else if (dirtyFiles.length > 0) {
|
|
620
|
+
score += 1;
|
|
621
|
+
reasons.push(`${dirtyFiles.length} dirty file(s) should be kept in the final handoff`);
|
|
622
|
+
}
|
|
623
|
+
if (instructionSurfaces.issues.length) {
|
|
624
|
+
score += 3;
|
|
625
|
+
reasons.push('active hook or instruction issue can change route behavior');
|
|
626
|
+
}
|
|
627
|
+
if (instructionSurfaces.warnings.length >= 2) {
|
|
628
|
+
score += 1;
|
|
629
|
+
reasons.push('multiple instruction surfaces can make guidance harder to predict');
|
|
630
|
+
}
|
|
631
|
+
if (detection.packageJson && !detection.commands.build && !detection.commands.typecheck) {
|
|
632
|
+
score += 1;
|
|
633
|
+
reasons.push('no build/typecheck script was detected for quick implementation verification');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const pressure = score >= 7 ? 'high' : score >= 3 ? 'medium' : 'low';
|
|
637
|
+
return {
|
|
638
|
+
schema: 'yam.context-pressure.v1',
|
|
639
|
+
generated_at: new Date().toISOString(),
|
|
640
|
+
project: dir,
|
|
641
|
+
pressure,
|
|
642
|
+
score,
|
|
643
|
+
why_pressure_is_rising: reasons,
|
|
644
|
+
signals,
|
|
645
|
+
beginner_insight: contextPressureInsight(pressure),
|
|
646
|
+
recommended_next_action: pressure === 'high'
|
|
647
|
+
? 'pause and narrow scope, refresh yam.project.md or memory summary, then choose the route deliberately'
|
|
648
|
+
: pressure === 'medium'
|
|
649
|
+
? 'keep the task scoped and summarize important changes before expanding'
|
|
650
|
+
: 'continue with the current route and the smallest honest verification',
|
|
651
|
+
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',
|
|
652
|
+
truth_status: 'partial'
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function projectPackPressureSignal(pack) {
|
|
657
|
+
if (!pack) return { present: false, path: '', age_days: null, words: 0, size_bytes: 0, missing_sections: REQUIRED_PACK_SECTIONS };
|
|
658
|
+
const text = await readText(pack).catch(() => '');
|
|
659
|
+
const stat = await fsp.stat(pack).catch(() => null);
|
|
660
|
+
return {
|
|
661
|
+
present: true,
|
|
662
|
+
path: pack,
|
|
663
|
+
age_days: stat ? Math.floor((Date.now() - stat.mtimeMs) / 86400000) : null,
|
|
664
|
+
words: countWords(text),
|
|
665
|
+
size_bytes: stat?.size || 0,
|
|
666
|
+
missing_sections: REQUIRED_PACK_SECTIONS.filter((section) => !hasHeading(text, section))
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function contextPressureInsight(pressure) {
|
|
671
|
+
if (pressure === 'high') 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.';
|
|
672
|
+
if (pressure === 'medium') return 'The task is still manageable, but scope or stale context may start causing repeated reading and weaker final explanations.';
|
|
673
|
+
return 'The current context looks small enough to keep momentum without heavy proof, as long as claims stay modest.';
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function printContextPressureReport(report) {
|
|
677
|
+
console.log('yam context pressure');
|
|
678
|
+
console.log(`Project: ${report.project}`);
|
|
679
|
+
console.log(`Pressure: ${report.pressure} (score ${report.score})`);
|
|
680
|
+
console.log(`Insight: ${report.beginner_insight}`);
|
|
681
|
+
if (report.why_pressure_is_rising.length) {
|
|
682
|
+
console.log('');
|
|
683
|
+
console.log('Why pressure is rising:');
|
|
684
|
+
for (const reason of report.why_pressure_is_rising) console.log(`- ${reason}`);
|
|
685
|
+
}
|
|
686
|
+
console.log('');
|
|
687
|
+
console.log(`Next: ${report.recommended_next_action}`);
|
|
688
|
+
console.log(`Route hint: ${report.route_hint}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async function cleanup(args = []) {
|
|
692
|
+
const subcommand = args[0] || 'help';
|
|
693
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return cleanupUsage();
|
|
694
|
+
if (subcommand === 'scan') return cleanupScan(args.slice(1));
|
|
695
|
+
console.error(`unknown cleanup command: ${subcommand}`);
|
|
696
|
+
return cleanupUsage();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function cleanupUsage() {
|
|
700
|
+
console.log(`yam cleanup
|
|
701
|
+
|
|
702
|
+
Usage:
|
|
703
|
+
yam cleanup scan [dir] [--json]
|
|
704
|
+
|
|
705
|
+
Notes:
|
|
706
|
+
Read-only cleanup scan. It never deletes files; it only reports confusing surfaces and safe next actions.
|
|
707
|
+
`);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function cleanupScan(args = []) {
|
|
711
|
+
const parsed = parseDirJsonArgs(args);
|
|
712
|
+
const report = await buildCleanupScanReport(parsed.dir);
|
|
713
|
+
if (parsed.json) {
|
|
714
|
+
console.log(JSON.stringify(report, null, 2));
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
printCleanupScanReport(report);
|
|
718
|
+
}
|
|
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)) findings.push(cleanupFinding('high', hook, 'active hooks can add hidden latency or route pressure', 'disable or edit only after user approval and backup'));
|
|
735
|
+
}
|
|
736
|
+
for (const skill of [...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
|
|
737
|
+
if (await exists(path.join(DEST, skill, 'SKILL.md'))) 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'));
|
|
738
|
+
}
|
|
739
|
+
const legacyPackageHits = await scanLimitedTextFiles(dir, ['package.json', 'README.md', 'COMMANDS.md', 'CHANGELOG.md'], /\b(yam-harness|yam-codex)\b/i);
|
|
740
|
+
for (const hit of legacyPackageHits) {
|
|
741
|
+
findings.push(cleanupFinding('low', hit.file, `legacy package name mention: ${hit.match}`, 'update public text only if it confuses install guidance'));
|
|
742
|
+
}
|
|
743
|
+
for (const artifact of await staleYamArtifacts(dir)) {
|
|
744
|
+
findings.push(cleanupFinding('low', artifact.file, artifact.reason, 'archive or refresh only if it misleads the current work'));
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
schema: 'yam.cleanup-scan.v1',
|
|
748
|
+
generated_at: new Date().toISOString(),
|
|
749
|
+
project: dir,
|
|
750
|
+
destructive: false,
|
|
751
|
+
findings,
|
|
752
|
+
risk_level: overallCleanupRisk(findings),
|
|
753
|
+
safe_next_action: findings.length ? 'review findings and make explicit user-approved cleanup decisions' : 'no cleanup action needed',
|
|
754
|
+
truth_status: 'partial'
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function cleanupFinding(riskLevel, surface, whyItMatters, safeNextAction) {
|
|
759
|
+
return {
|
|
760
|
+
risk_level: riskLevel,
|
|
761
|
+
surface,
|
|
762
|
+
why_it_matters: whyItMatters,
|
|
763
|
+
safe_next_action: safeNextAction,
|
|
764
|
+
destructive: false,
|
|
765
|
+
truth_status: 'partial'
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function overallCleanupRisk(findings = []) {
|
|
770
|
+
if (findings.some((item) => item.risk_level === 'high')) return 'high';
|
|
771
|
+
if (findings.some((item) => item.risk_level === 'medium')) return 'medium';
|
|
772
|
+
return 'low';
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async function scanLimitedTextFiles(dir, relativeFiles = [], pattern = /$^/) {
|
|
776
|
+
const hits = [];
|
|
777
|
+
for (const relative of relativeFiles) {
|
|
778
|
+
const file = path.join(dir, relative);
|
|
779
|
+
if (!await exists(file)) continue;
|
|
780
|
+
const text = await readText(file).catch(() => '');
|
|
781
|
+
const match = text.match(pattern);
|
|
782
|
+
if (match) hits.push({ file: relative, match: match[0] });
|
|
783
|
+
}
|
|
784
|
+
return hits;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async function staleYamArtifacts(dir) {
|
|
788
|
+
const candidates = [
|
|
789
|
+
path.join(dir, '.yam', 'proof.json'),
|
|
790
|
+
path.join(dir, '.yam', 'proof.md'),
|
|
791
|
+
path.join(dir, '.yam', 'runtime-proof.md'),
|
|
792
|
+
path.join(dir, '.yam', 'ueye-report.json')
|
|
793
|
+
];
|
|
794
|
+
const artifacts = [];
|
|
795
|
+
for (const file of candidates) {
|
|
796
|
+
if (!await exists(file)) continue;
|
|
797
|
+
const stat = await fsp.stat(file).catch(() => null);
|
|
798
|
+
if (stat && Date.now() - stat.mtimeMs > 14 * 86400000) {
|
|
799
|
+
artifacts.push({ file: path.relative(dir, file), reason: 'old proof/runtime artifact may be mistaken for current evidence' });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return artifacts;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function printCleanupScanReport(report) {
|
|
806
|
+
console.log('yam cleanup scan');
|
|
807
|
+
console.log(`Project: ${report.project}`);
|
|
808
|
+
console.log(`Risk level: ${report.risk_level}`);
|
|
809
|
+
console.log('Destructive: false');
|
|
810
|
+
if (!report.findings.length) {
|
|
811
|
+
console.log('No confusing cleanup surfaces found.');
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
for (const finding of report.findings) {
|
|
815
|
+
console.log(`- ${finding.risk_level}: ${finding.surface}`);
|
|
816
|
+
console.log(` why: ${finding.why_it_matters}`);
|
|
817
|
+
console.log(` next: ${finding.safe_next_action}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function parseDirJsonArgs(args = []) {
|
|
822
|
+
const flags = parseSimpleFlags(args, new Set(['json']));
|
|
823
|
+
const dir = flags._?.find(looksLikeDirectoryArg) || process.cwd();
|
|
824
|
+
return { dir, json: Boolean(flags.json) };
|
|
825
|
+
}
|
|
826
|
+
|
|
530
827
|
async function tools(args = []) {
|
|
531
828
|
const subcommand = args[0] || 'doctor';
|
|
532
829
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return toolsUsage();
|
|
@@ -587,6 +884,8 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
587
884
|
const projectSurfaces = await detectProjectToolSurfaces(dir, packageInfo.pkg);
|
|
588
885
|
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
589
886
|
const sqlScan = await scanSqlFiles(dir);
|
|
887
|
+
const contextPressure = await buildContextPressureReport(dir);
|
|
888
|
+
const realProbe = await buildRealProbeReport(dir, packageInfo.pkg, detection);
|
|
590
889
|
const rows = [
|
|
591
890
|
readinessRow('Codex home', await exists(codexHome) ? 'ready' : 'missing', codexHome),
|
|
592
891
|
readinessRow('Yam skills', await status({ quiet: true }) === 0 ? 'ready' : 'missing', DEST),
|
|
@@ -623,6 +922,8 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
623
922
|
instructionSurfaces,
|
|
624
923
|
projectSurfaces,
|
|
625
924
|
frameworkChecklist,
|
|
925
|
+
contextPressure,
|
|
926
|
+
realProbe,
|
|
626
927
|
sqlScan,
|
|
627
928
|
riskNotes,
|
|
628
929
|
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
@@ -667,6 +968,17 @@ function printToolsDoctorReport(data) {
|
|
|
667
968
|
}
|
|
668
969
|
}
|
|
669
970
|
|
|
971
|
+
if (data.contextPressure) {
|
|
972
|
+
console.log('');
|
|
973
|
+
console.log(`Context pressure: ${data.contextPressure.pressure} (${data.contextPressure.recommended_next_action})`);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (data.realProbe?.probes?.length) {
|
|
977
|
+
console.log('');
|
|
978
|
+
console.log(`Real probe: ${data.realProbe.readiness_truth_status}`);
|
|
979
|
+
for (const probe of data.realProbe.probes) console.log(`- ${probe.id}: ${probe.status} (${probe.truth_status})`);
|
|
980
|
+
}
|
|
981
|
+
|
|
670
982
|
if (data.sqlScan.filesScanned > 0) {
|
|
671
983
|
console.log('');
|
|
672
984
|
console.log(`SQL scan: ${data.sqlScan.filesScanned} file(s), ${data.sqlScan.findings.length} risk finding(s)`);
|
|
@@ -799,6 +1111,63 @@ function checklistRow(id, label, route) {
|
|
|
799
1111
|
return { id, label, route };
|
|
800
1112
|
}
|
|
801
1113
|
|
|
1114
|
+
async function buildRealProbeReport(dir = process.cwd(), pkg: AnyRecord | null = null, detection: AnyRecord = {}) {
|
|
1115
|
+
const probes = [];
|
|
1116
|
+
probes.push(realProbeRow('node_version', 'observed', process.version, 'verified'));
|
|
1117
|
+
const npmVersion = runReadOnlyCommandIn(dir, 'npm', ['--version']).trim();
|
|
1118
|
+
probes.push(realProbeRow('npm_version', npmVersion ? 'observed' : 'not_found', npmVersion || 'npm not found or not runnable', npmVersion ? 'verified' : 'partial'));
|
|
1119
|
+
probes.push(realProbeRow('yam_binary', 'observed', `${process.argv[1] || 'unknown'} @ ${VERSION}`, 'verified'));
|
|
1120
|
+
const scripts = detection.packageJson ? detection.commands : {};
|
|
1121
|
+
probes.push(realProbeRow('package_scripts', Object.values(scripts).some(Boolean) ? 'observed' : 'not_found', scripts, Object.values(scripts).some(Boolean) ? 'verified' : 'partial'));
|
|
1122
|
+
const playwrightPath = await localPackagePath(dir, 'playwright');
|
|
1123
|
+
probes.push(realProbeRow('playwright_package', playwrightPath ? 'observed' : 'not_found', playwrightPath || 'playwright package not found in project or yam package context', playwrightPath ? 'verified' : 'partial'));
|
|
1124
|
+
const browserCache = await pluginCacheHas('openai-bundled/browser');
|
|
1125
|
+
probes.push(realProbeRow('in_app_browser_plugin_cache', browserCache ? 'observed' : 'not_found', browserCache ? 'plugin cache present' : 'plugin cache not found locally', browserCache ? 'partial' : 'partial'));
|
|
1126
|
+
const tmux = await findExecutable('tmux');
|
|
1127
|
+
const zellij = await findExecutable('zellij');
|
|
1128
|
+
probes.push(realProbeRow('tmux', tmux ? 'observed' : 'not_found', tmux || 'not found on PATH', tmux ? 'verified' : 'partial'));
|
|
1129
|
+
probes.push(realProbeRow('zellij', zellij ? 'observed' : 'not_found', zellij || 'not found on PATH', zellij ? 'verified' : 'partial'));
|
|
1130
|
+
const gitInside = runReadOnlyCommandIn(dir, 'git', ['rev-parse', '--is-inside-work-tree']).trim();
|
|
1131
|
+
const gitDirty = runReadOnlyCommandIn(dir, 'git', ['status', '--short']).trim();
|
|
1132
|
+
probes.push(realProbeRow('git_state', gitInside === 'true' ? 'observed' : 'not_found', {
|
|
1133
|
+
inside_work_tree: gitInside === 'true',
|
|
1134
|
+
dirty_file_count: gitDirty ? gitDirty.split(/\r?\n/).length : 0
|
|
1135
|
+
}, gitInside === 'true' ? 'verified' : 'partial'));
|
|
1136
|
+
return {
|
|
1137
|
+
schema: 'yam.real-probe.v1',
|
|
1138
|
+
generated_at: new Date().toISOString(),
|
|
1139
|
+
project: path.resolve(dir),
|
|
1140
|
+
probes,
|
|
1141
|
+
readiness_truth_status: probes.some((probe) => probe.status === 'blocked') ? 'blocked' : 'partial',
|
|
1142
|
+
note: 'read-only local availability probe; no browser, server, database, or process was started'
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function realProbeRow(id, status, evidence, truthStatus = 'partial') {
|
|
1147
|
+
return {
|
|
1148
|
+
id,
|
|
1149
|
+
status,
|
|
1150
|
+
evidence,
|
|
1151
|
+
truth_status: truthStatus
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function localPackagePath(dir, packageName) {
|
|
1156
|
+
const candidates = [
|
|
1157
|
+
path.join(dir, 'node_modules', packageName, 'package.json'),
|
|
1158
|
+
path.join(ROOT, 'node_modules', packageName, 'package.json')
|
|
1159
|
+
];
|
|
1160
|
+
for (const candidate of candidates) {
|
|
1161
|
+
if (await exists(candidate)) return candidate;
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
const projectRequire = createRequire(path.join(dir, 'package.json'));
|
|
1165
|
+
return projectRequire.resolve(`${packageName}/package.json`);
|
|
1166
|
+
} catch {
|
|
1167
|
+
return '';
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
802
1171
|
function toolsDoctorNextActions(input) {
|
|
803
1172
|
return toolsDoctorNextActionDetails(input).map((action) => action.next_action);
|
|
804
1173
|
}
|
|
@@ -1155,6 +1524,13 @@ async function release(args = []) {
|
|
|
1155
1524
|
console.log('- Failed:');
|
|
1156
1525
|
for (const id of report.failed) console.log(` - ${id}`);
|
|
1157
1526
|
}
|
|
1527
|
+
if (report.publishBlockerEvidence?.length) {
|
|
1528
|
+
console.log('- Publish blockers:');
|
|
1529
|
+
for (const blocker of report.publishBlockerEvidence) {
|
|
1530
|
+
console.log(` - ${blocker.blocker_kind}: ${blocker.likely_cause}`);
|
|
1531
|
+
console.log(` next: ${blocker.safe_next_action}`);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1158
1534
|
if (report.next_actions?.length) {
|
|
1159
1535
|
console.log('- Next actions:');
|
|
1160
1536
|
for (const action of report.next_actions) console.log(` - ${action.next_action}`);
|
|
@@ -1176,7 +1552,8 @@ function runReleaseReport() {
|
|
|
1176
1552
|
const provenance = releaseProvenance(checks);
|
|
1177
1553
|
const tarball = releaseTarballProvenance();
|
|
1178
1554
|
const freshness = releaseFreshness(checks, provenance, tarball);
|
|
1179
|
-
const
|
|
1555
|
+
const publishBlockerEvidence = publishBlockerEvidenceFromRelease(checks, tarball);
|
|
1556
|
+
const nextActions = releaseNextActions(checks, provenance, tarball, publishBlockerEvidence);
|
|
1180
1557
|
return {
|
|
1181
1558
|
schema: 'yam.release-report.v1',
|
|
1182
1559
|
generated_at: startedAt,
|
|
@@ -1189,6 +1566,7 @@ function runReleaseReport() {
|
|
|
1189
1566
|
provenance,
|
|
1190
1567
|
tarball,
|
|
1191
1568
|
freshness,
|
|
1569
|
+
publishBlockerEvidence,
|
|
1192
1570
|
next_actions: nextActions
|
|
1193
1571
|
};
|
|
1194
1572
|
}
|
|
@@ -1268,7 +1646,7 @@ function releaseFreshness(checks = [], provenance: AnyRecord = {}, tarball: AnyR
|
|
|
1268
1646
|
};
|
|
1269
1647
|
}
|
|
1270
1648
|
|
|
1271
|
-
function releaseNextActions(checks = [], provenance: AnyRecord = {}, tarball: AnyRecord = {}) {
|
|
1649
|
+
function releaseNextActions(checks = [], provenance: AnyRecord = {}, tarball: AnyRecord = {}, publishBlockers = []) {
|
|
1272
1650
|
const actions = [];
|
|
1273
1651
|
for (const check of checks) {
|
|
1274
1652
|
if (check.status === 'passed') continue;
|
|
@@ -1283,9 +1661,71 @@ function releaseNextActions(checks = [], provenance: AnyRecord = {}, tarball: An
|
|
|
1283
1661
|
if (tarball.status !== 'checked') {
|
|
1284
1662
|
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'));
|
|
1285
1663
|
}
|
|
1664
|
+
for (const blocker of publishBlockers) {
|
|
1665
|
+
actions.push(nextActionDetail(`publish-${blocker.blocker_kind}`, blocker.truth_status === 'blocked' ? 'error' : 'warning', blocker.likely_cause, blocker.safe_next_action, 'npm publish'));
|
|
1666
|
+
}
|
|
1286
1667
|
return uniqueNextActionDetails(actions);
|
|
1287
1668
|
}
|
|
1288
1669
|
|
|
1670
|
+
function publishBlockerEvidenceFromRelease(checks = [], tarball: AnyRecord = {}) {
|
|
1671
|
+
const texts = [
|
|
1672
|
+
...checks.map((check) => `${check.id}\n${check.note || ''}`),
|
|
1673
|
+
tarball.reason || ''
|
|
1674
|
+
].filter(Boolean);
|
|
1675
|
+
const blockers = [];
|
|
1676
|
+
for (const text of texts) {
|
|
1677
|
+
const blocker = classifyPublishBlockerText(text);
|
|
1678
|
+
if (blocker) blockers.push(blocker);
|
|
1679
|
+
}
|
|
1680
|
+
const seen = new Set<string>();
|
|
1681
|
+
return blockers.filter((blocker) => {
|
|
1682
|
+
if (seen.has(blocker.blocker_kind)) return false;
|
|
1683
|
+
seen.add(blocker.blocker_kind);
|
|
1684
|
+
return true;
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function classifyPublishBlockerText(text = '') {
|
|
1689
|
+
const value = String(text || '');
|
|
1690
|
+
if (!value.trim()) return null;
|
|
1691
|
+
const excerpt = summarizeCheckOutput(value);
|
|
1692
|
+
if (/\b(ENEEDAUTH|E401|Unauthorized|whoami)\b/i.test(value)) {
|
|
1693
|
+
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`');
|
|
1694
|
+
}
|
|
1695
|
+
if (/\b(OTP|one-time password|two-factor|2FA)\b/i.test(value)) {
|
|
1696
|
+
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');
|
|
1697
|
+
}
|
|
1698
|
+
if (/\bE403\b|403\s+Forbidden|Forbidden\s+-\s+PUT|npm ERR!.*Forbidden/i.test(value)) {
|
|
1699
|
+
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');
|
|
1700
|
+
}
|
|
1701
|
+
if (/\bE404\b|Not Found - PUT/i.test(value)) {
|
|
1702
|
+
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');
|
|
1703
|
+
}
|
|
1704
|
+
if (/previously published|cannot publish over|already exists|You cannot publish over/i.test(value)) {
|
|
1705
|
+
return publishBlocker('version_already_published', excerpt, 'npm versions are immutable once published', 'bump package.json to the next patch version before publishing');
|
|
1706
|
+
}
|
|
1707
|
+
if (/(EACCES|EPERM|permission denied|cache|_cacache)/i.test(value)) {
|
|
1708
|
+
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`');
|
|
1709
|
+
}
|
|
1710
|
+
if (/(ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|registry unreachable|network timeout)/i.test(value)) {
|
|
1711
|
+
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');
|
|
1712
|
+
}
|
|
1713
|
+
if (/(tarball|package boundary|files list|npm pack)/i.test(value)) {
|
|
1714
|
+
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');
|
|
1715
|
+
}
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function publishBlocker(kind, excerpt, likelyCause, safeNextAction) {
|
|
1720
|
+
return {
|
|
1721
|
+
blocker_kind: kind,
|
|
1722
|
+
native_error_excerpt: excerpt,
|
|
1723
|
+
likely_cause: likelyCause,
|
|
1724
|
+
safe_next_action: safeNextAction,
|
|
1725
|
+
truth_status: 'partial'
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1289
1729
|
function runReadOnlyCommand(command, args = []) {
|
|
1290
1730
|
const result = spawnSync(command, args, {
|
|
1291
1731
|
cwd: ROOT,
|
|
@@ -1296,6 +1736,16 @@ function runReadOnlyCommand(command, args = []) {
|
|
|
1296
1736
|
return String(result.stdout || '');
|
|
1297
1737
|
}
|
|
1298
1738
|
|
|
1739
|
+
function runReadOnlyCommandIn(cwd, command, args = []) {
|
|
1740
|
+
const result = spawnSync(command, args, {
|
|
1741
|
+
cwd,
|
|
1742
|
+
encoding: 'utf8',
|
|
1743
|
+
timeout: 30000
|
|
1744
|
+
});
|
|
1745
|
+
if (result.status !== 0) return '';
|
|
1746
|
+
return String(result.stdout || '');
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1299
1749
|
function runReleaseCheck(id, commandSpec) {
|
|
1300
1750
|
const [command, args] = commandSpec;
|
|
1301
1751
|
const start = Date.now();
|
|
@@ -1336,6 +1786,7 @@ async function ueye(args = []) {
|
|
|
1336
1786
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return ueyeUsage();
|
|
1337
1787
|
if (subcommand === 'capture') return ueyeCapture(args.slice(1));
|
|
1338
1788
|
if (subcommand === 'compare') return ueyeCompare(args.slice(1));
|
|
1789
|
+
if (subcommand === 'preflight') return ueyePreflight(args.slice(1));
|
|
1339
1790
|
if (subcommand === 'report') return ueyeReport(args.slice(1));
|
|
1340
1791
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
1341
1792
|
return ueyeUsage();
|
|
@@ -1349,15 +1800,82 @@ Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/c
|
|
|
1349
1800
|
Usage:
|
|
1350
1801
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
1351
1802
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
1352
|
-
yam ueye
|
|
1803
|
+
yam ueye preflight [dir] [--json]
|
|
1804
|
+
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]
|
|
1353
1805
|
|
|
1354
1806
|
Notes:
|
|
1355
1807
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
1356
1808
|
compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
|
|
1357
|
-
report produces a proof-ready Ueye visual run report and continuity/comparison record without requiring a new capture.
|
|
1809
|
+
report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
|
|
1358
1810
|
`);
|
|
1359
1811
|
}
|
|
1360
1812
|
|
|
1813
|
+
async function ueyePreflight(args = []) {
|
|
1814
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
1815
|
+
const parsed = parseDirJsonArgs(args);
|
|
1816
|
+
const report = await buildUeyePreflightReport(parsed.dir);
|
|
1817
|
+
if (parsed.json) {
|
|
1818
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
console.log('Ueye preflight');
|
|
1822
|
+
console.log(`Project: ${report.project}`);
|
|
1823
|
+
console.log(`Preflight id: ${report.preflight_id}`);
|
|
1824
|
+
console.log(`Truth status: ${report.truth_status}`);
|
|
1825
|
+
for (const item of report.checklist) console.log(`- ${item.status}: ${item.item} (${item.next_action})`);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
async function buildUeyePreflightReport(targetDir = process.cwd()) {
|
|
1829
|
+
const dir = path.resolve(expandHome(targetDir || process.cwd()));
|
|
1830
|
+
const packageInfo = await projectPackageInfo(dir);
|
|
1831
|
+
const frameworkChecklist = await detectFrameworkChecklist(dir, packageInfo.pkg);
|
|
1832
|
+
const pack = await findProjectPack(dir);
|
|
1833
|
+
const playwrightPath = await localPackagePath(dir, 'playwright');
|
|
1834
|
+
const browserCache = await pluginCacheHas('openai-bundled/browser');
|
|
1835
|
+
const uiPaths = await existingRelativePaths(dir, ['app', 'src/app', 'pages', 'src/pages', 'components', 'src/components', 'styles', 'src/styles']);
|
|
1836
|
+
const screenshotAvailability = playwrightPath ? 'automated_capture_possible' : browserCache ? 'in_app_or_user_screenshot_needed' : 'user_screenshot_needed';
|
|
1837
|
+
const p0p1Risks = [];
|
|
1838
|
+
if (!frameworkChecklist.uiLike) p0p1Risks.push('target UI surface is not detected yet');
|
|
1839
|
+
if (!browserCache && !playwrightPath) p0p1Risks.push('no local capture surface was observed');
|
|
1840
|
+
if (!pack) p0p1Risks.push('project direction pack is missing');
|
|
1841
|
+
return {
|
|
1842
|
+
schema: 'yam.ueye-preflight.v1',
|
|
1843
|
+
preflight_id: `ueye-preflight-${timestampId()}`,
|
|
1844
|
+
generated_at: new Date().toISOString(),
|
|
1845
|
+
project: dir,
|
|
1846
|
+
detected_framework: frameworkChecklist.framework,
|
|
1847
|
+
ui_paths: uiPaths,
|
|
1848
|
+
reference_source_needed: true,
|
|
1849
|
+
target_states_needed: ['default', 'loading', 'error', 'empty', 'mobile'],
|
|
1850
|
+
mobile_responsive_check_needed: true,
|
|
1851
|
+
contrast_cta_accessibility_risk: frameworkChecklist.uiLike ? 'check before done claim' : 'unknown until UI target is identified',
|
|
1852
|
+
p0_p1_risk_candidates: p0p1Risks,
|
|
1853
|
+
screenshot_capture_availability: screenshotAvailability,
|
|
1854
|
+
preferred_surface: 'in-app-browser',
|
|
1855
|
+
checklist: [
|
|
1856
|
+
preflightItem('reference', 'reference source selected or user confirms no reference', 'needed', 'record reference path, screenshot, or text target before claiming design fidelity'),
|
|
1857
|
+
preflightItem('states', 'primary states are listed', 'needed', 'prepare default/loading/error/empty/mobile states when the UI supports them'),
|
|
1858
|
+
preflightItem('mobile', 'mobile/responsive pass planned', 'needed', 'capture or inspect a narrow viewport before verified visual claims'),
|
|
1859
|
+
preflightItem('contrast_cta_accessibility', 'CTA, contrast, and accessible labels reviewed', 'needed', 'mark P0/P1 risks before saying done'),
|
|
1860
|
+
preflightItem('capture', 'screenshot/capture surface available', screenshotAvailability === 'automated_capture_possible' ? 'observed' : 'partial', 'use in-app browser or user-provided screenshot if automation is unavailable')
|
|
1861
|
+
],
|
|
1862
|
+
completion_cap: 'preflight cannot prove visual quality without implementation screenshot and comparison evidence',
|
|
1863
|
+
truth_status: 'partial'
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function preflightItem(id, item, status, nextAction) {
|
|
1868
|
+
return { id, item, status, next_action: nextAction, truth_status: status === 'observed' ? 'verified' : 'partial' };
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
async function existingRelativePaths(dir, relativePaths = []) {
|
|
1872
|
+
const found = [];
|
|
1873
|
+
for (const relative of relativePaths) {
|
|
1874
|
+
if (await exists(path.join(dir, relative))) found.push(relative);
|
|
1875
|
+
}
|
|
1876
|
+
return found;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1361
1879
|
async function ueyeCapture(args = []) {
|
|
1362
1880
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
1363
1881
|
const flags = parseSimpleFlags(args, new Set(['url', 'out', 'viewport', 'wait-until', 'timeout', 'full-page', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'evidence-id', 'screenshot-id', 'json']));
|
|
@@ -1520,7 +2038,7 @@ async function ueyeCompare(args = []) {
|
|
|
1520
2038
|
|
|
1521
2039
|
async function ueyeReport(args = []) {
|
|
1522
2040
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
1523
|
-
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']));
|
|
2041
|
+
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']));
|
|
1524
2042
|
const reference = String(flags.reference || flags.ref || '');
|
|
1525
2043
|
const actual = String(flags.actual || flags.screenshot || '');
|
|
1526
2044
|
const referenceSources = [];
|
|
@@ -1586,7 +2104,9 @@ async function ueyeReport(args = []) {
|
|
|
1586
2104
|
truth_status: 'partial'
|
|
1587
2105
|
}));
|
|
1588
2106
|
}
|
|
1589
|
-
if (
|
|
2107
|
+
if (flags.comparison_result) {
|
|
2108
|
+
comparisonResult = normalizeUeyeComparisonResult(flags.comparison_result);
|
|
2109
|
+
} else if (reference && actual && referenceSources[0]?.source_hash === implementationSources[0]?.source_hash) {
|
|
1590
2110
|
comparisonResult = 'matched';
|
|
1591
2111
|
} else if (reference && actual) {
|
|
1592
2112
|
comparisonResult = 'different';
|
|
@@ -1605,6 +2125,22 @@ async function ueyeReport(args = []) {
|
|
|
1605
2125
|
},
|
|
1606
2126
|
comparison_result: comparisonResult,
|
|
1607
2127
|
design_quality: String(flags.design_quality || 'not-checked'),
|
|
2128
|
+
design_completion_gate: {
|
|
2129
|
+
mode: flags.strict || flags.gate_mode === 'strict' ? 'strict' : 'fast',
|
|
2130
|
+
completion_claim: flags.completion_claim || flags.completion_status || 'draft',
|
|
2131
|
+
design_score: numberOrNull(flags.design_score),
|
|
2132
|
+
min_design_score: numberOrNull(flags.min_design_score) ?? 8,
|
|
2133
|
+
p0: [...arrayFlag(flags.p0), ...arrayFlag(flags.open_p0), ...arrayFlag(flags.p0_risk)],
|
|
2134
|
+
p1: [...arrayFlag(flags.p1), ...arrayFlag(flags.open_p1)],
|
|
2135
|
+
states_checked: Boolean(flags.states_checked),
|
|
2136
|
+
mobile_checked: Boolean(flags.mobile_checked),
|
|
2137
|
+
responsive_checked: Boolean(flags.responsive_checked),
|
|
2138
|
+
contrast_checked: Boolean(flags.contrast_checked),
|
|
2139
|
+
accessibility_checked: Boolean(flags.accessibility_checked),
|
|
2140
|
+
cta_checked: Boolean(flags.cta_checked),
|
|
2141
|
+
direction_locked: Boolean(flags.direction_locked),
|
|
2142
|
+
reference_read: Boolean(flags.reference_read)
|
|
2143
|
+
},
|
|
1608
2144
|
blocked_reason: blockedReason,
|
|
1609
2145
|
next_action: String(flags.next_visual_action || flags.next_action || '')
|
|
1610
2146
|
});
|
|
@@ -1614,6 +2150,7 @@ async function ueyeReport(args = []) {
|
|
|
1614
2150
|
referenceSources,
|
|
1615
2151
|
implementationSources,
|
|
1616
2152
|
surfaceContext: report.surface_context,
|
|
2153
|
+
designCompletionGate: report.design_completion_gate,
|
|
1617
2154
|
comparisonResult,
|
|
1618
2155
|
designQuality: String(flags.design_quality || 'not-checked'),
|
|
1619
2156
|
similar: arrayFlag(flags.similar),
|
|
@@ -1634,6 +2171,12 @@ async function ueyeReport(args = []) {
|
|
|
1634
2171
|
const result = {
|
|
1635
2172
|
...report,
|
|
1636
2173
|
review_session_id: reviewSessionId,
|
|
2174
|
+
preflight: {
|
|
2175
|
+
preflight_id: String(flags.preflight_id || ''),
|
|
2176
|
+
p0_risks: arrayFlag(flags.p0_risk),
|
|
2177
|
+
quality_gate_notes: arrayFlag(flags.quality_gate_note),
|
|
2178
|
+
truth_status: flags.preflight_id || flags.p0_risk || flags.quality_gate_note ? 'partial' : 'assumed'
|
|
2179
|
+
},
|
|
1637
2180
|
tool_intent: 'visual',
|
|
1638
2181
|
capture_backend: String(flags.capture_backend || 'not-recorded'),
|
|
1639
2182
|
compare_backend: String(flags.compare_backend || 'local-file-hash'),
|
|
@@ -1672,6 +2215,7 @@ function buildUeyeComparisonReport({
|
|
|
1672
2215
|
referenceSources,
|
|
1673
2216
|
implementationSources,
|
|
1674
2217
|
surfaceContext,
|
|
2218
|
+
designCompletionGate,
|
|
1675
2219
|
comparisonResult,
|
|
1676
2220
|
designQuality,
|
|
1677
2221
|
similar,
|
|
@@ -1713,6 +2257,7 @@ function buildUeyeComparisonReport({
|
|
|
1713
2257
|
implementation_count: implementationSources.length
|
|
1714
2258
|
},
|
|
1715
2259
|
surface_context: surfaceContext || buildUeyeSurfaceContext(),
|
|
2260
|
+
design_completion_gate: designCompletionGate || null,
|
|
1716
2261
|
comparison_result: comparisonResult,
|
|
1717
2262
|
design_quality: designQuality,
|
|
1718
2263
|
similar,
|
|
@@ -1732,6 +2277,12 @@ function buildUeyeComparisonReport({
|
|
|
1732
2277
|
};
|
|
1733
2278
|
}
|
|
1734
2279
|
|
|
2280
|
+
function normalizeUeyeComparisonResult(value = '') {
|
|
2281
|
+
const text = String(value || '').toLowerCase();
|
|
2282
|
+
if (['matched', 'similar', 'different', 'not-verified', 'not-applicable'].includes(text)) return text;
|
|
2283
|
+
return 'not-verified';
|
|
2284
|
+
}
|
|
2285
|
+
|
|
1735
2286
|
async function media(args = []) {
|
|
1736
2287
|
const subcommand = args[0] || 'help';
|
|
1737
2288
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return mediaUsage();
|
|
@@ -1793,7 +2344,7 @@ function runtimeUsage() {
|
|
|
1793
2344
|
console.log(`yam runtime
|
|
1794
2345
|
|
|
1795
2346
|
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]
|
|
2347
|
+
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
2348
|
|
|
1798
2349
|
Notes:
|
|
1799
2350
|
Records a small runtime evidence shape. It does not start, stop, or inspect processes by itself.
|
|
@@ -1802,13 +2353,21 @@ Notes:
|
|
|
1802
2353
|
|
|
1803
2354
|
async function runtimeEvidence(args = []) {
|
|
1804
2355
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') 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']));
|
|
2356
|
+
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
2357
|
const evidence = buildRuntimeBackendEvidence({
|
|
1807
2358
|
backend: flags.backend,
|
|
1808
2359
|
claim: flags.claim,
|
|
1809
2360
|
evidence_id: String(flags.evidence_id || `runtime-${timestampId()}`),
|
|
1810
2361
|
command: String(flags.command || ''),
|
|
1811
2362
|
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2363
|
+
started_at: String(flags.started_at || ''),
|
|
2364
|
+
stopped_at: String(flags.stopped_at || ''),
|
|
2365
|
+
exit_code: numberOrNull(flags.exit_code),
|
|
2366
|
+
pid: numberOrNull(flags.pid),
|
|
2367
|
+
port: numberOrNull(flags.port),
|
|
2368
|
+
cleanup_method: String(flags.cleanup_method || ''),
|
|
2369
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
2370
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally),
|
|
1812
2371
|
note: String(flags.note || ''),
|
|
1813
2372
|
truth_status: isTruthStatus(flags.truth) ? flags.truth : undefined
|
|
1814
2373
|
});
|
|
@@ -1835,17 +2394,20 @@ function runtimeEvidenceDetails(flags: AnyRecord = {}) {
|
|
|
1835
2394
|
exit_code: numberOrNull(flags.exit_code),
|
|
1836
2395
|
screenshot_id: String(flags.screenshot_id || ''),
|
|
1837
2396
|
started_at: String(flags.started_at || ''),
|
|
1838
|
-
stopped_at: String(flags.stopped_at || '')
|
|
2397
|
+
stopped_at: String(flags.stopped_at || ''),
|
|
2398
|
+
cleanup_method: String(flags.cleanup_method || ''),
|
|
2399
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
2400
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally)
|
|
1839
2401
|
};
|
|
1840
2402
|
}
|
|
1841
2403
|
|
|
1842
2404
|
function runtimeDetailsHasVerification(details: AnyRecord = {}) {
|
|
1843
|
-
return Boolean(details.url || details.screenshot_id || details.pid !== null || details.port !== null || details.exit_code !== null || details.started_at || details.stopped_at);
|
|
2405
|
+
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);
|
|
1844
2406
|
}
|
|
1845
2407
|
|
|
1846
2408
|
function runtimeEvidenceNextAction(evidence) {
|
|
1847
2409
|
if (evidence.truth_status === 'proven' || evidence.truth_status === 'verified') return 'attach this evidence to `yam proof` when making a runtime claim';
|
|
1848
|
-
if (evidence.claim === 'cleanup_verified' && !evidence.
|
|
2410
|
+
if (evidence.claim === 'cleanup_verified' && !evidence.cleanup_observed && !evidence.left_running_intentionally) return 'rerun with --cleanup-observed plus exit/closure evidence, or record --left-running-intentionally';
|
|
1849
2411
|
if (evidence.backend === 'none' || evidence.backend === 'unknown') return 'record the actual runtime backend before claiming runtime verification';
|
|
1850
2412
|
return 'add command output, screenshot id, pid/session id, or cleanup proof before upgrading the claim';
|
|
1851
2413
|
}
|
|
@@ -2015,7 +2577,7 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
|
|
|
2015
2577
|
const key = rawKey.slice(2);
|
|
2016
2578
|
if (allowed.size && !allowed.has(key)) continue;
|
|
2017
2579
|
const normalizedKey = key.replace(/-/g, '_');
|
|
2018
|
-
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked'].includes(key)) {
|
|
2580
|
+
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)) {
|
|
2019
2581
|
flags[normalizedKey] = true;
|
|
2020
2582
|
continue;
|
|
2021
2583
|
}
|
|
@@ -2165,6 +2727,7 @@ async function proof(args = []) {
|
|
|
2165
2727
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
2166
2728
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
2167
2729
|
printProofList('Media proof', summary.mediaProof || []);
|
|
2730
|
+
printProofList('Design completion', summary.designCompletion || []);
|
|
2168
2731
|
printProofList('Changed surfaces', summary.changed);
|
|
2169
2732
|
printProofList('Skipped', summary.skipped);
|
|
2170
2733
|
printProofList('Blocked', summary.blocked);
|
|
@@ -2260,6 +2823,10 @@ function buildProofSummary(parsed, artifact) {
|
|
|
2260
2823
|
...structuredEvidenceList(artifact.mediaProof),
|
|
2261
2824
|
...structuredEvidenceList(parsed.flags.media_proof)
|
|
2262
2825
|
],
|
|
2826
|
+
designCompletion: [
|
|
2827
|
+
...structuredEvidenceList(artifact.designCompletion),
|
|
2828
|
+
...structuredEvidenceList(parsed.flags.design_completion)
|
|
2829
|
+
],
|
|
2263
2830
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
2264
2831
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
2265
2832
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -2325,6 +2892,9 @@ function renderProofMarkdown(summary) {
|
|
|
2325
2892
|
'## Media proof',
|
|
2326
2893
|
...renderProofMarkdownList(summary.mediaProof || []),
|
|
2327
2894
|
'',
|
|
2895
|
+
'## Design completion',
|
|
2896
|
+
...renderProofMarkdownList(summary.designCompletion || []),
|
|
2897
|
+
'',
|
|
2328
2898
|
'## Changed surfaces',
|
|
2329
2899
|
...renderProofMarkdownList(summary.changed),
|
|
2330
2900
|
'',
|
|
@@ -2357,7 +2927,7 @@ function renderProofMarkdownList(values) {
|
|
|
2357
2927
|
function parseProofArgs(args = []) {
|
|
2358
2928
|
const flags: AnyRecord = {};
|
|
2359
2929
|
const positionals: string[] = [];
|
|
2360
|
-
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']);
|
|
2930
|
+
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']);
|
|
2361
2931
|
for (let index = 0; index < args.length; index += 1) {
|
|
2362
2932
|
const arg = args[index];
|
|
2363
2933
|
if (arg.startsWith('--')) {
|
|
@@ -2365,13 +2935,13 @@ function parseProofArgs(args = []) {
|
|
|
2365
2935
|
const key = rawKey.slice(2);
|
|
2366
2936
|
if (!aliases.has(key)) continue;
|
|
2367
2937
|
const normalizedKey = key.replace(/-/g, '_');
|
|
2368
|
-
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked') {
|
|
2938
|
+
if (key === 'json' || key.startsWith('require-') || key === 'cleanup-checked' || key === 'cleanup-observed' || key === 'left-running-intentionally') {
|
|
2369
2939
|
flags[normalizedKey] = true;
|
|
2370
2940
|
continue;
|
|
2371
2941
|
}
|
|
2372
2942
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
2373
2943
|
if (inlineValue === undefined) index += 1;
|
|
2374
|
-
if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
2944
|
+
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)) {
|
|
2375
2945
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
2376
2946
|
} else {
|
|
2377
2947
|
flags[normalizedKey] = value;
|
|
@@ -2414,6 +2984,7 @@ function emptyProofArtifact() {
|
|
|
2414
2984
|
missionEnvelope: [],
|
|
2415
2985
|
rollbackHint: [],
|
|
2416
2986
|
mediaProof: [],
|
|
2987
|
+
designCompletion: [],
|
|
2417
2988
|
cleanup: '',
|
|
2418
2989
|
changed: [],
|
|
2419
2990
|
skipped: [],
|
|
@@ -2441,6 +3012,7 @@ async function parseProofJson(file, empty) {
|
|
|
2441
3012
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
2442
3013
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
2443
3014
|
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
3015
|
+
designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
|
|
2444
3016
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
2445
3017
|
changed: arrayFlag(data.changed || data.files),
|
|
2446
3018
|
skipped: arrayFlag(data.skipped),
|
|
@@ -2471,6 +3043,7 @@ async function parseProofMarkdown(file, empty) {
|
|
|
2471
3043
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
2472
3044
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
2473
3045
|
mediaProof: readProofList(text, 'Media proof'),
|
|
3046
|
+
designCompletion: readProofList(text, 'Design completion'),
|
|
2474
3047
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
2475
3048
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
2476
3049
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -2522,12 +3095,20 @@ function structuredEvidenceList(value) {
|
|
|
2522
3095
|
}
|
|
2523
3096
|
|
|
2524
3097
|
function directRuntimeBackendEvidence(flags) {
|
|
2525
|
-
if (!flags.runtime_backend && !flags.runtime_claim && !flags.runtime_evidence_id && !flags.runtime_command && !flags.runtime_note && !flags.cleanup_checked) return [];
|
|
3098
|
+
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) return [];
|
|
2526
3099
|
return [JSON.stringify(buildRuntimeBackendEvidence({
|
|
2527
3100
|
backend: flags.runtime_backend,
|
|
2528
3101
|
claim: flags.runtime_claim,
|
|
2529
3102
|
evidence_id: flags.runtime_evidence_id,
|
|
2530
3103
|
command: flags.runtime_command,
|
|
3104
|
+
pid: numberOrNull(flags.runtime_pid),
|
|
3105
|
+
port: numberOrNull(flags.runtime_port),
|
|
3106
|
+
exit_code: numberOrNull(flags.runtime_exit_code),
|
|
3107
|
+
started_at: String(flags.runtime_started_at || ''),
|
|
3108
|
+
stopped_at: String(flags.runtime_stopped_at || ''),
|
|
3109
|
+
cleanup_method: String(flags.runtime_cleanup_method || ''),
|
|
3110
|
+
cleanup_observed: Boolean(flags.cleanup_observed),
|
|
3111
|
+
left_running_intentionally: Boolean(flags.left_running_intentionally),
|
|
2531
3112
|
cleanup_checked: Boolean(flags.cleanup_checked),
|
|
2532
3113
|
note: flags.runtime_note
|
|
2533
3114
|
}))];
|
|
@@ -2556,6 +3137,9 @@ function formatStructuredEvidence(value) {
|
|
|
2556
3137
|
if (data.schema === 'yam.media-generation-proof.v1' || data.generation_requested || data.output_hash) {
|
|
2557
3138
|
return JSON.stringify(buildMediaGenerationProof(data));
|
|
2558
3139
|
}
|
|
3140
|
+
if (data.schema === 'yam.ueye-design-completion-gate.v1' || data.completion_claim || data.ready_to_claim_done !== undefined) {
|
|
3141
|
+
return JSON.stringify(buildUeyeDesignCompletionGate(data));
|
|
3142
|
+
}
|
|
2559
3143
|
return JSON.stringify(data);
|
|
2560
3144
|
} catch {
|
|
2561
3145
|
return text;
|
|
@@ -2564,7 +3148,7 @@ function formatStructuredEvidence(value) {
|
|
|
2564
3148
|
|
|
2565
3149
|
function hasProofEvidence(parsed) {
|
|
2566
3150
|
const flags = parsed.flags || {};
|
|
2567
|
-
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);
|
|
3151
|
+
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);
|
|
2568
3152
|
}
|
|
2569
3153
|
|
|
2570
3154
|
function printProofList(label, values) {
|
|
@@ -3261,6 +3845,8 @@ async function main() {
|
|
|
3261
3845
|
if (command === 'version') return console.log(VERSION);
|
|
3262
3846
|
if (command === 'detect') return detectProject(process.argv[3]);
|
|
3263
3847
|
if (command === 'pack') return inspectProjectPack(process.argv[3]);
|
|
3848
|
+
if (command === 'context') return context(process.argv.slice(3));
|
|
3849
|
+
if (command === 'cleanup') return cleanup(process.argv.slice(3));
|
|
3264
3850
|
if (command === 'budget') return budget(process.argv[3]);
|
|
3265
3851
|
if (command === 'measure') return measure(process.argv[3], process.argv.slice(4));
|
|
3266
3852
|
if (command === 'tools') return tools(process.argv.slice(3));
|