🎉 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
+263
View File
@@ -0,0 +1,263 @@
#!/usr/bin/env node
/**
* Orchestrator — V2 Protocol Stack → Factory Pipeline Bridge
*
* Minimal orchestrator that:
* 1. Accepts user input (natural language)
* 2. Generates a Task Tree + Plan record (AGENTS.md §12)
* 3. Routes to the correct factory chain via Factory Router
* 4. Captures Governance-compliant heartbeat into progress.log
* 5. Returns structured result summary
*
* Does NOT:
* - Re-implement AGENTS.md / GOVERNANCE.md rules
* - Run state machines
* - Replace existing CLI entry points
*
* Usage:
* node scripts/orchestrator.mjs --input "做一个宠物管理 App" [--output <dir>]
* node scripts/orchestrator.mjs --input "做一个在线教育平台" --domain education
*
* @module orchestrator
*/
import { writeFileSync, appendFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, "..");
// ── Constants ──────────────────────────────────────
const DEFAULT_OUTPUT = resolve(ROOT, "fullstack");
const PROGRESS_LOG_PATH = resolve(ROOT, "progress.log");
// ── CLI ────────────────────────────────────────────
function parseArgs() {
const args = process.argv.slice(2);
const opts = { input: null, output: null, domain: null, verbose: false, help: false };
for (let i = 0; i < args.length; i++) {
if (args[i] === "--input" && args[i + 1]) opts.input = args[++i];
else if (args[i] === "--output" && args[i + 1]) opts.output = args[++i];
else if (args[i] === "--domain" && args[i + 1]) opts.domain = args[++i];
else if (args[i] === "--verbose") opts.verbose = true;
else if (args[i] === "--help" || args[i] === "-h") opts.help = true;
}
return opts;
}
async function main() {
const opts = parseArgs();
if (opts.help) {
console.log(`
Orchestrator — V2 Protocol Stack ↔ Factory Pipeline
Usage:
node scripts/orchestrator.mjs --input <text> [options]
Options:
--input <text> 用户需求描述(必填)
--output <dir> 输出目录(default: fullstack/
--domain <name> 强制领域(override auto-detect
--verbose 详细输出
--help 显示帮助
Examples:
node scripts/orchestrator.mjs --input "做一个宠物管理 App"
node scripts/orchestrator.mjs --input "做一个在线教育平台" --output ./my-project
`);
return;
}
if (!opts.input) {
console.error("Error: --input is required. Use --help for usage.");
process.exit(1);
}
try {
const result = await run(opts.input, opts);
console.log(JSON.stringify(result.summary, null, 2));
} catch (e) {
console.error(`Orchestrator failed: ${e.message}`);
// Write failure heartbeat
writeHeartbeat({ status: "FAILED", remaining: [], risk: e.message, next_action: "investigate failure" });
process.exit(1);
}
}
// ── Main Pipeline ─────────────────────────────────
/**
* Orchestrator run — the unified entry point.
*
* @param {string} input — User's requirement description
* @param {object} opts
* @param {string} [opts.output] — Output directory
* @param {string} [opts.domain] — Force domain override
* @returns {Promise<{summary: object, plan: object, taskTree: object, heartbeat: object}>}
*/
export async function run(input, opts = {}) {
const startTs = new Date().toISOString();
const plan = buildPlan(input);
// ── 1. Context Init & Heartbeat ──
writeHeartbeat(plan.heartbeat);
// ── 2. V2 Task Tree ──
const taskTree = buildTaskTree(input, opts.domain);
// ── 3. SF-01: Project Intake (PRD generation) ──
const sf01 = await import("./project-intake-agent.mjs");
writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1"], remaining: plan.heartbeat.remaining.slice(1), next_action: "SF-02: generate architecture" });
const prd = sf01.generatePRD(input);
if (prd.error === "EMPTY_INPUT") {
throw new Error("PRD generation failed: empty input");
}
if (prd.error === "EXTRACTION_FAILED") {
throw new Error(prd.message);
}
// ── 4. SF-02: Architecture generation ──
const sf02 = await import("./architecture-agent.mjs");
writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1", "T2"], remaining: plan.heartbeat.remaining.slice(2), next_action: "SF-03~06: fullstack compose" });
const arch = sf02.generateArchitecture(prd);
if (arch.error) {
throw new Error(`Architecture generation failed: ${arch.message}`);
}
// ── 5. Factory Router: route to fullstack composer ──
const router = await import("./factory-router.mjs");
const routeResult = router.route(prd, { arch });
const factoryMod = await router.loadFactory(routeResult.factoryFn);
writeHeartbeat({ ...plan.heartbeat, status: "IN_PROGRESS", completed: ["T1", "T2", "T3"], remaining: plan.heartbeat.remaining.slice(3), next_action: "writing output" });
const result = await factoryMod.composeFullstack(prd, arch);
// ── 6. Write output ──
const outputDir = opts.output ? resolve(opts.output) : DEFAULT_OUTPUT;
factoryMod.writeFullstack(result, outputDir);
// ── 7. Governance: final heartbeat ──
writeHeartbeat({ ...plan.heartbeat, status: "ARCHIVED", completed: ["T1", "T2", "T3", "T4"], remaining: [], next_action: "none" });
const endTs = new Date().toISOString();
const durationMs = Date.now() - new Date(startTs).getTime();
// ── 8. Summary ──
const summary = {
projectName: prd.projectName,
chineseName: prd.chineseName || prd.projectName,
domain: prd.domain,
factory: routeResult.factory,
outputDir,
stats: result.stats,
startTs,
endTs,
durationMs,
};
return { summary, plan, taskTree, heartbeat: plan.heartbeat };
}
// ── V2 Protocol Artifacts ──────────────────────────
/**
* Build Task Tree per AGENTS.md §1.
*
* @param {string} input
* @param {string} [domainOverride]
* @returns {object} Task Tree
*/
function buildTaskTree(input, domainOverride) {
return {
root: {
id: "T0",
name: "Full-stack Product Generation",
depends_on: null,
description: `Generate full-stack product from: "${input.slice(0, 80)}"`,
},
tasks: [
{ id: "T1", name: "SF-01 Product Strategy", depends_on: ["T0"], description: "Parse requirements → PRD", parallelizable: false },
{ id: "T2", name: "SF-02 Architecture Design", depends_on: ["T1"], description: "PRD → Architecture JSON", parallelizable: false },
{ id: "T3", name: "SF-03~06 Full-stack Generation", depends_on: ["T2"], description: "Arch → NestJS + React code", parallelizable: false },
{ id: "T4", name: "Governance Verification", depends_on: ["T3"], description: "Verify output, write audit", parallelizable: false },
],
parallelGroups: [],
injectedRisks: [
"Domain match may fallback to generic → verify PRD.domain",
"EXTRACTION_FAILED if input too vague → need detailed input",
],
};
}
/**
* Build Plan per AGENTS.md §2.
*
* @param {string} input
* @returns {object} Plan + initial heartbeat
*/
function buildPlan(input) {
const subtasks = [
"T1: SF-01 Product Strategy — parse input, match domain, generate PRD",
"T2: SF-02 Architecture — generate arch.json from PRD",
"T3: SF-03~06 Full-stack — compose NestJS + React project",
"T4: Write output files and finalize",
];
const heartbeat = {
ts: new Date().toISOString(),
status: "IN_PROGRESS",
completed: [],
remaining: ["T1", "T2", "T3", "T4"],
risk: "",
next_action: "SF-01: generate PRD",
};
return {
goal: `Generate a full-stack web application from: "${input.slice(0, 100)}"`,
subtasks,
risks: [
"Input may be too vague → EXTRACTION_FAILED",
"Domain template may not fully match → generic fallback",
],
verificationStrategy: {
build: true,
tests: true,
lint: false,
},
heartbeat,
};
}
// ── Governance Logging ─────────────────────────────
/**
* Append a heartbeat entry to progress.log.
*
* Format: JSON Lines (GOVERNANCE.md §2.4)
*
* @param {object} entry
*/
function writeHeartbeat(entry) {
try {
const d = dirname(PROGRESS_LOG_PATH);
mkdirSync(d, { recursive: true });
appendFileSync(PROGRESS_LOG_PATH, JSON.stringify(entry) + "\n");
} catch (e) {
console.error(`[orchestrator] heartbeat write failed: ${e.message}`);
}
}
// ── CLI Bootstrap ─────────────────────────────────
if (process.argv[1] && (import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith(process.argv[1]))) {
main();
}