小橘 57c740cdde Revert "chore(workflow): auto-generated commit"
This reverts commit 75f2768a8c7713879bb2ab564f42f24bc609338e.
2026-04-28 15:49:22 +00:00

124 lines
3.6 KiB
TypeScript

import type { Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core";
import { cursorAgent, llmExtract } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
import { readNerveConfigText } from "../../lib/nerve-read.js";
import { resolveDashScopeProvider } from "../../lib/provider.js";
import { lastMetaForRole } from "../../lib/meta-helpers.js";
import { formatSpawnFailure } from "../../lib/spawn-utils.js";
import type { IssueReaderMeta } from "../issue-reader/index.js";
import { buildPlannerPrompt } from "./prompt.js";
export type PlannerMeta = {
plan: string | null;
targetFiles: string[] | null;
testCommands: string[] | null;
riskNotes: string[] | null;
planningOk: boolean;
failureKind: "none" | "planning_failed";
failureReason: string | null;
};
const planSchema = z.object({
targetFiles: z.array(z.string().default("")).default([]),
testCommands: z.array(z.string().default("")).default([]),
riskNotes: z.array(z.string().default("")).default([]),
});
export type BuildPlannerDeps = {
nerveRoot: string;
};
export function buildPlannerRole({ nerveRoot }: BuildPlannerDeps): Role<PlannerMeta> {
return async (_start: StartStep, messages: WorkflowMessage[]): Promise<RoleResult<PlannerMeta>> => {
const issueMeta = lastMetaForRole<IssueReaderMeta>(messages, "issue-reader");
if (issueMeta === null || !issueMeta.fetchOk || issueMeta.issue === null) {
return {
content: "planner cannot continue: issue-reader has no issue data",
meta: {
plan: null,
targetFiles: null,
testCommands: null,
riskNotes: null,
planningOk: false,
failureKind: "planning_failed",
failureReason: "missing issue data",
},
};
}
const prompt = buildPlannerPrompt({
issue: issueMeta.issue,
nerveYamlText: readNerveConfigText(nerveRoot),
});
const result = await cursorAgent({
prompt,
mode: "ask",
model: "auto",
cwd: nerveRoot,
env: null,
timeoutMs: null,
});
if (!result.ok) {
return {
content: `planner failed: ${formatSpawnFailure(result.error)}`,
meta: {
plan: null,
targetFiles: null,
testCommands: null,
riskNotes: null,
planningOk: false,
failureKind: "planning_failed",
failureReason: formatSpawnFailure(result.error),
},
};
}
const plan = result.value;
const provider = await resolveDashScopeProvider(nerveRoot);
if (provider === null) {
return {
content: plan,
meta: {
plan,
targetFiles: null,
testCommands: null,
riskNotes: null,
planningOk: true,
failureKind: "none",
failureReason: null,
},
};
}
const structured = await llmExtract({ text: plan, schema: planSchema, provider });
if (!structured.ok) {
return {
content: `${plan}\n\n[llmExtract error] ${JSON.stringify(structured.error)}`,
meta: {
plan,
targetFiles: null,
testCommands: null,
riskNotes: null,
planningOk: true,
failureKind: "none",
failureReason: null,
},
};
}
return {
content: plan,
meta: {
plan,
targetFiles: structured.value.targetFiles,
testCommands: structured.value.testCommands,
riskNotes: structured.value.riskNotes,
planningOk: true,
failureKind: "none",
failureReason: null,
},
};
};
}