diff --git a/src/core/assembler.ts b/src/core/assembler.ts index 1c43cdc..9bbff88 100644 --- a/src/core/assembler.ts +++ b/src/core/assembler.ts @@ -4,7 +4,7 @@ import { join } from "node:path"; import type { RuneConfig } from "../types.ts"; import { CommandError } from "../cli/errors.ts"; import { getChangeDir } from "./config.ts"; -import { parseTasks } from "./task-parser.ts"; +import { parseTasks, validateTaskFormat } from "./task-parser.ts"; import { applyCommandPrefix, getPmPrefix } from "./pm.ts"; export function assembleDiscussPrompt(config: RuneConfig): string { @@ -85,6 +85,10 @@ export async function assembleBuildPrompt( }); } + if (!config.metadata?.tracked) { + return applyCommandPrefix(build.prompt, config); + } + const changeDir = getChangeDir(projectRoot, changeName); const taskPath = join(changeDir, "task.md"); @@ -98,6 +102,8 @@ export async function assembleBuildPrompt( }); } + validateTaskFormat(taskContent); + const tasks = parseTasks(taskContent); const pendingTasks = tasks.filter((t) => !t.checked); diff --git a/tests/core/assembler.test.ts b/tests/core/assembler.test.ts index 2a2eb6d..d87adba 100644 --- a/tests/core/assembler.test.ts +++ b/tests/core/assembler.test.ts @@ -152,6 +152,48 @@ describe("assembleBuildPrompt", () => { expect(e.hint).toContain("plan nonexistent"); } }); + + it("tracked=false 时只输出通用提示词", async () => { + const config: RuneConfig = { + stages: { build: { prompt: "按规划文档逐步实现功能" } }, + metadata: { tracked: false }, + }; + const prompt = await assembleBuildPrompt(config, TMP_DIR, "user-auth"); + expect(prompt).toBe("按规划文档逐步实现功能"); + }); + + it("tracked=true 且 task.md 格式不合法时抛错", async () => { + const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth"); + await mkdir(changeDir, { recursive: true }); + await writeFile(join(changeDir, "task.md"), "# 标题\n无 checkbox"); + const config: RuneConfig = { + stages: { build: { prompt: "构建阶段" } }, + metadata: { tracked: true }, + }; + try { + await assembleBuildPrompt(config, TMP_DIR, "user-auth"); + expect.unreachable(); + } catch (e: any) { + expect(e.message).toContain("task.md"); + expect(e.message).toContain("checkbox"); + } + }); + + it("tracked=true 且 task.md 有空 checkbox 文本时抛错", async () => { + const changeDir = join(TMP_DIR, ".rune", "changes", "user-auth"); + await mkdir(changeDir, { recursive: true }); + await writeFile(join(changeDir, "task.md"), "- [ ] \n- [x] 有内容"); + const config: RuneConfig = { + stages: { build: { prompt: "构建阶段" } }, + metadata: { tracked: true }, + }; + try { + await assembleBuildPrompt(config, TMP_DIR, "user-auth"); + expect.unreachable(); + } catch (e: any) { + expect(e.message).toContain("checkbox"); + } + }); }); describe("assembleArchivePrompt", () => { @@ -187,7 +229,7 @@ describe("命令前缀替换", () => { it("assembleBuildPrompt 错误提示使用动态前缀", async () => { const config: RuneConfig = { stages: { build: { prompt: "构建阶段" } }, - metadata: { command: "pnpx @lanyuanxiaoyao/rune" }, + metadata: { command: "pnpx @lanyuanxiaoyao/rune", tracked: true }, }; try { await assembleBuildPrompt(config, TMP_DIR, "nonexistent-build");