From da826e20297c55fbe47070ebc77f5a6329764e54 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 9 Jun 2026 12:32:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=98=E6=9B=B4=E5=90=8D=E9=99=90?= =?UTF-8?q?=E5=88=B6=E4=B8=BA=E4=B8=AD=E6=96=87=E3=80=81=E8=8B=B1=E6=96=87?= =?UTF-8?q?=E5=92=8C=E7=9F=AD=E6=A8=AA=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli.ts | 12 ++++++++++++ tests/integration/flow.test.ts | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index c201a80..952e8ae 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -26,6 +26,15 @@ function requireProjectRoot(): string { return root; } +function validateChangeName(name: string): void { + if (!/^[\u4e00-\u9fa5a-zA-Z-]+$/.test(name)) { + throw new CommandError( + `变更名 "${name}" 包含不支持的字符`, + { hint: "变更名仅支持中文、英文和短横线(-)" }, + ); + } +} + function formatChangeStatus(change: ChangeStatus): string { const lines: string[] = []; lines.push(`变更:${change.name}`); @@ -129,6 +138,7 @@ cli.command("discuss", "讨论阶段").action(async () => { cli.command("plan ", "规划阶段").action( async (changeName: string, documentName: string) => { + validateChangeName(changeName); const root = requireProjectRoot(); const config = await loadConfig(root); const planDocs = config.stages.plan?.documents; @@ -166,6 +176,7 @@ cli.command("plan ", "规划阶段").action( cli.command("build ", "构建阶段").action( async (changeName: string) => { + validateChangeName(changeName); const root = requireProjectRoot(); const changeDir = getChangeDir(root, changeName); if (!existsSync(changeDir)) { @@ -181,6 +192,7 @@ cli.command("build ", "构建阶段").action( cli.command("archive ", "归档阶段").action( async (changeName: string) => { + validateChangeName(changeName); const root = requireProjectRoot(); const changeDir = getChangeDir(root, changeName); if (!existsSync(changeDir)) { diff --git a/tests/integration/flow.test.ts b/tests/integration/flow.test.ts index 3c8380b..feceb79 100644 --- a/tests/integration/flow.test.ts +++ b/tests/integration/flow.test.ts @@ -153,3 +153,24 @@ describe("完整 SDD 流程", () => { expect(taskDoc!.dependMet).toBe(true); }); }); + +describe("变更名校验", () => { + it("合法变更名(中文、英文、短横线)通过校验", async () => { + await runInit(TMP_DIR, ["opencode"]); + const config = await loadConfig(TMP_DIR); + await mkdir(getChangeDir(TMP_DIR, "用户-login"), { recursive: true }); + await writeFile(join(getChangeDir(TMP_DIR, "用户-login"), "design.md"), "# 设计"); + await writeFile(join(getChangeDir(TMP_DIR, "用户-login"), "task.md"), "- [ ] 任务"); + const prompt = await assemblePlanPrompt(config, TMP_DIR, "用户-login", "design"); + expect(prompt).toContain("用户-login"); + }); + + it("非法变更名(空格、下划线、特殊符号)被拒绝", () => { + const validRegex = /^[\u4e00-\u9fa5a-zA-Z-]+$/; + expect(validRegex.test("my change")).toBe(false); + expect(validRegex.test("my_change")).toBe(false); + expect(validRegex.test("my-change!")).toBe(false); + expect(validRegex.test("my.change")).toBe(false); + expect(validRegex.test("")).toBe(false); + }); +});