35 lines
4.5 KiB
JavaScript
35 lines
4.5 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { parse } from '@babel/parser';
|
|
import { combineEvidence } from './guard-evidence.mjs';
|
|
const directory = path.resolve(process.env.HM_QUALITY_OUTPUT ?? '_local/quality-20261010');
|
|
const read = name => JSON.parse(fs.readFileSync(path.join(directory, name), 'utf8'));
|
|
const slots = { FilterBar: 'filter-bar', DataTable: 'data-table', DashboardLayout: 'dashboard-layout' };
|
|
const screens = [];
|
|
for (let n = 1; n <= 10; n++) {
|
|
const id = String(n).padStart(2, '0');
|
|
const spec = JSON.parse(fs.readFileSync(`examples/ai-quality/${id}/requirements.json`, 'utf8'));
|
|
const guard = read(`guard-${id}.json`), runtime = read(`runtime-${id}.json`);
|
|
const combined = combineEvidence(guard, runtime);
|
|
screens.push({ id, title: spec.title, difficulty: spec.difficulty, mode: spec.mode, generation: spec.generation, sourceFingerprint: guard.sourceFingerprint, contractFingerprint: guard.contractFingerprint, rawGuard: guard.status, staticStatus: guard.staticStatus, combined, viewports: runtime.viewports, interactions: runtime.interactions, requiredPatterns: spec.patterns.map(name => ({ name, rendered: runtime.viewports.every(v => v.metrics?.patternSlots.includes(slots[name])) })) });
|
|
}
|
|
// Count public JSX sites in the shared consumer source once, not once per entry.
|
|
// This is source reuse coverage, not a percentage of mounted DOM or design quality.
|
|
const walk = (node, fn) => { if (!node || typeof node !== 'object') return; if (node.type) fn(node); for (const [key, value] of Object.entries(node)) if (!['loc', 'start', 'end', 'comments'].includes(key)) Array.isArray(value) ? value.forEach(v => walk(v, fn)) : walk(value, fn); };
|
|
const sourceReuse = [];
|
|
for (const file of fs.readdirSync('examples/ai-quality/shared').filter(f => f.endsWith('.tsx'))) {
|
|
const ast = parse(fs.readFileSync(`examples/ai-quality/shared/${file}`, 'utf8'), { sourceType: 'module', plugins: ['typescript', 'jsx'] });
|
|
const publicNames = new Set();
|
|
walk(ast, n => { if (n.type === 'ImportDeclaration' && n.source.value === '@hm-design/ui') n.specifiers.forEach(s => publicNames.add(s.local.name)); });
|
|
let hmSites = 0, nativeWidgetSites = 0;
|
|
walk(ast, n => { if (n.type === 'JSXOpeningElement' && n.name.type === 'JSXIdentifier') { if (publicNames.has(n.name.name)) hmSites++; if (['button', 'input', 'textarea', 'select', 'label', 'table', 'dialog'].includes(n.name.name)) nativeWidgetSites++; } });
|
|
sourceReuse.push({ file, hmSites, nativeWidgetSites });
|
|
}
|
|
const hmSites = sourceReuse.reduce((s, f) => s + f.hmSites, 0), nativeWidgetSites = sourceReuse.reduce((s, f) => s + f.nativeWidgetSites, 0);
|
|
const patterns = screens.flatMap(s => s.requiredPatterns);
|
|
const interactions = screens.flatMap(s => s.interactions);
|
|
const summary = { capturedAt: new Date().toISOString(), screens, sourceReuse: { definition: 'Public HM JSX sites / (public HM JSX sites + native widget JSX sites), shared TSX counted once; excludes layout/content HTML and local composition wrappers, not mounted DOM coverage', hmSites, nativeWidgetSites, rate: hmSites / (hmSites + nativeWidgetSites), files: sourceReuse }, requiredPatternReuse: { definition: 'Required Pattern occurrences observed at every required viewport; Navigation also reported separately', total: patterns.length, rendered: patterns.filter(p => p.rendered).length }, totals: { screens: screens.length, rawGuardPass: screens.filter(s => s.rawGuard === 'PASS').length, rawGuardFail: screens.filter(s => s.rawGuard === 'FAIL').length, rawGuardNotVerified: screens.filter(s => s.rawGuard === 'NOT VERIFIED').length, staticPass: screens.filter(s => s.staticStatus === 'PASS').length, combinedPass: screens.filter(s => s.combined.status === 'PASS').length, viewportPass: screens.flatMap(s => s.viewports).filter(v => v.status === 'PASS').length, interactionPass: interactions.filter(i => i.status === 'PASS').length, interactionTotal: interactions.length }, limitations: ['NON-COMPARABLE with previous experiment', 'Cross-model generation / sampling seed / exact reasoning level unavailable', 'Average design score NOT VERIFIED; no numerical rubric executed', 'Screen reader speech, physical touch, backend and full WCAG audit NOT VERIFIED'] };
|
|
fs.writeFileSync(path.join(directory, 'final-evidence.json'), JSON.stringify(summary, null, 2));
|
|
console.log(JSON.stringify({ totals: summary.totals, sourceReuse: summary.sourceReuse, requiredPatternReuse: summary.requiredPatternReuse }, null, 2));
|
|
if (screens.some(s => s.combined.status !== 'PASS') || patterns.some(p => !p.rendered)) process.exitCode = 1;
|