feat: opencode/claude-code 统一 command 格式、智能识别引导、新增 intro/create、移除 status
This commit is contained in:
@@ -6,33 +6,40 @@ import { writeIfChanged } from "./utils.ts";
|
||||
|
||||
const COMMANDS_DIR = ".claude/commands";
|
||||
|
||||
const STAGES_WITH_CHANGE_NAME = new Set(["create", "plan", "build", "archive"]);
|
||||
|
||||
function buildSmartGuide(command: string): string {
|
||||
return `如果用户没有指定变更名称,请按以下步骤智能识别:
|
||||
1. 运行 \`${command} status\` 查看当前所有变更
|
||||
2. 如果只有一个变更,直接使用该变更名
|
||||
3. 如果有多个变更,根据上下文推断最可能的变更
|
||||
4. 如果无法确定,向用户确认`;
|
||||
}
|
||||
|
||||
export async function injectClaudeCode(
|
||||
projectRoot: string,
|
||||
command: string = "rune",
|
||||
): Promise<void> {
|
||||
for (const stage of STAGES) {
|
||||
const hasChangeName = stage !== "discuss";
|
||||
const hasChangeName = STAGES_WITH_CHANGE_NAME.has(stage);
|
||||
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||
const smartGuide = hasChangeName ? `\n${buildSmartGuide(command)}\n` : "";
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
await mkdir(commandDir, { recursive: true });
|
||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
||||
if (!existsSync(commandPath)) {
|
||||
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : "";
|
||||
await writeFile(
|
||||
commandPath,
|
||||
`执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`,
|
||||
`执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${smartGuide}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
const statusPath = join(commandDir, "rune-status.md");
|
||||
if (!existsSync(statusPath)) {
|
||||
await writeFile(
|
||||
statusPath,
|
||||
`执行以下命令查看变更状态:\n\`\`\`bash\n${command} status\n\`\`\`\n`,
|
||||
);
|
||||
const introCommandPath = join(projectRoot, COMMANDS_DIR, "rune-intro.md");
|
||||
if (!existsSync(introCommandPath)) {
|
||||
await mkdir(join(projectRoot, COMMANDS_DIR), { recursive: true });
|
||||
await writeFile(introCommandPath, generateIntroCommand(command));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,20 +48,36 @@ export async function updateClaudeCode(
|
||||
command: string = "rune",
|
||||
): Promise<void> {
|
||||
for (const stage of STAGES) {
|
||||
const hasChangeName = stage !== "discuss";
|
||||
const hasChangeName = STAGES_WITH_CHANGE_NAME.has(stage);
|
||||
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||
const smartGuide = hasChangeName ? `\n${buildSmartGuide(command)}\n` : "";
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
await mkdir(commandDir, { recursive: true });
|
||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
||||
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称,请向用户确认。" : "";
|
||||
const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`;
|
||||
const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${smartGuide}\n`;
|
||||
await writeIfChanged(commandPath, newContent);
|
||||
}
|
||||
|
||||
const statusPath = join(projectRoot, COMMANDS_DIR, "rune-status.md");
|
||||
await writeIfChanged(
|
||||
statusPath,
|
||||
`执行以下命令查看变更状态:\n\`\`\`bash\n${command} status\n\`\`\`\n`,
|
||||
);
|
||||
const introCommandPath = join(projectRoot, COMMANDS_DIR, "rune-intro.md");
|
||||
await writeIfChanged(introCommandPath, generateIntroCommand(command));
|
||||
}
|
||||
|
||||
function generateIntroCommand(command: string): string {
|
||||
return `Rune 是基于规格驱动开发(SDD)的 AI 开发辅助工具。SDD 工作流程:
|
||||
|
||||
discuss → create → plan → build → archive
|
||||
|
||||
可用命令:
|
||||
- /rune-discuss — 自由讨论需求和方案
|
||||
- /rune-create — 创建变更目录
|
||||
- /rune-plan — 生成设计文档和任务清单
|
||||
- /rune-build — 按任务清单逐步实现
|
||||
- /rune-archive — 归档已完成的变更
|
||||
|
||||
查看当前状态:
|
||||
\`\`\`bash
|
||||
${command} status
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -7,80 +7,60 @@ import { writeIfChanged } from "./utils.ts";
|
||||
const COMMANDS_DIR = ".opencode/commands";
|
||||
const SKILLS_DIR = ".opencode/skills";
|
||||
|
||||
const STAGES_WITH_CHANGE_NAME = new Set(["create", "plan", "build", "archive"]);
|
||||
|
||||
export async function injectOpenCode(projectRoot: string, command: string = "rune"): Promise<void> {
|
||||
for (const stage of STAGES) {
|
||||
const hasChangeName = stage !== "discuss";
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
await mkdir(commandDir, { recursive: true });
|
||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
||||
if (!existsSync(commandPath)) {
|
||||
await writeFile(commandPath, generateCommand(stage, hasChangeName));
|
||||
await writeFile(commandPath, generateCommand(stage));
|
||||
}
|
||||
|
||||
const skillStageDir = join(projectRoot, SKILLS_DIR, `rune-${stage}`);
|
||||
await mkdir(skillStageDir, { recursive: true });
|
||||
const skillPath = join(skillStageDir, "SKILL.md");
|
||||
if (!existsSync(skillPath)) {
|
||||
await writeFile(skillPath, generateSkill(stage, hasChangeName, command));
|
||||
await writeFile(skillPath, generateSkill(stage, command));
|
||||
}
|
||||
}
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
const statusCommandPath = join(commandDir, "rune-status.md");
|
||||
if (!existsSync(statusCommandPath)) {
|
||||
await writeFile(statusCommandPath, generateStatusCommand());
|
||||
}
|
||||
|
||||
const statusSkillDir = join(projectRoot, SKILLS_DIR, "rune-status");
|
||||
await mkdir(statusSkillDir, { recursive: true });
|
||||
const statusSkillPath = join(statusSkillDir, "SKILL.md");
|
||||
if (!existsSync(statusSkillPath)) {
|
||||
await writeFile(statusSkillPath, generateStatusSkill(command));
|
||||
const introSkillDir = join(projectRoot, SKILLS_DIR, "rune-intro");
|
||||
await mkdir(introSkillDir, { recursive: true });
|
||||
const introSkillPath = join(introSkillDir, "SKILL.md");
|
||||
if (!existsSync(introSkillPath)) {
|
||||
await writeFile(introSkillPath, generateIntroSkill(command));
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateOpenCode(projectRoot: string, command: string = "rune"): Promise<void> {
|
||||
for (const stage of STAGES) {
|
||||
const hasChangeName = stage !== "discuss";
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
await mkdir(commandDir, { recursive: true });
|
||||
const commandPath = join(commandDir, `rune-${stage}.md`);
|
||||
const newCommand = generateCommand(stage, hasChangeName);
|
||||
await writeIfChanged(commandPath, newCommand);
|
||||
await writeIfChanged(commandPath, generateCommand(stage));
|
||||
|
||||
const skillStageDir = join(projectRoot, SKILLS_DIR, `rune-${stage}`);
|
||||
await mkdir(skillStageDir, { recursive: true });
|
||||
const skillPath = join(skillStageDir, "SKILL.md");
|
||||
const newSkill = generateSkill(stage, hasChangeName, command);
|
||||
await writeIfChanged(skillPath, newSkill);
|
||||
await writeIfChanged(skillPath, generateSkill(stage, command));
|
||||
}
|
||||
|
||||
const commandDir = join(projectRoot, COMMANDS_DIR);
|
||||
const statusCommandPath = join(commandDir, "rune-status.md");
|
||||
await writeIfChanged(statusCommandPath, generateStatusCommand());
|
||||
|
||||
const statusSkillDir = join(projectRoot, SKILLS_DIR, "rune-status");
|
||||
await mkdir(statusSkillDir, { recursive: true });
|
||||
const statusSkillPath = join(statusSkillDir, "SKILL.md");
|
||||
await writeIfChanged(statusSkillPath, generateStatusSkill(command));
|
||||
const introSkillDir = join(projectRoot, SKILLS_DIR, "rune-intro");
|
||||
await mkdir(introSkillDir, { recursive: true });
|
||||
const introSkillPath = join(introSkillDir, "SKILL.md");
|
||||
await writeIfChanged(introSkillPath, generateIntroSkill(command));
|
||||
}
|
||||
|
||||
function generateCommand(stage: string, hasChangeName: boolean): string {
|
||||
if (hasChangeName) {
|
||||
return `请调用 rune-${stage} skill 执行 ${stage} 阶段。
|
||||
|
||||
如果用户没有指定变更名称,请向用户确认要操作的变更名称。
|
||||
`;
|
||||
}
|
||||
function generateCommand(stage: string): string {
|
||||
return `请调用 rune-${stage} skill 执行 ${stage} 阶段。
|
||||
`;
|
||||
}
|
||||
|
||||
function generateSkill(stage: string, hasChangeName: boolean, command: string): string {
|
||||
function generateSkill(stage: string, command: string): string {
|
||||
const hasChangeName = STAGES_WITH_CHANGE_NAME.has(stage);
|
||||
const cmd = hasChangeName ? `${command} ${stage} <变更名>` : `${command} ${stage}`;
|
||||
const nameHint = hasChangeName ? `将 <变更名> 替换为实际的变更名称。\n` : "";
|
||||
|
||||
let extraGuide = "";
|
||||
if (stage === "plan") {
|
||||
@@ -89,11 +69,22 @@ function generateSkill(stage: string, hasChangeName: boolean, command: string):
|
||||
|
||||
const descriptionMap: Record<string, string> = {
|
||||
discuss: "Use when 需要进入 SDD 讨论阶段,自由讨论需求和架构方案",
|
||||
create: "Use when 需要创建变更目录,为 SDD 流程准备变更工作区",
|
||||
plan: "Use when 需要进入 SDD 规划阶段,生成设计文档和任务清单",
|
||||
build: "Use when 需要进入 SDD 构建阶段,按任务清单逐步实现变更",
|
||||
archive: "Use when 需要进入 SDD 归档阶段,确认变更完成并归档",
|
||||
};
|
||||
|
||||
let smartGuide = "";
|
||||
if (hasChangeName) {
|
||||
smartGuide = `如果用户没有指定变更名称,请按以下步骤智能识别:
|
||||
1. 运行 \`${command} status\` 查看当前所有变更
|
||||
2. 如果只有一个变更,直接使用该变更名
|
||||
3. 如果有多个变更,根据上下文推断最可能的变更
|
||||
4. 如果无法确定,向用户确认
|
||||
`;
|
||||
}
|
||||
|
||||
return `---
|
||||
name: rune-${stage}
|
||||
description: ${descriptionMap[stage] ?? `Use when 需要执行 SDD ${stage} 阶段`}
|
||||
@@ -107,29 +98,49 @@ description: ${descriptionMap[stage] ?? `Use when 需要执行 SDD ${stage} 阶
|
||||
${cmd}
|
||||
\`\`\`
|
||||
|
||||
${nameHint}${extraGuide}将命令输出作为工作指引,执行当前阶段的工作。
|
||||
${smartGuide}${extraGuide}将命令输出作为工作指引,执行当前阶段的工作。
|
||||
`;
|
||||
}
|
||||
|
||||
function generateStatusCommand(): string {
|
||||
return `请调用 rune-status skill 查看当前所有变更状态。
|
||||
`;
|
||||
}
|
||||
|
||||
function generateStatusSkill(command: string): string {
|
||||
function generateIntroSkill(command: string): string {
|
||||
return `---
|
||||
name: rune-status
|
||||
description: Use when 需要查看当前所有 Rune 变更的状态和任务进度
|
||||
name: rune-intro
|
||||
description: Use when 用户首次接触 Rune,需要了解 SDD 工作流程和使用方式
|
||||
---
|
||||
|
||||
# 状态查看
|
||||
# Rune 简介
|
||||
|
||||
执行以下命令:
|
||||
Rune 是基于规格驱动开发(SDD)的 AI 开发辅助工具。它通过结构化的阶段流程,帮助 AI 编辑器和开发者高效协作。
|
||||
|
||||
## SDD 工作流程
|
||||
|
||||
\`\`\`
|
||||
discuss → create → plan → build → archive
|
||||
探索 创建 规划 构建 归档
|
||||
\`\`\`
|
||||
|
||||
## 可用命令
|
||||
|
||||
| 阶段 | 编辑器命令 | 说明 |
|
||||
|------|-----------|------|
|
||||
| discuss | /rune-discuss | 自由讨论需求和方案 |
|
||||
| create | /rune-create | 创建变更目录 |
|
||||
| plan | /rune-plan | 生成设计文档和任务清单 |
|
||||
| build | /rune-build | 按任务清单逐步实现 |
|
||||
| archive | /rune-archive | 归档已完成的变更 |
|
||||
|
||||
查看当前状态:
|
||||
|
||||
\`\`\`bash
|
||||
${command} status
|
||||
\`\`\`
|
||||
|
||||
将命令输出展示给用户。
|
||||
## 快速开始
|
||||
|
||||
1. 使用 /rune-discuss 进入讨论,自由探索需求
|
||||
2. 使用 /rune-create 创建变更目录
|
||||
3. 使用 /rune-plan 生成设计文档和任务清单
|
||||
4. 使用 /rune-build 按任务顺序实现功能
|
||||
5. 使用 /rune-archive 归档已完成的变更
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user