diff --git a/src/adapters/claude-code.ts b/src/adapters/claude-code.ts index c6771b0..e4a118d 100644 --- a/src/adapters/claude-code.ts +++ b/src/adapters/claude-code.ts @@ -6,7 +6,10 @@ import { writeIfChanged } from "./utils.ts"; const COMMANDS_DIR = ".claude/commands"; -export async function injectClaudeCode(projectRoot: string): Promise { +export async function injectClaudeCode( + projectRoot: string, + command: string = "rune", +): Promise { for (const stage of STAGES) { const hasChangeName = stage !== "discuss"; @@ -14,7 +17,7 @@ export async function injectClaudeCode(projectRoot: string): Promise { await mkdir(commandDir, { recursive: true }); const commandPath = join(commandDir, `rune-${stage}.md`); if (!existsSync(commandPath)) { - const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`; + const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`; const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : ""; await writeFile( commandPath, @@ -26,23 +29,32 @@ export async function injectClaudeCode(projectRoot: string): Promise { const commandDir = join(projectRoot, COMMANDS_DIR); const statusPath = join(commandDir, "rune-status.md"); if (!existsSync(statusPath)) { - await writeFile(statusPath, `执行以下命令查看变更状态:\n\`\`\`bash\nrune status\n\`\`\`\n`); + await writeFile( + statusPath, + `执行以下命令查看变更状态:\n\`\`\`bash\n${command} status\n\`\`\`\n`, + ); } } -export async function updateClaudeCode(projectRoot: string): Promise { +export async function updateClaudeCode( + projectRoot: string, + command: string = "rune", +): Promise { for (const stage of STAGES) { const hasChangeName = stage !== "discuss"; const commandDir = join(projectRoot, COMMANDS_DIR); await mkdir(commandDir, { recursive: true }); const commandPath = join(commandDir, `rune-${stage}.md`); - const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`; + const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`; const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : ""; const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`; await writeIfChanged(commandPath, newContent); } const statusPath = join(projectRoot, COMMANDS_DIR, "rune-status.md"); - await writeIfChanged(statusPath, `执行以下命令查看变更状态:\n\`\`\`bash\nrune status\n\`\`\`\n`); + await writeIfChanged( + statusPath, + `执行以下命令查看变更状态:\n\`\`\`bash\n${command} status\n\`\`\`\n`, + ); } diff --git a/src/adapters/opencode.ts b/src/adapters/opencode.ts index 3e7f48e..df93dfb 100644 --- a/src/adapters/opencode.ts +++ b/src/adapters/opencode.ts @@ -2,11 +2,12 @@ import { existsSync } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { STAGES } from "../types.ts"; +import { writeIfChanged } from "./utils.ts"; const COMMANDS_DIR = ".opencode/commands"; const SKILLS_DIR = ".opencode/skills"; -export async function injectOpenCode(projectRoot: string): Promise { +export async function injectOpenCode(projectRoot: string, command: string = "rune"): Promise { for (const stage of STAGES) { const hasChangeName = stage !== "discuss"; @@ -21,7 +22,7 @@ export async function injectOpenCode(projectRoot: string): Promise { await mkdir(skillStageDir, { recursive: true }); const skillPath = join(skillStageDir, "SKILL.md"); if (!existsSync(skillPath)) { - await writeFile(skillPath, generateSkill(stage, hasChangeName)); + await writeFile(skillPath, generateSkill(stage, hasChangeName, command)); } } @@ -35,11 +36,11 @@ export async function injectOpenCode(projectRoot: string): Promise { await mkdir(statusSkillDir, { recursive: true }); const statusSkillPath = join(statusSkillDir, "SKILL.md"); if (!existsSync(statusSkillPath)) { - await writeFile(statusSkillPath, generateStatusSkill()); + await writeFile(statusSkillPath, generateStatusSkill(command)); } } -export async function updateOpenCode(projectRoot: string): Promise { +export async function updateOpenCode(projectRoot: string, command: string = "rune"): Promise { for (const stage of STAGES) { const hasChangeName = stage !== "discuss"; @@ -52,7 +53,7 @@ export async function updateOpenCode(projectRoot: string): Promise { const skillStageDir = join(projectRoot, SKILLS_DIR, `rune-${stage}`); await mkdir(skillStageDir, { recursive: true }); const skillPath = join(skillStageDir, "SKILL.md"); - const newSkill = generateSkill(stage, hasChangeName); + const newSkill = generateSkill(stage, hasChangeName, command); await writeIfChanged(skillPath, newSkill); } @@ -63,11 +64,9 @@ export async function updateOpenCode(projectRoot: string): Promise { const statusSkillDir = join(projectRoot, SKILLS_DIR, "rune-status"); await mkdir(statusSkillDir, { recursive: true }); const statusSkillPath = join(statusSkillDir, "SKILL.md"); - await writeIfChanged(statusSkillPath, generateStatusSkill()); + await writeIfChanged(statusSkillPath, generateStatusSkill(command)); } -import { writeIfChanged } from "./utils.ts"; - function generateCommand(stage: string, hasChangeName: boolean): string { if (hasChangeName) { return `请调用 rune-${stage} skill 执行 ${stage} 阶段。 @@ -79,13 +78,13 @@ function generateCommand(stage: string, hasChangeName: boolean): string { `; } -function generateSkill(stage: string, hasChangeName: boolean): string { - const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`; +function generateSkill(stage: string, hasChangeName: boolean, command: string): string { + const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`; const nameHint = hasChangeName ? `将 <变更名> 替换为实际的变更名称。\n` : ""; let extraGuide = ""; if (stage === "plan") { - extraGuide = `\n规划阶段应先运行 \`rune status <变更名>\` 获取当前有哪些文档需要编写,再按依赖顺序逐个生成。\n`; + extraGuide = `\n规划阶段应先运行 \`${command} status <变更名>\` 获取当前有哪些文档需要编写,再按依赖顺序逐个生成。\n`; } const descriptionMap: Record = { @@ -117,7 +116,7 @@ function generateStatusCommand(): string { `; } -function generateStatusSkill(): string { +function generateStatusSkill(command: string): string { return `--- name: rune-status description: Use when 需要查看当前所有 Rune 变更的状态和任务进度 @@ -128,7 +127,7 @@ description: Use when 需要查看当前所有 Rune 变更的状态和任务进 执行以下命令: \`\`\`bash -rune status +${command} status \`\`\` 将命令输出展示给用户。 diff --git a/tests/adapters/claude-code.test.ts b/tests/adapters/claude-code.test.ts index e078647..61dc78a 100644 --- a/tests/adapters/claude-code.test.ts +++ b/tests/adapters/claude-code.test.ts @@ -118,3 +118,29 @@ describe("updateClaudeCode", () => { expect(existsSync(join(TMP_DIR, ".claude", "commands", "rune-status.md"))).toBe(true); }); }); + +describe("injectClaudeCode with command prefix", () => { + it("使用自定义前缀生成 command 文件", async () => { + await injectClaudeCode(TMP_DIR, "bunx @lanyuanxiaoyao/rune"); + const content = await readFile( + join(TMP_DIR, ".claude", "commands", "rune-discuss.md"), + "utf-8", + ); + expect(content).toContain("bunx @lanyuanxiaoyao/rune discuss"); + }); + + it("不传前缀时使用默认 rune 前缀", async () => { + await injectClaudeCode(TMP_DIR); + const content = await readFile( + join(TMP_DIR, ".claude", "commands", "rune-discuss.md"), + "utf-8", + ); + expect(content).toContain("rune discuss"); + }); + + it("status command 使用自定义前缀", async () => { + await injectClaudeCode(TMP_DIR, "npx @lanyuanxiaoyao/rune"); + const content = await readFile(join(TMP_DIR, ".claude", "commands", "rune-status.md"), "utf-8"); + expect(content).toContain("npx @lanyuanxiaoyao/rune status"); + }); +}); diff --git a/tests/adapters/opencode.test.ts b/tests/adapters/opencode.test.ts index 5675baa..1b7464a 100644 --- a/tests/adapters/opencode.test.ts +++ b/tests/adapters/opencode.test.ts @@ -148,3 +148,50 @@ describe("updateOpenCode", () => { expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md"))).toBe(true); }); }); + +describe("injectOpenCode with command prefix", () => { + it("使用自定义前缀生成 skill 文件", async () => { + const tmpDir = join(import.meta.dir, "__tmp_opencode_prefix_test__"); + await mkdir(tmpDir, { recursive: true }); + try { + await injectOpenCode(tmpDir, "bunx @lanyuanxiaoyao/rune"); + const content = await readFile( + join(tmpDir, ".opencode", "skills", "rune-discuss", "SKILL.md"), + "utf-8", + ); + expect(content).toContain("bunx @lanyuanxiaoyao/rune discuss"); + } finally { + await rm(tmpDir, { recursive: true, force: true }); + } + }); + + it("不传前缀时使用默认 rune 前缀", async () => { + const tmpDir = join(import.meta.dir, "__tmp_opencode_default_test__"); + await mkdir(tmpDir, { recursive: true }); + try { + await injectOpenCode(tmpDir); + const content = await readFile( + join(tmpDir, ".opencode", "skills", "rune-discuss", "SKILL.md"), + "utf-8", + ); + expect(content).toContain("rune discuss"); + } finally { + await rm(tmpDir, { recursive: true, force: true }); + } + }); + + it("status skill 使用自定义前缀", async () => { + const tmpDir = join(import.meta.dir, "__tmp_opencode_status_test__"); + await mkdir(tmpDir, { recursive: true }); + try { + await injectOpenCode(tmpDir, "pnpx @lanyuanxiaoyao/rune"); + const content = await readFile( + join(tmpDir, ".opencode", "skills", "rune-status", "SKILL.md"), + "utf-8", + ); + expect(content).toContain("pnpx @lanyuanxiaoyao/rune status"); + } finally { + await rm(tmpDir, { recursive: true, force: true }); + } + }); +});