#!/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; });