Files
APAW/agent-evolution/scripts/build-research-dashboard.ts
¨NW¨ 3badb259cc feat: bidirectional research dashboard + agent config fixes
- Integrate apaw_agent_model_research_v3.html as standalone dashboard
- Add model-benchmarks.json with 32 agents, 11 scored models, 11 recommendations
- Add build-research-dashboard.ts: inject live data into template → standalone HTML
- Add rebuild-template.cjs: regenerate template from v3.html source
- Add sync-benchmarks-from-yaml.cjs: sync YAML → JSON round-trip
- Add sync-model-research.ts: apply recommendation matrix to config files
- Add model-benchmarks.schema.json and model-research.schema.json for validation
- Add bidirectional-data-flow.md architecture documentation
- Add log-execution.cjs pipeline hook
- Update capability-index.yaml: add fallback_models, failover_strategy
- Update kilo-meta.json, kilo.jsonc, KILO_SPEC.md with synced models
- Update evolution.md / research.md / self-evolution.md / evolutionary-sync.md docs
- Fix security-auditor.md: quote YAML color (#DC2626)
- Fix orchestrator.md: remove duplicate devops-engineer key
- Build research-dashboard.html (106KB standalone) + dated archive
2026-04-29 21:04:22 +01:00

237 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bun
/**
* Build APAW Agent Model Research Dashboard from live data.
*
* Reads model-benchmarks.json and injects into template HTML.
* Creates standalone dashboard with embedded JSON data.
*
* Usage:
* bun run agent-evolution/scripts/build-research-dashboard.ts # build once
* bun run agent-evolution/scripts/build-research-dashboard.ts --watch # watch mode
* bun run agent-evolution/scripts/build-research-dashboard.ts --template path/to/custom.html
*/
import { existsSync, readFileSync, writeFileSync, watch } from 'fs';
import { join, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const DATA_FILE = join(__dirname, '../data/model-benchmarks.json');
const DEFAULT_TEMPLATE = join(__dirname, '../research-dashboard.template.html');
const OUTPUT_FILE = join(__dirname, '../research-dashboard.html');
const DIST_DIR = join(__dirname, '../dist');
interface BenchmarksData {
version: string;
generated: string;
source: string;
total_agents: number;
total_models_tracked: number;
providers: string[];
models: any[];
agent_model_scores: any[];
agent_current_config: any[];
groq_models: any[];
recommendations: any[];
impact_data: any[];
}
function buildDashboard(templatePath: string = DEFAULT_TEMPLATE): boolean {
console.log('🔧 Building APAW Agent Model Research Dashboard');
// Validate inputs
if (!existsSync(DATA_FILE)) {
console.error(`❌ Data file not found: ${DATA_FILE}`);
console.error(' Please run research cycle first: bun run /research models');
return false;
}
if (!existsSync(templatePath)) {
console.error(`❌ Template file not found: ${templatePath}`);
console.error(' Using default template:', DEFAULT_TEMPLATE);
if (!existsSync(DEFAULT_TEMPLATE)) {
console.error(' Default template also missing. Create template first.');
return false;
}
templatePath = DEFAULT_TEMPLATE;
}
// Read and validate JSON data
let data: BenchmarksData;
try {
const rawData = readFileSync(DATA_FILE, 'utf-8');
data = JSON.parse(rawData);
console.log(`📖 Read model-benchmarks.json (${rawData.length} bytes)`);
} catch (error) {
console.error(`❌ Failed to parse JSON data: ${error}`);
return false;
}
// Validate required fields
if (!data.models || !Array.isArray(data.models)) {
console.error('❌ Missing or invalid "models" array in data');
return false;
}
if (!data.agent_model_scores || !Array.isArray(data.agent_model_scores)) {
console.error('❌ Missing or invalid "agent_model_scores" array in data');
return false;
}
console.log(` Models: ${data.models.length}`);
console.log(` Agents: ${data.agent_model_scores.length}`);
console.log(` Providers: ${data.providers?.join(', ') || 'unknown'}`);
console.log(` Generated: ${data.generated}`);
// Read HTML template
let html: string;
try {
html = readFileSync(templatePath, 'utf-8');
console.log(`📖 Read template: ${templatePath} (${html.length} bytes)`);
} catch (error) {
console.error(`❌ Failed to read template: ${error}`);
return false;
}
// Find and replace placeholder — must match exact text in template
const placeholder = '// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\nconst EMBEDDED_DATA = {};\n';
if (!html.includes(placeholder)) {
// Try looser match with any line endings
const loosePlaceholder = html.match(/\/\/\s*BENCHMARK_DATA_PLACEHOLDER[^\n]*\r?\n\s*const\s+EMBEDDED_DATA\s*=\s*\{\}\s*;\r?\n/);
if (!loosePlaceholder) {
console.error('❌ Placeholder not found in template');
console.error(' Expected: "// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\\nconst EMBEDDED_DATA = {};\\n"');
const match = html.match(/BENCHMARK_DATA_PLACEHOLDER/);
if (match) {
const start = Math.max(0, match.index - 20);
const end = Math.min(html.length, match.index + 120);
console.error(' Found near:', JSON.stringify(html.slice(start, end)));
}
return false;
}
html = html.replace(loosePlaceholder[0], `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`);
} else {
html = html.replace(placeholder, `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`);
}
// Update title with metadata if present (match any tag with APAW... in it)
const titleRegex = /<title>[^<]*APAW[^<]*<\/title>/;
if (titleRegex.test(html)) {
const newTitle = `APAW Agent Model Research — generated ${data.generated.slice(0, 10)}`;
html = html.replace(titleRegex, `<title>${newTitle}</title>`);
}
// Update subtitle if present
const subtitlePattern = /<div class="sub">([^<]*)<\/div>/;
const newSubtitle = `<div class="sub">Live dashboard • ${data.models.length} models × ${data.agent_model_scores.length} agents • ${data.generated.slice(0, 10)}</div>`;
if (subtitlePattern.test(html)) {
html = html.replace(subtitlePattern, newSubtitle);
}
// Write output file
try {
writeFileSync(OUTPUT_FILE, html);
console.log(`✅ Output written to: ${OUTPUT_FILE} (${html.length} bytes)`);
} catch (error) {
console.error(`❌ Failed to write output: ${error}`);
return false;
}
// Create dated version in dist directory
try {
if (!existsSync(DIST_DIR)) {
require('fs').mkdirSync(DIST_DIR, { recursive: true });
}
const dateStr = data.generated.slice(0, 10).replace(/-/g, '_');
const distFile = join(DIST_DIR, `research-dashboard-${dateStr}.html`);
writeFileSync(distFile, html);
console.log(`📁 Dated copy: ${distFile}`);
} catch (error) {
console.warn(`⚠️ Could not create dated copy: ${error}`);
}
// Print summary
const recommendations = data.recommendations || [];
console.log('\n📊 Summary:');
console.log(` • Agents tracked: ${data.total_agents || data.agent_model_scores.length}`);
console.log(` • Models benchmarked: ${data.total_models_tracked || data.models.length}`);
console.log(` • Providers: ${data.providers?.join(', ')}`);
console.log(` • Recommendations: ${recommendations.length}`);
if (recommendations.length >577.0) {
const highImpact = recommendations.filter((r: any) => r.impact === 'high').length;
const applied = recommendations.filter((r: any) => r.to_model?.includes('✅')).length;
console.log(` • High-impact recommendations: ${highImpact}`);
console.log(` • Applied recommendations: ${applied}`);
}
return true;
}
function watchMode(): void {
console.log('👀 Watch mode enabled - monitoring data and template files');
console.log(' Press Ctrl+C to stop');
let timeout: Timer | null = null;
watch(DATA_FILE, (eventType) => {
if (eventType === 'change') {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
console.log('\n🔄 Data file changed, rebuilding...');
buildDashboard();
}, 500);
}
});
watch(DEFAULT_TEMPLATE, (eventType) => {
if (eventType === 'change') {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
console.log('\n🔄 Template file changed, rebuilding...');
buildDashboard();
}, 500);
}
});
}
// Parse CLI arguments
const args = process.argv.slice(2);
let watchModeEnabled = false;
let customTemplate: string | undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--watch') {
watchModeEnabled = true;
} else if (args[i] === '--template' && i + 1 < args.length) {
customTemplate = args[i + 1];
i++;
} else if (args[i] === '--help' || args[i] === '-h') {
console.log(`
Usage: bun run agent-evolution/scripts/build-research-dashboard.ts [options]
Options:
--watch Watch for changes and rebuild automatically
--template <path> Use custom HTML template file
--help, -h Show this help message
Examples:
bun run agent-evolution/scripts/build-research-dashboard.ts
bun run agent-evolution/scripts/build-research-dashboard.ts --watch
bun run agent-evolution/scripts/build-research-dashboard.ts --template custom.html
`);
process.exit(0);
}
}
// Main execution
if (watchModeEnabled) {
// Build once then watch
buildDashboard(customTemplate);
watchMode();
} else {
const success = buildDashboard(customTemplate);
process.exit(success ? 0 : 1);
}