feat: 适配器支持动态命令前缀
This commit is contained in:
@@ -6,7 +6,10 @@ import { writeIfChanged } from "./utils.ts";
|
|||||||
|
|
||||||
const COMMANDS_DIR = ".claude/commands";
|
const COMMANDS_DIR = ".claude/commands";
|
||||||
|
|
||||||
export async function injectClaudeCode(projectRoot: string): Promise<void> {
|
export async function injectClaudeCode(
|
||||||
|
projectRoot: string,
|
||||||
|
command: string = "rune",
|
||||||
|
): Promise<void> {
|
||||||
for (const stage of STAGES) {
|
for (const stage of STAGES) {
|
||||||
const hasChangeName = stage !== "discuss";
|
const hasChangeName = stage !== "discuss";
|
||||||
|
|
||||||
@@ -14,7 +17,7 @@ export async function injectClaudeCode(projectRoot: string): Promise<void> {
|
|||||||
await mkdir(commandDir, { recursive: true });
|
await mkdir(commandDir, { recursive: true });
|
||||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
const commandPath = join(commandDir, `rune-${stage}.md`);
|
||||||
if (!existsSync(commandPath)) {
|
if (!existsSync(commandPath)) {
|
||||||
const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`;
|
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||||
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : "";
|
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : "";
|
||||||
await writeFile(
|
await writeFile(
|
||||||
commandPath,
|
commandPath,
|
||||||
@@ -26,23 +29,32 @@ export async function injectClaudeCode(projectRoot: string): Promise<void> {
|
|||||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||||
const statusPath = join(commandDir, "rune-status.md");
|
const statusPath = join(commandDir, "rune-status.md");
|
||||||
if (!existsSync(statusPath)) {
|
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<void> {
|
export async function updateClaudeCode(
|
||||||
|
projectRoot: string,
|
||||||
|
command: string = "rune",
|
||||||
|
): Promise<void> {
|
||||||
for (const stage of STAGES) {
|
for (const stage of STAGES) {
|
||||||
const hasChangeName = stage !== "discuss";
|
const hasChangeName = stage !== "discuss";
|
||||||
|
|
||||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||||
await mkdir(commandDir, { recursive: true });
|
await mkdir(commandDir, { recursive: true });
|
||||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
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 nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : "";
|
||||||
const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`;
|
const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`;
|
||||||
await writeIfChanged(commandPath, newContent);
|
await writeIfChanged(commandPath, newContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusPath = join(projectRoot, COMMANDS_DIR, "rune-status.md");
|
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`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { existsSync } from "node:fs";
|
|||||||
import { mkdir, writeFile } from "node:fs/promises";
|
import { mkdir, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { STAGES } from "../types.ts";
|
import { STAGES } from "../types.ts";
|
||||||
|
import { writeIfChanged } from "./utils.ts";
|
||||||
|
|
||||||
const COMMANDS_DIR = ".opencode/commands";
|
const COMMANDS_DIR = ".opencode/commands";
|
||||||
const SKILLS_DIR = ".opencode/skills";
|
const SKILLS_DIR = ".opencode/skills";
|
||||||
|
|
||||||
export async function injectOpenCode(projectRoot: string): Promise<void> {
|
export async function injectOpenCode(projectRoot: string, command: string = "rune"): Promise<void> {
|
||||||
for (const stage of STAGES) {
|
for (const stage of STAGES) {
|
||||||
const hasChangeName = stage !== "discuss";
|
const hasChangeName = stage !== "discuss";
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export async function injectOpenCode(projectRoot: string): Promise<void> {
|
|||||||
await mkdir(skillStageDir, { recursive: true });
|
await mkdir(skillStageDir, { recursive: true });
|
||||||
const skillPath = join(skillStageDir, "SKILL.md");
|
const skillPath = join(skillStageDir, "SKILL.md");
|
||||||
if (!existsSync(skillPath)) {
|
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<void> {
|
|||||||
await mkdir(statusSkillDir, { recursive: true });
|
await mkdir(statusSkillDir, { recursive: true });
|
||||||
const statusSkillPath = join(statusSkillDir, "SKILL.md");
|
const statusSkillPath = join(statusSkillDir, "SKILL.md");
|
||||||
if (!existsSync(statusSkillPath)) {
|
if (!existsSync(statusSkillPath)) {
|
||||||
await writeFile(statusSkillPath, generateStatusSkill());
|
await writeFile(statusSkillPath, generateStatusSkill(command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateOpenCode(projectRoot: string): Promise<void> {
|
export async function updateOpenCode(projectRoot: string, command: string = "rune"): Promise<void> {
|
||||||
for (const stage of STAGES) {
|
for (const stage of STAGES) {
|
||||||
const hasChangeName = stage !== "discuss";
|
const hasChangeName = stage !== "discuss";
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ export async function updateOpenCode(projectRoot: string): Promise<void> {
|
|||||||
const skillStageDir = join(projectRoot, SKILLS_DIR, `rune-${stage}`);
|
const skillStageDir = join(projectRoot, SKILLS_DIR, `rune-${stage}`);
|
||||||
await mkdir(skillStageDir, { recursive: true });
|
await mkdir(skillStageDir, { recursive: true });
|
||||||
const skillPath = join(skillStageDir, "SKILL.md");
|
const skillPath = join(skillStageDir, "SKILL.md");
|
||||||
const newSkill = generateSkill(stage, hasChangeName);
|
const newSkill = generateSkill(stage, hasChangeName, command);
|
||||||
await writeIfChanged(skillPath, newSkill);
|
await writeIfChanged(skillPath, newSkill);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +64,9 @@ export async function updateOpenCode(projectRoot: string): Promise<void> {
|
|||||||
const statusSkillDir = join(projectRoot, SKILLS_DIR, "rune-status");
|
const statusSkillDir = join(projectRoot, SKILLS_DIR, "rune-status");
|
||||||
await mkdir(statusSkillDir, { recursive: true });
|
await mkdir(statusSkillDir, { recursive: true });
|
||||||
const statusSkillPath = join(statusSkillDir, "SKILL.md");
|
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 {
|
function generateCommand(stage: string, hasChangeName: boolean): string {
|
||||||
if (hasChangeName) {
|
if (hasChangeName) {
|
||||||
return `请调用 rune-${stage} skill 执行 ${stage} 阶段。
|
return `请调用 rune-${stage} skill 执行 ${stage} 阶段。
|
||||||
@@ -79,13 +78,13 @@ function generateCommand(stage: string, hasChangeName: boolean): string {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSkill(stage: string, hasChangeName: boolean): string {
|
function generateSkill(stage: string, hasChangeName: boolean, command: string): string {
|
||||||
const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`;
|
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||||
const nameHint = hasChangeName ? `将 <变更名> 替换为实际的变更名称。\n` : "";
|
const nameHint = hasChangeName ? `将 <变更名> 替换为实际的变更名称。\n` : "";
|
||||||
|
|
||||||
let extraGuide = "";
|
let extraGuide = "";
|
||||||
if (stage === "plan") {
|
if (stage === "plan") {
|
||||||
extraGuide = `\n规划阶段应先运行 \`rune status <变更名>\` 获取当前有哪些文档需要编写,再按依赖顺序逐个生成。\n`;
|
extraGuide = `\n规划阶段应先运行 \`${command} status <变更名>\` 获取当前有哪些文档需要编写,再按依赖顺序逐个生成。\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionMap: Record<string, string> = {
|
const descriptionMap: Record<string, string> = {
|
||||||
@@ -117,7 +116,7 @@ function generateStatusCommand(): string {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateStatusSkill(): string {
|
function generateStatusSkill(command: string): string {
|
||||||
return `---
|
return `---
|
||||||
name: rune-status
|
name: rune-status
|
||||||
description: Use when 需要查看当前所有 Rune 变更的状态和任务进度
|
description: Use when 需要查看当前所有 Rune 变更的状态和任务进度
|
||||||
@@ -128,7 +127,7 @@ description: Use when 需要查看当前所有 Rune 变更的状态和任务进
|
|||||||
执行以下命令:
|
执行以下命令:
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
rune status
|
${command} status
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
将命令输出展示给用户。
|
将命令输出展示给用户。
|
||||||
|
|||||||
@@ -118,3 +118,29 @@ describe("updateClaudeCode", () => {
|
|||||||
expect(existsSync(join(TMP_DIR, ".claude", "commands", "rune-status.md"))).toBe(true);
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -148,3 +148,50 @@ describe("updateOpenCode", () => {
|
|||||||
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md"))).toBe(true);
|
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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user