feat: opencode/claude-code 统一 command 格式、智能识别引导、新增 intro/create、移除 status

This commit is contained in:
2026-06-10 13:16:02 +08:00
parent 49f523146f
commit 1f6e49e336
4 changed files with 199 additions and 120 deletions

View File

@@ -15,13 +15,13 @@ afterEach(async () => {
});
describe("injectOpenCode", () => {
it("生成 discuss、plan、build、archive 的 command 和 skill 文件", async () => {
it("生成 discuss、create、plan、build、archive 的 command 和 skill 文件", async () => {
await injectOpenCode(TMP_DIR);
const commands = await readdir(join(TMP_DIR, ".opencode", "commands"));
const skills = await readdir(join(TMP_DIR, ".opencode", "skills"));
for (const stage of ["discuss", "plan", "build", "archive"]) {
for (const stage of ["discuss", "create", "plan", "build", "archive"]) {
expect(commands).toContain(`rune-${stage}.md`);
expect(skills).toContain(`rune-${stage}`);
expect(existsSync(join(TMP_DIR, ".opencode", "skills", `rune-${stage}`, "SKILL.md"))).toBe(
@@ -30,47 +30,60 @@ describe("injectOpenCode", () => {
}
});
it("生成 rune-status command 和 skill", async () => {
it("生成 rune-status command 和 skill", async () => {
await injectOpenCode(TMP_DIR);
const commands = await readdir(join(TMP_DIR, ".opencode", "commands"));
const skills = await readdir(join(TMP_DIR, ".opencode", "skills"));
expect(commands).toContain("rune-status.md");
expect(skills).toContain("rune-status");
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md"))).toBe(true);
expect(commands).not.toContain("rune-status.md");
expect(skills).not.toContain("rune-status");
});
it("command 文件包含 skill 调用指令", async () => {
it("生成 rune-intro skill无对应 command", async () => {
await injectOpenCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
"utf-8",
);
expect(content).toContain("rune-discuss");
const commands = await readdir(join(TMP_DIR, ".opencode", "commands"));
const skills = await readdir(join(TMP_DIR, ".opencode", "skills"));
expect(commands).not.toContain("rune-intro.md");
expect(skills).toContain("rune-intro");
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-intro", "SKILL.md"))).toBe(true);
});
it("skill 文件包含 bash 命令", async () => {
it("command 文件统一格式:只包含 skill 调用指令", async () => {
await injectOpenCode(TMP_DIR);
for (const stage of ["discuss", "create", "plan", "build", "archive"]) {
const content = await readFile(
join(TMP_DIR, ".opencode", "commands", `rune-${stage}.md`),
"utf-8",
);
expect(content).toContain(`rune-${stage} skill`);
expect(content).not.toContain("变更名");
}
});
it("create/plan/build/archive skill 包含变更名智能识别引导", async () => {
await injectOpenCode(TMP_DIR);
for (const stage of ["create", "plan", "build", "archive"]) {
const content = await readFile(
join(TMP_DIR, ".opencode", "skills", `rune-${stage}`, "SKILL.md"),
"utf-8",
);
expect(content).toContain("智能识别");
expect(content).toContain("rune status");
}
});
it("discuss skill 不包含智能识别引导", async () => {
await injectOpenCode(TMP_DIR);
const content = await readFile(
join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"),
"utf-8",
);
expect(content).toContain("rune discuss");
expect(content).toContain("description");
expect(content).toContain("name: rune-discuss");
});
it("plan/build/archive skill 包含变更名称参数提示", async () => {
await injectOpenCode(TMP_DIR);
for (const stage of ["plan", "build", "archive"]) {
const content = await readFile(
join(TMP_DIR, ".opencode", "skills", `rune-${stage}`, "SKILL.md"),
"utf-8",
);
expect(content).toContain("变更名");
}
expect(content).not.toContain("智能识别");
});
it("plan skill 包含运行 status 的引导", async () => {
@@ -88,7 +101,7 @@ describe("injectOpenCode", () => {
join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"),
"utf-8",
);
expect(content).not.toContain("rune status");
expect(content).not.toContain("规划阶段应先运行");
});
it("重复注入时不覆盖已存在的文件", async () => {
@@ -142,10 +155,15 @@ describe("updateOpenCode", () => {
expect(content).toContain("rune-discuss");
});
it("更新 status 命令和 skill", async () => {
it("更新时生成 rune-intro skill", async () => {
await updateOpenCode(TMP_DIR);
expect(existsSync(join(TMP_DIR, ".opencode", "commands", "rune-status.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-intro", "SKILL.md"))).toBe(true);
});
it("不生成 rune-status", async () => {
await updateOpenCode(TMP_DIR);
expect(existsSync(join(TMP_DIR, ".opencode", "commands", "rune-status.md"))).toBe(false);
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md"))).toBe(false);
});
});
@@ -180,16 +198,31 @@ describe("injectOpenCode with command prefix", () => {
}
});
it("status skill 使用自定义前缀", async () => {
const tmpDir = join(import.meta.dir, "__tmp_opencode_status_test__");
it("create skill 使用自定义前缀", async () => {
const tmpDir = join(import.meta.dir, "__tmp_opencode_create_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"),
join(tmpDir, ".opencode", "skills", "rune-create", "SKILL.md"),
"utf-8",
);
expect(content).toContain("pnpx @lanyuanxiaoyao/rune status");
expect(content).toContain("pnpx @lanyuanxiaoyao/rune create");
} finally {
await rm(tmpDir, { recursive: true, force: true });
}
});
it("rune-intro skill 使用自定义前缀", async () => {
const tmpDir = join(import.meta.dir, "__tmp_opencode_intro_test__");
await mkdir(tmpDir, { recursive: true });
try {
await injectOpenCode(tmpDir, "bunx @lanyuanxiaoyao/rune");
const content = await readFile(
join(tmpDir, ".opencode", "skills", "rune-intro", "SKILL.md"),
"utf-8",
);
expect(content).toContain("bunx @lanyuanxiaoyao/rune status");
} finally {
await rm(tmpDir, { recursive: true, force: true });
}