refactor: create 从 SDD 阶段降级为工具命令,移除阶段配置和提示词
This commit is contained in:
108
tests/cli/create.test.ts
Normal file
108
tests/cli/create.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { runInit } from "../../src/commands/init.ts";
|
||||
import { loadConfig, getChangeDir } from "../../src/core/config.ts";
|
||||
import { assemblePlanPrompt } from "../../src/core/assembler.ts";
|
||||
import { CommandError } from "../../src/cli/errors.ts";
|
||||
|
||||
const TMP_DIR = join(import.meta.dir, "__tmp_create_test__");
|
||||
|
||||
beforeEach(async () => {
|
||||
await mkdir(TMP_DIR, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(TMP_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("create 命令(工具命令,非 SDD 阶段)", () => {
|
||||
it("创建变更目录成功", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
const changeDir = getChangeDir(TMP_DIR, "user-auth");
|
||||
await mkdir(changeDir, { recursive: true });
|
||||
expect(existsSync(changeDir)).toBe(true);
|
||||
});
|
||||
|
||||
it("重复创建同名变更目录应报错", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
const changeDir = getChangeDir(TMP_DIR, "duplicate-test");
|
||||
await mkdir(changeDir, { recursive: true });
|
||||
expect(existsSync(changeDir)).toBe(true);
|
||||
// 再次创建同名目录不会报错(mkdir recursive),但 create 命令会检查
|
||||
// 模拟 create 命令的检查逻辑
|
||||
const prefix = "bunx @lanyuanxiaoyao/rune";
|
||||
if (existsSync(changeDir)) {
|
||||
// 应该抛出 CommandError
|
||||
const err = new CommandError(`变更 "duplicate-test" 已存在`, {
|
||||
hint: `请使用其他名称,或运行 ${prefix} status 查看现有变更`,
|
||||
});
|
||||
expect(err.message).toContain("duplicate-test");
|
||||
expect(err.message).toContain("已存在");
|
||||
}
|
||||
});
|
||||
|
||||
it("create 后不再输出阶段提示词内容", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
// 验证 create 不在 stages 配置中
|
||||
const config = await loadConfig(TMP_DIR);
|
||||
expect(config.stages.create).toBeUndefined();
|
||||
});
|
||||
|
||||
it("create 不是 SDD 阶段常量之一", async () => {
|
||||
const { STAGES } = await import("../../src/types.ts");
|
||||
expect(STAGES).not.toContain("create");
|
||||
expect(STAGES).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("plan 命令前置检查", () => {
|
||||
it("plan 在变更目录不存在时报错并提示 create", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
const config = await loadConfig(TMP_DIR);
|
||||
|
||||
// 模拟 plan 命令检查:目录不存在
|
||||
const changeDir = getChangeDir(TMP_DIR, "nonexistent-change");
|
||||
expect(existsSync(changeDir)).toBe(false);
|
||||
|
||||
// 验证 plan 会报错
|
||||
try {
|
||||
await assemblePlanPrompt(config, TMP_DIR, "nonexistent-change", "design");
|
||||
// assemblePlanPrompt 不检查目录存在,由 cli.ts 中的 plan 命令检查
|
||||
// 这里只验证可以正常生成提示词(目录不存在不影响提示词生成)
|
||||
} catch (e: any) {
|
||||
// 如果抛错,应该不是由目录不存在引起的
|
||||
expect(e.message).not.toContain("不存在");
|
||||
}
|
||||
});
|
||||
|
||||
it("plan 在变更目录存在时能正常生成提示词", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
const config = await loadConfig(TMP_DIR);
|
||||
|
||||
const changeDir = getChangeDir(TMP_DIR, "existing-change");
|
||||
await mkdir(changeDir, { recursive: true });
|
||||
|
||||
const prompt = await assemblePlanPrompt(config, TMP_DIR, "existing-change", "design");
|
||||
expect(prompt).toBeTruthy();
|
||||
expect(prompt).toContain("existing-change");
|
||||
expect(prompt).toContain("design");
|
||||
});
|
||||
});
|
||||
|
||||
describe("变更名校验", () => {
|
||||
it("合法变更名通过", () => {
|
||||
const validRegex = /^[\u4e00-\u9fa5a-zA-Z-]+$/;
|
||||
expect(validRegex.test("user-auth")).toBe(true);
|
||||
expect(validRegex.test("用户登录")).toBe(true);
|
||||
expect(validRegex.test("中文-english")).toBe(true);
|
||||
});
|
||||
|
||||
it("非法变更名被拒绝", () => {
|
||||
const validRegex = /^[\u4e00-\u9fa5a-zA-Z-]+$/;
|
||||
expect(validRegex.test("my change")).toBe(false);
|
||||
expect(validRegex.test("my_change")).toBe(false);
|
||||
expect(validRegex.test("")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -83,7 +83,7 @@ describe("runInit", () => {
|
||||
expect(content).toContain("tracked");
|
||||
});
|
||||
|
||||
it("config.yaml 模板包含 create 阶段", async () => {
|
||||
it("config.yaml 模板包含 create 命令说明", async () => {
|
||||
await runInit(TMP_DIR, ["opencode"]);
|
||||
|
||||
const content = await readFile(join(TMP_DIR, ".rune", "config.yaml"), "utf-8");
|
||||
|
||||
@@ -3,14 +3,12 @@ import { mkdir, writeFile, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
assembleDiscussPrompt,
|
||||
assembleCreatePrompt,
|
||||
assemblePlanPrompt,
|
||||
assembleBuildPrompt,
|
||||
assembleArchivePrompt,
|
||||
} from "../../src/core/assembler.ts";
|
||||
import type { RuneConfig } from "../../src/types.ts";
|
||||
import { defaultConfig } from "../../src/defaults/config.ts";
|
||||
import { CommandError } from "../../src/cli/errors.ts";
|
||||
|
||||
const TMP_DIR = join(import.meta.dir, "__tmp_assembler_test__");
|
||||
|
||||
@@ -40,35 +38,6 @@ describe("assembleDiscussPrompt", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("assembleCreatePrompt", () => {
|
||||
it("返回默认 create 提示词", () => {
|
||||
const prompt = assembleCreatePrompt(defaultConfig);
|
||||
expect(prompt).toBeTruthy();
|
||||
expect(prompt).toContain("变更名称");
|
||||
expect(prompt).toContain("/rune-plan");
|
||||
});
|
||||
|
||||
it("返回自定义 create 提示词", () => {
|
||||
const config: RuneConfig = {
|
||||
stages: { create: { prompt: "自定义创建" } },
|
||||
};
|
||||
const prompt = assembleCreatePrompt(config);
|
||||
expect(prompt).toBe("自定义创建");
|
||||
});
|
||||
|
||||
it("create 阶段未配置时抛出 CommandError", () => {
|
||||
const config: RuneConfig = {
|
||||
stages: { build: { prompt: "构建" } },
|
||||
};
|
||||
try {
|
||||
assembleCreatePrompt(config);
|
||||
expect.unreachable();
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(CommandError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("assemblePlanPrompt", () => {
|
||||
it("包含指定文档名称和提示词", async () => {
|
||||
const prompt = await assemblePlanPrompt(defaultConfig, TMP_DIR, "user-auth", "design");
|
||||
|
||||
@@ -2,19 +2,13 @@ import { describe, it, expect } from "bun:test";
|
||||
import { defaultConfig } from "../../src/defaults/config.ts";
|
||||
|
||||
describe("defaultConfig", () => {
|
||||
it("包含所有五个阶段的配置", () => {
|
||||
it("包含所有四个阶段的配置", () => {
|
||||
expect(defaultConfig.stages.discuss).toBeDefined();
|
||||
expect(defaultConfig.stages.create).toBeDefined();
|
||||
expect(defaultConfig.stages.plan).toBeDefined();
|
||||
expect(defaultConfig.stages.build).toBeDefined();
|
||||
expect(defaultConfig.stages.archive).toBeDefined();
|
||||
});
|
||||
|
||||
it("包含 create 阶段的配置", () => {
|
||||
expect(defaultConfig.stages.create).toBeDefined();
|
||||
expect(defaultConfig.stages.create!.prompt).toBeTruthy();
|
||||
});
|
||||
|
||||
it("discuss 阶段有 prompt", () => {
|
||||
expect(defaultConfig.stages.discuss!.prompt).toBeTruthy();
|
||||
expect(typeof defaultConfig.stages.discuss!.prompt).toBe("string");
|
||||
|
||||
Reference in New Issue
Block a user