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:
Vectry
2026-02-09 15:22:50 +00:00
parent efdc282da5
commit 79dad6124f
72 changed files with 10132 additions and 136 deletions

View File

@@ -0,0 +1,49 @@
import type { ModuleNode, DependencyEdge } from "@codeboard/shared";
function sanitizeId(name: string): string {
return name.replace(/[^a-zA-Z0-9]/g, "_");
}
function truncateLabel(name: string, max = 20): string {
return name.length > max ? name.slice(0, max - 1) + "\u2026" : name;
}
export function generateArchitectureDiagram(
modules: ModuleNode[],
deps: DependencyEdge[]
): string {
if (modules.length === 0) {
return "flowchart TD\n empty[No modules detected]";
}
const lines: string[] = ["flowchart TD"];
const moduleIds = new Map<string, string>();
for (const mod of modules) {
const id = sanitizeId(mod.name);
moduleIds.set(mod.path, id);
const fileCount = mod.files.length;
lines.push(` ${id}["${truncateLabel(mod.name)}\\n${fileCount} files"]`);
}
const edgeSet = new Set<string>();
for (const dep of deps) {
const sourceModule = modules.find((m) => m.files.includes(dep.source));
const targetModule = modules.find((m) => m.files.includes(dep.target));
if (!sourceModule || !targetModule) continue;
if (sourceModule.path === targetModule.path) continue;
const sourceId = moduleIds.get(sourceModule.path);
const targetId = moduleIds.get(targetModule.path);
if (!sourceId || !targetId) continue;
const edgeKey = `${sourceId}-${targetId}`;
if (edgeSet.has(edgeKey)) continue;
edgeSet.add(edgeKey);
lines.push(` ${sourceId} --> ${targetId}`);
}
return lines.join("\n");
}

View File

@@ -0,0 +1,48 @@
import type { FileNode, DependencyEdge } from "@codeboard/shared";
function sanitizeId(path: string): string {
return path.replace(/[^a-zA-Z0-9]/g, "_");
}
function shortenPath(path: string): string {
const parts = path.split("/");
if (parts.length <= 2) return path;
return parts.slice(-2).join("/");
}
export function generateDependencyGraph(
files: FileNode[],
deps: DependencyEdge[]
): string {
if (files.length === 0) {
return "graph LR\n empty[No files detected]";
}
const maxFiles = 30;
const topFiles = files.slice(0, maxFiles);
const topPaths = new Set(topFiles.map((f) => f.path));
const lines: string[] = ["graph LR"];
for (const file of topFiles) {
const id = sanitizeId(file.path);
const label = shortenPath(file.path);
lines.push(` ${id}["${label}"]`);
}
const edgeSet = new Set<string>();
for (const dep of deps) {
if (!topPaths.has(dep.source) || !topPaths.has(dep.target)) continue;
if (dep.source === dep.target) continue;
const edgeKey = `${dep.source}-${dep.target}`;
if (edgeSet.has(edgeKey)) continue;
edgeSet.add(edgeKey);
const sourceId = sanitizeId(dep.source);
const targetId = sanitizeId(dep.target);
lines.push(` ${sourceId} --> ${targetId}`);
}
return lines.join("\n");
}

View File

@@ -0,0 +1,2 @@
export { generateArchitectureDiagram } from "./architecture.js";
export { generateDependencyGraph } from "./dependency.js";