import { PrismaClient, type Prisma } from "@prisma/client"; import { seedMethodFacets } from "./seed/methodFacets"; import { seedTemplateFacets } from "./seed/templateFacets"; /** * Curated rule templates for GET /api/templates. * Home “Popular templates” list uses `lib/templates/governanceTemplateCatalog.ts` (Figma 21764-16435); * DB titles/descriptions should stay aligned with `governanceTemplateCatalog.ts`. * `body.sections` use the same category row labels as the final-review Rule * (Values, Communication, Membership, Decision-making, Conflict management) so * template review matches that layout; `entries[].title` = chip labels, `body` = supporting text. * Chip titles per template are sourced from the product **Template Composition** workbook (xlsx column * layout: Decision-making, Membership Policies, Values, Communication, Conflict Management), mapped in * `COMPOSITION_BY_SLUG` below. */ /** Starter `body` for catalog templates — five category rows match template review / final-review layout. */ function governancePatternBody(coreValues: string): Prisma.InputJsonValue { return { sections: [ { categoryName: "Values", entries: [{ title: "Core stance", body: coreValues }], }, { categoryName: "Communication", entries: [ { title: "Transparency", body: "Updates and decisions are shared through agreed channels so members stay aligned.", }, ], }, { categoryName: "Membership", entries: [ { title: "Participation", body: "Roles and eligibility are explicit so people know how to take part.", }, ], }, { categoryName: "Decision-making", entries: [ { title: "Process", body: "Steps are documented so legitimacy stays high as you scale.", }, ], }, { categoryName: "Conflict management", entries: [ { title: "Resolution", body: "Disputes use fair, documented paths before they harden into splits.", }, ], }, ], }; } /** Chip titles from Template Composition.xlsx; bodies stay empty — presets hydrate at publish / display. */ function entriesFromCompositionCell(cell: string): { title: string; body: string }[] { const trimmed = cell.trim(); if (!trimmed) return []; return trimmed .split(/,\s*/) .map((title) => title.trim()) .filter(Boolean) .map((title) => ({ title, body: "" })); } function bodyFromXlsxComposition(row: { decisionMaking: string; membership: string; values: string; communication: string; conflict: string; }): Prisma.InputJsonValue { return { sections: [ { categoryName: "Values", entries: entriesFromCompositionCell(row.values) }, { categoryName: "Communication", entries: entriesFromCompositionCell(row.communication), }, { categoryName: "Membership", entries: entriesFromCompositionCell(row.membership), }, { categoryName: "Decision-making", entries: entriesFromCompositionCell(row.decisionMaking), }, { categoryName: "Conflict management", entries: entriesFromCompositionCell(row.conflict), }, ], }; } /** * Curated template chip rows. Chip titles are card labels from * `messages/en/create/customRule/*.json` (October 2026 deck remake), so * `methodSlugFromTitle` resolves each one to a live `methods[].id`. * Values match `coreValues.json` labels (case-insensitive). */ const COMPOSITION_BY_SLUG: Record< string, { decisionMaking: string; membership: string; values: string; communication: string; conflict: string; } > = { consensus: { decisionMaking: "Decision speeds, Consensus decision-making, Modified consensus", membership: "Members approve new members, Vouching", values: "Consensus, Community care, Horizontalism", communication: "How we run meetings, In-person meetings, Loomio", conflict: "Conflict ladder, Mediation", }, "consensus-clusters": { decisionMaking: "Consent (sociocracy), Delegated decision-making", membership: "Join by contributing, Orientation", values: "Decentralization, Adaptability, Autonomy", communication: "How we run meetings, Shared records, Slack", conflict: "Conflict ladder, Circle processes, Restorative practices", }, "solidarity-network": { decisionMaking: "Do-ocracy, Modified consensus, Shared money", membership: "Open admission, Vouching, Member groups", values: "Solidarity, Mutual aid, Anti-oppression", communication: "Signal, Matrix / Element", conflict: "Conflict ladder, Restorative practices", }, "sortition-jury": { decisionMaking: "Panel drawn by lot, Rotating roles", membership: "Open admission, Roster upkeep", values: "Fairness, Equity, Transparency", communication: "In-person meetings, Video meetings, Shared records", conflict: "Conflict ladder, Hearing panel", }, "liquid-democracy": { decisionMaking: "Liquid democracy, Continuous voting", membership: "Open admission, One person one account", values: "Agency, Flexibility, Transparency", communication: "Loomio, Discourse (forum)", conflict: "Conflict ladder, Non-binding arbitration", }, "do-ocracy": { decisionMaking: "Do-ocracy, Lazy consensus, Two-person sign-off", membership: "Join by contributing, Rest and workload limits", values: "Agency, Autonomy, Voluntarism", communication: "GitHub / GitLab, Discord", conflict: "Conflict ladder, Mediation", }, "quadratic-governance": { decisionMaking: "Quadratic voting, Two-person sign-off", membership: "One person one account, Member share or dues", values: "Fairness, Innovation, Independence", communication: "Discourse (forum), Discord", conflict: "Conflict ladder, Conflict working group", }, "federated-clusters": { decisionMaking: "Consensus seeking with delegates", membership: "Member groups, Membership agreement", values: "Interoperability, Localism, Interdependence", communication: "Matrix / Element, Discourse (forum)", conflict: "Conflict ladder, Hearing panel, Non-binding arbitration", }, devolution: { decisionMaking: "One person decides, Delegated decision-making", membership: "Starting with one person, Open admission", values: "Capacity building, Education, Maintenance", communication: "Discord, GitHub / GitLab", conflict: "Conflict ladder, Conflict workshops, Managerial decision", }, "benevolent-dictator": { decisionMaking: "One person decides, Hierarchical decision-making", membership: "Join by contributing, Contributors and maintainers", values: "Reliability, Stewardship, Leadership", communication: "Email distribution list, GitHub / GitLab", conflict: "Conflict ladder, Managerial decision", }, petition: { decisionMaking: "Petition and referendum, Majority rule, Ranked choice voting", membership: "Open admission, One person one account", values: "Freedom of information, Accountability, Participation", communication: "Loomio, Discourse (forum)", conflict: "Conflict ladder, Non-binding arbitration, Legal action", }, "self-appointed-board": { decisionMaking: "Appointed board, Advisory committees, Coordinating committee", membership: "Application and review, Paid staff", values: "Stewardship, Resilience, Reliability", communication: "Video meetings, Email distribution list", conflict: "Conflict ladder, Harassment and discrimination complaints, Hearing panel", }, "elected-board": { decisionMaking: "Elected board of directors, General meeting, Majority rule", membership: "Application and review, Membership agreement", values: "Accountability, Transparency, Trust", communication: "Email distribution list, Slack", conflict: "Conflict ladder, Mediation, Conflict working group", }, }; function bodyFromSlugComposition(slug: string): Prisma.InputJsonValue { const row = COMPOSITION_BY_SLUG[slug]; if (!row) { return governancePatternBody("Template composition pending."); } return bodyFromXlsxComposition(row); } const TEMPLATES: { slug: string; title: string; category: string; description: string; sortOrder: number; featured: boolean; body: Prisma.InputJsonValue; }[] = [ { slug: "consensus-clusters", title: "Circles", category: "Governance pattern", description: "Units called Circles have the ability to decide and act on matters in their domains, which their members agree on through a Council.", sortOrder: 0, featured: false, body: bodyFromSlugComposition("consensus-clusters"), }, { slug: "consensus", title: "Consensus", category: "Governance pattern", description: "Important decisions require unanimous agreement. Proposals pass only if no serious objections remain.", sortOrder: 1, featured: true, body: bodyFromSlugComposition("consensus"), }, { slug: "elected-board", title: "Elected Board", category: "Governance pattern", description: "An elected board determines policies and organizes their implementation.", sortOrder: 2, featured: false, body: bodyFromSlugComposition("elected-board"), }, { slug: "petition", title: "Petition", category: "Governance pattern", description: "Any participant can propose a rule change. If enough sign it, it goes to a general vote.", sortOrder: 3, featured: false, body: bodyFromSlugComposition("petition"), }, { slug: "solidarity-network", title: "Solidarity Network", category: "Governance pattern", description: 'Power is held by autonomous "cells." A central hub acts as a switchboard for resources but cannot dictate cell activities.', sortOrder: 4, featured: true, body: bodyFromSlugComposition("solidarity-network"), }, { slug: "sortition-jury", title: "Sortition (Jury)", category: "Governance pattern", description: "A representative sample of the community is chosen by lottery to form a temporary council.", sortOrder: 5, featured: false, body: bodyFromSlugComposition("sortition-jury"), }, { slug: "liquid-democracy", title: "Liquid Democracy", category: "Governance pattern", description: "Members can vote directly or delegate their vote to a trusted peer on a per-topic basis.", sortOrder: 6, featured: false, body: bodyFromSlugComposition("liquid-democracy"), }, { slug: "do-ocracy", title: "Do-ocracy", category: "Governance pattern", description: "Authority is granted to those doing the work. If you do the task, you decide how it gets done.", sortOrder: 7, featured: true, body: bodyFromSlugComposition("do-ocracy"), }, { slug: "quadratic-governance", title: "Quadratic Governance", category: "Governance pattern", description: "Voting cost is squared (V²), preventing a majority from steamrolling a passionate minority.", sortOrder: 8, featured: false, body: bodyFromSlugComposition("quadratic-governance"), }, { slug: "federated-clusters", title: "Federated Clusters", category: "Governance pattern", description: "Independent groups share a central brand/charter but have total autonomy over internal rules.", sortOrder: 9, featured: false, body: bodyFromSlugComposition("federated-clusters"), }, { slug: "devolution", title: "Devolution", category: "Governance pattern", description: "Starts as a Dictatorship for speed, moving to a Board, and finally to full community ownership.", sortOrder: 10, featured: true, body: bodyFromSlugComposition("devolution"), }, { slug: "benevolent-dictator", title: "Benevolent Dictator", category: "Governance pattern", description: "A single individual holds ultimate power, usually intended as a temporary state until the project is stable.", sortOrder: 11, featured: false, body: bodyFromSlugComposition("benevolent-dictator"), }, { slug: "self-appointed-board", title: "Self-Appointed Board", category: "Governance pattern", description: "An existing board selects its own successors to preserve a specific mission over time.", sortOrder: 12, featured: false, body: bodyFromSlugComposition("self-appointed-board"), }, ]; const prisma = new PrismaClient(); async function main() { for (const row of TEMPLATES) { const { slug, title, category, description, sortOrder, featured, body } = row; await prisma.ruleTemplate.upsert({ where: { slug }, create: { slug, title, category, description, sortOrder, featured, body, }, update: { title, category, description, sortOrder, featured, body, }, }); } const facetSeed = await seedMethodFacets(prisma); // eslint-disable-next-line no-console -- seed CLI feedback console.log( `Seeded MethodFacet rows: ${Object.entries(facetSeed.rowsBySection) .map(([section, count]) => `${section}=${count}`) .join(", ")}`, ); const templateFacetSeed = await seedTemplateFacets(prisma); // eslint-disable-next-line no-console -- seed CLI feedback console.log( `Seeded TemplateFacet rows: ${templateFacetSeed.rowCount}`, ); } main() .then(() => { // eslint-disable-next-line no-console -- seed CLI feedback console.log(`Seeded ${TEMPLATES.length} rule template(s).`); }) .catch((e) => { console.error(e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });