From 78caec6449ae4452d2279dfa1794911b2982ea5d Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 9 Jun 2026 20:37:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=8B=BC?= =?UTF-8?q?=E8=A3=85=E4=BD=BF=E7=94=A8=E5=8A=A8=E6=80=81=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/assembler.ts | 12 ++++++---- tests/core/assembler.test.ts | 46 +++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/core/assembler.ts b/src/core/assembler.ts index 12a5188..1c43cdc 100644 --- a/src/core/assembler.ts +++ b/src/core/assembler.ts @@ -5,6 +5,7 @@ import type { RuneConfig } from "../types.ts"; import { CommandError } from "../cli/errors.ts"; import { getChangeDir } from "./config.ts"; import { parseTasks } from "./task-parser.ts"; +import { applyCommandPrefix, getPmPrefix } from "./pm.ts"; export function assembleDiscussPrompt(config: RuneConfig): string { const discuss = config.stages.discuss; @@ -12,7 +13,7 @@ export function assembleDiscussPrompt(config: RuneConfig): string { throw new CommandError("讨论阶段未配置", { hint: "请在 .rune/config.yaml 中配置 stages.discuss", }); - return discuss.prompt; + return applyCommandPrefix(discuss.prompt, config); } export async function assemblePlanPrompt( @@ -69,7 +70,7 @@ export async function assemblePlanPrompt( } parts.push(`\n请将文档写入目录:${changeDir}`); - return parts.join("\n"); + return applyCommandPrefix(parts.join("\n"), config); } export async function assembleBuildPrompt( @@ -91,8 +92,9 @@ export async function assembleBuildPrompt( try { taskContent = await readFile(taskPath, "utf-8"); } catch { + const prefix = getPmPrefix(config); throw new CommandError(`变更 "${changeName}" 尚未完成规划,task.md 不存在`, { - hint: `请先完成规划阶段:rune plan ${changeName} 生成所有规划文档`, + hint: `请先完成规划阶段:${prefix} plan ${changeName} 生成所有规划文档`, }); } @@ -114,7 +116,7 @@ export async function assembleBuildPrompt( } parts.push(`\n请从第一个待执行任务开始。完成后更新 ${taskPath} 中的 checkbox。`); - return parts.join("\n"); + return applyCommandPrefix(parts.join("\n"), config); } export async function assembleArchivePrompt( @@ -154,5 +156,5 @@ export async function assembleArchivePrompt( } parts.push(archive.prompt); - return parts.join("\n"); + return applyCommandPrefix(parts.join("\n"), config); } diff --git a/tests/core/assembler.test.ts b/tests/core/assembler.test.ts index 843277d..2a2eb6d 100644 --- a/tests/core/assembler.test.ts +++ b/tests/core/assembler.test.ts @@ -149,7 +149,7 @@ describe("assembleBuildPrompt", () => { } catch (e: any) { expect(e.message).toContain("尚未完成规划"); expect(e.message).toContain("nonexistent"); - expect(e.hint).toContain("rune plan"); + expect(e.hint).toContain("plan nonexistent"); } }); }); @@ -163,3 +163,47 @@ describe("assembleArchivePrompt", () => { expect(prompt).toContain("归档"); }); }); + +describe("命令前缀替换", () => { + it("assembleDiscussPrompt 替换 rune 为配置前缀", () => { + const config: RuneConfig = { + stages: { discuss: { prompt: "执行 rune status 查看状态" } }, + metadata: { command: "bunx @lanyuanxiaoyao/rune" }, + }; + const prompt = assembleDiscussPrompt(config); + expect(prompt).toContain("bunx @lanyuanxiaoyao/rune status"); + expect(prompt).not.toContain("执行 rune "); + }); + + it("assembleDiscussPrompt 无配置时追加降级说明", () => { + const config: RuneConfig = { + stages: { discuss: { prompt: "执行 rune status 查看" } }, + }; + const prompt = assembleDiscussPrompt(config); + expect(prompt).toContain("bunx @lanyuanxiaoyao/rune status"); + expect(prompt).toContain("如果没有安装 bun"); + }); + + it("assembleBuildPrompt 错误提示使用动态前缀", async () => { + const config: RuneConfig = { + stages: { build: { prompt: "构建阶段" } }, + metadata: { command: "pnpx @lanyuanxiaoyao/rune" }, + }; + try { + await assembleBuildPrompt(config, TMP_DIR, "nonexistent-build"); + expect.unreachable(); + } catch (e: any) { + expect(e.hint).toContain("pnpx @lanyuanxiaoyao/rune plan nonexistent-build"); + } + }); + + it("不替换 /rune- 形式", () => { + const config: RuneConfig = { + stages: { discuss: { prompt: "使用 /rune-plan 进入规划,然后 rune build 构建" } }, + metadata: { command: "bunx @lanyuanxiaoyao/rune" }, + }; + const prompt = assembleDiscussPrompt(config); + expect(prompt).toContain("/rune-plan"); + expect(prompt).toContain("bunx @lanyuanxiaoyao/rune build"); + }); +});