Files
Rune-Spec/src/core/assembler.ts

160 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import type { RuneConfig } from "../types.ts";
import { CommandError } from "../cli/errors.ts";
import { getChangeDir } from "./config.ts";
import { parseTasks, validateTaskFormat } from "./task-parser.ts";
import { applyCommandPrefix, getPmPrefix } from "./pm.ts";
export function assembleDiscussPrompt(config: RuneConfig): string {
const discuss = config.stages.discuss;
if (!discuss)
throw new CommandError("讨论阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.discuss",
});
return applyCommandPrefix(discuss.prompt, config);
}
export async function assemblePlanPrompt(
config: RuneConfig,
projectRoot: string,
changeName: string,
documentName: string,
): Promise<string> {
const plan = config.stages.plan;
if (!plan)
throw new CommandError("规划阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.plan",
});
const doc = plan.documents.find((d) => d.name === documentName);
if (!doc) {
throw new CommandError(`文档 "${documentName}" 不在配置的 plan.documents 中`, {
hint: `可用文档:${plan.documents.map((d) => d.name).join(", ")}`,
});
}
const changeDir = getChangeDir(projectRoot, changeName);
const parts: string[] = [];
parts.push(`# 规划阶段:${changeName}`);
parts.push("");
parts.push(`## 文档:${doc.name}.md`);
parts.push(doc.prompt);
const docPath = join(changeDir, `${doc.name}.md`);
if (existsSync(docPath)) {
parts.push(`\n文档已存在请先读取 ${docPath} 查看已有内容,在此基础上修订。`);
}
if (doc.template) {
parts.push(`\n### 格式模板:\n${doc.template}`);
}
if (doc.depend && doc.depend.length > 0) {
parts.push("\n---\n");
parts.push("## 依赖说明\n");
parts.push("本文档依赖以下前置文档:");
for (const dep of doc.depend) {
const depPath = join(changeDir, `${dep}.md`);
const depCompleted = existsSync(depPath);
const status = depCompleted ? "已完成" : "未完成";
parts.push(`- ${dep}.md${status}`);
}
parts.push("\n请先阅读已完成的前置文档确保内容一致。");
}
parts.push(`\n请将文档写入目录${changeDir}`);
return applyCommandPrefix(parts.join("\n"), config);
}
export async function assembleBuildPrompt(
config: RuneConfig,
projectRoot: string,
changeName: string,
): Promise<string> {
const build = config.stages.build;
if (!build) {
throw new CommandError("构建阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.build",
});
}
if (!config.metadata?.tracked) {
return applyCommandPrefix(build.prompt, config);
}
const changeDir = getChangeDir(projectRoot, changeName);
const taskPath = join(changeDir, "task.md");
let taskContent: string;
try {
taskContent = await readFile(taskPath, "utf-8");
} catch {
const prefix = getPmPrefix(config);
throw new CommandError(`变更 "${changeName}" 尚未完成规划task.md 不存在`, {
hint: `请先完成规划阶段:${prefix} plan ${changeName} task 生成任务文档`,
});
}
validateTaskFormat(taskContent);
const tasks = parseTasks(taskContent);
const pendingTasks = tasks.filter((t) => !t.checked);
if (pendingTasks.length === 0) {
return `所有任务已完成。变更 "${changeName}" 可以归档。`;
}
const parts: string[] = [];
parts.push(`# 构建阶段:${changeName}\n`);
parts.push(build.prompt);
parts.push(`\n请先读取 ${taskPath} 查看任务列表。`);
parts.push(`当前有 ${pendingTasks.length} 个待执行任务,从第一个未完成的任务开始。`);
parts.push(`完成后更新 ${taskPath} 中的 checkbox。`);
return applyCommandPrefix(parts.join("\n"), config);
}
export async function assembleArchivePrompt(
config: RuneConfig,
projectRoot: string,
changeName: string,
): Promise<string> {
const archive = config.stages.archive;
if (!archive)
throw new CommandError("归档阶段未配置", {
hint: "请在 .rune/config.yaml 中配置 stages.archive",
});
const parts: string[] = [];
parts.push(`# 归档阶段:${changeName}\n`);
if (config.metadata?.tracked) {
const changeDir = getChangeDir(projectRoot, changeName);
const taskPath = join(changeDir, "task.md");
if (existsSync(taskPath)) {
try {
const taskContent = await readFile(taskPath, "utf-8");
const tasks = parseTasks(taskContent);
const incompleteTasks = tasks.filter((t) => !t.checked);
if (incompleteTasks.length > 0) {
parts.push("## ⚠️ 警告:存在未完成的任务\n");
parts.push(`请先读取 ${taskPath} 检查是否有未完成的任务。`);
parts.push("如有未完成任务,询问用户是否确认在任务未全部完成的情况下归档。");
parts.push("如用户确认,则继续归档;否则中止并返回构建阶段。");
parts.push("");
}
} catch {
// task.md 读取失败时不追加警告
}
}
}
parts.push(archive.prompt);
return applyCommandPrefix(parts.join("\n"), config);
}