feat: CLI 入口和 init 命令
This commit is contained in:
103
src/cli.ts
Normal file
103
src/cli.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bun
|
||||
import { cac } from "cac";
|
||||
import { join } from "node:path";
|
||||
import { mkdir, rename } from "node:fs/promises";
|
||||
import { runInit } from "./commands/init.ts";
|
||||
import { findProjectRoot, loadConfig, getChangeDir, getArchiveDir } from "./core/config.ts";
|
||||
import {
|
||||
assembleDiscussPrompt,
|
||||
assemblePlanPrompt,
|
||||
assembleBuildPrompt,
|
||||
assembleArchivePrompt,
|
||||
} from "./core/assembler.ts";
|
||||
import { scanChanges } from "./core/scanner.ts";
|
||||
|
||||
const cli = cac("rune");
|
||||
|
||||
cli.command("init [...tools]", "初始化 Rune 并注入工具配置").action(
|
||||
async (tools: string[]) => {
|
||||
if (!tools || tools.length === 0) {
|
||||
console.error("请指定至少一个工具,如:rune init opencode");
|
||||
process.exit(1);
|
||||
}
|
||||
await runInit(process.cwd(), tools);
|
||||
console.log(`Rune 初始化完成,已注入工具:${tools.join(", ")}`);
|
||||
},
|
||||
);
|
||||
|
||||
cli.command("discuss", "讨论阶段").action(async () => {
|
||||
const root = findProjectRoot();
|
||||
if (!root) {
|
||||
console.error("未找到 .rune 目录,请先运行 rune init");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = await loadConfig(root);
|
||||
const prompt = assembleDiscussPrompt(config);
|
||||
console.log(prompt);
|
||||
});
|
||||
|
||||
cli
|
||||
.command("plan <change-name>", "规划阶段")
|
||||
.action(async (changeName: string) => {
|
||||
const root = findProjectRoot();
|
||||
if (!root) {
|
||||
console.error("未找到 .rune 目录,请先运行 rune init");
|
||||
process.exit(1);
|
||||
}
|
||||
await mkdir(getChangeDir(root, changeName), { recursive: true });
|
||||
const config = await loadConfig(root);
|
||||
const prompt = await assemblePlanPrompt(config, root, changeName);
|
||||
console.log(prompt);
|
||||
});
|
||||
|
||||
cli
|
||||
.command("build <change-name>", "构建阶段")
|
||||
.action(async (changeName: string) => {
|
||||
const root = findProjectRoot();
|
||||
if (!root) {
|
||||
console.error("未找到 .rune 目录,请先运行 rune init");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = await loadConfig(root);
|
||||
const prompt = await assembleBuildPrompt(config, root, changeName);
|
||||
console.log(prompt);
|
||||
});
|
||||
|
||||
cli
|
||||
.command("archive <change-name>", "归档阶段")
|
||||
.action(async (changeName: string) => {
|
||||
const root = findProjectRoot();
|
||||
if (!root) {
|
||||
console.error("未找到 .rune 目录,请先运行 rune init");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = await loadConfig(root);
|
||||
const prompt = assembleArchivePrompt(config, changeName);
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const src = getChangeDir(root, changeName);
|
||||
const dest = join(getArchiveDir(root), `${today}-${changeName}`);
|
||||
await rename(src, dest);
|
||||
console.log(prompt);
|
||||
});
|
||||
|
||||
cli.command("status", "查看变更状态").action(async () => {
|
||||
const root = findProjectRoot();
|
||||
if (!root) {
|
||||
console.error("未找到 .rune 目录,请先运行 rune init");
|
||||
process.exit(1);
|
||||
}
|
||||
const changes = await scanChanges(root);
|
||||
if (changes.length === 0) {
|
||||
console.log("当前无进行中的变更。");
|
||||
return;
|
||||
}
|
||||
for (const change of changes) {
|
||||
const progress = change.taskProgress
|
||||
? ` (${change.taskProgress.completed}/${change.taskProgress.total} 任务)`
|
||||
: "";
|
||||
console.log(`- ${change.name}${progress} [${change.documents.join(", ")}]`);
|
||||
}
|
||||
});
|
||||
|
||||
cli.help();
|
||||
cli.parse();
|
||||
39
src/commands/init.ts
Normal file
39
src/commands/init.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { stringify as stringifyYaml } from "yaml";
|
||||
import { defaultConfig } from "../defaults/config.ts";
|
||||
import { CHANGES_DIR, ARCHIVE_DIR, RUNE_DIR, CONFIG_FILE } from "../types.ts";
|
||||
import { injectOpenCode } from "../adapters/opencode.ts";
|
||||
import { injectClaudeCode } from "../adapters/claude-code.ts";
|
||||
|
||||
const SUPPORTED_TOOLS: Record<string, (root: string) => Promise<void>> = {
|
||||
opencode: injectOpenCode,
|
||||
"claude-code": injectClaudeCode,
|
||||
};
|
||||
|
||||
export async function runInit(
|
||||
projectRoot: string,
|
||||
tools: string[],
|
||||
): Promise<void> {
|
||||
for (const tool of tools) {
|
||||
if (!SUPPORTED_TOOLS[tool]) {
|
||||
throw new Error(`不支持的工具: ${tool}`);
|
||||
}
|
||||
}
|
||||
|
||||
const runeDir = join(projectRoot, RUNE_DIR);
|
||||
await mkdir(runeDir, { recursive: true });
|
||||
await mkdir(join(runeDir, CHANGES_DIR), { recursive: true });
|
||||
await mkdir(join(runeDir, ARCHIVE_DIR), { recursive: true });
|
||||
|
||||
const configPath = join(runeDir, CONFIG_FILE);
|
||||
if (!existsSync(configPath)) {
|
||||
const yaml = stringifyYaml(defaultConfig);
|
||||
await writeFile(configPath, yaml, "utf-8");
|
||||
}
|
||||
|
||||
for (const tool of tools) {
|
||||
await SUPPORTED_TOOLS[tool](projectRoot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user