/** * kcode 首次启动:生成 Trellis 工作流文件 + KCode 平台配置。 * * - .trellis/ ← project-templates/ 拷贝(workflow.md, scripts/ 等) * - .trellis/spec/ ← KCode 金蝶 spec template(README + backend + shared + guides) * - .kcode/ ← project-templates/ 拷贝(settings.json, extensions/, agents/, skills/) * - AGENTS.md / CLAUDE.md ← 模板拷贝 * - 创建 00-bootstrap-guidelines 任务 */ import * as fs from "node:fs"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { ROOT_AGENTS_MD, ROOT_CLAUDE_MD } from "./project-init-templates"; import { initKcodeDir, initTrellisDir, loadAllInitTemplates } from "./project-template-loader"; import { writeProductsToProjectMd } from "./product-md"; export interface InitKingdeeProjectResult { generated: string[]; } function ensureFile(filePath: string, content: string, generated: string[]): void { if (fs.existsSync(filePath)) return; try { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, content, "utf8"); generated.push(filePath); } catch { console.warn(`⚠ 无法创建 ${filePath}`); } } function pickContent(loaded: string | undefined, fallback: string): string { return loaded?.trim() ? loaded : fallback; } function shouldSkipTemplateEntry(name: string): boolean { return name === "__pycache__" || name.endsWith(".pyc") || name.endsWith(".pyo"); } function copyDir(src: string, dest: string, generated: string[]): void { if (!fs.existsSync(src)) return; fs.mkdirSync(dest, { recursive: true }); const entries = fs.readdirSync(src, { withFileTypes: true }); for (const entry of entries) { if (shouldSkipTemplateEntry(entry.name)) continue; const s = path.join(src, entry.name); const d = path.join(dest, entry.name); if (entry.isDirectory()) { copyDir(s, d, generated); } else if (entry.isFile() && !fs.existsSync(d)) { fs.cpSync(s, d); generated.push(d); } } } function productIdsFromInitLine(productLine?: string | null): string[] { if (productLine === "cosmic") return ["cosmic-java"]; if (productLine === "enterprise") return ["enterprise-csharp"]; return []; } export function initKingdeeProjectFiles(cwd: string, productLine?: string | null): InitKingdeeProjectResult { const generated: string[] = []; const t = loadAllInitTemplates(cwd); // 1. 拷贝 .trellis/ 核心目录(spec/ 单独按 KCode 模板整体拷贝) generated.push(...initTrellisDir(cwd)); // 2. 拷贝 .kcode/ 平台配置,供 KCode runtime 原生发现 extension/skills/prompts generated.push(...initKcodeDir(cwd)); // 3. 拷贝 KCode 金蝶 spec template const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const specBase = path.join(pkgRoot, "project-templates", ".trellis", "spec"); copyDir(specBase, path.join(cwd, ".trellis", "spec"), generated); const productIds = productIdsFromInitLine(productLine); if (productIds.length > 0) { writeProductsToProjectMd(cwd, productIds); } // 4. 创建 bootstrap 任务 const taskDir = path.join(cwd, ".trellis", "tasks", "00-bootstrap-guidelines"); if (!fs.existsSync(taskDir)) { fs.mkdirSync(taskDir, { recursive: true }); const taskJson = { id: "00-bootstrap-guidelines", name: "00-bootstrap-guidelines", title: "Bootstrap Guidelines", description: "Fill in the initial project specifications and conventions", status: "planning", dev_type: "docs", priority: "P1", createdAt: new Date().toISOString().slice(0, 10), relatedFiles: [ ".trellis/spec/README.md", ".trellis/spec/shared/project-context.md", ".trellis/spec/backend/index.md", ], }; fs.writeFileSync(path.join(taskDir, "task.json"), JSON.stringify(taskJson, null, 2), "utf8"); generated.push(path.join(taskDir, "task.json")); const prd = `# Bootstrap Guidelines\n\n## Goal\n\nFill in the initial project specifications in \`.trellis/spec/\` with real project conventions.\n\n## What to Do\n1. Review \`.trellis/spec/README.md\`\n2. Confirm product profile in \`.trellis/spec/shared/project-context.md\`\n3. Review the relevant backend and shared specs for this project\n4. Archive this task when done.\n`; fs.writeFileSync(path.join(taskDir, "prd.md"), prd, "utf8"); generated.push(path.join(taskDir, "prd.md")); } // 5. AGENTS.md / CLAUDE.md ensureFile(path.join(cwd, "AGENTS.md"), pickContent(t.rootAgents, ROOT_AGENTS_MD), generated); ensureFile(path.join(cwd, "CLAUDE.md"), pickContent(t.rootClaude, ROOT_CLAUDE_MD), generated); return { generated }; } export function formatInitKingdeeProjectMessage(cwd: string, generated: string[]): string | undefined { if (generated.length === 0) return undefined; const lines = ["\n首次启动完成。已生成 Trellis 工作流文件:"]; for (const abs of generated) { lines.push(` ✓ ${path.relative(cwd, abs)}`); } lines.push("\n请先阅读 .trellis/workflow.md 了解工作流,再开始开发。\n"); return lines.join("\n"); }