🎉 init: 小龙的工作空间

This commit is contained in:
大海
2026-06-06 10:40:48 +08:00
commit a188ee1426
3201 changed files with 231817 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
#!/usr/bin/env node
/**
* End-to-End Generator Benchmark v1
*
* 完整链路:需求 → 前端 → 后端 → 全栈 → Electron → Release
* 10 个领域全量验证,输出 end-to-end-benchmark-v1.md
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
import { resolve, dirname, join } from "node:path";
import { execSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = dirname(resolve(fileURLToPath(import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
const BENCH_ROOT = resolve(WORKSPACE, ".benchmark");
const SCRIPTS = resolve(WORKSPACE, "scripts");
const REPORT_PATH = resolve(WORKSPACE, "end-to-end-benchmark-v1.md");
const DOMAINS = [
{ id: "petcare", input: "做一个宠物护理管理平台,宠物主人可以管理宠物档案、健康日程、日常记录和成长相册" },
{ id: "crm", input: "做一个客户关系管理系统,支持客户管理、销售漏斗、跟进记录和数据分析仪表盘" },
{ id: "inventory", input: "做一个库存管理系统,支持商品入库出库、库存盘点、供应商管理和库存预警" },
{ id: "ticket", input: "做一个工单系统,支持工单创建、分配、处理流程、优先级管理和工单归档" },
{ id: "blog-cms", input: "做一个博客内容管理系统,支持文章发布、分类标签、评论管理和媒体库" },
{ id: "project-mgmt", input: "做一个项目管理系统,支持项目看板、任务分配、甘特图和团队协作" },
{ id: "hr", input: "做一个人力资源管理系统,支持员工档案、考勤管理、招聘流程和绩效评估" },
{ id: "asset", input: "做一个固定资产管理系统,支持资产登记、领用归还、折旧计算和盘点统计" },
{ id: "course", input: "做一个在线课程管理系统,支持课程发布、章节管理、学员进度和作业批改" },
{ id: "appointment", input: "做一个预约管理系统,支持服务项目、时间段预约、客户通知和预约统计" },
];
// ═══════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════
function cmd(c, opts = {}) {
try {
return execSync(c, { cwd: WORKSPACE, encoding: "utf8", ...opts, stdio: opts.stdio ?? "pipe" });
} catch (e) {
return { error: e.message, stderr: e.stderr?.toString() || "", stdout: e.stdout?.toString() || "" };
}
}
function runAgent(script, args) {
const t0 = Date.now();
const result = cmd(`node ${join(SCRIPTS, script)} ${args}`);
const ms = Date.now() - t0;
if (result.error) {
try { return { ms, ...JSON.parse(result.stdout?.trim() || "{}") }; } catch { return { ms, error: result.error }; }
}
try { return { ms, ...JSON.parse(result.trim()) }; } catch { return { ms, raw: result }; }
}
function countFiles(dir) {
try {
const r = execSync(`find ${dir} -type f 2>/dev/null | wc -l`, { encoding: "utf8", cwd: WORKSPACE });
return parseInt(r.trim(), 10);
} catch { return 0; }
}
function loadJSON(path) {
try { return JSON.parse(readFileSync(path, "utf8")); } catch { return {}; }
}
// ═══════════════════════════════════════════════════════
// Validation
// ═══════════════════════════════════════════════════════
function validateFrontend(dir) {
if (!existsSync(dir)) return { pass: false, reason: "dir missing" };
const checks = {
packageJson: existsSync(join(dir, "package.json")),
appDir: existsSync(join(dir, "app")),
services: existsSync(join(dir, "services")) || existsSync(join(dir, "src", "services")),
types: existsSync(join(dir, "types")) || existsSync(join(dir, "src", "types")),
};
const fileCount = countFiles(dir);
const pass = checks.packageJson && checks.appDir && fileCount > 10;
return { pass, fileCount, ...checks };
}
function validateBackend(dir) {
if (!existsSync(dir)) return { pass: false, reason: "dir missing" };
const checks = {
packageJson: existsSync(join(dir, "package.json")),
routes: existsSync(join(dir, "src", "routes")),
services: existsSync(join(dir, "src", "services")),
auth: existsSync(join(dir, "src", "routes", "auth.ts")) || existsSync(join(dir, "src", "routes", "auth-route.ts")),
db: existsSync(join(dir, "src", "db")) || existsSync(join(dir, "src", "schema")),
};
const fileCount = countFiles(dir);
const pass = checks.packageJson && checks.routes && checks.services && fileCount > 10;
return { pass, fileCount, ...checks };
}
function validateFullstack(dir) {
if (!existsSync(dir)) return { pass: false, reason: "dir missing" };
const checks = {
packageJson: existsSync(join(dir, "package.json")),
web: existsSync(join(dir, "apps", "web")),
api: existsSync(join(dir, "apps", "api")),
shared: existsSync(join(dir, "packages")),
};
const fileCount = countFiles(dir);
const pass = checks.packageJson && checks.web && checks.api && fileCount > 30;
return { pass, fileCount, ...checks };
}
function validateElectron(dir) {
if (!existsSync(dir)) return { pass: false, reason: "dir missing" };
const checks = {
packageJson: existsSync(join(dir, "package.json")),
main: existsSync(join(dir, "electron", "main.ts")),
preload: existsSync(join(dir, "electron", "preload.ts")),
ipc: existsSync(join(dir, "electron", "ipc.ts")),
yml: existsSync(join(dir, "electron-builder.yml")),
};
const fileCount = countFiles(dir);
const pass = checks.packageJson && checks.main && checks.preload && fileCount >= 8;
return { pass, fileCount, ...checks };
}
function validateRelease(dir) {
if (!existsSync(dir)) return { pass: false, reason: "dir missing" };
const rDir = join(dir, "release");
const checks = {
versionJson: existsSync(join(rDir, "version.json")),
manifest: existsSync(join(rDir, "manifests", "manifest.json")),
checksums: existsSync(join(rDir, "checksums", "checksums.txt")),
releaseNotes: existsSync(join(rDir, "release-notes", "release-notes.md")),
buildInfo: existsSync(join(rDir, "build-info.json")),
windows: existsSync(join(rDir, "windows")),
macos: existsSync(join(rDir, "macos")),
linux: existsSync(join(rDir, "linux")),
};
// Validate content
if (checks.versionJson) {
try {
const v = JSON.parse(readFileSync(join(rDir, "version.json"), "utf8"));
checks.versionValid = !!(v.name && v.version && v.platforms);
} catch { checks.versionValid = false; }
}
if (checks.manifest) {
try {
const m = JSON.parse(readFileSync(join(rDir, "manifests", "manifest.json"), "utf8"));
checks.manifestValid = !!(m.project && m.files?.length > 0);
} catch { checks.manifestValid = false; }
}
if (checks.checksums) {
const content = readFileSync(join(rDir, "checksums", "checksums.txt"), "utf8");
const lines = content.trim().split("\n").filter(l => l.length > 0);
checks.checksumCount = lines.length;
checks.checksumsValid = lines.every(l => l.split(/\s+/)[0]?.length === 64);
}
if (checks.releaseNotes) {
const notes = readFileSync(join(rDir, "release-notes", "release-notes.md"), "utf8");
checks.notesValid = notes.includes("Version") && notes.includes("Installation");
}
if (checks.buildInfo) {
try {
const bi = JSON.parse(readFileSync(join(rDir, "build-info.json"), "utf8"));
checks.buildInfoValid = !!(bi.nodeVersion && bi.generatorVersion);
} catch { checks.buildInfoValid = false; }
}
// Platform file counts
for (const p of ["windows", "macos", "linux"]) {
if (checks[p]) {
try { checks[p + "Count"] = readdirSync(join(rDir, p)).length; } catch { checks[p + "Count"] = 0; }
}
}
const fileCount = countFiles(dir);
const pass = Object.values(checks).every(v => v !== false && v !== 0);
return { pass, fileCount, ...checks };
}
// ═══════════════════════════════════════════════════════
// Pipeline
// ═══════════════════════════════════════════════════════
function runPipeline(domain) {
const B = join(BENCH_ROOT, domain.id);
mkdirSync(B, { recursive: true });
const result = { id: domain.id, input: domain.input, stages: {} };
// SF-01: PRD
const s1 = runAgent("project-intake-agent.mjs", `--input "${domain.input}" --output "${join(B, "prd.json")}"`);
result.stages.prd = { ms: s1.ms, projectName: s1.projectName, domain: s1.domain, error: s1.error };
result.projectName = s1.projectName;
// SF-02: Architecture
const s2 = runAgent("architecture-agent.mjs", `--input "${join(B, "prd.json")}" --output "${join(B, "arch.json")}"`);
result.stages.arch = { ms: s2.ms, modules: s2.moduleCount, error: s2.error };
// SF-03: Frontend
const feOut = join(B, "frontend");
const s3 = runAgent("frontend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${feOut}"`);
const feVal = validateFrontend(feOut);
result.stages.frontend = { ms: s3.ms, files: s3.stats?.totalFiles || feVal.fileCount, pass: feVal.pass, error: s3.error };
// SF-04: Backend
const beOut = join(B, "backend");
const s4 = runAgent("backend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${beOut}"`);
const beVal = validateBackend(beOut);
result.stages.backend = { ms: s4.ms, files: s4.stats?.totalFiles || beVal.fileCount, pass: beVal.pass, error: s4.error };
// SF-05: Fullstack
const fsOut = join(B, "fullstack");
const s5 = runAgent("fullstack-composer-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${fsOut}"`);
const fsVal = validateFullstack(fsOut);
result.stages.fullstack = { ms: s5.ms, files: s5.stats?.totalFiles || fsVal.fileCount, pass: fsVal.pass, error: s5.error };
// SF-06: Electron
const elOut = join(B, "electron");
const s6 = runAgent("electron-builder-agent.mjs", `--input "${fsOut}" --output "${elOut}" --prd "${join(B, "prd.json")}"`);
const elVal = validateElectron(elOut);
result.stages.electron = { ms: s6.ms, files: s6.stats?.totalFiles || elVal.fileCount, pass: elVal.pass, error: s6.error };
// SF-07: Release
const rlOut = join(B, "release");
const s7 = runAgent("release-builder-agent.mjs", `--input "${fsOut}" --output "${rlOut}"`);
const rlVal = validateRelease(rlOut);
result.stages.release = { ms: s7.ms, files: s7.stats?.totalFiles || rlVal.fileCount, pass: rlVal.pass, error: s7.error };
// Totals
result.totalMs = Object.values(result.stages).reduce((s, v) => s + (v.ms || 0), 0);
result.totalFiles = Object.values(result.stages).reduce((s, v) => s + (v.files || 0), 0);
result.allPass = Object.values(result.stages).every(v => v.pass !== false);
return result;
}
// ═══════════════════════════════════════════════════════
// Report
// ═══════════════════════════════════════════════════════
function generateReport(allResults) {
const L = [];
const h = (t) => { L.push(t); return L; };
const now = new Date();
h(`# End-to-End Generator Benchmark v1`);
h(``);
h(`> ${now.toISOString()}`);
h(`> 链路: 需求 → 前端 → 后端 → 全栈 → Electron → Release`);
h(`> 领域: ${allResults.length}`);
h(``);
// ── Summary ──
const passCount = allResults.filter(r => r.allPass).length;
const totalFiles = allResults.reduce((s, r) => s + r.totalFiles, 0);
const avgMs = (allResults.reduce((s, r) => s + r.totalMs, 0) / allResults.length).toFixed(0);
h(`## Summary`);
h(``);
h(`| Metric | Value |`);
h(`|--------|-------|`);
h(`| Domains | ${allResults.length} |`);
h(`| PASS | ${passCount}/${allResults.length} |`);
h(`| FAIL | ${allResults.length - passCount}/${allResults.length} |`);
h(`| Total Files Generated | ${totalFiles} |`);
h(`| Avg Pipeline Time | ${avgMs}ms |`);
h(``);
// ── Domain Results ──
h(`## Domain Results`);
h(``);
h(`| Domain | PRD | Arch | Frontend | Backend | Fullstack | Electron | Release | Files | Time | Result |`);
h(`|--------|-----|------|----------|---------|-----------|----------|---------|-------|------|--------|`);
for (const r of allResults) {
const s = r.stages;
const p = (v) => v.pass === false ? "❌" : "✅";
const t = (v) => v.ms ? `${v.ms}ms` : "—";
h(`| ${r.id} | ${t(s.prd)} | ${t(s.arch)} | ${p(s.frontend)} ${s.frontend.files || 0}f | ${p(s.backend)} ${s.backend.files || 0}f | ${p(s.fullstack)} ${s.fullstack.files || 0}f | ${p(s.electron)} ${s.electron.files || 0}f | ${p(s.release)} ${s.release.files || 0}f | ${r.totalFiles} | ${r.totalMs}ms | **${r.allPass ? "PASS" : "FAIL"}** |`);
}
h(``);
// ── Per-Stage Stats ──
h(`## Per-Stage Statistics`);
h(``);
const stages = ["prd", "arch", "frontend", "backend", "fullstack", "electron", "release"];
const stageNames = ["SF-01 PRD", "SF-02 Arch", "SF-03 Frontend", "SF-04 Backend", "SF-05 Fullstack", "SF-06 Electron", "SF-07 Release"];
h(`| Stage | Avg Time | Min | Max | Avg Files | Pass |`);
h(`|-------|----------|-----|-----|-----------|------|`);
for (let i = 0; i < stages.length; i++) {
const key = stages[i];
const times = allResults.map(r => r.stages[key]?.ms || 0);
const files = allResults.map(r => r.stages[key]?.files || 0);
const passes = allResults.filter(r => r.stages[key]?.pass !== false).length;
const avg = (times.reduce((a, b) => a + b, 0) / times.length).toFixed(0);
const min = Math.min(...times);
const max = Math.max(...times);
const avgF = (files.reduce((a, b) => a + b, 0) / files.length).toFixed(0);
h(`| ${stageNames[i]} | ${avg}ms | ${min}ms | ${max}ms | ${avgF} | ${passes}/${allResults.length} |`);
}
h(``);
// ── File Generation ──
h(`## File Generation`);
h(``);
h(`| Domain | Frontend | Backend | Fullstack | Electron | Release | Total |`);
h(`|--------|----------|---------|-----------|----------|---------|-------|`);
for (const r of allResults) {
const s = r.stages;
h(`| ${r.id} | ${s.frontend.files || 0} | ${s.backend.files || 0} | ${s.fullstack.files || 0} | ${s.electron.files || 0} | ${s.release.files || 0} | ${r.totalFiles} |`);
}
h(``);
// ── Failure Details ──
const failures = allResults.filter(r => !r.allPass);
if (failures.length > 0) {
h(`## Failure Details`);
h(``);
for (const r of failures) {
h(`### ${r.id}`);
h(``);
for (const [stage, data] of Object.entries(r.stages)) {
if (data.pass === false) {
h(`- **${stage}:** FAIL${data.error ? " — " + data.error : ""}`);
}
}
h(``);
}
}
// ── Final Status ──
h(`## Final Status`);
h(``);
if (passCount === allResults.length) {
h(`🎉 **End-to-End Generator — PASS** (${passCount}/${allResults.length})`);
h(``);
h(`完整链路 需求 → 前端 → 后端 → 全栈 → Electron → Release 全部通过。`);
h(`系统具备跨领域泛化能力,可作为通用 Web 全栈 + 桌面应用生成器。`);
} else {
h(`⚠️ **End-to-End Generator — PARTIAL** (${passCount}/${allResults.length})`);
h(``);
h(`Failed: ${failures.map(r => r.id).join(", ")}`);
}
h(``);
h(`---`);
h(`*End-to-End Generator Benchmark v1 — ${now.toISOString()}*`);
return L.join("\n");
}
// ═══════════════════════════════════════════════════════
// Main
// ═══════════════════════════════════════════════════════
async function main() {
console.log(`\n🧪 End-to-End Generator Benchmark v1`);
console.log(` ${DOMAINS.length} domains × 7 stages = ${DOMAINS.length * 7} checks`);
console.log(` 链路: 需求 → 前端 → 后端 → 全栈 → Electron → Release\n`);
const allResults = [];
for (const domain of DOMAINS) {
console.log(`${"─".repeat(50)}`);
console.log(`🏗️ ${domain.id}`);
const result = runPipeline(domain);
allResults.push(result);
const status = result.allPass ? "✅ PASS" : "❌ FAIL";
console.log(` ${result.projectName || "?"} | ${result.totalFiles} files | ${result.totalMs}ms | ${status}`);
}
const report = generateReport(allResults);
writeFileSync(REPORT_PATH, report, "utf8");
const passCount = allResults.filter(r => r.allPass).length;
console.log(`\n${"═".repeat(50)}`);
console.log(`📊 ${passCount}/${DOMAINS.length} PASS`);
console.log(`📄 ${REPORT_PATH}`);
}
main().catch(e => {
console.error("Benchmark failed:", e);
process.exit(1);
});