🎉 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
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/env node
/**
* Orchestrator Test Suite — V2 × Factory Fusion
*
* Tests the minimal orchestrator pipeline:
* - Empty input → error
* - Valid input → PRD + Arch + Full-stack generation
* - progress.log heartbeat entries are written
* - Task Tree + Plan structure are returned
* - Governance integration (heartbeat frequency, status flow)
*
* Run: node --test test/orchestrator.test.mjs
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { existsSync, readFileSync, rmSync, mkdirSync } from "node:fs";
import { resolve, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
const ROOT = __dirname;
const SCRIPTS = join(ROOT, "scripts");
let runOrchestrator;
// ═══════════════════════════════════════════════════════
// 1 — Empty / Invalid Input
// ═══════════════════════════════════════════════════════
describe("1 — Empty / Invalid Input", () => {
before(async () => {
const mod = await import(join(SCRIPTS, "orchestrator.mjs"));
runOrchestrator = mod.run;
});
it("空输入抛出 EMPTY_INPUT 错误", async () => {
try {
await runOrchestrator("");
assert.fail("Should have thrown");
} catch (e) {
assert.ok(e.message.includes("empty input"));
}
});
it("空格输入抛出 EMPTY_INPUT 错误", async () => {
try {
await runOrchestrator(" ");
assert.fail("Should have thrown");
} catch (e) {
assert.ok(e.message.includes("empty input"));
}
});
it("极简输入仍能生成完整项目", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-test");
const result = await runOrchestrator("做一个宠物管理 App", { output: tmpDir });
assert.ok(result.summary.projectName);
assert.ok(result.summary.outputDir);
assert.ok(result.summary.stats.totalFiles > 50);
rmSync(tmpDir, { recursive: true, force: true });
});
});
// ═══════════════════════════════════════════════════════
// 2 — Task Tree + Plan Structure
// ═══════════════════════════════════════════════════════
describe("2 — Task Tree + Plan Structure", () => {
it("返回 plan 包含 goal / subtasks / risks", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-plan-test");
const result = await runOrchestrator("做一个在线教育平台", { output: tmpDir });
assert.ok(result.plan.goal);
assert.ok(Array.isArray(result.plan.subtasks));
assert.ok(result.plan.subtasks.length === 4);
assert.ok(Array.isArray(result.plan.risks));
assert.ok(result.plan.heartbeat);
rmSync(tmpDir, { recursive: true, force: true });
});
it("返回 taskTree 包含 root + tasks", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-tasktree-test");
const result = await runOrchestrator("做一个电商小程序", { output: tmpDir });
assert.ok(result.taskTree.root);
assert.equal(result.taskTree.root.id, "T0");
assert.ok(Array.isArray(result.taskTree.tasks));
assert.ok(result.taskTree.tasks.length === 4);
assert.ok(result.taskTree.injectedRisks.length > 0);
rmSync(tmpDir, { recursive: true, force: true });
});
});
// ═══════════════════════════════════════════════════════
// 3 — progress.log Heartbeat (Governance Integration)
// ═══════════════════════════════════════════════════════
describe("3 — progress.log Heartbeat", () => {
const LOG_PATH = join(ROOT, "progress.log");
before(async () => {
// Clean up
rmSync(LOG_PATH, { force: true });
});
it("progress.log 写入 5 条心跳", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-heartbeat-test");
await runOrchestrator("做一个宠物管理 App", { output: tmpDir });
const content = readFileSync(LOG_PATH, "utf8");
const lines = content.trim().split("\n").filter(Boolean);
assert.equal(lines.length, 5);
// Parse all entries
const entries = lines.map(l => JSON.parse(l));
// First: IN_PROGRESS (NOT_STARTED phase)
assert.equal(entries[0].status, "IN_PROGRESS");
assert.deepStrictEqual(entries[0].completed, []);
// Middle: progressive completion
assert.equal(entries[1].status, "IN_PROGRESS");
assert.deepStrictEqual(entries[1].completed, ["T1"]);
assert.equal(entries[2].status, "IN_PROGRESS");
assert.deepStrictEqual(entries[2].completed, ["T1", "T2"]);
assert.equal(entries[3].status, "IN_PROGRESS");
assert.deepStrictEqual(entries[3].completed, ["T1", "T2", "T3"]);
// Final: ARCHIVED
assert.equal(entries[4].status, "ARCHIVED");
assert.deepStrictEqual(entries[4].completed, ["T1", "T2", "T3", "T4"]);
assert.deepStrictEqual(entries[4].remaining, []);
assert.equal(entries[4].next_action, "none");
rmSync(tmpDir, { recursive: true, force: true });
});
it("progress.log 每条心跳都有 ts/status/completed/remaining/next_action", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-schema-test");
await runOrchestrator("做一个在线教育平台", { output: tmpDir });
const content = readFileSync(LOG_PATH, "utf8");
const lines = content.trim().split("\n").filter(Boolean);
for (const line of lines) {
const entry = JSON.parse(line);
assert.ok(entry.ts, "Missing ts");
assert.ok(entry.status, "Missing status");
assert.ok(Array.isArray(entry.completed), "Missing completed array");
assert.ok(Array.isArray(entry.remaining), "Missing remaining array");
assert.ok("next_action" in entry, "Missing next_action");
}
rmSync(tmpDir, { recursive: true, force: true });
});
});
// ═══════════════════════════════════════════════════════
// 4 — Pipeline Integration
// ═══════════════════════════════════════════════════════
describe("4 — Pipeline Integration", () => {
it("输出目录包含 apps/web/package.json 和 apps/api/package.json", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-pipeline-test");
await runOrchestrator("做一个宠物管理 App", { output: tmpDir });
assert.ok(existsSync(join(tmpDir, "apps", "web", "package.json")), "Missing apps/web/package.json");
assert.ok(existsSync(join(tmpDir, "apps", "api", "package.json")), "Missing apps/api/package.json");
assert.ok(existsSync(join(tmpDir, "packages", "shared-types", "package.json")), "Missing shared-types/package.json");
assert.ok(existsSync(join(tmpDir, "package.json")), "Missing root package.json");
rmSync(tmpDir, { recursive: true, force: true });
});
it("多次运行不产生冲突(progress.log 追加)", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-multi-test");
const logPath = join(ROOT, "progress.log");
// Reset log for this test
rmSync(logPath, { force: true });
// Run 1
await runOrchestrator("做一个宠物管理 App", { output: join(tmpDir, "v1") });
// Run 2
await runOrchestrator("做一个在线教育平台", { output: join(tmpDir, "v2") });
const content = readFileSync(logPath, "utf8");
const lines = content.trim().split("\n").filter(Boolean);
// 2 runs × 5 heartbeats = 10
assert.equal(lines.length, 10);
rmSync(tmpDir, { recursive: true, force: true });
});
});
// ═══════════════════════════════════════════════════════
// 5 — Summary Structure
// ═══════════════════════════════════════════════════════
describe("5 — Summary Structure", () => {
it("summary 包含所有必需字段", async () => {
const tmpDir = resolve(ROOT, ".tmp-orchestrator-summary-test");
const result = await runOrchestrator("做一个宠物管理 App", { output: tmpDir });
const s = result.summary;
assert.ok(s.projectName, "Missing projectName");
assert.ok(s.chineseName, "Missing chineseName");
assert.ok(s.domain, "Missing domain");
assert.ok(s.factory, "Missing factory");
assert.ok(s.outputDir, "Missing outputDir");
assert.ok(s.stats, "Missing stats");
assert.ok(s.startTs, "Missing startTs");
assert.ok(s.endTs, "Missing endTs");
assert.ok(typeof s.durationMs === "number", "Missing durationMs");
assert.ok(s.durationMs >= 0, "durationMs should be >= 0");
rmSync(tmpDir, { recursive: true, force: true });
});
});