chore: 添加 .oxfmtrc.json 并格式化全部代码

This commit is contained in:
2026-06-09 14:22:33 +08:00
parent ebd5bb4051
commit b82f1caf0b
23 changed files with 209 additions and 373 deletions

View File

@@ -14,12 +14,8 @@ export async function injectClaudeCode(projectRoot: string): Promise<void> {
await mkdir(commandDir, { recursive: true });
const commandPath = join(commandDir, `rune-${stage}.md`);
if (!existsSync(commandPath)) {
const cmd = hasChangeName
? `rune ${stage} <变更名>`
: `rune ${stage}`;
const nameHint = hasChangeName
? "\n如果用户没有指定变更名称请向用户确认。"
: "";
const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`;
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称请向用户确认。" : "";
await writeFile(
commandPath,
`执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`,
@@ -30,10 +26,7 @@ export async function injectClaudeCode(projectRoot: string): Promise<void> {
const commandDir = join(projectRoot, COMMANDS_DIR);
const statusPath = join(commandDir, "rune-status.md");
if (!existsSync(statusPath)) {
await writeFile(
statusPath,
`执行以下命令查看变更状态:\n\`\`\`bash\nrune status\n\`\`\`\n`,
);
await writeFile(statusPath, `执行以下命令查看变更状态:\n\`\`\`bash\nrune status\n\`\`\`\n`);
}
}
@@ -44,12 +37,8 @@ export async function updateClaudeCode(projectRoot: string): Promise<void> {
const commandDir = join(projectRoot, COMMANDS_DIR);
await mkdir(commandDir, { recursive: true });
const commandPath = join(commandDir, `rune-${stage}.md`);
const cmd = hasChangeName
? `rune ${stage} <变更名>`
: `rune ${stage}`;
const nameHint = hasChangeName
? "\n如果用户没有指定变更名称请向用户确认。"
: "";
const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`;
const nameHint = hasChangeName ? "\n如果用户没有指定变更名称请向用户确认。" : "";
const newContent = `执行以下命令,将输出作为当前阶段的工作指引:\n\`\`\`bash\n${cmd}\n\`\`\`${nameHint}\n`;
await writeIfChanged(commandPath, newContent);
}

View File

@@ -81,9 +81,7 @@ function generateCommand(stage: string, hasChangeName: boolean): string {
function generateSkill(stage: string, hasChangeName: boolean): string {
const cmd = hasChangeName ? `rune ${stage} <变更名>` : `rune ${stage}`;
const nameHint = hasChangeName
? `将 <变更名> 替换为实际的变更名称。\n`
: "";
const nameHint = hasChangeName ? `将 <变更名> 替换为实际的变更名称。\n` : "";
let extraGuide = "";
if (stage === "plan") {

View File

@@ -17,7 +17,6 @@ import { printError } from "./cli/output.ts";
import { showGlobalHelp, showCommandHelp } from "./cli/help.ts";
import type { ChangeStatus, RuneConfig } from "./types.ts";
function requireProjectRoot(): string {
const root = findProjectRoot();
if (!root) {
@@ -28,10 +27,9 @@ function requireProjectRoot(): string {
export function validateChangeName(name: string): void {
if (!/^[\u4e00-\u9fa5a-zA-Z-]+$/.test(name)) {
throw new CommandError(
`变更名 "${name}" 包含不支持的字符`,
{ hint: "变更名仅支持中文、英文和短横线(-" },
);
throw new CommandError(`变更名 "${name}" 包含不支持的字符`, {
hint: "变更名仅支持中文、英文和短横线(-",
});
}
}
@@ -46,9 +44,10 @@ export function formatChangeStatus(change: ChangeStatus, config?: RuneConfig): s
lines.push(` ${doc.name}.md ✓ 已完成`);
} else {
const docConfig = planDocs?.find((d) => d.name === doc.name);
const depInfo = !doc.dependMet && docConfig?.depend?.length
? `(依赖 ${docConfig.depend.map((d) => `${d}.md`).join("、")}`
: "";
const depInfo =
!doc.dependMet && docConfig?.depend?.length
? `(依赖 ${docConfig.depend.map((d) => `${d}.md`).join("")}`
: "";
lines.push(` ${doc.name}.md ○ 待完成${depInfo}`);
}
}
@@ -117,48 +116,44 @@ cli.command("version", "显示版本号").action(() => {
console.log(`rune v${pkg.version}`);
});
cli.command("init [...tools]", "初始化 Rune 并注入工具配置").action(
async (tools: string[]) => {
if (!tools || tools.length === 0) {
throw new UsageError("请指定至少一个工具", {
usage: "rune init <工具...>",
hint: "如rune init opencode",
});
}
await runInit(process.cwd(), tools);
console.log(`Rune 初始化完成,已注入工具:${tools.join(", ")}`);
},
);
cli.command("init [...tools]", "初始化 Rune 并注入工具配置").action(async (tools: string[]) => {
if (!tools || tools.length === 0) {
throw new UsageError("请指定至少一个工具", {
usage: "rune init <工具...>",
hint: "如:rune init opencode",
});
}
await runInit(process.cwd(), tools);
console.log(`Rune 初始化完成,已注入工具:${tools.join(", ")}`);
});
cli.command("update [...tools]", "更新已注入的工具配置").action(
async (tools: string[]) => {
if (!tools || tools.length === 0) {
throw new UsageError("请指定至少一个工具", {
usage: "rune update <工具...>",
hint: "如rune update opencode",
cli.command("update [...tools]", "更新已注入的工具配置").action(async (tools: string[]) => {
if (!tools || tools.length === 0) {
throw new UsageError("请指定至少一个工具", {
usage: "rune update <工具...>",
hint: "如:rune update opencode",
});
}
const root = requireProjectRoot();
const { updateOpenCode } = await import("./adapters/opencode.ts");
const { updateClaudeCode } = await import("./adapters/claude-code.ts");
const { SUPPORTED_TOOLS } = await import("./commands/init.ts");
const updaters: Record<string, (root: string) => Promise<void>> = {
opencode: updateOpenCode,
"claude-code": updateClaudeCode,
};
for (const tool of tools) {
if (!SUPPORTED_TOOLS[tool]) {
throw new CommandError(`不支持的工具: ${tool}`, {
hint: `支持的工具: ${Object.keys(SUPPORTED_TOOLS).join(", ")}`,
});
}
const root = requireProjectRoot();
const { updateOpenCode } = await import("./adapters/opencode.ts");
const { updateClaudeCode } = await import("./adapters/claude-code.ts");
const { SUPPORTED_TOOLS } = await import("./commands/init.ts");
const updaters: Record<string, (root: string) => Promise<void>> = {
opencode: updateOpenCode,
"claude-code": updateClaudeCode,
};
for (const tool of tools) {
if (!SUPPORTED_TOOLS[tool]) {
throw new CommandError(`不支持的工具: ${tool}`, {
hint: `支持的工具: ${Object.keys(SUPPORTED_TOOLS).join(", ")}`,
});
}
}
for (const tool of tools) {
await updaters[tool](root);
}
console.log(`工具配置已更新:${tools.join(", ")}`);
},
);
}
for (const tool of tools) {
await updaters[tool](root);
}
console.log(`工具配置已更新:${tools.join(", ")}`);
});
cli.command("discuss", "讨论阶段").action(async () => {
const root = requireProjectRoot();
@@ -167,19 +162,17 @@ cli.command("discuss", "讨论阶段").action(async () => {
console.log(prompt);
});
cli.command("plan <change-name> <document-name>", "规划阶段").action(
async (changeName: string, documentName: string) => {
cli
.command("plan <change-name> <document-name>", "规划阶段")
.action(async (changeName: string, documentName: string) => {
validateChangeName(changeName);
const root = requireProjectRoot();
const config = await loadConfig(root);
const planDocs = config.stages.plan?.documents;
if (!planDocs || !planDocs.find((d) => d.name === documentName)) {
throw new CommandError(
`文档 "${documentName}" 不在配置的 plan.documents 中`,
{
hint: `可用文档:${planDocs?.map((d) => d.name).join(", ") ?? "无"}`,
},
);
throw new CommandError(`文档 "${documentName}" 不在配置的 plan.documents 中`, {
hint: `可用文档:${planDocs?.map((d) => d.name).join(", ") ?? "无"}`,
});
}
const changeDir = getChangeDir(root, changeName);
@@ -187,9 +180,7 @@ cli.command("plan <change-name> <document-name>", "规划阶段").action(
const doc = planDocs.find((d) => d.name === documentName)!;
if (doc.depend && doc.depend.length > 0) {
const missing = doc.depend.filter(
(dep) => !existsSync(join(changeDir, `${dep}.md`)),
);
const missing = doc.depend.filter((dep) => !existsSync(join(changeDir, `${dep}.md`)));
if (missing.length > 0) {
throw new CommandError(
`文档 "${documentName}" 的前置依赖未满足:${missing.map((d) => `${d}.md`).join("、")} 尚未完成`,
@@ -202,71 +193,64 @@ cli.command("plan <change-name> <document-name>", "规划阶段").action(
const prompt = await assemblePlanPrompt(config, root, changeName, documentName);
console.log(prompt);
},
);
});
cli.command("build <change-name>", "构建阶段").action(
async (changeName: string) => {
validateChangeName(changeName);
const root = requireProjectRoot();
const changeDir = getChangeDir(root, changeName);
if (!existsSync(changeDir)) {
throw new CommandError(`变更 '${changeName}' 不存在`, {
hint: `请先运行 rune plan ${changeName} 创建变更`,
cli.command("build <change-name>", "构建阶段").action(async (changeName: string) => {
validateChangeName(changeName);
const root = requireProjectRoot();
const changeDir = getChangeDir(root, changeName);
if (!existsSync(changeDir)) {
throw new CommandError(`变更 '${changeName}' 不存在`, {
hint: `请先运行 rune plan ${changeName} 创建变更`,
});
}
const config = await loadConfig(root);
const prompt = await assembleBuildPrompt(config, root, changeName);
console.log(prompt);
});
cli.command("archive <change-name>", "归档阶段").action(async (changeName: string) => {
validateChangeName(changeName);
const root = requireProjectRoot();
const changeDir = getChangeDir(root, changeName);
if (!existsSync(changeDir)) {
throw new CommandError(`变更 '${changeName}' 不存在`, {
hint: `请先运行 rune plan ${changeName} 创建变更`,
});
}
const config = await loadConfig(root);
const prompt = await assembleArchivePrompt(config, root, changeName);
const today = new Date().toISOString().slice(0, 10);
const src = changeDir;
const dest = join(getArchiveDir(root), `${today}-${changeName}`);
await rename(src, dest);
console.log(prompt);
});
cli.command("status [change-name]", "查看变更状态").action(async (changeName?: string) => {
const root = requireProjectRoot();
const config = await loadConfig(root);
const changes = await scanChanges(root, config);
if (changeName) {
const change = changes.find((c) => c.name === changeName);
if (!change) {
throw new CommandError(`变更 "${changeName}" 不存在`, {
hint: "运行 rune status 查看所有变更",
});
}
const config = await loadConfig(root);
const prompt = await assembleBuildPrompt(config, root, changeName);
console.log(prompt);
},
);
cli.command("archive <change-name>", "归档阶段").action(
async (changeName: string) => {
validateChangeName(changeName);
const root = requireProjectRoot();
const changeDir = getChangeDir(root, changeName);
if (!existsSync(changeDir)) {
throw new CommandError(`变更 '${changeName}' 不存在`, {
hint: `请先运行 rune plan ${changeName} 创建变更`,
});
console.log(formatChangeStatus(change, config));
} else {
if (changes.length === 0) {
console.log("当前无进行中的变更。");
return;
}
const config = await loadConfig(root);
const prompt = await assembleArchivePrompt(config, root, changeName);
const today = new Date().toISOString().slice(0, 10);
const src = changeDir;
const dest = join(getArchiveDir(root), `${today}-${changeName}`);
await rename(src, dest);
console.log(prompt);
},
);
cli.command("status [change-name]", "查看变更状态").action(
async (changeName?: string) => {
const root = requireProjectRoot();
const config = await loadConfig(root);
const changes = await scanChanges(root, config);
if (changeName) {
const change = changes.find((c) => c.name === changeName);
if (!change) {
throw new CommandError(`变更 "${changeName}" 不存在`, {
hint: "运行 rune status 查看所有变更",
});
}
for (const change of changes) {
console.log(formatChangeStatus(change, config));
} else {
if (changes.length === 0) {
console.log("当前无进行中的变更。");
return;
}
for (const change of changes) {
console.log(formatChangeStatus(change, config));
console.log("---\n");
}
console.log("---\n");
}
},
);
}
});
export function mapError(e: unknown): CliError {
if (e instanceof CliError) {

View File

@@ -2,10 +2,7 @@ export class CliError extends Error {
readonly hint?: string;
readonly usage?: string;
constructor(
message: string,
opts?: { hint?: string; usage?: string },
) {
constructor(message: string, opts?: { hint?: string; usage?: string }) {
super(message);
this.name = this.constructor.name;
this.hint = opts?.hint;

View File

@@ -16,10 +16,7 @@ const COMMANDS: Record<string, CommandHelpDef> = {
usage: "rune init <工具...>",
args: [{ name: "<工具...>", desc: "要注入的 AI 工具,如 opencode、claude-code" }],
detail: "在当前项目中创建 .rune 目录结构,并注入指定 AI 工具的 command 和 skill 配置文件。",
examples: [
"rune init opencode",
"rune init opencode claude-code",
],
examples: ["rune init opencode", "rune init opencode claude-code"],
},
discuss: {
name: "discuss",
@@ -36,38 +33,30 @@ const COMMANDS: Record<string, CommandHelpDef> = {
description: "规划:生成指定文档的规划提示词",
usage: "rune 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",
{ 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"],
},
build: {
name: "build",
alias: "build <名称>",
description: "构建:生成构建阶段提示词",
usage: "rune build <change-name>",
args: [{ name: "<change-name>", desc: "变更名称,如 \"add-login\"" }],
args: [{ name: "<change-name>", desc: '变更名称,如 "add-login"' }],
detail: "生成构建阶段的提示词。变更目录需已存在(通过 rune plan 创建)。",
examples: [
"rune build add-user-auth",
"rune build fix-memory-leak",
],
examples: ["rune build add-user-auth", "rune build fix-memory-leak"],
},
archive: {
name: "archive",
alias: "archive <名称>",
description: "归档:归档变更并生成提示词",
usage: "rune archive <change-name>",
args: [{ name: "<change-name>", desc: "变更名称,如 \"add-login\"" }],
args: [{ name: "<change-name>", desc: '变更名称,如 "add-login"' }],
detail: "将变更目录从 .rune/changes/ 移动到 .rune/archive/,并生成归档阶段提示词。",
examples: [
"rune archive add-user-auth",
"rune archive fix-memory-leak",
],
examples: ["rune archive add-user-auth", "rune archive fix-memory-leak"],
},
update: {
name: "update",
@@ -75,11 +64,9 @@ const COMMANDS: Record<string, CommandHelpDef> = {
description: "更新:更新已注入的编辑器配置",
usage: "rune update <工具...>",
args: [{ name: "<工具...>", desc: "要更新的 AI 工具,如 opencode、claude-code" }],
detail: "对比已注入的命令和 skill 文件,与内置版本不一致时覆盖,不存在时新建。用于升级 Rune 后同步编辑器配置。",
examples: [
"rune update opencode",
"rune update opencode claude-code",
],
detail:
"对比已注入的命令和 skill 文件,与内置版本不一致时覆盖,不存在时新建。用于升级 Rune 后同步编辑器配置。",
examples: ["rune update opencode", "rune update opencode claude-code"],
},
status: {
name: "status",
@@ -88,10 +75,7 @@ const COMMANDS: Record<string, CommandHelpDef> = {
usage: "rune status [change-name]",
args: [{ name: "[change-name]", desc: "可选,指定查看的变更名称" }],
detail: "展示各文档完成状态、依赖满足情况、规划进度和下一步建议。不传参数则显示所有变更。",
examples: [
"rune status",
"rune status add-user-auth",
],
examples: ["rune status", "rune status add-user-auth"],
},
};
@@ -118,7 +102,7 @@ export function showGlobalHelp(): string {
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 plan add-login design 规划 "add-login" 的设计文档');
lines.push(" rune status 查看当前变更状态");
return lines.join("\n");
@@ -128,12 +112,7 @@ export function showCommandHelp(name: string): string | null {
const cmd = COMMANDS[name];
if (!cmd) return null;
const lines: string[] = [
`rune ${cmd.name}${cmd.description}`,
"",
"用法:",
` ${cmd.usage}`,
];
const lines: string[] = [`rune ${cmd.name}${cmd.description}`, "", "用法:", ` ${cmd.usage}`];
if (cmd.args.length > 0) {
lines.push("");

View File

@@ -43,10 +43,7 @@ export const SUPPORTED_TOOLS: Record<string, (root: string) => Promise<void>> =
"claude-code": injectClaudeCode,
};
export async function runInit(
projectRoot: string,
tools: string[],
): Promise<void> {
export async function runInit(projectRoot: string, tools: string[]): Promise<void> {
for (const tool of tools) {
if (!SUPPORTED_TOOLS[tool]) {
throw new CommandError(`不支持的工具: ${tool}`, {

View File

@@ -8,7 +8,8 @@ import { parseTasks } from "./task-parser.ts";
export function assembleDiscussPrompt(config: RuneConfig): string {
const discuss = config.stages.discuss;
if (!discuss) throw new CommandError("讨论阶段未配置", {
if (!discuss)
throw new CommandError("讨论阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.discuss",
});
return discuss.prompt;
@@ -21,7 +22,8 @@ export async function assemblePlanPrompt(
documentName: string,
): Promise<string> {
const plan = config.stages.plan;
if (!plan) throw new CommandError("规划阶段未配置", {
if (!plan)
throw new CommandError("规划阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.plan",
});
@@ -110,9 +112,7 @@ export async function assembleBuildPrompt(
for (const task of pendingTasks) {
parts.push(`- [ ] ${task.text}`);
}
parts.push(
`\n请从第一个待执行任务开始。完成后更新 ${taskPath} 中的 checkbox。`,
);
parts.push(`\n请从第一个待执行任务开始。完成后更新 ${taskPath} 中的 checkbox。`);
return parts.join("\n");
}
@@ -123,7 +123,8 @@ export async function assembleArchivePrompt(
changeName: string,
): Promise<string> {
const archive = config.stages.archive;
if (!archive) throw new CommandError("归档阶段未配置", {
if (!archive)
throw new CommandError("归档阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.archive",
});

View File

@@ -7,9 +7,7 @@ import { ConfigError } from "../cli/errors.ts";
import type { RuneConfig } from "../types.ts";
import { RUNE_DIR, CONFIG_FILE, CHANGES_DIR, ARCHIVE_DIR } from "../types.ts";
export function findProjectRoot(
startDir: string = process.cwd(),
): string | null {
export function findProjectRoot(startDir: string = process.cwd()): string | null {
let dir = startDir;
while (true) {
if (existsSync(join(dir, RUNE_DIR))) {
@@ -49,9 +47,7 @@ export function validateConfig(config: RuneConfig): void {
throw new ConfigError(`文档 "${doc.name}" 不能依赖自身`);
}
if (!docNames.has(dep)) {
throw new ConfigError(
`文档 "${doc.name}" 依赖 "${dep}" 不存在于 plan.documents 中`,
);
throw new ConfigError(`文档 "${doc.name}" 依赖 "${dep}" 不存在于 plan.documents 中`);
}
}
}
@@ -81,9 +77,7 @@ export function validateConfig(config: RuneConfig): void {
for (const doc of plan.documents) {
path.length = 0;
if (hasCycle(doc.name)) {
throw new ConfigError(
`文档间存在循环依赖:${path.join(" → ")}`,
);
throw new ConfigError(`文档间存在循环依赖:${path.join(" → ")}`);
}
}
}

View File

@@ -29,9 +29,7 @@ export async function scanChanges(
const fileName = `${docConfig.name}.md`;
const completed = mdFiles.has(fileName);
const deps = docConfig.depend ?? [];
const dependMet =
deps.length === 0 ||
deps.every((dep) => mdFiles.has(`${dep}.md`));
const dependMet = deps.length === 0 || deps.every((dep) => mdFiles.has(`${dep}.md`));
return { name: docConfig.name, completed, dependMet };
});
} else {
@@ -64,8 +62,7 @@ export async function scanChanges(
taskProgress,
});
}
} catch {
}
} catch {}
return results;
}