import { mkdir, writeFile } from 'node:fs/promises'; import { basename, dirname, resolve } from 'node:path'; import type { ArchScanReport } from '../index.js'; const S_EDITION_VERSION = '1.0.0'; const LAYER_COLORS: Record = { api: '#58a6ff', service: '#4caf50', data: '#ff8c00', ui: '#b388ff', utility: '#8b949e', unknown: '#6e7681', }; const SEVERITY_COLORS: Record = { CRITICAL: '#ff4444', HIGH: '#ff8c00', MEDIUM: '#ffd700', LOW: '#4caf50', }; /** * Generates and persists architecture HTML report under `.supreme2l/reports`. */ export async function generateArchReport(projectPath: string, report: ArchScanReport): Promise { const normalizedReport: ArchScanReport = { ...report, meta: { ...report.meta, s_edition_version: S_EDITION_VERSION, }, }; const html = generateArchHtml(normalizedReport); const reportFileName = `arch-report-${formatFileTimestamp(new Date())}.html`; const outputPath = resolve(projectPath, '.supreme2l', 'reports', reportFileName); await mkdir(dirname(outputPath), { recursive: true }); await writeFile(outputPath, html, 'utf8'); return outputPath; } function generateArchHtml(report: ArchScanReport): string { const projectName = basename(report.meta.project_path) || report.meta.project_path; const vulnerableNodeIds = new Set(report.vulnerabilities.flatMap((item) => item.affected_nodes)); const vulnerableNodeList = JSON.stringify([...vulnerableNodeIds]); const normalizedNodes = report.graph.nodes.map((node) => { const uaNode = node as unknown as Record; const id = String(uaNode.id ?? 'unknown-node'); const filePath = typeof uaNode.filePath === 'string' ? uaNode.filePath : undefined; const path = typeof uaNode.path === 'string' ? uaNode.path : filePath ?? id; const layer = typeof uaNode.layer === 'string' ? uaNode.layer : inferLayerFromId(id); return { ...uaNode, id, name: typeof uaNode.name === 'string' ? uaNode.name : id, path, filePath, layer, }; }); const normalizedEdges = report.graph.edges.map((edge) => { const uaEdge = edge as unknown as Record; const source = typeof uaEdge.from === 'string' ? uaEdge.from : String(uaEdge.source ?? ''); const target = typeof uaEdge.to === 'string' ? uaEdge.to : String(uaEdge.target ?? ''); const type = typeof uaEdge.type === 'string' ? uaEdge.type : 'contains'; return { ...uaEdge, source, target, type, }; }); const graphData = JSON.stringify({ nodes: normalizedNodes, edges: normalizedEdges }); const cards = report.vulnerabilities .map((vulnerability) => renderVulnerabilityCard(vulnerability)) .join('\n'); return ` S-Edition Architecture Report

Qwen Code S-Edition Architecture Report

Project: ${escapeHtml(report.meta.project_path)}
Generated: ${escapeHtml(report.meta.timestamp)} · Version: ${escapeHtml(report.meta.s_edition_version)}
Nodes: ${String(report.meta.graph_stats.total_nodes)} · Edges: ${String(report.meta.graph_stats.total_edges)} · Fallback: ${report.meta.graph_stats.used_fallback ? 'yes' : 'no'}
Summary: ${String(report.vulnerabilities.length)} architectural vulnerabilities found in ${escapeHtml(projectName)}

Knowledge Graph

Graph visualization initialized.

Vulnerabilities (${String(report.vulnerabilities.length)} found)

${cards.length > 0 ? cards : '
No architectural vulnerabilities reported.
'}
`; } function inferLayerFromId(id: string): string { const normalized = id.toLowerCase(); if (normalized.includes('service')) return 'service'; if (normalized.includes('model') || normalized.includes('db')) return 'data'; if (normalized.includes('api') || normalized.includes('route')) return 'api'; if (normalized.includes('frontend') || normalized.includes('component')) return 'ui'; if (normalized.includes('util') || normalized.includes('lib')) return 'utility'; return 'unknown'; } function renderVulnerabilityCard(vulnerability: ArchScanReport['vulnerabilities'][number]): string { const severityClass = vulnerability.severity.toLowerCase(); const affected = vulnerability.affected_nodes.map((node) => `${escapeHtml(node)}`).join(', '); const severityColor = SEVERITY_COLORS[vulnerability.severity] ?? '#6e7681'; return `
${escapeHtml(vulnerability.severity)}
${escapeHtml(vulnerability.id)}
Category: ${escapeHtml(vulnerability.category)}
Affected nodes: ${affected}
Description: ${escapeHtml(vulnerability.description)}
Recommendation: ${escapeHtml(vulnerability.recommendation)}
`; } function escapeHtml(value: string): string { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatFileTimestamp(date: Date): string { const year = String(date.getFullYear()); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}${month}${day}-${hours}${minutes}${seconds}`; }