clawdbot-workspace/goosefactory/scripts/seed-from-factory.ts

219 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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<string, unknown>;
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);
});