diff --git a/src/cli.ts b/src/cli.ts index d9223cf..c201a80 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,6 +15,8 @@ import { scanChanges } from "./core/scanner.ts"; import { UsageError, ConfigError, CommandError, InternalError, CliError } from "./cli/errors.ts"; import { printError } from "./cli/output.ts"; import { showGlobalHelp, showCommandHelp } from "./cli/help.ts"; +import type { ChangeStatus } from "./types.ts"; +import { defaultConfig } from "./defaults/config.ts"; function requireProjectRoot(): string { const root = findProjectRoot(); @@ -24,6 +26,62 @@ function requireProjectRoot(): string { return root; } +function formatChangeStatus(change: ChangeStatus): string { + const lines: string[] = []; + lines.push(`变更:${change.name}`); + + lines.push(" 规划阶段:"); + for (const doc of change.documents) { + if (doc.completed) { + lines.push(` ${doc.name}.md ✓ 已完成`); + } else { + const docConfig = defaultConfig.stages.plan?.documents.find((d) => d.name === doc.name); + const depInfo = !doc.dependMet && docConfig?.depend?.length + ? `(依赖 ${docConfig.depend.map((d) => `${d}.md`).join("、")})` + : ""; + lines.push(` ${doc.name}.md ○ 待完成${depInfo}`); + } + } + + const completedCount = change.documents.filter((d) => d.completed).length; + lines.push(` 规划进度:${completedCount}/${change.documents.length} 文档已完成`); + + if (change.buildUnlocked) { + lines.push(" 构建阶段:已解锁"); + } else { + lines.push(" 构建阶段:未解锁(需完成规划)"); + } + + if (change.taskProgress) { + lines.push(` 任务进度:${change.taskProgress.completed}/${change.taskProgress.total} 已完成`); + } + + lines.push(""); + lines.push(` 建议下一步:${suggestNextStep(change)}`); + + return lines.join("\n"); +} + +function suggestNextStep(change: ChangeStatus): string { + if (!change.planCompleted) { + const nextDoc = change.documents.find((d) => !d.completed && d.dependMet); + if (nextDoc) { + return `rune plan ${change.name} ${nextDoc.name}`; + } + return `完成前置依赖后再规划文档`; + } + + if (change.taskProgress && change.taskProgress.completed < change.taskProgress.total) { + return `rune build ${change.name}`; + } + + if (change.taskProgress && change.taskProgress.completed === change.taskProgress.total) { + return `rune archive ${change.name}`; + } + + return `rune build ${change.name}`; +} + const cli = cac("rune"); cli.command("", "").action(() => { @@ -140,20 +198,32 @@ cli.command("archive ", "归档阶段").action( }, ); -cli.command("status", "查看变更状态").action(async () => { - const root = requireProjectRoot(); - 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.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 查看所有变更", + }); + } + console.log(formatChangeStatus(change)); + } else { + if (changes.length === 0) { + console.log("当前无进行中的变更。"); + return; + } + for (const change of changes) { + console.log(formatChangeStatus(change)); + console.log("---\n"); + } + } + }, +); function handleError(e: unknown): never { if (e instanceof CliError) {