#!/usr/bin/env tsx /** * Seed GooseFactory Database from MCP Command Center State * * Reads the current MCP pipeline state from mcpengine-repo * and converts it into GooseFactory pipeline records. * * Usage: npx tsx scripts/seed-from-factory.ts */ import { readFileSync, existsSync } from "node:fs"; import { resolve } from "node:path"; // ─── Configuration ─── const STATE_PATHS = [ resolve(import.meta.dirname ?? ".", "../../../mcpengine-repo/infra/command-center/state.json"), resolve(import.meta.dirname ?? ".", "../../mcpengine-repo/infra/command-center/state.json"), resolve(import.meta.dirname ?? ".", "../../../mcp-command-center/state.json"), ]; const DB_URL = process.env.DATABASE_URL || "postgresql://goosefactory:goosefactory_dev@localhost:5432/goosefactory"; // ─── Types for the old state format ─── interface OldState { version: number; phases: Array<{ id: number; name: string; stages: number[] }>; stages: Array<{ id: number; name: string; phase: number }>; mcps: Array<{ id: number; name: string; slug?: string; stage: number; priority?: string; status?: string; assignee?: string; tags?: string[]; metadata?: Record; startedAt?: string; completedAt?: string; stageHistory?: Array<{ stage: number; enteredAt: string; completedAt?: string }>; }>; decisions: { pending: unknown[]; history: unknown[]; }; } // ─── Stage Mapping (old stage IDs → GooseFactory stages) ─── function mapStageToFactory(stageId: number, stages: OldState["stages"]): string { const stage = stages.find((s) => s.id === stageId); if (!stage) return "intake"; const name = stage.name.toLowerCase(); // Map old phases/stages to GooseFactory pipeline stages if (name.includes("identified") || name.includes("research") || name.includes("architecture")) return "intake"; if (name.includes("scaffold") || name.includes("bootstrap") || name.includes("init")) return "scaffolding"; if (name.includes("build") || name.includes("implement") || name.includes("develop") || name.includes("code")) return "building"; if (name.includes("test") || name.includes("qa") || name.includes("validation")) return "testing"; if (name.includes("review") || name.includes("audit")) return "review"; if (name.includes("staging") || name.includes("deploy") || name.includes("pre-prod")) return "staging"; if (name.includes("production") || name.includes("live") || name.includes("release")) return "production"; if (name.includes("publish") || name.includes("marketplace") || name.includes("launched")) return "published"; // Phase-based fallback const phaseStage = stages.find((s) => s.id === stageId); if (phaseStage) { const phaseId = phaseStage.phase; if (phaseId <= 1) return "intake"; if (phaseId === 2) return "building"; if (phaseId === 3) return "testing"; if (phaseId === 4) return "review"; if (phaseId === 5) return "staging"; if (phaseId === 6) return "production"; if (phaseId >= 7) return "published"; } return "intake"; } function mapPriority(priority?: string): string { if (!priority) return "medium"; const p = priority.toLowerCase(); if (p === "critical" || p === "urgent") return "critical"; if (p === "high") return "high"; if (p === "low") return "low"; return "medium"; } function slugify(name: string): string { return name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); } // ─── Main ─── async function main() { console.log("🌱 GooseFactory Seed Script\n"); // Find state file let statePath: string | null = null; for (const p of STATE_PATHS) { if (existsSync(p)) { statePath = p; break; } } if (!statePath) { console.log("⚠️ No MCP command center state.json found at:"); STATE_PATHS.forEach((p) => console.log(` ${p}`)); console.log("\n Skipping seed. You can seed manually later."); process.exit(0); } console.log(`📄 Reading state from: ${statePath}`); const raw = readFileSync(statePath, "utf-8"); const state: OldState = JSON.parse(raw); console.log(` Found ${state.mcps.length} MCP servers`); console.log(` Found ${state.stages.length} stages across ${state.phases.length} phases\n`); // Convert MCPs to GooseFactory pipelines const pipelines = state.mcps.map((mcp) => { const factoryStage = mapStageToFactory(mcp.stage, state.stages); const slug = mcp.slug || slugify(mcp.name); // Determine status based on stage let status = "active"; if (factoryStage === "published") status = "completed"; if (mcp.status === "paused" || mcp.status === "blocked") status = "paused"; if (mcp.status === "failed") status = "failed"; return { name: mcp.name, slug, template: "mcp-server-standard", platform: slug.replace(/-mcp.*/, ""), currentStage: factoryStage, status, priority: mapPriority(mcp.priority), config: mcp.metadata || {}, metadata: { importedFrom: "mcp-command-center", originalStageId: mcp.stage, originalId: mcp.id, tags: mcp.tags || [], }, startedAt: mcp.startedAt || new Date().toISOString(), completedAt: mcp.completedAt || null, }; }); // Generate SQL inserts console.log("📝 Generated pipeline records:\n"); const sqlStatements: string[] = []; for (const p of pipelines) { const sql = `INSERT INTO pipelines (name, slug, template, platform, current_stage, status, priority, config, metadata, started_at, completed_at) VALUES ('${p.name.replace(/'/g, "''")}', '${p.slug.replace(/'/g, "''")}', '${p.template}', '${p.platform.replace(/'/g, "''")}', '${p.currentStage}', '${p.status}', '${p.priority}', '${JSON.stringify(p.config).replace(/'/g, "''")}', '${JSON.stringify(p.metadata).replace(/'/g, "''")}', '${p.startedAt}', ${p.completedAt ? `'${p.completedAt}'` : "NULL"}) ON CONFLICT (slug) DO UPDATE SET current_stage = EXCLUDED.current_stage, status = EXCLUDED.status, priority = EXCLUDED.priority, metadata = EXCLUDED.metadata, updated_at = NOW();`; sqlStatements.push(sql); console.log(` 📦 ${p.name} → ${p.currentStage} (${p.status})`); } // Write SQL file for manual import const sqlPath = resolve(import.meta.dirname ?? ".", "../infra/db/seed/seed-from-factory.sql"); const sqlDir = resolve(import.meta.dirname ?? ".", "../infra/db/seed"); const { mkdirSync, writeFileSync } = await import("node:fs"); mkdirSync(sqlDir, { recursive: true }); writeFileSync(sqlPath, `-- GooseFactory Seed Data\n-- Generated from MCP Command Center state.json\n-- Generated at: ${new Date().toISOString()}\n\n${sqlStatements.join("\n\n")}\n`); console.log(`\n✅ SQL seed file written to: ${sqlPath}`); console.log(` Total pipelines: ${pipelines.length}`); // Try to execute against database try { // Dynamic import to avoid hard dependency on postgres const { default: postgres } = await import("postgres"); const sql = postgres(DB_URL); console.log("\n🔌 Connecting to database..."); for (const stmt of sqlStatements) { await sql.unsafe(stmt); } console.log(`✅ Seeded ${pipelines.length} pipelines into database`); await sql.end(); } catch (err) { const error = err as Error; if (error.message?.includes("Cannot find module") || error.message?.includes("connect")) { console.log("\n⚠️ Could not connect to database — SQL file saved for manual import:"); console.log(` psql "$DATABASE_URL" < ${sqlPath}`); } else { console.error("\n⚠️ Database seed error:", error.message); console.log(` SQL file saved for manual import: ${sqlPath}`); } } } main().catch((err) => { console.error("Fatal error:", err); process.exit(1); });