#!/usr/bin/env node
import fs from 'fs/promises';
import fsSync from 'fs';
import os from 'os';
import path from 'path';
import { execSync, spawn } from 'child_process';
import { performance } from 'perf_hooks';
import { chromium } from 'playwright';
import { PARSEVL_URL } from '../src/data/versions.js';
import { getCookie } from '../src/server/helpers.js';
import { WorkflowExecutor } from '../src/vl/workflow-executor.js';
import { VLProjectContext } from '../src/vl/project-context.js';
import { createVLValidateTool } from '../src/tools/vl-validate.js';
import { extractFromFileTree, validateMeta } from '../src/vl/metadata-extractor.js';
const MODEL = process.env.VL_CODE_MODEL || 'claude-opus-4-6';
const REPORT_MODEL = process.env.VL_REPORT_MODEL || MODEL;
const TARGET_LANG = 'zh-CN';
const TODAY = new Date().toISOString().slice(0, 10);
const DATE_SLUG = TODAY.replace(/-/g, '');
const TEST_ROOT = path.join(os.homedir(), 'Documents', 'VLProjects', '_tests');
const REPORT_DIR = path.join(process.cwd(), 'docs', 'benchmarks');
const DOCCENTER_BASE = 'https://v4pre.visuallogic.ai/api/12027022';
const METHOD_ORDER = ['direct-full', 'meta-first', '3-file', '6-file', '9-file'];
const DIRECT_FILE_CONCURRENCY = Number(process.env.VL_FILE_CONCURRENCY || '6');
const METHOD_LABELS = {
'direct-full': 'Direct Full',
'meta-first': 'Meta First',
'3-file': 'Workflow 3-File',
'6-file': 'Workflow 6-File',
'9-file': 'Workflow 9-File',
};
const WORKFLOW_DOC_IDS = {
'3-file': 8,
'6-file': 34,
'9-file': 52,
};
const CONTRACT = {
projectName: 'CampusOps',
projectDescription: 'Desktop-first and mobile-friendly multi-campus operations cockpit for facilities dispatchers and supervisors.',
databaseFile: 'Database/CampusOps.vdb',
themeFile: 'Theme/Theme.vth',
app: {
id: 'CampusOpsApp',
filePath: 'Apps/CampusOpsApp.vx',
routes: [
{ path: 'overview', sectionId: 'OverviewPage' },
{ path: 'schedule', sectionId: 'ScheduleBoard' },
{ path: 'work-orders', sectionId: 'WorkOrderDesk' },
{ path: 'alerts', sectionId: 'AlertCenter' },
{ path: 'settings', sectionId: 'SettingsPage' },
],
},
tables: [
{
id: 'Campus',
fields: [
{ name: 'name', type: 'STRING' },
{ name: 'region', type: 'STRING' },
{ name: 'manager', type: 'STRING' },
{ name: 'activeAlerts', type: 'INT' },
],
},
{
id: 'Technician',
fields: [
{ name: 'name', type: 'STRING' },
{ name: 'campusId', type: 'INT' },
{ name: 'skillTag', type: 'STRING' },
{ name: 'shiftStatus', type: 'STRING' },
{ name: 'utilizationRate', type: 'FLOAT' },
],
},
{
id: 'WorkOrder',
fields: [
{ name: 'campusId', type: 'INT' },
{ name: 'title', type: 'STRING' },
{ name: 'priority', type: 'STRING' },
{ name: 'status', type: 'STRING' },
{ name: 'assigneeId', type: 'INT' },
{ name: 'slaHours', type: 'INT' },
],
},
{
id: 'AlertRule',
fields: [
{ name: 'campusId', type: 'INT' },
{ name: 'ruleName', type: 'STRING' },
{ name: 'thresholdValue', type: 'FLOAT' },
{ name: 'enabled', type: 'BOOL' },
],
},
{
id: 'AlertEvent',
fields: [
{ name: 'campusId', type: 'INT' },
{ name: 'ruleId', type: 'INT' },
{ name: 'severity', type: 'STRING' },
{ name: 'status', type: 'STRING' },
{ name: 'message', type: 'STRING' },
],
},
{
id: 'UserPreference',
fields: [
{ name: 'density', type: 'STRING' },
{ name: 'defaultCampusId', type: 'INT' },
{ name: 'emailDigest', type: 'BOOL' },
],
},
],
services: [
{
domainId: 'OperationsOverview',
filePath: 'Services/OperationsOverview.vs',
purpose: 'dashboard KPIs and summary cards',
methods: [
{ id: 'GetOverviewMetrics', params: 'campusId(INT)', returns: '{success:BOOL,data:OBJECT}' },
],
},
{
domainId: 'ScheduleService',
filePath: 'Services/ScheduleService.vs',
purpose: 'technician assignment list and shift filters',
methods: [
{ id: 'ListAssignments', params: 'campusId(INT),shiftStatus(STRING)', returns: '{success:BOOL,data:[{}]}' },
],
},
{
domainId: 'WorkOrderService',
filePath: 'Services/WorkOrderService.vs',
purpose: 'work order list and status updates',
methods: [
{ id: 'ListWorkOrders', params: 'campusId(INT),priority(STRING),status(STRING)', returns: '{success:BOOL,data:[{}]}' },
{ id: 'UpdateWorkOrderStatus', params: 'workOrderId(INT),status(STRING)', returns: '{success:BOOL}' },
],
},
{
domainId: 'AlertService',
filePath: 'Services/AlertService.vs',
purpose: 'alert event list and acknowledgement',
methods: [
{ id: 'ListAlerts', params: 'campusId(INT),severity(STRING),status(STRING)', returns: '{success:BOOL,data:[{}]}' },
{ id: 'AcknowledgeAlert', params: 'alertId(INT)', returns: '{success:BOOL}' },
],
},
{
domainId: 'SettingsService',
filePath: 'Services/SettingsService.vs',
purpose: 'settings and threshold preferences',
methods: [
{ id: 'GetSettings', params: 'campusId(INT)', returns: '{success:BOOL,data:OBJECT}' },
{ id: 'SaveSettings', params: 'campusId(INT),density(STRING),threshold(FLOAT)', returns: '{success:BOOL}' },
],
},
],
components: [
{
id: 'KpiCard',
filePath: 'ExtComponents/KpiCard.cp',
purpose: 'title, numeric value, helper text, optional intent tone',
},
{
id: 'StatusPill',
filePath: 'ExtComponents/StatusPill.cp',
purpose: 'compact status chip for priority or lifecycle states',
},
{
id: 'FilterToolbar',
filePath: 'ExtComponents/FilterToolbar.cp',
purpose: 'filter row with campus and status selectors plus clear action',
},
{
id: 'AlertListItem',
filePath: 'ExtComponents/AlertListItem.cp',
purpose: 'alert row with severity, message, meta, and acknowledge button',
},
],
sections: [
{
id: 'OverviewPage',
filePath: 'Sections/OverviewPage.sc',
consumesServices: ['OperationsOverview.GetOverviewMetrics'],
usesComponents: ['KpiCard'],
purpose: 'overview dashboard with KPI cards and campus summary rows',
},
{
id: 'ScheduleBoard',
filePath: 'Sections/ScheduleBoard.sc',
consumesServices: ['ScheduleService.ListAssignments'],
usesComponents: ['FilterToolbar', 'StatusPill'],
purpose: 'schedule and technician assignment table',
},
{
id: 'WorkOrderDesk',
filePath: 'Sections/WorkOrderDesk.sc',
consumesServices: ['WorkOrderService.ListWorkOrders', 'WorkOrderService.UpdateWorkOrderStatus'],
usesComponents: ['FilterToolbar', 'StatusPill'],
purpose: 'work order list with status change action',
},
{
id: 'AlertCenter',
filePath: 'Sections/AlertCenter.sc',
consumesServices: ['AlertService.ListAlerts', 'AlertService.AcknowledgeAlert'],
usesComponents: ['FilterToolbar', 'AlertListItem'],
purpose: 'alert center with severity list and acknowledgement actions',
},
{
id: 'SettingsPage',
filePath: 'Sections/SettingsPage.sc',
consumesServices: ['SettingsService.GetSettings', 'SettingsService.SaveSettings'],
usesComponents: ['StatusPill'],
purpose: 'alert threshold and density settings form',
},
],
};
const EXPECTED_PATHS = [
CONTRACT.databaseFile,
CONTRACT.themeFile,
...CONTRACT.services.map((item) => item.filePath),
...CONTRACT.components.map((item) => item.filePath),
...CONTRACT.sections.map((item) => item.filePath),
CONTRACT.app.filePath,
];
const REQUIREMENT = `
Build a desktop-first and mobile-friendly operations cockpit called CampusOps for a multi-campus facilities team.
Business scope:
- Roles: dispatcher and supervisor.
- Pages: overview, schedule, work-orders, alerts, settings.
- Data entities: Campus, Technician, WorkOrder, AlertRule, AlertEvent, UserPreference.
- Key interactions:
- Overview shows KPI cards for open work orders, overdue SLA, active alerts, and technician utilization.
- Schedule page lists technician assignments by campus and lets users filter by campus, technician, and shift status.
- Work order page lists work orders with filters by campus, priority, and status, and supports changing the order status.
- Alert center lists alert events with severity, source campus, and acknowledgement actions.
- Settings page edits alert thresholds and dashboard density preferences.
Design direction:
- Theme should feel enterprise and operational: deep teal primary, slate surfaces, amber warning, red danger, soft elevated cards, pill filters.
- Prefer card/list/table layouts instead of advanced chart widgets.
- Keep interactions compile-safe and easy to preview.
Exact naming contract:
- Project name: CampusOps
- Database file: Database/CampusOps.vdb
- Theme file: Theme/Theme.vth
- Service files:
- Services/OperationsOverview.vs
- Services/ScheduleService.vs
- Services/WorkOrderService.vs
- Services/AlertService.vs
- Services/SettingsService.vs
- Component files:
- ExtComponents/KpiCard.cp
- ExtComponents/StatusPill.cp
- ExtComponents/FilterToolbar.cp
- ExtComponents/AlertListItem.cp
- Section files:
- Sections/OverviewPage.sc
- Sections/ScheduleBoard.sc
- Sections/WorkOrderDesk.sc
- Sections/AlertCenter.sc
- Sections/SettingsPage.sc
- App file: Apps/CampusOpsApp.vx
Use the current latest VL syntax and current latest THEME 6.5.
Output compile-safe code only.
`.trim();
function normalizeCookie(cookie) {
if (!cookie) return '';
return String(cookie).startsWith('ih5bearer=') ? String(cookie) : `ih5bearer=${cookie}`;
}
function projectNameWithFallback(baseName) {
let candidate = baseName;
let n = 2;
while (fsSync.existsSync(path.join(TEST_ROOT, candidate))) {
candidate = baseName.replace(/Test$/, `Run${n}Test`);
n += 1;
}
return candidate;
}
async function ensureProjectScaffold(projectDir) {
for (const rel of ['Apps', 'Sections', 'ExtComponents', 'Services', 'Database', 'Theme', 'Process', '.vl-code']) {
await fs.mkdir(path.join(projectDir, rel), { recursive: true });
}
}
async function fetchDocInfo(docId, cookie) {
const res = await fetch(`${DOCCENTER_BASE}/SERVICE_DocCenter_GetDocById`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': normalizeCookie(cookie),
},
body: JSON.stringify({ docId }),
});
const data = await res.json();
const doc = data?.data || {};
return {
docId,
title: doc.name || '',
updatedAt: doc._update || '',
content: String(doc.currentContent || doc.content || ''),
};
}
function stripCodeFences(text) {
const trimmed = String(text || '').trim();
const fence = trimmed.match(/```(?:[a-zA-Z0-9_-]+)?\s*([\s\S]*?)```/);
return fence ? fence[1].trim() : trimmed;
}
async function runClaudePrompt(prompt, { systemPrompt = '', model = MODEL, timeoutMs = 10 * 60 * 1000 } = {}) {
return await new Promise((resolve, reject) => {
const args = ['--print', '--no-session-persistence', '--model', model, '--tools', ''];
if (systemPrompt) args.push('--system-prompt', systemPrompt);
const env = { ...process.env, NO_PROXY: 'localhost,127.0.0.1,::1' };
delete env.CLAUDECODE;
const proc = spawn('claude', args, {
stdio: ['pipe', 'pipe', 'pipe'],
env,
});
let stdout = '';
let stderr = '';
let finished = false;
const timer = setTimeout(() => {
proc.kill('SIGTERM');
reject(new Error(`claude prompt timed out after ${Math.round(timeoutMs / 1000)}s`));
}, timeoutMs);
proc.stdout.on('data', (chunk) => {
stdout += chunk.toString();
});
proc.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
proc.on('error', (err) => {
if (finished) return;
finished = true;
clearTimeout(timer);
reject(err);
});
proc.on('close', (code) => {
if (finished) return;
finished = true;
clearTimeout(timer);
if (code !== 0) {
reject(new Error(`claude exited with code ${code}: ${stderr.slice(0, 800)}`));
return;
}
resolve(stdout.trim());
});
proc.stdin.write(prompt);
proc.stdin.end();
});
}
function extractJson(text) {
const candidate = stripCodeFences(text);
try {
return JSON.parse(candidate);
} catch {}
const firstBrace = candidate.indexOf('{');
const lastBrace = candidate.lastIndexOf('}');
if (firstBrace >= 0 && lastBrace > firstBrace) {
return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
}
throw new Error('could not extract JSON from response');
}
function normalizeStringPatches(text, latestVlVersion) {
return String(text || '')
.replace(/VL_VERSION:3\.5/g, `VL_VERSION:${latestVlVersion}`)
.replace(/\bVL 3\.5\b/g, `VL ${latestVlVersion}`)
.replace(/\bVL \(Visual Language\) v3\.5\b/g, `VL (Visual Language) v${latestVlVersion}`)
.replace(/"vlVersion":\s*"3\.5"/g, `"vlVersion": "${latestVlVersion}"`)
.replace(/vlVersion:\s*'3\.5'/g, `vlVersion: '${latestVlVersion}'`);
}
function deepPatchStrings(value, latestVlVersion) {
if (typeof value === 'string') return normalizeStringPatches(value, latestVlVersion);
if (Array.isArray(value)) return value.map((item) => deepPatchStrings(item, latestVlVersion));
if (!value || typeof value !== 'object') return value;
const out = {};
for (const [key, inner] of Object.entries(value)) {
out[key] = deepPatchStrings(inner, latestVlVersion);
}
return out;
}
function buildLatestDigest({ latestVlVersion, themeTitle }) {
return `
Latest reference baseline:
- Latest DocCenter VL syntax document reports version ${latestVlVersion}.
- Latest theme document is ${themeTitle}.
- Use current latest syntax header // VL_VERSION:${latestVlVersion}.
Essential VL rules distilled from the latest docs:
- File types: .vx App, .sc Section, .cp Component, .vs ServiceDomain, .vdb Database, .vth Theme.
- Cross references: App -> Section/Component only; Section -> ServiceDomain/Component only; Service and Component do not cross-reference others.
- Indentation uses leading hyphens, never spaces.
- App required section order: SysConfig, Frontend Global Vars, Frontend Derived Vars, Frontend Tree, Frontend Event Handlers, Frontend Internal Methods, Frontend Pipeline Funcs.
- Section required section order: Frontend Public Props, Frontend Public Events, Frontend Public Methods, Frontend Global Vars, Frontend Derived Vars, Frontend Tree, Frontend Event Handlers, Frontend Internal Methods, Frontend Pipeline Funcs.
- Component required section order: Frontend Public Props, Frontend Public Events, Frontend Derived Vars, Frontend Tree, Frontend Event Handlers, Frontend Internal Methods, Frontend Pipeline Funcs.
- ServiceDomain required section order: Backend Environment Vars, Backend Tree, Services, Backend Event Handlers, Transactions, Backend Internal Methods, Backend Pipeline Funcs.
- Theme file order: # Meta -> optional # Design Tokens -> # Point Slot Values -> optional # Overrides.
- The theme heading must be exactly # Point Slot Values. Never use legacy # Coordinate Values.
- Style values must stay compile-safe. Prefer static string literals in style slots and simple widgets/layouts.
- Do not invent unsupported widgets or speculative syntax.
`.trim();
}
async function mapLimit(items, limit, iterator) {
const results = new Array(items.length);
let nextIndex = 0;
async function worker() {
while (nextIndex < items.length) {
const current = nextIndex;
nextIndex += 1;
results[current] = await iterator(items[current], current);
}
}
const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, () => worker());
await Promise.all(workers);
return results;
}
function buildDirectContract() {
return {
projectName: CONTRACT.projectName,
projectDescription: CONTRACT.projectDescription,
database: {
filePath: CONTRACT.databaseFile,
tables: CONTRACT.tables,
},
theme: {
filePath: CONTRACT.themeFile,
style: 'enterprise-light',
direction: 'deep teal primary, slate neutrals, amber warning, red danger, soft elevated cards, pill filters',
},
services: CONTRACT.services,
components: CONTRACT.components,
sections: CONTRACT.sections,
app: CONTRACT.app,
};
}
function directContextSlice(kind, target) {
if (kind === 'database') {
return {
filePath: target.filePath,
tables: CONTRACT.tables,
};
}
if (kind === 'theme') {
return {
filePath: target.filePath,
themeName: 'CampusOps',
direction: 'deep teal primary, slate surfaces, amber warning, red danger, soft elevated cards, pill filters',
requiredMeta: {
mode: 'light',
version: '6.5.0',
styleSpaceVersion: '1.6',
base_theme: 'Platform/Theme-Default-Light@1',
profile: 'enterprise',
},
requiredGroups: ['intent', 'emphasis', 'shape', 'surface', 'textRole', 'state'],
};
}
if (kind === 'service') {
return {
filePath: target.filePath,
service: target,
databaseTables: CONTRACT.tables.map((table) => ({ id: table.id, fields: table.fields })),
};
}
if (kind === 'component') {
return {
filePath: target.filePath,
component: target,
themeDirection: 'enterprise-light, operational, card/list/table friendly',
};
}
if (kind === 'section') {
return {
filePath: target.filePath,
section: target,
services: CONTRACT.services.filter((service) =>
target.consumesServices?.some((entry) => String(entry).startsWith(`${service.domainId}.`))
),
components: CONTRACT.components.filter((component) => target.usesComponents?.includes(component.id)),
};
}
if (kind === 'app') {
return {
filePath: target.filePath,
app: target,
sections: CONTRACT.sections.map((section) => ({
id: section.id,
filePath: section.filePath,
purpose: section.purpose,
})),
};
}
return { filePath: target.filePath };
}
function metaContextSlice(kind, target, meta) {
if (kind === 'database') {
return {
filePath: target.filePath,
database: meta.database,
dataSchema: meta.dataSchema,
};
}
if (kind === 'theme') {
return {
filePath: target.filePath,
theme: meta.theme,
direction: 'enterprise-light, operational, teal/slate/amber/red',
requiredMeta: {
mode: 'light',
version: '6.5.0',
styleSpaceVersion: '1.6',
base_theme: 'Platform/Theme-Default-Light@1',
profile: 'enterprise',
},
};
}
if (kind === 'service') {
return {
filePath: target.filePath,
service: target,
dataSchema: meta.dataSchema,
};
}
if (kind === 'component') {
return {
filePath: target.filePath,
component: target,
};
}
if (kind === 'section') {
return {
filePath: target.filePath,
section: target,
services: (meta.services || []).filter((service) =>
target.consumesServices?.some((entry) => String(entry).startsWith(`${service.domainId}.`))
),
components: (meta.components || []).filter((component) => target.usesComponents?.includes(component.id)),
};
}
if (kind === 'app') {
return {
filePath: target.filePath,
app: target,
sections: meta.sections || [],
};
}
return { filePath: target.filePath };
}
function uniqueBy(items, keyFn) {
const seen = new Set();
const out = [];
for (const item of items || []) {
const key = keyFn(item);
if (seen.has(key)) continue;
seen.add(key);
out.push(item);
}
return out;
}
function directBatchContextSlice(kind, targets) {
if (kind === 'service') {
return {
services: targets,
databaseTables: CONTRACT.tables.map((table) => ({ id: table.id, fields: table.fields })),
};
}
if (kind === 'component') {
return {
components: targets,
themeDirection: 'enterprise-light, operational, card/list/table friendly',
};
}
if (kind === 'section') {
const services = uniqueBy(
CONTRACT.services.filter((service) =>
targets.some((section) => section.consumesServices?.some((entry) => String(entry).startsWith(`${service.domainId}.`)))
),
(service) => service.domainId,
);
const components = uniqueBy(
CONTRACT.components.filter((component) =>
targets.some((section) => section.usesComponents?.includes(component.id))
),
(component) => component.id,
);
return { sections: targets, services, components };
}
return { targets };
}
function metaBatchContextSlice(kind, targets, meta) {
if (kind === 'service') {
return {
services: targets,
dataSchema: meta.dataSchema,
};
}
if (kind === 'component') {
return {
components: targets,
};
}
if (kind === 'section') {
const services = uniqueBy(
(meta.services || []).filter((service) =>
targets.some((section) => section.consumesServices?.some((entry) => String(entry).startsWith(`${service.domainId}.`)))
),
(service) => service.domainId,
);
const components = uniqueBy(
(meta.components || []).filter((component) =>
targets.some((section) => section.usesComponents?.includes(component.id))
),
(component) => component.id,
);
return { sections: targets, services, components };
}
return { targets };
}
function buildBatchPrompt({ kind, targets, digest, latestVlVersion, context }) {
const rootContracts = targets.map((target) => {
const { open, close } = expectedRootInfo(kind, target);
return `- ${target.filePath}: open ${open} | close ${close}`;
}).join('\n');
return `
You are generating multiple VL ${kind} files for the CampusOps benchmark.
${digest}
Global requirement:
${REQUIREMENT}
Batch context:
${JSON.stringify(context, null, 2)}
Exact file/root contracts:
${rootContracts}
Output rules:
- Return ONLY valid JSON.
- The JSON must be an object mapping exact relative file paths to raw VL source strings.
- Include every target file exactly once.
- Every value must start with // VL_VERSION:${latestVlVersion}
- Every value must satisfy its exact root contract.
- Do not include markdown fences or explanations.
`.trim();
}
async function runBatchPhaseWithRepair({
kind,
targets,
projectDir,
processDir,
batchName,
latestVlVersion,
batchPrompt,
batchSystemPrompt,
buildSinglePrompt,
}) {
const records = [];
const repairs = [];
let batchDurationMs = 0;
let batchFailed = false;
try {
const batch = await generateMappedFiles({
prompt: batchPrompt,
projectDir,
promptPath: path.join(processDir, `${batchName}.prompt.txt`),
rawPath: path.join(processDir, `${batchName}.raw.txt`),
systemPrompt: batchSystemPrompt,
});
batchDurationMs = batch.durationMs;
} catch (error) {
batchFailed = true;
await fs.writeFile(path.join(processDir, `${batchName}.error.txt`), String(error?.stack || error?.message || error), 'utf-8');
}
if (!batchFailed) {
for (const target of targets) {
const targetPath = path.join(projectDir, target.filePath);
let content = '';
try {
content = await fs.readFile(targetPath, 'utf-8');
} catch {
repairs.push({ target, issues: ['batch output missing target file'] });
continue;
}
const issues = validateGeneratedVlShape({ kind, target, content, latestVlVersion });
if (issues.length) {
repairs.push({ target, issues });
continue;
}
records.push({
kind,
filePath: target.filePath,
durationMs: batchDurationMs,
attempts: 1,
issues: [],
});
}
} else {
for (const target of targets) {
repairs.push({ target, issues: ['batch generation failed'] });
}
}
const repairRecords = await mapLimit(repairs, Math.min(DIRECT_FILE_CONCURRENCY, Math.max(1, repairs.length)), async ({ target, issues }) => {
const safeName = path.basename(target.filePath).replace(/[^\w.-]/g, '_');
const result = await generateSingleFile({
kind,
target,
latestVlVersion,
prompt: buildSinglePrompt(target),
targetPath: path.join(projectDir, target.filePath),
promptPath: path.join(processDir, `${safeName}.repair.prompt.txt`),
rawPath: path.join(processDir, `${safeName}.repair.raw.txt`),
systemPrompt: `Generate only the VL source for ${target.filePath}.`,
});
return {
kind,
filePath: target.filePath,
durationMs: batchDurationMs + result.durationMs,
attempts: 1 + result.attempts,
issues: [...issues, ...result.issues],
};
});
return [...records, ...repairRecords];
}
function singularKind(kind) {
return String(kind || '').replace(/s$/, '');
}
function expectedRootInfo(kind, target) {
switch (singularKind(kind)) {
case 'database':
return { open: '', close: '' };
case 'theme':
return { open: '', close: '' };
case 'service':
return {
open: ``,
close: ``,
};
case 'component':
return {
open: ``,
close: ``,
};
case 'section':
return {
open: ``,
close: ``,
};
case 'app':
return {
open: ``,
close: ``,
};
default:
throw new Error(`Unknown kind for root info: ${kind}`);
}
}
function requiredHeadersForKind(kind) {
switch (singularKind(kind)) {
case 'service':
return [
'# Backend Environment Vars',
'# Backend Tree',
'# Services',
'# Backend Event Handlers',
'# Transactions',
'# Backend Internal Methods',
'# Backend Pipeline Funcs',
];
case 'component':
return [
'# Frontend Public Props',
'# Frontend Public Events',
'# Frontend Derived Vars',
'# Frontend Tree',
'# Frontend Event Handlers',
'# Frontend Internal Methods',
'# Frontend Pipeline Funcs',
];
case 'section':
return [
'# Frontend Public Props',
'# Frontend Public Events',
'# Frontend Public Methods',
'# Frontend Global Vars',
'# Frontend Derived Vars',
'# Frontend Tree',
'# Frontend Event Handlers',
'# Frontend Internal Methods',
'# Frontend Pipeline Funcs',
];
case 'app':
return [
'# SysConfig',
'# Frontend Global Vars',
'# Frontend Derived Vars',
'# Frontend Tree',
'# Frontend Event Handlers',
'# Frontend Internal Methods',
'# Frontend Pipeline Funcs',
];
case 'theme':
return ['# Meta', '# Point Slot Values'];
default:
return [];
}
}
function buildRootGuide(kind, target, latestVlVersion) {
const { open, close } = expectedRootInfo(kind, target);
if (singularKind(kind) === 'database') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
Minimal shape example:
// VL_VERSION:${latestVlVersion}
${open}
data:[{"_id":1,"name":"North Campus","region":"North","manager":"Avery Chen","activeAlerts":3,"_user":"1","_create":"2026-03-01 09:00:00","_update":"2026-03-01 09:00:00"}]
- type:STRING notNull:true
${close}
`.trim();
}
if (singularKind(kind) === 'theme') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
Minimal shape example:
// VL_VERSION:${latestVlVersion}
${open}
# Meta
mode:"light"
version:"6.5.0"
base_theme:"Platform/Theme-Default-Light@1"
# Point Slot Values
intent.primary.intentBg:#0D9488
# Overrides
${close}
`.trim();
}
if (singularKind(kind) === 'service') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
- Do not split the root into " ${target.domainId}".
Minimal shape example:
// VL_VERSION:${latestVlVersion}
${open}
# Backend Environment Vars
# Backend Tree
# Services
SERVICE Example();RETURN {success:BOOL}
-RETURN {success:true}
# Backend Event Handlers
# Transactions
# Backend Internal Methods
# Backend Pipeline Funcs
${close}
`.trim();
}
if (singularKind(kind) === 'component') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
Minimal shape example:
// VL_VERSION:${latestVlVersion}
// Preview: width:220px height:120px
${open} padding:"16px"
# Frontend Public Props
$title(STRING) = ""
# Frontend Public Events
# Frontend Derived Vars
# Frontend Tree
value:$title
# Frontend Event Handlers
# Frontend Internal Methods
# Frontend Pipeline Funcs
${close}
`.trim();
}
if (singularKind(kind) === 'section') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
Minimal shape example:
// VL_VERSION:${latestVlVersion}
${open} padding:"24px"
# Frontend Public Props
# Frontend Public Events
# Frontend Public Methods
# Frontend Global Vars
$isLoading(BOOL) = false
# Frontend Derived Vars
# Frontend Tree
- value:"Example"
# Frontend Event Handlers
${open}.@init()
-init()
# Frontend Internal Methods
METHOD init(); RETURN {success:BOOL}
-RETURN {success:true}
# Frontend Pipeline Funcs
${close}
`.trim();
}
if (singularKind(kind) === 'app') {
return `
Exact root contract for this file:
- Opening root must start with exactly: ${open}
- Closing root must be exactly: ${close}
Minimal shape example:
// VL_VERSION:${latestVlVersion}
${open}
# SysConfig
DEVICE_TARGET:"PC"
SCREEN_RESOLUTION:"1440x900"
# Frontend Global Vars
$currentRoute(STRING) = "overview"
# Frontend Derived Vars
# Frontend Tree
path:"overview"
-
# Frontend Event Handlers
# Frontend Internal Methods
# Frontend Pipeline Funcs
${close}
`.trim();
}
throw new Error(`Unknown kind for root guide: ${kind}`);
}
function escapeRegex(text) {
return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function validateGeneratedVlShape({ kind, target, content, latestVlVersion }) {
const issues = [];
const text = String(content || '').trim();
const { open, close } = expectedRootInfo(kind, target);
const lines = text.split('\n');
if (lines[0]?.trim() !== `// VL_VERSION:${latestVlVersion}`) {
issues.push(`first line must be // VL_VERSION:${latestVlVersion}`);
}
if (!new RegExp(`^${escapeRegex(open)}(?:\\s|$)`, 'm').test(text)) {
issues.push(`opening root must start with ${open}`);
}
if (!new RegExp(`^${escapeRegex(close)}\\s*$`, 'm').test(text)) {
issues.push(`closing root must be ${close}`);
}
const headers = requiredHeadersForKind(kind);
let cursor = -1;
for (const header of headers) {
const index = text.indexOf(header);
if (index === -1) {
issues.push(`missing required header ${header}`);
continue;
}
if (index < cursor) {
issues.push(`header out of order ${header}`);
}
cursor = index;
}
return issues;
}
function buildRepairPrompt({ originalPrompt, kind, target, issues, invalidContent, latestVlVersion }) {
return `
${originalPrompt}
The previous output for ${target.filePath} was invalid.
Validation failures:
${issues.map((issue) => `- ${issue}`).join('\n')}
Root and structure reminder:
${buildRootGuide(kind, target, latestVlVersion)}
Previous invalid output:
${invalidContent}
Rewrite the complete file from scratch.
Return only raw VL source.
Do not explain anything.
`.trim();
}
function buildDirectFilePrompt({ kind, target, digest, latestThemeDoc, latestVlVersion }) {
const contextJson = JSON.stringify(directContextSlice(kind, target), null, 2);
const base = `
You are generating one VL file for the CampusOps benchmark.
${digest}
Global requirement:
${REQUIREMENT}
File-specific context:
${contextJson}
Common output rules:
- Output only raw VL source code for the requested file.
- Do not use markdown fences.
- The first line must be // VL_VERSION:${latestVlVersion}
- Keep identifiers, routes, and file references exactly aligned with the contract.
- Use only compile-safe VL constructs.
- Use real VL root declarations with angle brackets.
- Never use prose roots like "ServiceDomain Foo" or freeform HTML tags.
${buildRootGuide(kind, target, latestVlVersion)}
`.trim();
if (kind === 'database') {
return `${base}
Target file: ${target.filePath}
Generate the .vdb file with realistic seed data for all declared tables.`;
}
if (kind === 'theme') {
return `${base}
Target file: ${target.filePath}
Generate a custom Theme 6.5 file for CampusOps.
Do not paste the official theme doc.
Keep the file concise but complete, compile-safe, and aligned to the required meta fields and slot groups in file-context.`;
}
if (kind === 'service') {
return `${base}
Target file: ${target.filePath}
Service contract:
${JSON.stringify(target, null, 2)}
Generate one ServiceDomain file with safe optional filters and realistic mock responses backed by the declared tables.`;
}
if (kind === 'component') {
return `${base}
Target file: ${target.filePath}
Component contract:
${JSON.stringify(target, null, 2)}
Generate one reusable pure UI component.`;
}
if (kind === 'section') {
return `${base}
Target file: ${target.filePath}
Section contract:
${JSON.stringify(target, null, 2)}
Available services:
${JSON.stringify(CONTRACT.services, null, 2)}
Available components:
${JSON.stringify(CONTRACT.components, null, 2)}
Generate one Section file with local state, service calls, and simple event handlers.`;
}
if (kind === 'app') {
return `${base}
Target file: ${target.filePath}
App contract:
${JSON.stringify(target, null, 2)}
Available sections:
${JSON.stringify(CONTRACT.sections, null, 2)}
Generate one App file with a clear sidebar + content layout and route switching.`;
}
throw new Error(`Unknown direct generation kind: ${kind}`);
}
async function generateSingleFile({ kind, target, latestVlVersion, prompt, targetPath, promptPath, rawPath, systemPrompt }) {
await fs.mkdir(path.dirname(promptPath), { recursive: true });
await fs.writeFile(promptPath, prompt, 'utf-8');
let finalRaw = '';
let finalContent = '';
let totalDurationMs = 0;
let attempts = 0;
let issues = [];
for (let attempt = 1; attempt <= 2; attempt += 1) {
attempts = attempt;
const attemptPrompt = attempt === 1 ? prompt : buildRepairPrompt({
originalPrompt: prompt,
kind,
target,
issues,
invalidContent: finalContent,
latestVlVersion,
});
if (attempt > 1) {
await fs.writeFile(promptPath.replace(/\.prompt\.txt$/, `.retry${attempt - 1}.prompt.txt`), attemptPrompt, 'utf-8');
}
const startedAt = performance.now();
const raw = await runClaudePrompt(attemptPrompt, { systemPrompt });
totalDurationMs += Math.round(performance.now() - startedAt);
finalRaw = raw;
finalContent = stripCodeFences(raw).trim() + '\n';
issues = validateGeneratedVlShape({ kind, target, content: finalContent, latestVlVersion });
if (attempt > 1) {
await fs.writeFile(rawPath.replace(/\.raw\.txt$/, `.retry${attempt - 1}.raw.txt`), raw, 'utf-8');
}
if (!issues.length) break;
}
await fs.writeFile(rawPath, finalRaw, 'utf-8');
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.writeFile(targetPath, finalContent, 'utf-8');
return {
durationMs: totalDurationMs,
attempts,
issues,
};
}
function coerceFileMap(parsed) {
if (!parsed || typeof parsed !== 'object') {
throw new Error('expected a JSON object for file map output');
}
if (parsed.files && typeof parsed.files === 'object' && !Array.isArray(parsed.files)) {
return parsed.files;
}
if (parsed.artifacts && typeof parsed.artifacts === 'object' && !Array.isArray(parsed.artifacts)) {
return parsed.artifacts;
}
if (Array.isArray(parsed.files)) {
return Object.fromEntries(
parsed.files
.filter((item) => item && typeof item.path === 'string' && typeof item.content === 'string')
.map((item) => [item.path, item.content])
);
}
return parsed;
}
async function writeObjectFiles(projectDir, filesMap) {
const written = [];
for (const [relPath, content] of Object.entries(filesMap || {})) {
if (typeof content !== 'string') continue;
const target = path.join(projectDir, relPath);
await fs.mkdir(path.dirname(target), { recursive: true });
await fs.writeFile(target, content.trim() + '\n', 'utf-8');
written.push(relPath);
}
return written.sort();
}
async function generateMappedFiles({ prompt, projectDir, promptPath, rawPath, systemPrompt }) {
const startedAt = performance.now();
const raw = await runClaudePrompt(prompt, { systemPrompt });
const durationMs = Math.round(performance.now() - startedAt);
await fs.mkdir(path.dirname(promptPath), { recursive: true });
await fs.writeFile(promptPath, prompt, 'utf-8');
await fs.writeFile(rawPath, raw, 'utf-8');
const parsed = extractJson(raw);
const filesMap = coerceFileMap(parsed);
const writtenPaths = await writeObjectFiles(projectDir, filesMap);
return {
durationMs,
writtenPaths,
};
}
async function runDirectFullBaseline({ projectDir, digest, latestThemeDoc, latestVlVersion }) {
await ensureProjectScaffold(projectDir);
const processDir = path.join(projectDir, 'Process', 'DirectFull');
const startedAt = performance.now();
const runSingle = async (kind, target) => {
const safeName = path.basename(target.filePath).replace(/[^\w.-]/g, '_');
const result = await generateSingleFile({
kind,
target,
latestVlVersion,
prompt: buildDirectFilePrompt({
kind,
target,
digest,
latestThemeDoc,
latestVlVersion,
}),
targetPath: path.join(projectDir, target.filePath),
promptPath: path.join(processDir, `${safeName}.prompt.txt`),
rawPath: path.join(processDir, `${safeName}.raw.txt`),
systemPrompt: `Generate only the VL source for ${target.filePath}.`,
});
return {
kind,
filePath: target.filePath,
durationMs: result.durationMs,
attempts: result.attempts,
issues: result.issues,
};
};
const [databaseRecord, themeRecord, serviceRecords, componentRecords, sectionRecords, appRecord] = await Promise.all([
runSingle('database', { filePath: CONTRACT.databaseFile }),
runSingle('theme', { filePath: CONTRACT.themeFile }),
runBatchPhaseWithRepair({
kind: 'service',
targets: CONTRACT.services,
projectDir,
processDir,
batchName: 'services.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'service',
targets: CONTRACT.services,
digest,
latestVlVersion,
context: directBatchContextSlice('service', CONTRACT.services),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested service file path to its VL source.',
buildSinglePrompt: (target) => buildDirectFilePrompt({
kind: 'service',
target,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
runBatchPhaseWithRepair({
kind: 'component',
targets: CONTRACT.components,
projectDir,
processDir,
batchName: 'components.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'component',
targets: CONTRACT.components,
digest,
latestVlVersion,
context: directBatchContextSlice('component', CONTRACT.components),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested component file path to its VL source.',
buildSinglePrompt: (target) => buildDirectFilePrompt({
kind: 'component',
target,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
runBatchPhaseWithRepair({
kind: 'section',
targets: CONTRACT.sections,
projectDir,
processDir,
batchName: 'sections.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'section',
targets: CONTRACT.sections,
digest,
latestVlVersion,
context: directBatchContextSlice('section', CONTRACT.sections),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested section file path to its VL source.',
buildSinglePrompt: (target) => buildDirectFilePrompt({
kind: 'section',
target,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
runSingle('app', CONTRACT.app),
]);
const records = [
databaseRecord,
themeRecord,
...serviceRecords,
...componentRecords,
...sectionRecords,
appRecord,
];
return {
method: 'direct-full',
durationMs: Math.round(performance.now() - startedAt),
firstArtifactMs: records.length ? Math.min(...records.map((item) => item.durationMs)) : null,
fileStats: records,
};
}
function buildMetaPrompt({ digest, latestVlVersion }) {
return `
Generate the complete ProjectMeta JSON for the CampusOps benchmark.
${digest}
Global requirement:
${REQUIREMENT}
Exact schema and naming requirements:
- specVersion must be "ProjectMeta/1.0"
- projectName must be "CampusOps"
- vlVersion must be "${latestVlVersion}"
- config.themeFile must be "Theme/Theme.vth"
- database.file must be "Database/CampusOps.vdb"
- theme.file must be "Theme/Theme.vth"
- Service domains must be exactly: OperationsOverview, ScheduleService, WorkOrderService, AlertService, SettingsService
- Components must be exactly: KpiCard, StatusPill, FilterToolbar, AlertListItem
- Sections must be exactly: OverviewPage, ScheduleBoard, WorkOrderDesk, AlertCenter, SettingsPage
- App must be exactly: CampusOpsApp
- Every service/component/section/app must include its exact filePath
- Every section must include explicit consumesServices and usesComponents arrays
- App routes must point to the declared sections
Required top-level keys:
{
"specVersion": "ProjectMeta/1.0",
"projectName": "CampusOps",
"projectDescription": "...",
"vlVersion": "${latestVlVersion}",
"config": {...},
"database": {...},
"theme": {...},
"dataSchema": {...},
"services": [...],
"components": [...],
"sections": [...],
"apps": [...]
}
Output ONLY valid JSON.
`.trim();
}
function normalizeProjectMeta(meta, latestVlVersion) {
const out = meta && typeof meta === 'object' ? structuredClone(meta) : {};
out.specVersion = 'ProjectMeta/1.0';
out.projectName = CONTRACT.projectName;
out.projectDescription = out.projectDescription || CONTRACT.projectDescription;
out.vlVersion = latestVlVersion;
out.config = out.config || {};
out.config.themeFile = CONTRACT.themeFile;
out.config.defaultDevice = out.config.defaultDevice || 'PC';
out.config.defaultResolution = out.config.defaultResolution || '1440x900';
out.config.colorScheme = out.config.colorScheme || 'light';
out.database = out.database || {};
out.database.file = CONTRACT.databaseFile;
out.theme = out.theme || {};
out.theme.file = CONTRACT.themeFile;
out.dataSchema = out.dataSchema || {};
out.dataSchema.tables = Array.isArray(out.dataSchema.tables) ? out.dataSchema.tables : [];
out.dataSchema.relations = Array.isArray(out.dataSchema.relations) ? out.dataSchema.relations : [];
out.services = Array.isArray(out.services) ? out.services : [];
out.components = Array.isArray(out.components) ? out.components : [];
out.sections = Array.isArray(out.sections) ? out.sections : [];
out.apps = Array.isArray(out.apps) ? out.apps : [];
const servicePaths = new Map(CONTRACT.services.map((item) => [item.domainId, item.filePath]));
const componentPaths = new Map(CONTRACT.components.map((item) => [item.id, item.filePath]));
const sectionPaths = new Map(CONTRACT.sections.map((item) => [item.id, item.filePath]));
out.services = out.services.map((item) => {
const domainId = item.domainId || item.id || '';
return {
...item,
domainId,
filePath: item.filePath || servicePaths.get(domainId) || `Services/${domainId}.vs`,
methods: Array.isArray(item.methods) ? item.methods : [],
virtualTables: Array.isArray(item.virtualTables) ? item.virtualTables : [],
};
});
out.components = out.components.map((item) => {
const id = item.id || item.componentId || '';
return {
...item,
id,
filePath: item.filePath || componentPaths.get(id) || `ExtComponents/${id}.cp`,
props: Array.isArray(item.props) ? item.props : [],
slots: Array.isArray(item.slots) ? item.slots : [],
};
});
out.sections = out.sections.map((item) => {
const id = item.id || item.sectionId || '';
return {
...item,
id,
filePath: item.filePath || sectionPaths.get(id) || `Sections/${id}.sc`,
consumesServices: Array.isArray(item.consumesServices) ? item.consumesServices : [],
usesComponents: Array.isArray(item.usesComponents) ? item.usesComponents : [],
bindings: Array.isArray(item.bindings) ? item.bindings : [],
events: Array.isArray(item.events) ? item.events : [],
};
});
out.apps = out.apps.map((item) => {
const id = item.id || item.appId || '';
return {
...item,
id,
filePath: item.filePath || (id ? `Apps/${id}.vx` : CONTRACT.app.filePath),
routes: Array.isArray(item.routes) ? item.routes : [],
layout: item.layout && typeof item.layout === 'object' ? item.layout : {},
};
});
return out;
}
function buildMetaBasedFilePrompt({ kind, target, meta, digest, latestThemeDoc, latestVlVersion }) {
const contextJson = JSON.stringify(metaContextSlice(kind, target, meta), null, 2);
const base = `
You are generating one VL file for the CampusOps benchmark from ProjectMeta.
${digest}
Global requirement:
${REQUIREMENT}
Relevant ProjectMeta slice:
${contextJson}
Common output rules:
- Output only raw VL source code for the requested file.
- Do not use markdown fences.
- The first line must be // VL_VERSION:${latestVlVersion}
- Keep identifiers and file references aligned with ProjectMeta.
- Use compile-safe VL only.
- Use real VL root declarations with angle brackets.
- Never use prose roots like "ServiceDomain Foo" or freeform HTML tags.
${buildRootGuide(kind, target, latestVlVersion)}
`.trim();
if (kind === 'database') {
return `${base}
Target file: ${target.filePath}
Generate the database file that matches the ProjectMeta dataSchema with realistic seed data.`;
}
if (kind === 'theme') {
return `${base}
Target file: ${target.filePath}
Generate the Theme 6.5 file for CampusOps.
Do not paste the official theme doc.
Keep the file concise but complete, compile-safe, and keep base_theme:"Platform/Theme-Default-Light@1".`;
}
if (kind === 'service') {
return `${base}
Target file: ${target.filePath}
Service spec:
${JSON.stringify(target, null, 2)}
Generate one ServiceDomain file based on ProjectMeta.`;
}
if (kind === 'component') {
return `${base}
Target file: ${target.filePath}
Component spec:
${JSON.stringify(target, null, 2)}
Generate one pure UI component file.`;
}
if (kind === 'section') {
return `${base}
Target file: ${target.filePath}
Section spec:
${JSON.stringify(target, null, 2)}
Generate one Section file with service calls consistent with ProjectMeta.`;
}
if (kind === 'app') {
return `${base}
Target file: ${target.filePath}
App spec:
${JSON.stringify(target, null, 2)}
Generate one App file consistent with ProjectMeta routes and section usage.`;
}
throw new Error(`Unknown meta-based generation kind: ${kind}`);
}
async function runMetaFirstBaseline({ projectDir, digest, latestThemeDoc, latestVlVersion }) {
await ensureProjectScaffold(projectDir);
const processDir = path.join(projectDir, 'Process', 'MetaFirst');
const metaPrompt = buildMetaPrompt({ digest, latestVlVersion });
const metaRawPath = path.join(processDir, 'ProjectMeta.raw.txt');
const metaPromptPath = path.join(processDir, 'ProjectMeta.prompt.txt');
await fs.mkdir(processDir, { recursive: true });
await fs.writeFile(metaPromptPath, metaPrompt, 'utf-8');
const metaStartedAt = performance.now();
const metaRaw = await runClaudePrompt(metaPrompt, {
systemPrompt: 'Generate only valid ProjectMeta JSON.',
timeoutMs: 8 * 60 * 1000,
});
const metaDurationMs = Math.round(performance.now() - metaStartedAt);
await fs.writeFile(metaRawPath, metaRaw, 'utf-8');
const parsedMeta = extractJson(metaRaw);
const meta = normalizeProjectMeta(parsedMeta, latestVlVersion);
await fs.writeFile(path.join(projectDir, '.vl-code', 'ProjectMeta.json'), JSON.stringify(meta, null, 2), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', 'ProjectMeta.json'), JSON.stringify(meta, null, 2), 'utf-8');
const startedAt = performance.now();
const runSingle = async (kind, target) => {
const safeName = path.basename(target.filePath).replace(/[^\w.-]/g, '_');
const result = await generateSingleFile({
kind,
target,
latestVlVersion,
prompt: buildMetaBasedFilePrompt({
kind,
target,
meta,
digest,
latestThemeDoc,
latestVlVersion,
}),
targetPath: path.join(projectDir, target.filePath),
promptPath: path.join(processDir, `${safeName}.prompt.txt`),
rawPath: path.join(processDir, `${safeName}.raw.txt`),
systemPrompt: `Generate only the VL source for ${target.filePath}.`,
});
return {
kind,
filePath: target.filePath,
durationMs: result.durationMs,
attempts: result.attempts,
issues: result.issues,
};
};
const appPromises = meta.apps.map((target) => runSingle('app', target));
const [databaseRecord, themeRecord, serviceRecords, componentRecords, sectionRecords, appRecords] = await Promise.all([
runSingle('database', { filePath: meta.database?.file || CONTRACT.databaseFile }),
runSingle('theme', { filePath: meta.theme?.file || CONTRACT.themeFile }),
runBatchPhaseWithRepair({
kind: 'service',
targets: meta.services,
projectDir,
processDir,
batchName: 'services.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'service',
targets: meta.services,
digest,
latestVlVersion,
context: metaBatchContextSlice('service', meta.services, meta),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested service file path to its VL source.',
buildSinglePrompt: (target) => buildMetaBasedFilePrompt({
kind: 'service',
target,
meta,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
runBatchPhaseWithRepair({
kind: 'component',
targets: meta.components,
projectDir,
processDir,
batchName: 'components.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'component',
targets: meta.components,
digest,
latestVlVersion,
context: metaBatchContextSlice('component', meta.components, meta),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested component file path to its VL source.',
buildSinglePrompt: (target) => buildMetaBasedFilePrompt({
kind: 'component',
target,
meta,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
runBatchPhaseWithRepair({
kind: 'section',
targets: meta.sections,
projectDir,
processDir,
batchName: 'sections.batch',
latestVlVersion,
batchPrompt: buildBatchPrompt({
kind: 'section',
targets: meta.sections,
digest,
latestVlVersion,
context: metaBatchContextSlice('section', meta.sections, meta),
}),
batchSystemPrompt: 'Generate only a JSON object mapping each requested section file path to its VL source.',
buildSinglePrompt: (target) => buildMetaBasedFilePrompt({
kind: 'section',
target,
meta,
digest,
latestThemeDoc,
latestVlVersion,
}),
}),
Promise.all(appPromises),
]);
const records = [
databaseRecord,
themeRecord,
...serviceRecords,
...componentRecords,
...sectionRecords,
...appRecords,
];
return {
method: 'meta-first',
durationMs: metaDurationMs + Math.round(performance.now() - startedAt),
firstArtifactMs: metaDurationMs,
metaDurationMs,
meta,
fileStats: records,
};
}
async function fetchWorkflowJson(docId, cookie) {
const info = await fetchDocInfo(docId, cookie);
return {
...info,
workflow: JSON.parse(info.content),
};
}
function patchWorkflowForLatest(workflow, { latestVlVersion, latestThemeContent }) {
const patched = deepPatchStrings(workflow, latestVlVersion);
for (const step of patched.steps || []) {
if (/^SeedTheme_/i.test(step.id || '')) {
step.in = step.in || {};
step.in.content = latestThemeContent;
}
}
return patched;
}
function buildWorkflowRequirement(latestVlVersion) {
return `
${REQUIREMENT}
Additional generation constraints:
- Use latest VL syntax header // VL_VERSION:${latestVlVersion} for generated VL source files.
- Use Theme 6.5 enterprise semantics.
- Keep the output compile-safe and deterministic.
`.trim();
}
async function runWorkflowBaseline({ method, projectDir, cookie, latestSyntaxDoc, latestThemeContent, latestVlVersion }) {
await ensureProjectScaffold(projectDir);
const workflowDoc = await fetchWorkflowJson(WORKFLOW_DOC_IDS[method], cookie);
const workflow = patchWorkflowForLatest(workflowDoc.workflow, {
latestVlVersion,
latestThemeContent,
});
await fs.writeFile(path.join(projectDir, 'Process', `${method}.workflow.source.json`), workflowDoc.content, 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${method}.workflow.patched.json`), JSON.stringify(workflow, null, 2), 'utf-8');
const executor = new WorkflowExecutor({
workDir: projectDir,
model: REPORT_MODEL,
llmProvider: 'cli',
cookie,
vlVersion: latestVlVersion,
});
const originalResolveDocs = executor._resolveDocCenterDocs.bind(executor);
executor._resolveDocCenterDocs = async (docs, cb) => {
await originalResolveDocs(docs, cb);
for (const key of Object.keys(docs)) {
if (typeof docs[key] === 'string') {
docs[key] = normalizeStringPatches(docs[key], latestVlVersion);
}
}
if (Object.prototype.hasOwnProperty.call(docs, '1')) {
docs['1'] = latestSyntaxDoc;
}
};
const timeline = [];
const logLines = [];
const fileEvents = [];
const startedAt = performance.now();
await new Promise((resolve, reject) => {
executor.execute(workflow, {
userRequest: buildWorkflowRequirement(latestVlVersion),
userRequirement: buildWorkflowRequirement(latestVlVersion),
targetLang: TARGET_LANG,
}, {
onText: (text) => {
const line = String(text || '').trim();
if (line) logLines.push(line);
},
onNodeStart: (info) => {
timeline.push({
nodeId: info.nodeId,
title: info.title,
type: info.type,
status: 'start',
atMs: Math.round(performance.now() - startedAt),
});
},
onNodeDone: (info) => {
timeline.push({
nodeId: info.nodeId,
title: info.title,
type: info.type,
status: 'done',
durationMs: info.duration_ms || 0,
atMs: Math.round(performance.now() - startedAt),
});
},
onNodeError: (info) => {
timeline.push({
nodeId: info.nodeId,
title: info.title,
type: info.type,
status: 'error',
error: info.error || 'unknown error',
atMs: Math.round(performance.now() - startedAt),
});
},
onFileWritten: (filePath) => {
fileEvents.push({
filePath,
atMs: Math.round(performance.now() - startedAt),
});
},
onDone: (info) => resolve(info),
onError: (message) => reject(new Error(message || `${method} workflow generation failed`)),
});
});
const durationMs = Math.round(performance.now() - startedAt);
await fs.writeFile(path.join(projectDir, 'Process', `${method}.timeline.json`), JSON.stringify(timeline, null, 2), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${method}.log.txt`), logLines.join('\n'), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${method}.files.json`), JSON.stringify(fileEvents, null, 2), 'utf-8');
return {
method,
durationMs,
firstArtifactMs: fileEvents.length ? Math.min(...fileEvents.map((item) => item.atMs)) : null,
timeline,
workflowDocTitle: workflowDoc.title,
workflowDocUpdatedAt: workflowDoc.updatedAt,
};
}
async function packageProjectFiles(projectDir, zipPath) {
execSync(
`cd "${projectDir}" && find . -type f \\( -name "*.vx" -o -name "*.sc" -o -name "*.cp" -o -name "*.vs" -o -name "*.vdb" -o -name "*.vth" \\) | zip -q -@ "${zipPath}"`,
{ timeout: 30_000 },
);
}
async function runCloudAction(projectDir, cookie, action) {
const zipPath = path.join(projectDir, `__${action}.zip`);
try {
await packageProjectFiles(projectDir, zipPath);
const zipBuffer = await fs.readFile(zipPath);
const res = await fetch(`${PARSEVL_URL}/edtfn/parsevl`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': normalizeCookie(cookie),
},
body: JSON.stringify({
action,
file: `data:application/zip;base64,${zipBuffer.toString('base64')}`,
...(action === 'parsePjt' ? { download: true } : {}),
projectName: path.basename(projectDir),
}),
});
const json = await res.json();
const diagnostics = Array.isArray(json?.data?.errList) ? json.data.errList : [];
const hardErrors = diagnostics.filter((item) => String(item?.level || '').toLowerCase() !== 'warning');
const warnings = diagnostics.filter((item) => String(item?.level || '').toLowerCase() === 'warning');
return {
action,
httpOk: res.ok,
code: json?.code ?? null,
success: res.ok && hardErrors.length === 0 && (json?.code === 0 || json?.code === 200 || json?.success === true || action === 'lintPjt'),
errCount: hardErrors.length,
warningCount: warnings.length,
errList: diagnostics.slice(0, 30),
previewUrls: json?.data?.appPreviewUrlMap || {},
message: json?.message || '',
raw: json,
};
} catch (error) {
return {
action,
success: false,
error: error.message,
errCount: null,
warningCount: null,
errList: [],
previewUrls: {},
};
} finally {
try {
await fs.unlink(zipPath);
} catch {}
}
}
function parseValidationSummary(validationText) {
const text = String(validationText || '');
if (/All \d+ VL files passed validation\./.test(text)) {
return { errors: 0, warnings: 0, raw: text };
}
const match = text.match(/Validation:\s+(\d+)\s+errors,\s+(\d+)\s+warnings/i);
return {
errors: match ? Number(match[1]) : null,
warnings: match ? Number(match[2]) : null,
raw: text,
};
}
async function collectFileContents(projectDir) {
const result = {};
async function walk(currentDir, prefix = '') {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name.startsWith('.') && entry.name !== '.vl-code') continue;
const fullPath = path.join(currentDir, entry.name);
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
await walk(fullPath, relPath);
} else if (/\.(vx|sc|cp|vs|vdb|vth|json|txt|md)$/i.test(entry.name)) {
try {
result[relPath] = await fs.readFile(fullPath, 'utf-8');
} catch {}
}
}
}
await walk(projectDir);
return result;
}
function countLocByExt(fileMap) {
const stats = {};
for (const [relPath, content] of Object.entries(fileMap || {})) {
const ext = path.extname(relPath) || 'none';
const lines = String(content || '').split('\n');
if (!stats[ext]) stats[ext] = { files: 0, lines: 0, nonEmptyLines: 0 };
stats[ext].files += 1;
stats[ext].lines += lines.length;
stats[ext].nonEmptyLines += lines.filter((line) => line.trim()).length;
}
return stats;
}
async function renderMetadataSnapshot({ projectDir, label, meta }) {
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1800, height: 1400 } });
const viewerUrl = `file://${path.resolve(process.cwd(), 'public', 'metadata-viewer.html')}`;
await page.goto(viewerUrl);
await page.evaluate((payload) => {
window.postMessage({ type: 'loadMetadata', data: payload }, '*');
}, meta);
await page.waitForFunction(() => document.querySelectorAll('.node').length > 0, { timeout: 10_000 });
const counts = await page.evaluate(() => ({
nodeCount: document.querySelectorAll('.node').length,
connectionCount: document.querySelectorAll('.conn').length,
}));
const screenshotPath = path.join(projectDir, 'Process', `${label}.meta-viewer.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
return {
success: true,
...counts,
screenshotPath,
};
} catch (error) {
return {
success: false,
error: error.message,
nodeCount: 0,
connectionCount: 0,
screenshotPath: null,
};
} finally {
await browser.close();
}
}
function loadSavedMeta(fileMap) {
for (const relPath of ['.vl-code/ProjectMeta.json', 'Process/ProjectMeta.json']) {
if (!fileMap[relPath]) continue;
try {
const parsed = JSON.parse(fileMap[relPath]);
if (parsed && typeof parsed === 'object') return parsed;
} catch {}
}
return null;
}
async function analyzeProject(projectDir, { cookie, methodLabel }) {
const ctx = new VLProjectContext(projectDir);
await ctx.load();
const validateTool = createVLValidateTool(ctx);
const validationText = await validateTool.execute({ file_path: 'all' });
const validation = parseValidationSummary(validationText);
const hint = await runCloudAction(projectDir, cookie, 'lintPjt');
const compile = await runCloudAction(projectDir, cookie, 'parsePjt');
const fileMap = await collectFileContents(projectDir);
const vlFileMap = Object.fromEntries(
Object.entries(fileMap).filter(([relPath]) => /\.(vx|sc|cp|vs|vdb|vth)$/i.test(relPath))
);
const extractedMeta = extractFromFileTree(vlFileMap, projectDir);
const metaValidation = validateMeta(extractedMeta);
const savedMeta = loadSavedMeta(fileMap);
const metaForRender = savedMeta || extractedMeta;
const metaRender = await renderMetadataSnapshot({
projectDir,
label: methodLabel,
meta: metaForRender,
});
await fs.writeFile(path.join(projectDir, 'Process', `${methodLabel}.validation.txt`), validation.raw || '', 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${methodLabel}.hint.json`), JSON.stringify(hint, null, 2), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${methodLabel}.compile.json`), JSON.stringify(compile, null, 2), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${methodLabel}.extracted-meta.json`), JSON.stringify(extractedMeta, null, 2), 'utf-8');
await fs.writeFile(path.join(projectDir, 'Process', `${methodLabel}.meta-render.json`), JSON.stringify(metaRender, null, 2), 'utf-8');
const actualPaths = Object.keys(vlFileMap).sort();
const missingPaths = EXPECTED_PATHS.filter((relPath) => !actualPaths.includes(relPath));
const extraPaths = actualPaths.filter((relPath) => !EXPECTED_PATHS.includes(relPath));
return {
projectDir,
fileCount: actualPaths.length,
actualPaths,
missingPaths,
extraPaths,
validation,
hint: {
success: hint.success,
errCount: hint.errCount,
warningCount: hint.warningCount,
errList: hint.errList,
},
compile: {
success: compile.success,
errCount: compile.errCount,
warningCount: compile.warningCount,
previewUrls: compile.previewUrls,
errList: compile.errList,
},
metadataValid: metaValidation.valid,
metadataIssues: metaValidation.issues,
metaRender,
extractedMetaSummary: {
projectName: extractedMeta.projectName || null,
tables: Array.isArray(extractedMeta.dataSchema?.tables) ? extractedMeta.dataSchema.tables.length : 0,
services: Array.isArray(extractedMeta.services) ? extractedMeta.services.length : 0,
components: Array.isArray(extractedMeta.components) ? extractedMeta.components.length : 0,
sections: Array.isArray(extractedMeta.sections) ? extractedMeta.sections.length : 0,
apps: Array.isArray(extractedMeta.apps) ? extractedMeta.apps.length : 0,
},
locByExt: countLocByExt(vlFileMap),
};
}
function summarizeMethod(label, runMeta, analysis) {
return {
label: METHOD_LABELS[label] || label,
durationMs: runMeta.durationMs,
firstArtifactMs: runMeta.firstArtifactMs ?? null,
fileCount: analysis.fileCount,
missingPaths: analysis.missingPaths.length,
extraPaths: analysis.extraPaths.length,
localValidationErrors: analysis.validation.errors,
localValidationWarnings: analysis.validation.warnings,
hintErrors: analysis.hint.errCount,
hintWarnings: analysis.hint.warningCount,
compileSuccess: analysis.compile.success,
compileErrors: analysis.compile.errCount,
compileWarnings: analysis.compile.warningCount,
previewCount: Object.keys(analysis.compile.previewUrls || {}).length,
metadataValid: analysis.metadataValid,
metadataIssueCount: analysis.metadataIssues.length,
metaRenderSuccess: analysis.metaRender.success,
metaNodeCount: analysis.metaRender.nodeCount,
metaConnectionCount: analysis.metaRender.connectionCount,
};
}
function buildMarkdownReport(report) {
const bundleZipPath = report.bundle?.zipPath || 'pending';
const lines = [
`# VL Native Generation Benchmark (${TODAY})`,
'',
'## Baseline',
'',
`- Model: ${REPORT_MODEL}`,
`- Target language: ${TARGET_LANG}`,
`- Latest VL syntax doc: ${report.docs.syntax.title} (${report.docs.syntax.updatedAt})`,
`- Latest THEME doc: ${report.docs.theme.title} (${report.docs.theme.updatedAt})`,
`- Workflow spec doc: ${report.docs.workflowSpec.title} (${report.docs.workflowSpec.updatedAt})`,
'',
'## Requirement',
'',
REQUIREMENT,
'',
'## Summary',
'',
'| Method | Time(ms) | Hint E/W | Compile E/W | Compile OK | Meta Render | Missing | Preview |',
'| --- | ---: | ---: | ---: | --- | --- | ---: | ---: |',
];
for (const method of METHOD_ORDER) {
const summary = report.methods[method].summary;
lines.push(
`| ${summary.label} | ${summary.durationMs} | ${summary.hintErrors ?? 'n/a'}/${summary.hintWarnings ?? 'n/a'} | ${summary.compileErrors ?? 'n/a'}/${summary.compileWarnings ?? 'n/a'} | ${summary.compileSuccess} | ${summary.metaRenderSuccess} (${summary.metaNodeCount}) | ${summary.missingPaths} | ${summary.previewCount} |`
);
}
lines.push('');
lines.push('## Corrections Applied');
lines.push('');
lines.push('- Workflow prompt/doc strings containing `3.5` were patched in-memory to the latest syntax version before workflow execution.');
lines.push('- `SeedTheme` nodes were forced to use the latest Theme 6.5 document content from DocCenter.');
lines.push('- Workflow doc path `1` was overwritten in-memory with the latest syntax document content from DocCenter instead of the repo-pinned local snapshot.');
lines.push('');
lines.push('## Outputs');
lines.push('');
for (const method of METHOD_ORDER) {
const item = report.methods[method];
lines.push(`### ${item.summary.label}`);
lines.push('');
lines.push(`- Project dir: ${item.analysis.projectDir}`);
lines.push(`- Screenshot: ${item.analysis.metaRender.screenshotPath || 'n/a'}`);
lines.push(`- Missing paths: ${item.analysis.missingPaths.join(', ') || 'none'}`);
lines.push(`- Extra paths: ${item.analysis.extraPaths.join(', ') || 'none'}`);
lines.push(`- Metadata issues: ${item.analysis.metadataIssues.join(' | ') || 'none'}`);
lines.push('');
}
lines.push('## Bundle');
lines.push('');
lines.push(`- Zip: ${bundleZipPath}`);
lines.push('');
return lines.join('\n');
}
async function copyFileSafe(src, dest) {
await fs.mkdir(path.dirname(dest), { recursive: true });
await fs.copyFile(src, dest);
}
async function createBundle(report, reportJsonPath, reportMdPath) {
const bundleReportDir = path.join(TEST_ROOT, `VLNativeBenchmarkReports_${DATE_SLUG}`);
await fs.rm(bundleReportDir, { recursive: true, force: true });
await fs.mkdir(bundleReportDir, { recursive: true });
await copyFileSafe(reportJsonPath, path.join(bundleReportDir, path.basename(reportJsonPath)));
await copyFileSafe(reportMdPath, path.join(bundleReportDir, path.basename(reportMdPath)));
const zipPath = path.join(TEST_ROOT, `vl-native-benchmark-${DATE_SLUG}.zip`);
const bundleItems = [
...METHOD_ORDER.map((method) => path.basename(report.methods[method].analysis.projectDir)),
path.basename(bundleReportDir),
];
execSync(`cd "${TEST_ROOT}" && rm -f "${zipPath}" && zip -qr "${zipPath}" ${bundleItems.map((item) => `"${item}"`).join(' ')}`, {
timeout: 120_000,
});
return {
zipPath,
bundleReportDir,
};
}
function findLatestExistingProject(baseName, requireComplete = false) {
const prefix = baseName.replace(/Test$/, '');
const matches = fsSync.readdirSync(TEST_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix))
.map((entry) => {
const fullPath = path.join(TEST_ROOT, entry.name);
let mtimeMs = 0;
try {
mtimeMs = fsSync.statSync(fullPath).mtimeMs;
} catch {}
return { fullPath, mtimeMs };
})
.sort((a, b) => b.mtimeMs - a.mtimeMs);
for (const match of matches) {
if (!requireComplete || projectLooksComplete(match.fullPath)) return match.fullPath;
}
return null;
}
function projectLooksComplete(projectDir) {
return EXPECTED_PATHS.every((relPath) => fsSync.existsSync(path.join(projectDir, relPath)));
}
function estimateRunFromFiles(projectDir, method) {
const timestamps = [];
const artifactTimes = [];
function visit(dir) {
if (!fsSync.existsSync(dir)) return;
for (const entry of fsSync.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
visit(fullPath);
continue;
}
if (!/(\.prompt\.txt|\.raw\.txt|\.workflow\.(source|patched)\.json|\.timeline\.json|\.log\.txt|\.files\.json|ProjectMeta\.json)$/i.test(entry.name)) {
continue;
}
try {
const stat = fsSync.statSync(fullPath);
timestamps.push(stat.mtimeMs);
} catch {}
}
}
visit(path.join(projectDir, 'Process'));
for (const relPath of EXPECTED_PATHS) {
const fullPath = path.join(projectDir, relPath);
try {
artifactTimes.push(fsSync.statSync(fullPath).mtimeMs);
} catch {}
}
const start = timestamps.length ? Math.min(...timestamps) : Date.now();
const end = timestamps.length ? Math.max(...timestamps) : start;
const firstArtifact = artifactTimes.length ? Math.min(...artifactTimes) : start;
return {
method,
durationMs: Math.max(0, Math.round(end - start)),
firstArtifactMs: Math.max(0, Math.round(firstArtifact - start)),
resumed: true,
};
}
function buildFailedAnalysis(projectDir, error) {
return {
projectDir,
fileCount: 0,
actualPaths: [],
missingPaths: [...EXPECTED_PATHS],
extraPaths: [],
validation: { errors: null, warnings: null, raw: String(error || '') },
hint: { success: false, errCount: null, warningCount: null, errList: [] },
compile: { success: false, errCount: null, warningCount: null, previewUrls: {}, errList: [] },
metadataValid: false,
metadataIssues: [String(error || 'analysis failed')],
metaRender: { success: false, error: String(error || 'analysis failed'), nodeCount: 0, connectionCount: 0, screenshotPath: null },
extractedMetaSummary: { projectName: null, tables: 0, services: 0, components: 0, sections: 0, apps: 0 },
locByExt: {},
};
}
async function main() {
await fs.mkdir(TEST_ROOT, { recursive: true });
await fs.mkdir(REPORT_DIR, { recursive: true });
const cookie = getCookie({ workDir: process.cwd(), cookie: '' });
if (!cookie) {
throw new Error('No DocCenter/cloud cookie found. Cannot run benchmark.');
}
const docs = {
syntax: await fetchDocInfo(1, cookie),
theme: await fetchDocInfo(4, cookie),
workflowSpec: await fetchDocInfo(2, cookie),
};
const latestVlVersion = docs.syntax.content.match(/Current version:\s*`\/\/\s*VL_VERSION:([^`]+)`/i)?.[1]?.trim() || '3.6';
const latestSyntaxDoc = normalizeStringPatches(docs.syntax.content, latestVlVersion);
const latestThemeContent = normalizeStringPatches(docs.theme.content, latestVlVersion);
const digest = buildLatestDigest({ latestVlVersion, themeTitle: docs.theme.title });
const resumeExisting = process.env.VL_RESUME_EXISTING === '1';
const baseProjectNames = {
'direct-full': `CampusOpsDirectLatest${DATE_SLUG}Test`,
'meta-first': `CampusOpsMetaLatest${DATE_SLUG}Test`,
'3-file': `CampusOps3FileLatest${DATE_SLUG}Test`,
'6-file': `CampusOps6FileLatest${DATE_SLUG}Test`,
'9-file': `CampusOps9FileLatest${DATE_SLUG}Test`,
};
const projects = Object.fromEntries(
Object.entries(baseProjectNames).map(([method, baseName]) => {
const existing = resumeExisting ? findLatestExistingProject(baseName, true) : null;
return [
method,
existing || path.join(TEST_ROOT, projectNameWithFallback(baseName)),
];
})
);
const runs = {};
const analyses = {};
if (resumeExisting && projectLooksComplete(projects['direct-full'])) {
runs['direct-full'] = estimateRunFromFiles(projects['direct-full'], 'direct-full');
} else {
runs['direct-full'] = await runDirectFullBaseline({
projectDir: projects['direct-full'],
digest,
latestThemeDoc: latestThemeContent,
latestVlVersion,
});
}
analyses['direct-full'] = await analyzeProject(projects['direct-full'], {
cookie,
methodLabel: 'direct-full',
});
if (resumeExisting && projectLooksComplete(projects['meta-first'])) {
runs['meta-first'] = estimateRunFromFiles(projects['meta-first'], 'meta-first');
} else {
runs['meta-first'] = await runMetaFirstBaseline({
projectDir: projects['meta-first'],
digest,
latestThemeDoc: latestThemeContent,
latestVlVersion,
});
}
analyses['meta-first'] = await analyzeProject(projects['meta-first'], {
cookie,
methodLabel: 'meta-first',
});
for (const method of ['3-file', '6-file', '9-file']) {
try {
if (resumeExisting && projectLooksComplete(projects[method])) {
runs[method] = estimateRunFromFiles(projects[method], method);
} else {
runs[method] = await runWorkflowBaseline({
method,
projectDir: projects[method],
cookie,
latestSyntaxDoc,
latestThemeContent,
latestVlVersion,
});
}
} catch (error) {
runs[method] = {
method,
durationMs: null,
firstArtifactMs: null,
error: error.message,
};
}
try {
analyses[method] = await analyzeProject(projects[method], {
cookie,
methodLabel: method,
});
} catch (error) {
analyses[method] = buildFailedAnalysis(projects[method], error.message);
}
}
const report = {
createdAt: new Date().toISOString(),
model: MODEL,
requirement: REQUIREMENT,
docs: {
syntax: {
title: docs.syntax.title,
updatedAt: docs.syntax.updatedAt,
latestVlVersion,
},
theme: {
title: docs.theme.title,
updatedAt: docs.theme.updatedAt,
},
workflowSpec: {
title: docs.workflowSpec.title,
updatedAt: docs.workflowSpec.updatedAt,
},
},
methods: {},
};
for (const method of METHOD_ORDER) {
report.methods[method] = {
run: runs[method],
analysis: analyses[method],
summary: summarizeMethod(method, runs[method], analyses[method]),
};
}
const reportJsonPath = path.join(REPORT_DIR, `vl-native-benchmark-${DATE_SLUG}.json`);
const reportMdPath = path.join(REPORT_DIR, `vl-native-benchmark-${DATE_SLUG}.md`);
await fs.writeFile(reportJsonPath, JSON.stringify(report, null, 2), 'utf-8');
await fs.writeFile(reportMdPath, buildMarkdownReport(report), 'utf-8');
report.bundle = await createBundle(report, reportJsonPath, reportMdPath);
await fs.writeFile(reportJsonPath, JSON.stringify(report, null, 2), 'utf-8');
await fs.writeFile(reportMdPath, buildMarkdownReport(report), 'utf-8');
console.log(JSON.stringify({
reportJsonPath,
reportMdPath,
zipPath: report.bundle.zipPath,
latestVlVersion,
methods: Object.fromEntries(
METHOD_ORDER.map((method) => [method, report.methods[method].summary])
),
}, null, 2));
}
main().catch((error) => {
console.error('[benchmark-vl-native-generation] failed:', error.message);
process.exitCode = 1;
});