import type { ExtensionAPI } from "@jaeswift/jae-coding-agent"; import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; export default function (pi: ExtensionAPI) { pi.on("before_agent_start", async (_event, ctx) => { const dnaPath = join(ctx.cwd, ".jae", "project-dna.md"); if (existsSync(dnaPath)) { const dna = readFileSync(dnaPath, "utf-8").trim(); if (dna.length > 0) { return { systemPrompt: `\n\n# Project DNA (auto-generated fingerprint)\n\n${dna}` }; } } }); pi.on("session_start", async (_event, ctx) => { const dnaPath = join(ctx.cwd, ".jae", "project-dna.md"); if (!existsSync(dnaPath)) { ctx.ui.notify("\u{1F9EC} No project DNA found. Run /dna to auto-analyze your project.", "info"); } }); pi.registerCommand("dna", { description: "Auto-analyze project and generate .jae/project-dna.md fingerprint", handler: async (args, ctx) => { const cwd = ctx.cwd; const jaeDir = join(cwd, ".jae"); const dnaPath = join(jaeDir, "project-dna.md"); if (args.trim() === "show" && existsSync(dnaPath)) { const dna = readFileSync(dnaPath, "utf-8"); ctx.ui.notify(`\u{1F9EC} Project DNA:\n${dna.substring(0, 1500)}`, "info"); return; } ctx.ui.notify("\u{1F50D} Analyzing project...", "info"); const sections: string[] = ["# Project DNA\n"]; // Tech stack detection const checks = [ { file: "package.json", label: "Node.js" }, { file: "Cargo.toml", label: "Rust" }, { file: "go.mod", label: "Go" }, { file: "pyproject.toml", label: "Python (pyproject)" }, { file: "requirements.txt", label: "Python (pip)" }, { file: "Gemfile", label: "Ruby" }, { file: "pom.xml", label: "Java (Maven)" }, { file: "build.gradle", label: "Java/Kotlin (Gradle)" }, { file: "docker-compose.yml", label: "Docker Compose" }, { file: "Dockerfile", label: "Docker" }, { file: ".github/workflows", label: "GitHub Actions" }, { file: "tsconfig.json", label: "TypeScript" }, { file: ".eslintrc.json", label: "ESLint" }, { file: "vitest.config.ts", label: "Vitest" }, { file: "jest.config.js", label: "Jest" }, { file: "tailwind.config.js", label: "Tailwind CSS" }, { file: "next.config.js", label: "Next.js" }, { file: "vite.config.ts", label: "Vite" }, ]; const detected: string[] = []; for (const c of checks) { if (existsSync(join(cwd, c.file))) detected.push(c.label); } sections.push(`## Tech Stack\n${detected.length > 0 ? detected.map((d) => `- ${d}`).join("\n") : "- Unknown"}\n`); // Package.json details const pkgPath = join(cwd, "package.json"); if (existsSync(pkgPath)) { try { const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")); sections.push( `## Package Info\n- Name: ${pkg.name || "unknown"}\n- Version: ${pkg.version || "unknown"}\n- Description: ${pkg.description || "none"}\n`, ); if (pkg.dependencies) { const deps = Object.keys(pkg.dependencies).slice(0, 20); sections.push(`## Dependencies (top 20)\n${deps.map((d) => `- ${d}`).join("\n")}\n`); } if (pkg.scripts) { const scripts = Object.entries(pkg.scripts).slice(0, 15) as [string, string][]; sections.push(`## Scripts\n${scripts.map(([k, v]) => `- \`${k}\`: ${v}`).join("\n")}\n`); } } catch { /* ignore parse errors */ } } // Git info try { const gitResult = await pi.exec("git", ["log", "--oneline", "-10"], { cwd }); if (gitResult.code === 0 && gitResult.stdout.trim()) { sections.push(`## Recent Git History\n\`\`\`\n${gitResult.stdout.trim()}\n\`\`\`\n`); } const branchResult = await pi.exec("git", ["branch", "--show-current"], { cwd }); if (branchResult.code === 0 && branchResult.stdout.trim()) { sections.push(`## Current Branch: ${branchResult.stdout.trim()}\n`); } } catch { /* not a git repo */ } // File stats try { const findResult = await pi.exec( "find", [".", "-maxdepth", "3", "-type", "f", "-not", "-path", "*/node_modules/*", "-not", "-path", "*/.git/*", "-not", "-path", "*/dist/*", "-not", "-path", "*/.jae/*"], { cwd }, ); if (findResult.code === 0) { const files = findResult.stdout.trim().split("\n").filter(Boolean); sections.push(`## File Count: ${files.length}\n`); const extCounts: Record = {}; for (const f of files) { const ext = f.includes(".") ? (f.split(".").pop() || "none") : "(no ext)"; extCounts[ext] = (extCounts[ext] || 0) + 1; } const sorted = Object.entries(extCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 15); sections.push(`## File Types\n${sorted.map(([ext, count]) => `- .${ext}: ${count}`).join("\n")}\n`); } } catch { /* ignore */ } sections.push(`\n---\nGenerated: ${new Date().toISOString()}\n`); const dnaContent = sections.join("\n"); if (!existsSync(jaeDir)) mkdirSync(jaeDir, { recursive: true }); writeFileSync(dnaPath, dnaContent, "utf-8"); ctx.ui.notify( `\u{1F9EC} Project DNA generated (${dnaContent.length} chars). Saved to .jae/project-dna.md`, "info", ); }, }); }