feat: initial CodeBoard monorepo scaffold
Turborepo monorepo with npm workspaces: - apps/web: Next.js 14 frontend with Tailwind v4, SSE progress, doc viewer - apps/worker: BullMQ job processor (clone → parse → LLM generate) - packages/shared: TypeScript types - packages/parser: Babel-based AST parser (JS/TS) + regex (Python) - packages/llm: OpenAI/Anthropic provider abstraction + prompt pipeline - packages/diagrams: Mermaid architecture & dependency graph generators - packages/database: Prisma schema (PostgreSQL) - Docker multi-stage build (web + worker targets) All packages compile successfully with tsc and next build.
This commit is contained in:
153
packages/llm/src/pipeline.ts
Normal file
153
packages/llm/src/pipeline.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import type { CodeStructure, GeneratedDocs, FileNode } from "@codeboard/shared";
|
||||
import type { LLMProvider } from "./providers/base.js";
|
||||
import { buildArchitecturePrompt } from "./prompts/architecture-overview.js";
|
||||
import { buildModuleSummaryPrompt } from "./prompts/module-summary.js";
|
||||
import { buildPatternsPrompt } from "./prompts/patterns-detection.js";
|
||||
import { buildGettingStartedPrompt } from "./prompts/getting-started.js";
|
||||
|
||||
function parseSection(text: string, header: string): string {
|
||||
const regex = new RegExp(`## ${header}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
||||
const match = regex.exec(text);
|
||||
return match?.[1]?.trim() ?? "";
|
||||
}
|
||||
|
||||
function parseMermaid(text: string): string {
|
||||
const match = /```mermaid\s*\n([\s\S]*?)```/.exec(text);
|
||||
return match?.[1]?.trim() ?? "flowchart TD\n A[No diagram generated]";
|
||||
}
|
||||
|
||||
function parseList(text: string): string[] {
|
||||
return text
|
||||
.split("\n")
|
||||
.map((l) => l.replace(/^[-*]\s*/, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export async function generateDocumentation(
|
||||
codeStructure: CodeStructure,
|
||||
provider: LLMProvider,
|
||||
onProgress?: (stage: string, progress: number) => void
|
||||
): Promise<GeneratedDocs> {
|
||||
onProgress?.("architecture", 10);
|
||||
|
||||
const archMessages = buildArchitecturePrompt(codeStructure);
|
||||
const archResponse = await provider.chat(archMessages);
|
||||
|
||||
const architectureOverview = parseSection(archResponse, "Architecture Overview");
|
||||
const techStackRaw = parseSection(archResponse, "Tech Stack");
|
||||
const architectureDiagram = parseMermaid(archResponse);
|
||||
const techStack = techStackRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
||||
|
||||
onProgress?.("modules", 30);
|
||||
|
||||
const moduleLimit = Math.min(codeStructure.modules.length, 10);
|
||||
const moduleSummaries = await Promise.all(
|
||||
codeStructure.modules.slice(0, moduleLimit).map(async (mod) => {
|
||||
const moduleFiles: FileNode[] = codeStructure.files.filter((f) =>
|
||||
mod.files.includes(f.path)
|
||||
);
|
||||
|
||||
if (moduleFiles.length === 0) {
|
||||
return {
|
||||
name: mod.name,
|
||||
path: mod.path,
|
||||
summary: "Empty module — no parseable files found.",
|
||||
keyFiles: [],
|
||||
publicApi: [],
|
||||
dependsOn: [],
|
||||
dependedBy: [],
|
||||
};
|
||||
}
|
||||
|
||||
const messages = buildModuleSummaryPrompt(mod, moduleFiles);
|
||||
const response = await provider.chat(messages, { model: undefined });
|
||||
|
||||
const summary = parseSection(response, "Summary");
|
||||
const keyFilesRaw = parseList(parseSection(response, "Key Files"));
|
||||
const publicApi = parseList(parseSection(response, "Public API"));
|
||||
|
||||
const dependsOn = [
|
||||
...new Set(
|
||||
moduleFiles.flatMap((f) =>
|
||||
f.imports
|
||||
.map((imp) => imp.source)
|
||||
.filter((s) => !s.startsWith("."))
|
||||
)
|
||||
),
|
||||
].slice(0, 10);
|
||||
|
||||
const dependedBy = codeStructure.dependencies
|
||||
.filter((d) => mod.files.includes(d.target))
|
||||
.map((d) => d.source)
|
||||
.filter((s) => !mod.files.includes(s))
|
||||
.slice(0, 10);
|
||||
|
||||
return {
|
||||
name: mod.name,
|
||||
path: mod.path,
|
||||
summary: summary || "Module analyzed but no summary generated.",
|
||||
keyFiles: keyFilesRaw.map((kf) => ({ path: kf, purpose: "" })),
|
||||
publicApi,
|
||||
dependsOn,
|
||||
dependedBy,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
onProgress?.("patterns", 60);
|
||||
|
||||
const patternsMessages = buildPatternsPrompt(codeStructure);
|
||||
const patternsResponse = await provider.chat(patternsMessages);
|
||||
|
||||
const conventions = parseList(parseSection(patternsResponse, "Coding Conventions"));
|
||||
const designPatterns = parseList(parseSection(patternsResponse, "Design Patterns"));
|
||||
const architecturalDecisions = parseList(parseSection(patternsResponse, "Architectural Decisions"));
|
||||
|
||||
onProgress?.("getting-started", 80);
|
||||
|
||||
const gsMessages = buildGettingStartedPrompt(
|
||||
codeStructure,
|
||||
architectureOverview
|
||||
);
|
||||
const gsResponse = await provider.chat(gsMessages);
|
||||
|
||||
const prerequisites = parseList(parseSection(gsResponse, "Prerequisites"));
|
||||
const setupSteps = parseList(parseSection(gsResponse, "Setup Steps"));
|
||||
const firstTask = parseSection(gsResponse, "Your First Task");
|
||||
|
||||
onProgress?.("complete", 100);
|
||||
|
||||
const languages = [...new Set(codeStructure.files.map((f) => f.language))];
|
||||
|
||||
return {
|
||||
id: "",
|
||||
repoUrl: "",
|
||||
repoName: "",
|
||||
generatedAt: new Date().toISOString(),
|
||||
sections: {
|
||||
overview: {
|
||||
title: "Architecture Overview",
|
||||
description: architectureOverview,
|
||||
architectureDiagram,
|
||||
techStack,
|
||||
keyMetrics: {
|
||||
files: codeStructure.files.length,
|
||||
modules: codeStructure.modules.length,
|
||||
languages,
|
||||
},
|
||||
},
|
||||
modules: moduleSummaries,
|
||||
patterns: {
|
||||
conventions,
|
||||
designPatterns,
|
||||
architecturalDecisions,
|
||||
},
|
||||
gettingStarted: {
|
||||
prerequisites,
|
||||
setupSteps,
|
||||
firstTask,
|
||||
},
|
||||
dependencyGraph: architectureDiagram,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user