spb/autoscholar
Public
1import { readdir, readFile } from "node:fs/promises";2import { join } from "node:path";3import { type Db, researchTemplates } from "@autoscholar/db";4import { eq } from "drizzle-orm";56const TEMPLATE_META: Record<string, { name: string; mode: string; description: string }> = {7 general: {8 name: "General research brief",9 mode: "empirical",10 description: "Discipline-agnostic starter covering every section a good research instruction needs.",11 },12 empirical: {13 name: "Empirical study",14 mode: "empirical",15 description: "Data-driven analysis with provenance, executed code, identification limits and robustness.",16 },17 theoretical: {18 name: "Theoretical result",19 mode: "theoretical",20 description: "Definitions, propositions, derivations, counterexample search and optional formal proof.",21 },22 computational: {23 name: "Computational / AI study",24 mode: "computational",25 description: "Executable benchmark with baselines, seeds, ablations and resource budgets.",26 },27 review: {28 name: "Literature review",29 mode: "review",30 description: "Systematic or narrative synthesis with search protocol, screening and verified references.",31 },32};3334/** Loads research/templates/<slug>/CLAUDE.md into the DB (idempotent; bumps version when content changes). */35export async function syncTemplates(db: Db, templatesDir: string): Promise<string[]> {36 const synced: string[] = [];37 let dirs: string[] = [];38 try {39 dirs = (await readdir(templatesDir, { withFileTypes: true }))40 .filter((d) => d.isDirectory() && TEMPLATE_META[d.name])41 .map((d) => d.name);42 } catch {43 return synced;44 }45 for (const slug of dirs) {46 let content: string;47 try {48 content = await readFile(join(templatesDir, slug, "CLAUDE.md"), "utf8");49 } catch {50 continue;51 }52 const meta = TEMPLATE_META[slug]!;53 const [existing] = await db54 .select()55 .from(researchTemplates)56 .where(eq(researchTemplates.slug, slug))57 .limit(1);58 if (!existing) {59 await db60 .insert(researchTemplates)61 .values({ slug, name: meta.name, mode: meta.mode, description: meta.description, content });62 synced.push(slug);63 } else if (existing.content !== content) {64 await db65 .update(researchTemplates)66 .set({67 content,68 version: existing.version + 1,69 updatedAt: new Date(),70 name: meta.name,71 description: meta.description,72 mode: meta.mode,73 })74 .where(eq(researchTemplates.id, existing.id));75 synced.push(slug);76 }77 }78 return synced;79}8081export async function listTemplates(db: Db) {82 return db.select().from(researchTemplates).orderBy(researchTemplates.slug);83}84