feat: 实现 Tier 1 命令级 mock agent
This commit is contained in:
119
tests/agent/tier1-command.ts
Normal file
119
tests/agent/tier1-command.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { mkdir, writeFile, readFile, rename } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { RuneConfig, DocumentConfig } from "../../src/types.ts";
|
||||
import type { AgentRunner, AgentResult } from "./runner.ts";
|
||||
import { getChangeDir, getArchiveDir } from "../../src/core/config.ts";
|
||||
import { parseTasks } from "../../src/core/task-parser.ts";
|
||||
|
||||
export class CommandLevelRunner implements AgentRunner {
|
||||
readonly tier = 1;
|
||||
|
||||
async runPlan(
|
||||
projectDir: string,
|
||||
changeName: string,
|
||||
docName: string,
|
||||
config: RuneConfig,
|
||||
): Promise<AgentResult> {
|
||||
const changeDir = getChangeDir(projectDir, changeName);
|
||||
await mkdir(changeDir, { recursive: true });
|
||||
|
||||
const planStage = config.stages.plan;
|
||||
if (!planStage) {
|
||||
throw new Error("plan 阶段未配置");
|
||||
}
|
||||
|
||||
const docConfig = planStage.documents.find((d) => d.name === docName);
|
||||
if (!docConfig) {
|
||||
throw new Error(`文档 "${docName}" 未在 plan.documents 中配置`);
|
||||
}
|
||||
|
||||
const content = this.renderDocument(docConfig, changeName);
|
||||
const filePath = join(changeDir, `${docName}.md`);
|
||||
await writeFile(filePath, content);
|
||||
|
||||
return { files: [`${docName}.md`] };
|
||||
}
|
||||
|
||||
async runBuild(
|
||||
projectDir: string,
|
||||
changeName: string,
|
||||
_config: RuneConfig,
|
||||
): Promise<AgentResult> {
|
||||
const changeDir = getChangeDir(projectDir, changeName);
|
||||
const taskPath = join(changeDir, "task.md");
|
||||
|
||||
let taskContent: string;
|
||||
try {
|
||||
taskContent = await readFile(taskPath, "utf-8");
|
||||
} catch {
|
||||
throw new Error(`变更 "${changeName}" 的 task.md 不存在,请先完成规划`);
|
||||
}
|
||||
|
||||
const tasks = parseTasks(taskContent);
|
||||
const pending = tasks.filter((t) => !t.checked);
|
||||
|
||||
if (pending.length === 0) {
|
||||
return { files: [] };
|
||||
}
|
||||
|
||||
const files: string[] = [];
|
||||
for (const task of pending) {
|
||||
const oldLine = `- [ ] ${task.text}`;
|
||||
const newLine = `- [x] ${task.text}`;
|
||||
taskContent = taskContent.replace(oldLine, newLine);
|
||||
const implFile = `${task.text
|
||||
.replace(/[^a-zA-Z\u4e00-\u9fa5]+/g, "-")
|
||||
.replace(/^-|-$/g, "")
|
||||
.toLowerCase()}.ts`;
|
||||
await writeFile(join(changeDir, implFile), `// ${task.text}\n`);
|
||||
files.push(implFile);
|
||||
}
|
||||
|
||||
await writeFile(taskPath, taskContent);
|
||||
files.push("task.md");
|
||||
|
||||
return { files };
|
||||
}
|
||||
|
||||
async runArchive(
|
||||
projectDir: string,
|
||||
changeName: string,
|
||||
_config: RuneConfig,
|
||||
): Promise<AgentResult> {
|
||||
const changeDir = getChangeDir(projectDir, changeName);
|
||||
const taskPath = join(changeDir, "task.md");
|
||||
|
||||
try {
|
||||
const taskContent = await readFile(taskPath, "utf-8");
|
||||
const tasks = parseTasks(taskContent);
|
||||
const pending = tasks.filter((t) => !t.checked);
|
||||
|
||||
if (pending.length > 0) {
|
||||
throw new Error(`变更 "${changeName}" 存在 ${pending.length} 个未完成任务,无法归档`);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message.includes("未完成任务")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const archiveDir = getArchiveDir(projectDir);
|
||||
await mkdir(archiveDir, { recursive: true });
|
||||
const dest = join(archiveDir, `${today}-${changeName}`);
|
||||
await rename(changeDir, dest);
|
||||
|
||||
return { files: [] };
|
||||
}
|
||||
|
||||
private renderDocument(doc: DocumentConfig, changeName: string): string {
|
||||
if (doc.template) {
|
||||
return doc.template.replace(/\{\{change-name\}\}/g, changeName) + "\n";
|
||||
}
|
||||
return `# ${doc.name}\n\n${doc.prompt}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
export function createRunner(): AgentRunner {
|
||||
return new CommandLevelRunner();
|
||||
}
|
||||
Reference in New Issue
Block a user