feat: 帮助文本支持动态命令前缀

This commit is contained in:
2026-06-09 20:22:44 +08:00
parent 589eaa120e
commit ce00751585
2 changed files with 51 additions and 42 deletions

View File

@@ -2,10 +2,10 @@ interface CommandHelpDef {
name: string;
alias: string;
description: string;
usage: string;
usageTemplate: string;
args: { name: string; desc: string }[];
detail: string;
examples: string[];
exampleArgs: string[];
}
const COMMANDS: Record<string, CommandHelpDef> = {
@@ -13,78 +13,78 @@ const COMMANDS: Record<string, CommandHelpDef> = {
name: "init",
alias: "init <工具...>",
description: "初始化 Rune 并注入工具配置",
usage: "rune init <工具...>",
usageTemplate: "init <工具...>",
args: [{ name: "<工具...>", desc: "要注入的 AI 工具,如 opencode、claude-code" }],
detail: "在当前项目中创建 .rune 目录结构,并注入指定 AI 工具的 command 和 skill 配置文件。",
examples: ["rune init opencode", "rune init opencode claude-code"],
exampleArgs: ["init opencode", "init opencode claude-code"],
},
discuss: {
name: "discuss",
alias: "discuss",
description: "讨论:生成讨论阶段提示词",
usage: "rune discuss",
usageTemplate: "discuss",
args: [],
detail: "生成 SDD 流程中讨论阶段的提示词,输出到标准输出。需要先运行 rune init 初始化项目。",
examples: ["rune discuss"],
detail: "生成 SDD 流程中讨论阶段的提示词,输出到标准输出。需要先运行 init 初始化项目。",
exampleArgs: ["discuss"],
},
plan: {
name: "plan",
alias: "plan <变更> <文档>",
description: "规划:生成指定文档的规划提示词",
usage: "rune plan <change-name> <document-name>",
usageTemplate: "plan <change-name> <document-name>",
args: [
{ name: "<change-name>", desc: '变更名称,如 "add-login"' },
{ name: "<document-name>", desc: '文档名称,如 "design"、"task"' },
],
detail:
"生成规划阶段指定文档的提示词。依赖的前置文档必须已完成。可用文档由配置中的 plan.documents 定义。",
examples: ["rune plan add-user-auth design", "rune plan add-user-auth task"],
exampleArgs: ["plan add-user-auth design", "plan add-user-auth task"],
},
build: {
name: "build",
alias: "build <名称>",
description: "构建:生成构建阶段提示词",
usage: "rune build <change-name>",
usageTemplate: "build <change-name>",
args: [{ name: "<change-name>", desc: '变更名称,如 "add-login"' }],
detail: "生成构建阶段的提示词。变更目录需已存在(通过 rune plan 创建)。",
examples: ["rune build add-user-auth", "rune build fix-memory-leak"],
detail: "生成构建阶段的提示词。变更目录需已存在(通过 plan 创建)。",
exampleArgs: ["build add-user-auth", "build fix-memory-leak"],
},
archive: {
name: "archive",
alias: "archive <名称>",
description: "归档:归档变更并生成提示词",
usage: "rune archive <change-name>",
usageTemplate: "archive <change-name>",
args: [{ name: "<change-name>", desc: '变更名称,如 "add-login"' }],
detail: "将变更目录从 .rune/changes/ 移动到 .rune/archive/,并生成归档阶段提示词。",
examples: ["rune archive add-user-auth", "rune archive fix-memory-leak"],
exampleArgs: ["archive add-user-auth", "archive fix-memory-leak"],
},
update: {
name: "update",
alias: "update <工具...>",
description: "更新:更新已注入的编辑器配置",
usage: "rune update <工具...>",
usageTemplate: "update <工具...>",
args: [{ name: "<工具...>", desc: "要更新的 AI 工具,如 opencode、claude-code" }],
detail:
"对比已注入的命令和 skill 文件,与内置版本不一致时覆盖,不存在时新建。用于升级 Rune 后同步编辑器配置。",
examples: ["rune update opencode", "rune update opencode claude-code"],
exampleArgs: ["update opencode", "update opencode claude-code"],
},
status: {
name: "status",
alias: "status [变更]",
description: "查看:展示变更状态与下一步建议",
usage: "rune status [change-name]",
usageTemplate: "status [change-name]",
args: [{ name: "[change-name]", desc: "可选,指定查看的变更名称" }],
detail: "展示各文档完成状态、依赖满足情况、规划进度和下一步建议。不传参数则显示所有变更。",
examples: ["rune status", "rune status add-user-auth"],
exampleArgs: ["status", "status add-user-auth"],
},
};
export function showGlobalHelp(): string {
export function showGlobalHelp(prefix: string = "rune"): string {
const lines: string[] = [
"Rune — 基于规格驱动开发SDD的 AI 开发辅助工具",
"",
"用法:",
" rune <命令> [参数]",
` ${prefix} <命令> [参数]`,
"",
"命令:",
];
@@ -100,19 +100,24 @@ export function showGlobalHelp(): string {
lines.push("");
lines.push("示例:");
lines.push(" rune init opencode 初始化并注入 OpenCode 配置");
lines.push(" rune update opencode 更新 OpenCode 配置");
lines.push(' rune plan add-login design 规划 "add-login" 的设计文档');
lines.push(" rune status 查看当前变更状态");
lines.push(` ${prefix} init opencode 初始化并注入 OpenCode 配置`);
lines.push(` ${prefix} update opencode 更新 OpenCode 配置`);
lines.push(` ${prefix} plan add-login design 规划 "add-login" 的设计文档`);
lines.push(` ${prefix} status 查看当前变更状态`);
return lines.join("\n");
}
export function showCommandHelp(name: string): string | null {
export function showCommandHelp(name: string, prefix: string = "rune"): string | null {
const cmd = COMMANDS[name];
if (!cmd) return null;
const lines: string[] = [`rune ${cmd.name}${cmd.description}`, "", "用法:", ` ${cmd.usage}`];
const lines: string[] = [
`${prefix} ${cmd.name}${cmd.description}`,
"",
"用法:",
` ${prefix} ${cmd.usageTemplate}`,
];
if (cmd.args.length > 0) {
lines.push("");
@@ -128,8 +133,8 @@ export function showCommandHelp(name: string): string | null {
lines.push("");
lines.push("示例:");
for (const ex of cmd.examples) {
lines.push(` ${ex}`);
for (const ex of cmd.exampleArgs) {
lines.push(` ${prefix} ${ex}`);
}
return lines.join("\n");

View File

@@ -2,9 +2,20 @@ import { describe, it, expect } from "bun:test";
import { showGlobalHelp, showCommandHelp } from "../../src/cli/help.ts";
describe("showGlobalHelp", () => {
it("包含所有命令行", () => {
it("默认前缀时包含 rune", () => {
const output = showGlobalHelp();
expect(output).toContain("rune <命令> [参数]");
expect(output).toContain("rune init opencode");
});
it("自定义前缀时使用传入前缀", () => {
const output = showGlobalHelp("bunx @lanyuanxiaoyao/rune");
expect(output).toContain("bunx @lanyuanxiaoyao/rune <命令> [参数]");
expect(output).toContain("bunx @lanyuanxiaoyao/rune init opencode");
});
it("包含所有命令行", () => {
const output = showGlobalHelp("rune");
expect(output).toContain("init <工具...>");
expect(output).toContain("discuss");
expect(output).toContain("plan <变更> <文档>");
@@ -15,37 +26,30 @@ describe("showGlobalHelp", () => {
expect(output).toContain("version");
});
it("包含示例", () => {
const output = showGlobalHelp();
expect(output).toContain("rune init opencode");
expect(output).toContain("rune plan add-login design");
expect(output).toContain("rune status");
});
it("以标题行开头", () => {
const output = showGlobalHelp();
const output = showGlobalHelp("rune");
expect(output.startsWith("Rune")).toBe(true);
});
});
describe("showCommandHelp", () => {
it("plan 命令包含用法、参数、描述、示例", () => {
const output = showCommandHelp("plan");
expect(output).toContain("rune plan <change-name> <document-name>");
const output = showCommandHelp("plan", "bunx @lanyuanxiaoyao/rune");
expect(output).toContain("bunx @lanyuanxiaoyao/rune plan <change-name> <document-name>");
expect(output).toContain("<change-name>");
expect(output).toContain("<document-name>");
expect(output).toContain("规划阶段");
expect(output).toContain("rune plan add-user-auth");
expect(output).toContain("bunx @lanyuanxiaoyao/rune plan add-user-auth");
});
it("init 命令包含工具参数说明", () => {
const output = showCommandHelp("init");
const output = showCommandHelp("init", "rune");
expect(output).toContain("rune init <工具...>");
expect(output).toContain("opencode");
});
it("不存在的命令返回 null", () => {
const output = showCommandHelp("nonexistent");
const output = showCommandHelp("nonexistent", "rune");
expect(output).toBeNull();
});
});