🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Regression Gate — SF-08
|
||||
*
|
||||
* 执行所有验证 gate,全部通过才允许发布。
|
||||
* Exit 0 = PASS, Exit 1 = FAIL.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/regression-gate.mjs [--strict]
|
||||
*
|
||||
* Gates:
|
||||
* 1. 10 Domain E2E Benchmark
|
||||
* 2. 170 Regression Tests
|
||||
* 3. 5 Domain Semantic Spot-Check
|
||||
*
|
||||
* @module regression-gate
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(resolve(fileURLToPath(import.meta.url)));
|
||||
const WORKSPACE = resolve(__dirname, "..");
|
||||
|
||||
const STRICT = process.argv.includes("--strict");
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Gate Definitions
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
const SEMANTIC_SPOTCHECK = [
|
||||
{ name: "BookShelf", input: "做一个图书管理系统,支持书籍库、阅读状态、阅读进度、书评和统计", expectTables: ["books", "reading_status"] },
|
||||
{ name: "TeamFlow", input: "做一个团队协作工具,支持看板视图、任务管理、团队成员和活动日志", expectTables: ["boards", "tasks"] },
|
||||
{ name: "Contract", input: "做一个合同管理工具,支持合同创建、审批流程、到期提醒和合同归档", expectTables: ["contracts", "approvals"] },
|
||||
{ name: "Inspection", input: "做一个设备巡检系统,支持巡检计划、巡检记录、故障上报和设备台账", expectTables: ["equipment", "faults"] },
|
||||
{ name: "Warehouse", input: "做一个仓库出入库工具,支持入库登记、出库审批、库存盘点和库存预警", expectTables: ["stock_in", "stock_out"] },
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Helpers
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function run(cmd, label) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const out = execSync(cmd, { cwd: WORKSPACE, encoding: "utf8", stdio: "pipe", timeout: 180_000 });
|
||||
const ms = Date.now() - start;
|
||||
return { pass: true, ms, output: out };
|
||||
} catch (e) {
|
||||
const ms = Date.now() - start;
|
||||
return { pass: false, ms, output: e.stderr || e.stdout || e.message };
|
||||
}
|
||||
}
|
||||
|
||||
function gateHeader(n, label) {
|
||||
console.log(`\n${"═".repeat(60)}`);
|
||||
console.log(` GATE ${n}: ${label}`);
|
||||
console.log(`${"═".repeat(60)}`);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Gate 1: 10 Domain E2E Benchmark
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function gateBenchmark() {
|
||||
gateHeader(1, "10 Domain E2E Benchmark");
|
||||
const r = run("node scripts/e2e-benchmark-v1.mjs", "benchmark");
|
||||
const passMatch = r.output.match(/(\d+)\/(\d+) PASS/);
|
||||
const passCount = passMatch ? parseInt(passMatch[1]) : 0;
|
||||
const total = passMatch ? parseInt(passMatch[2]) : 0;
|
||||
const pass = passCount === 10 && total === 10;
|
||||
console.log(` Result: ${passCount}/${total} PASS (${r.ms}ms)`);
|
||||
console.log(` Status: ${pass ? "✅ PASS" : "❌ FAIL"}`);
|
||||
return pass;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Gate 2: 170 Regression Tests
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function gateRegression() {
|
||||
gateHeader(2, "170 Regression Tests");
|
||||
const r = run("node --test test/e2e-regression.test.mjs", "regression");
|
||||
const passMatch = r.output.match(/pass (\d+)/);
|
||||
const failMatch = r.output.match(/fail (\d+)/);
|
||||
const passCount = passMatch ? parseInt(passMatch[1]) : 0;
|
||||
const failCount = failMatch ? parseInt(failMatch[1]) : 0;
|
||||
const pass = passCount === 170 && failCount === 0;
|
||||
console.log(` Result: ${passCount} pass, ${failCount} fail (${r.ms}ms)`);
|
||||
console.log(` Status: ${pass ? "✅ PASS" : "❌ FAIL"}`);
|
||||
return pass;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Gate 3: Semantic Spot-Check
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function gateSemantic() {
|
||||
gateHeader(3, "5 Domain Semantic Spot-Check");
|
||||
let allPass = true;
|
||||
|
||||
for (const tc of SEMANTIC_SPOTCHECK) {
|
||||
const tmpDir = `/tmp/gate-${tc.name.toLowerCase()}`;
|
||||
try {
|
||||
execSync(`node scripts/project-intake-agent.mjs --input "${tc.input}" --output ${tmpDir}/prd.json`, { cwd: WORKSPACE, encoding: "utf8", stdio: "pipe" });
|
||||
execSync(`node scripts/architecture-agent.mjs --input ${tmpDir}/prd.json --output ${tmpDir}/arch.json`, { cwd: WORKSPACE, encoding: "utf8", stdio: "pipe" });
|
||||
|
||||
const arch = JSON.parse(readFileSync(`${tmpDir}/arch.json`, "utf8"));
|
||||
const tables = arch.databaseSchema.map(t => t.table);
|
||||
const hasChinese = tables.some(t => /[\u4e00-\u9fff]/.test(t));
|
||||
const hasExpected = tc.expectTables.every(et => tables.includes(et));
|
||||
const hasDomainFields = arch.databaseSchema.filter(t => t.table !== "users").some(t => {
|
||||
const fields = t.fields.filter(f => !["id", "user_id", "created_at", "updated_at"].includes(f.name));
|
||||
return fields.some(f => !["title", "description", "status", "data"].includes(f.name));
|
||||
});
|
||||
|
||||
const pass = !hasChinese && hasExpected && hasDomainFields;
|
||||
console.log(` ${pass ? "✅" : "❌"} ${tc.name}: tables=[${tables.join(", ")}]${hasChinese ? " [CHINESE!]" : ""}${!hasExpected ? " [MISSING TABLES!]" : ""}${!hasDomainFields ? " [GENERIC FIELDS!]" : ""}`);
|
||||
if (!pass) allPass = false;
|
||||
} catch (e) {
|
||||
console.log(` ❌ ${tc.name}: ${e.message.split("\n")[0]}`);
|
||||
allPass = false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n Status: ${allPass ? "✅ PASS" : "❌ FAIL"}`);
|
||||
return allPass;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Main
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
async function main() {
|
||||
console.log("🔒 Regression Gate — v1.1.0-semantic");
|
||||
console.log(` Mode: ${STRICT ? "STRICT" : "NORMAL"}`);
|
||||
console.log(` Time: ${new Date().toISOString()}`);
|
||||
|
||||
const results = {
|
||||
benchmark: gateBenchmark(),
|
||||
regression: gateRegression(),
|
||||
semantic: gateSemantic(),
|
||||
};
|
||||
|
||||
console.log(`\n${"═".repeat(60)}`);
|
||||
console.log(" SUMMARY");
|
||||
console.log(`${"═".repeat(60)}`);
|
||||
for (const [name, pass] of Object.entries(results)) {
|
||||
console.log(` ${pass ? "✅" : "❌"} ${name}`);
|
||||
}
|
||||
|
||||
const allPass = Object.values(results).every(Boolean);
|
||||
console.log(`\n ${allPass ? "🟢 ALL GATES PASS — Release approved" : "🔴 GATE FAILED — Release blocked"}`);
|
||||
console.log("");
|
||||
|
||||
process.exit(allPass ? 0 : 1);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error("Gate runner crashed:", e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user