From ee01bd87abdf64e2bcd1c14c65801e1cc7a8a458 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 9 Jun 2026 10:49:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20assemblePlanPrompt=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=8C=89=E5=8D=95=E6=96=87=E6=A1=A3=E7=BB=84=E8=A3=85=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BE=9D=E8=B5=96=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli.ts | 8 +++-- src/core/assembler.ts | 54 ++++++++++++++++++---------- tests/core/assembler.test.ts | 66 +++++++++++++++++++++++++++++++--- tests/integration/flow.test.ts | 4 +-- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 07e4847..88c6652 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -74,8 +74,12 @@ cli.command("plan ", "规划阶段").action( const root = requireProjectRoot(); await mkdir(getChangeDir(root, changeName), { recursive: true }); const config = await loadConfig(root); - const prompt = await assemblePlanPrompt(config, root, changeName); - console.log(prompt); + const plan = config.stages.plan; + if (!plan) throw new Error("plan 阶段未配置"); + for (const doc of plan.documents) { + const prompt = await assemblePlanPrompt(config, root, changeName, doc.name); + console.log(prompt); + } }, ); diff --git a/src/core/assembler.ts b/src/core/assembler.ts index 717c726..2d3fbab 100644 --- a/src/core/assembler.ts +++ b/src/core/assembler.ts @@ -15,35 +15,51 @@ export async function assemblePlanPrompt( config: RuneConfig, projectRoot: string, changeName: string, + documentName: string, ): Promise { const plan = config.stages.plan; if (!plan) throw new Error("plan 阶段未配置"); + const doc = plan.documents.find((d) => d.name === documentName); + if (!doc) { + throw new Error(`文档 "${documentName}" 不在配置的 plan.documents 中`); + } + const changeDir = getChangeDir(projectRoot, changeName); const parts: string[] = []; - parts.push(`# 规划阶段:${changeName}\n`); - parts.push("请为当前变更生成以下文档:\n"); + parts.push(`# 规划阶段:${changeName}`); + parts.push(""); + parts.push(`## 文档:${doc.name}.md`); + parts.push(doc.prompt); - for (const doc of plan.documents) { - parts.push(`## 文档:${doc.name}.md`); - parts.push(doc.prompt); - - const docPath = join(changeDir, `${doc.name}.md`); - if (existsSync(docPath)) { - const existing = await readFile(docPath, "utf-8"); - parts.push(`\n### 已有内容(请在此基础上修订):\n${existing}`); - } - - if (doc.template) { - const rendered = doc.template.replace(/\{\{change-name\}\}/g, changeName); - parts.push(`\n### 格式模板:\n${rendered}`); - } - - parts.push(""); + const docPath = join(changeDir, `${doc.name}.md`); + if (existsSync(docPath)) { + const existing = await readFile(docPath, "utf-8"); + parts.push(`\n### 已有内容(请在此基础上修订):\n${existing}`); } - parts.push(`请将文档写入目录:${changeDir}`); + if (doc.template) { + const rendered = doc.template.replace(/\{\{change-name\}\}/g, changeName); + parts.push(`\n### 格式模板:\n${rendered}`); + } + + if (doc.depend && doc.depend.length > 0) { + parts.push("\n---\n"); + parts.push("## 依赖说明\n"); + parts.push("本文档依赖以下前置文档:"); + + for (const dep of doc.depend) { + const depPath = join(changeDir, `${dep}.md`); + const depCompleted = existsSync(depPath); + const status = depCompleted ? "已完成" : "未完成"; + parts.push(`- ${dep}.md(${status})`); + } + + parts.push("\n请先阅读已完成的前置文档,确保内容一致。"); + } + + parts.push(`\n请将文档写入目录:${changeDir}`); return parts.join("\n"); } diff --git a/tests/core/assembler.test.ts b/tests/core/assembler.test.ts index 5ea71e0..77fda23 100644 --- a/tests/core/assembler.test.ts +++ b/tests/core/assembler.test.ts @@ -37,16 +37,16 @@ describe("assembleDiscussPrompt", () => { }); describe("assemblePlanPrompt", () => { - it("包含变更名称和文档指引", async () => { + it("包含指定文档名称和提示词", async () => { const prompt = await assemblePlanPrompt( defaultConfig, TMP_DIR, "user-auth", + "design", ); expect(prompt).toContain("user-auth"); expect(prompt).toContain("design"); - expect(prompt).toContain("task"); - expect(prompt).toContain("格式模板"); + expect(prompt).not.toContain("task"); }); it("包含已有文档内容(重复 plan 场景)", async () => { @@ -57,6 +57,7 @@ describe("assemblePlanPrompt", () => { defaultConfig, TMP_DIR, "user-auth", + "design", ); expect(prompt).toContain("已有设计"); expect(prompt).toContain("在此基础上修订"); @@ -67,12 +68,67 @@ describe("assemblePlanPrompt", () => { defaultConfig, TMP_DIR, "user-auth", + "design", ); expect(prompt).toContain("user-auth 设计文档"); - expect(prompt).toContain("user-auth 任务列表"); expect(prompt).not.toContain("{{change-name}}"); }); + it("包含依赖说明(有依赖时)", async () => { + const config: RuneConfig = { + stages: { + plan: { + documents: [ + { name: "design", prompt: "生成设计" }, + { name: "task", prompt: "生成任务", depend: ["design"] }, + ], + }, + }, + }; + const prompt = await assemblePlanPrompt( + config, + TMP_DIR, + "user-auth", + "task", + ); + expect(prompt).toContain("依赖说明"); + expect(prompt).toContain("design.md"); + }); + + it("无依赖时不包含依赖说明", async () => { + const prompt = await assemblePlanPrompt( + defaultConfig, + TMP_DIR, + "user-auth", + "design", + ); + expect(prompt).not.toContain("依赖说明"); + }); + + it("依赖说明标注完成状态", async () => { + const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth"); + await mkdir(changeDir, { recursive: true }); + await writeFile(join(changeDir, "design.md"), "# 设计"); + + const config: RuneConfig = { + stages: { + plan: { + documents: [ + { name: "design", prompt: "生成设计" }, + { name: "task", prompt: "生成任务", depend: ["design"] }, + ], + }, + }, + }; + const prompt = await assemblePlanPrompt( + config, + TMP_DIR, + "user-auth", + "task", + ); + expect(prompt).toContain("已完成"); + }); + it("使用自定义 plan 配置", async () => { const config: RuneConfig = { stages: { @@ -87,7 +143,7 @@ describe("assemblePlanPrompt", () => { }, }, }; - const prompt = await assemblePlanPrompt(config, TMP_DIR, "my-feature"); + const prompt = await assemblePlanPrompt(config, TMP_DIR, "my-feature", "spec"); expect(prompt).toContain("spec"); expect(prompt).toContain("my-feature 规格"); expect(prompt).not.toContain("design"); diff --git a/tests/integration/flow.test.ts b/tests/integration/flow.test.ts index ef46c45..c77958b 100644 --- a/tests/integration/flow.test.ts +++ b/tests/integration/flow.test.ts @@ -34,7 +34,7 @@ describe("完整 SDD 流程", () => { const changeName = "user-auth"; await mkdir(getChangeDir(TMP_DIR, changeName), { recursive: true }); - const planPrompt = await assemblePlanPrompt(config, TMP_DIR, changeName); + const planPrompt = await assemblePlanPrompt(config, TMP_DIR, changeName, "design"); expect(planPrompt).toContain("user-auth"); const changeDir = getChangeDir(TMP_DIR, changeName); @@ -118,7 +118,7 @@ describe("完整 SDD 流程", () => { expect(discussPrompt).toBe("自定义讨论"); await mkdir(getChangeDir(TMP_DIR, "test"), { recursive: true }); - const planPrompt = await assemblePlanPrompt(config, TMP_DIR, "test"); + const planPrompt = await assemblePlanPrompt(config, TMP_DIR, "test", "spec"); expect(planPrompt).toContain("spec"); expect(planPrompt).toContain("test 规格"); expect(planPrompt).not.toContain("design");