import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import type { RoleResult, StartStep, WorkflowDefinition, WorkflowMessage } from "@uncaged/nerve-core"; import { END } from "@uncaged/nerve-core"; import type { SpawnError } from "@uncaged/nerve-workflow-utils"; import { cursorAgent, isDryRun, llmExtract, nerveAgentContext, readNerveYaml, spawnSafe, } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; const HOME = process.env.HOME ?? "/home/azureuser"; const NERVE_ROOT = join(HOME, ".uncaged-nerve"); const WORKFLOWS_DIR = join(NERVE_ROOT, "workflows"); type PlannerRole = { name: string; goal: string; io: string; }; type WorkflowMeta = { planner: { userPrompt: string; workflowName: string; roles: PlannerRole[]; flowTransitions: string; validationLoopsDesign: string; externalDeps: string; dataFlow: string; planMarkdown: string; }; coder: { workflowName: string; attempt: number; files: { indexTs: boolean; packageJson: boolean; tsconfigJson: boolean }; lintPassed: boolean; buildPassed: boolean; lintLog: string; buildLog: string; cursorOutput: string; reason: string | null; }; tester: { workflowName: string; attempt: number; passed: boolean; dryRunLog: string; reason: string; }; committer: { invoked: boolean; success: boolean; branch: string | null; commitHash: string | null; pushed: boolean | null; log: string; error: string | null; }; }; const roleSchema = z .object({ name: z.string().default(""), goal: z.string().default(""), io: z.string().default(""), }) .default({ name: "", goal: "", io: "" }); const plannerExtractSchema = z.object({ workflowName: z .string() .default("") .describe("kebab-case workflow name under workflows/, e.g. issue-fixer"), roles: z.array(roleSchema).default([]), flowTransitions: z.preprocess((v) => (Array.isArray(v) ? v.join("\n") : v), z.string().default("")), validationLoopsDesign: z.preprocess((v) => (Array.isArray(v) ? v.join("\n") : v), z.string().default("")), externalDeps: z.preprocess((v) => (Array.isArray(v) ? v.join(", ") : v), z.string().default("")), dataFlow: z.preprocess((v) => (Array.isArray(v) ? v.join("\n") : v), z.string().default("")), planMarkdown: z.preprocess((v) => (Array.isArray(v) ? v.join("\n") : v), z.string().default("")), }); function getNerveYaml(): string { const result = readNerveYaml({ nerveRoot: NERVE_ROOT }); return result.ok ? result.value : "# nerve.yaml unavailable"; } function buildSenseGeneratorReference(): string { const p = join(WORKFLOWS_DIR, "sense-generator", "index.ts"); if (!existsSync(p)) { return "(missing workflows/sense-generator/index.ts)"; } return readFileSync(p, "utf-8"); } function formatSpawnFailure(error: SpawnError): string { if (error.kind === "spawn_failed") { return error.message; } if (error.kind === "timeout") { return `timeout stdout=${error.stdout.slice(0, 300)} stderr=${error.stderr.slice(0, 300)}`; } return `exit ${error.exitCode} stderr=${error.stderr.slice(0, 500)}`; } async function cfgGet(key: string): Promise { const result = await spawnSafe("cfg", ["get", key], { cwd: NERVE_ROOT, env: null, timeoutMs: 10_000, }); if (!result.ok) { return null; } const v = result.value.stdout.trim(); return v.length > 0 ? v : null; } async function resolveDashScopeProvider(): Promise<{ baseUrl: string; apiKey: string; model: string; } | null> { const apiKey = process.env.DASHSCOPE_API_KEY ?? (await cfgGet("DASHSCOPE_API_KEY")); const baseUrl = process.env.DASHSCOPE_BASE_URL ?? (await cfgGet("DASHSCOPE_BASE_URL")); const model = process.env.DASHSCOPE_MODEL ?? (await cfgGet("DASHSCOPE_MODEL")) ?? "qwen-plus"; if (!apiKey || !baseUrl) { return null; } return { apiKey, baseUrl, model }; } function lastMetaForRole(messages: WorkflowMessage[], role: string): M | null { for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === role) { return messages[i].meta as M; } } return null; } function scanGeneratedCodePitfalls(source: string): string[] { const issues: string[] = []; if (/\bawait\s+import\s*\(/.test(source)) { issues.push("Found await import() in generated workflow code"); } if (/\bimport\s*\(\s*["'`]/.test(source) && !source.includes("Dynamic import required")) { issues.push("Found undocumented dynamic import() call"); } if (!/\bexport\s+default\s+/.test(source)) { issues.push("Missing default export of WorkflowDefinition"); } return issues; } function inferWorkflowName(messages: WorkflowMessage[]): string { const tester = lastMetaForRole(messages, "tester"); if (tester !== null && tester.workflowName.trim().length > 0) { return tester.workflowName.trim(); } const coder = lastMetaForRole(messages, "coder"); if (coder !== null && coder.workflowName.trim().length > 0) { return coder.workflowName.trim(); } const planner = lastMetaForRole(messages, "planner"); if (planner !== null && planner.workflowName.trim().length > 0) { return planner.workflowName.trim(); } return ""; } async function runLintAndBuild( workflowDir: string, dry: boolean, ): Promise<{ lintPassed: boolean; buildPassed: boolean; lintLog: string; buildLog: string; reason: string | null; }> { const lintRun = await spawnSafe("pnpm", ["run", "check"], { cwd: workflowDir, env: null, timeoutMs: 300_000, dryRun: dry, }); if (!lintRun.ok) { return { lintPassed: false, buildPassed: false, lintLog: formatSpawnFailure(lintRun.error), buildLog: "", reason: `lint failed: ${formatSpawnFailure(lintRun.error)}`, }; } const lintLog = lintRun.value.stderr.trim() || lintRun.value.stdout.trim() || "(no output)"; const tscRun = await spawnSafe("npx", ["tsc", "--noEmit"], { cwd: workflowDir, env: null, timeoutMs: 300_000, dryRun: dry, }); if (!tscRun.ok) { return { lintPassed: true, buildPassed: false, lintLog, buildLog: formatSpawnFailure(tscRun.error), reason: `build failed: ${formatSpawnFailure(tscRun.error)}`, }; } const buildLog = tscRun.value.stderr.trim() || tscRun.value.stdout.trim() || "(no output)"; return { lintPassed: true, buildPassed: true, lintLog, buildLog, reason: null }; } async function runTesterDryRun( workflowName: string, planner: WorkflowMeta["planner"], coder: WorkflowMeta["coder"], dry: boolean, ): Promise<{ passed: boolean; reason: string; log: string }> { if (dry) { return { passed: true, reason: "dry-run mode", log: "[dry-run] tester skipped external checks", }; } const prompt = `You are testing a generated Nerve workflow by doing a dry-run review. Workflow: ${workflowName} Planner specification: ${JSON.stringify( { roles: planner.roles, flowTransitions: planner.flowTransitions, validationLoopsDesign: planner.validationLoopsDesign, externalDeps: planner.externalDeps, dataFlow: planner.dataFlow, }, null, 2, )} Coder output summary: ${coder.cursorOutput.slice(0, 6000)} Required checks: 1) Verify role transitions are coherent and terminates to END. 2) Verify generated workflow adheres to planner intent. 3) Verify retry loops are explicit for recoverable failures. 4) Verify no obvious runtime-breaking issue in generated index.ts. Return exactly: PASS|| or FAIL||`; const run = await cursorAgent({ prompt, mode: "ask", cwd: NERVE_ROOT, env: null, timeoutMs: null, dryRun: false, }); if (!run.ok) { return { passed: false, reason: `tester agent failed: ${formatSpawnFailure(run.error)}`, log: "", }; } const text = run.value.trim(); const pass = text.startsWith("PASS|"); const fail = text.startsWith("FAIL|"); if (!pass && !fail) { return { passed: false, reason: "tester format invalid", log: text }; } const parts = text.split("|"); const reason = parts[1] ?? "no reason"; const log = parts.slice(2).join("|").trim(); return { passed: pass, reason, log }; } async function runHermesCommitter( workflowName: string, userPrompt: string, testerReason: string, dry: boolean, ): Promise<{ invoked: boolean; success: boolean; branch: string | null; commitHash: string | null; pushed: boolean | null; log: string; error: string | null; }> { const task = `You are a git committer subagent for Nerve workflow generation. Repository root: ${NERVE_ROOT} Goal: - Commit and push generated workflow "${workflowName}". - Handle dirty worktree safely (do not discard unrelated user edits). - Detect default branch automatically. - Create a focused branch for this workflow update. - Stage only workflow files and required config updates. Context: - User prompt summary: ${userPrompt.slice(0, 500)} - Tester result: ${testerReason} Expected output format: BRANCH= COMMIT= PUSHED= LOG_START
LOG_END`; if (dry) { return { invoked: true, success: true, branch: "wf/dry-run", commitHash: null, pushed: null, log: "[dry-run] skipped hermes committer", error: null, }; } const commandAttempts: Array<{ cmd: string; args: string[] }> = [ { cmd: "hermes-agent", args: ["--cwd", NERVE_ROOT, "--task", task] }, { cmd: "hermes", args: ["agent", "--cwd", NERVE_ROOT, "--task", task] }, ]; for (const candidate of commandAttempts) { const run = await spawnSafe(candidate.cmd, candidate.args, { cwd: NERVE_ROOT, env: null, timeoutMs: 600_000, dryRun: false, }); if (!run.ok) { continue; } const text = `${run.value.stdout}\n${run.value.stderr}`; const branch = text.match(/^BRANCH=(.*)$/m)?.[1]?.trim() ?? null; const commitHash = text.match(/^COMMIT=(.*)$/m)?.[1]?.trim() ?? null; const pushedText = text.match(/^PUSHED=(.*)$/m)?.[1]?.trim().toLowerCase() ?? "unknown"; const pushed = pushedText === "true" ? true : pushedText === "false" ? false : null; return { invoked: true, success: true, branch: branch && branch.length > 0 ? branch : null, commitHash: commitHash && commitHash.length > 0 ? commitHash : null, pushed, log: text.slice(0, 20_000), error: null, }; } const fallback = await cursorAgent({ prompt: `Run this git committer task in repository ${NERVE_ROOT}:\n\n${task}`, mode: "default", cwd: NERVE_ROOT, env: null, timeoutMs: null, dryRun: false, }); if (!fallback.ok) { return { invoked: true, success: false, branch: null, commitHash: null, pushed: null, log: "", error: `hermes and fallback both failed: ${formatSpawnFailure(fallback.error)}`, }; } const out = fallback.value; const branch = out.match(/(?:branch|BRANCH)\s*[:=]\s*([^\s]+)/)?.[1] ?? null; const commitHash = out.match(/[a-f0-9]{7,40}/)?.[0] ?? null; return { invoked: true, success: true, branch, commitHash, pushed: out.toLowerCase().includes("push") ? true : null, log: out.slice(0, 20_000), error: null, }; } const workflow: WorkflowDefinition = { name: "workflow-generator", roles: { async planner( start: StartStep, _messages: WorkflowMessage[], ): Promise> { const dry = isDryRun(start); const provider = await resolveDashScopeProvider(); const userPrompt = start.content; if (provider === null) { return { content: "Cannot run planner: missing DASHSCOPE_API_KEY or DASHSCOPE_BASE_URL.", meta: { userPrompt, workflowName: "", roles: [], flowTransitions: "", validationLoopsDesign: "", externalDeps: "", dataFlow: "", planMarkdown: "", }, }; } const planningText = `Design a Nerve workflow plan from this request. ${nerveAgentContext} User request: ${userPrompt} Target root: ${NERVE_ROOT} Workflow dir root: ${WORKFLOWS_DIR} Reference structure: \`\`\`ts ${buildSenseGeneratorReference().slice(0, 18_000)} \`\`\` Current nerve.yaml: \`\`\`yaml ${getNerveYaml()} \`\`\` Produce a complete markdown plan that includes: - workflow name - roles list - flow/transitions - validation loops design - external deps - data flow`; const extracted = await llmExtract({ text: planningText, schema: plannerExtractSchema, provider, dryRun: dry, }); if (!extracted.ok) { return { content: `[planner] llmExtract failed: ${JSON.stringify(extracted.error)}`, meta: { userPrompt, workflowName: "", roles: [], flowTransitions: "", validationLoopsDesign: "", externalDeps: "", dataFlow: "", planMarkdown: "", }, }; } const value = extracted.value; const planMarkdown = value.planMarkdown.length > 0 ? value.planMarkdown : [ `# Workflow Plan`, `- workflowName: ${value.workflowName}`, ``, `## Roles`, ...value.roles.map((r) => `- ${r.name}: ${r.goal} (${r.io})`), ``, `## Flow Transitions`, value.flowTransitions, ``, `## Validation Loops`, value.validationLoopsDesign, ``, `## External Dependencies`, value.externalDeps, ``, `## Data Flow`, value.dataFlow, ].join("\n"); return { content: planMarkdown, meta: { userPrompt, workflowName: value.workflowName, roles: value.roles, flowTransitions: value.flowTransitions, validationLoopsDesign: value.validationLoopsDesign, externalDeps: value.externalDeps, dataFlow: value.dataFlow, planMarkdown, }, }; }, async coder(start: StartStep, messages: WorkflowMessage[]): Promise> { const dry = isDryRun(start); const plannerMeta = lastMetaForRole(messages, "planner"); const previousTester = lastMetaForRole(messages, "tester"); const attempt = messages.filter((m) => m.role === "coder").length + 1; if (plannerMeta === null || plannerMeta.workflowName.trim().length === 0) { return { content: "coder cannot continue: missing planner output", meta: { workflowName: "", attempt, files: { indexTs: false, packageJson: false, tsconfigJson: false }, lintPassed: false, buildPassed: false, lintLog: "", buildLog: "", cursorOutput: "", reason: "missing planner output", }, }; } const wfName = plannerMeta.workflowName.trim(); const feedback = previousTester !== null && previousTester.passed === false ? `\n\nPrevious tester failure to fix:\n${previousTester.reason}\n${previousTester.dryRunLog}\n` : ""; const codingPrompt = `Implement a Nerve workflow package under ${WORKFLOWS_DIR}/${wfName}/. Planner output: ${plannerMeta.planMarkdown} Structured planner fields: ${JSON.stringify( { workflowName: plannerMeta.workflowName, roles: plannerMeta.roles, flowTransitions: plannerMeta.flowTransitions, validationLoopsDesign: plannerMeta.validationLoopsDesign, externalDeps: plannerMeta.externalDeps, dataFlow: plannerMeta.dataFlow, }, null, 2, )} ${feedback} Required files: 1) ${WORKFLOWS_DIR}/${wfName}/index.ts 2) ${WORKFLOWS_DIR}/${wfName}/package.json 3) ${WORKFLOWS_DIR}/${wfName}/tsconfig.json 4) update ${NERVE_ROOT}/nerve.yaml with workflows.${wfName} Rules: - keep WorkflowDefinition pattern - no dynamic import() - use types (not interfaces) - include retry-aware moderator routing - write compile-ready TypeScript`; const agentRun = await cursorAgent({ prompt: codingPrompt, mode: "default", cwd: NERVE_ROOT, env: null, timeoutMs: null, dryRun: dry, }); const workflowDir = join(WORKFLOWS_DIR, wfName); const files = { indexTs: existsSync(join(workflowDir, "index.ts")), packageJson: existsSync(join(workflowDir, "package.json")), tsconfigJson: existsSync(join(workflowDir, "tsconfig.json")), }; const missing = [ files.indexTs ? null : "index.ts", files.packageJson ? null : "package.json", files.tsconfigJson ? null : "tsconfig.json", ].filter((x) => x !== null) as string[]; if (!agentRun.ok) { return { content: `coder failed: ${formatSpawnFailure(agentRun.error)}`, meta: { workflowName: wfName, attempt, files, lintPassed: false, buildPassed: false, lintLog: "", buildLog: "", cursorOutput: "", reason: formatSpawnFailure(agentRun.error), }, }; } if (missing.length > 0) { return { content: `coder failed: missing required files (${missing.join(", ")})`, meta: { workflowName: wfName, attempt, files, lintPassed: false, buildPassed: false, lintLog: "", buildLog: "", cursorOutput: agentRun.value, reason: `missing files: ${missing.join(", ")}`, }, }; } const source = readFileSync(join(workflowDir, "index.ts"), "utf-8"); const pitfalls = scanGeneratedCodePitfalls(source); if (pitfalls.length > 0) { return { content: `coder static check failed:\n${pitfalls.join("\n")}`, meta: { workflowName: wfName, attempt, files, lintPassed: false, buildPassed: false, lintLog: pitfalls.join("\n"), buildLog: "", cursorOutput: agentRun.value, reason: pitfalls.join("; "), }, }; } const check = await runLintAndBuild(workflowDir, dry); const passed = check.lintPassed && check.buildPassed; return { content: passed ? `coder PASS: lint+build ok\n\n${check.lintLog}\n\n${check.buildLog}` : `coder FAIL: ${check.reason ?? "unknown error"}`, meta: { workflowName: wfName, attempt, files, lintPassed: check.lintPassed, buildPassed: check.buildPassed, lintLog: check.lintLog, buildLog: check.buildLog, cursorOutput: agentRun.value, reason: check.reason, }, }; }, async tester(start: StartStep, messages: WorkflowMessage[]): Promise> { const dry = isDryRun(start); const plannerMeta = lastMetaForRole(messages, "planner"); const coderMeta = lastMetaForRole(messages, "coder"); const attempt = messages.filter((m) => m.role === "tester").length + 1; if (plannerMeta === null || coderMeta === null) { return { content: "tester cannot continue: missing planner/coder output", meta: { workflowName: "", attempt, passed: false, dryRunLog: "", reason: "missing planner/coder output", }, }; } if (!coderMeta.lintPassed || !coderMeta.buildPassed) { return { content: "tester blocked: coder has not passed lint+build", meta: { workflowName: coderMeta.workflowName, attempt, passed: false, dryRunLog: `${coderMeta.lintLog}\n\n${coderMeta.buildLog}`, reason: "coder did not pass lint+build", }, }; } const dryRun = await runTesterDryRun(coderMeta.workflowName, plannerMeta, coderMeta, dry); return { content: `${dryRun.passed ? "PASS" : "FAIL"} — ${dryRun.reason}`, meta: { workflowName: coderMeta.workflowName, attempt, passed: dryRun.passed, dryRunLog: dryRun.log, reason: dryRun.reason, }, }; }, async committer( start: StartStep, messages: WorkflowMessage[], ): Promise> { const dry = isDryRun(start); const planner = lastMetaForRole(messages, "planner"); const tester = lastMetaForRole(messages, "tester"); const workflowName = inferWorkflowName(messages); if (planner === null || tester === null || workflowName.length === 0) { return { content: "committer skipped: missing planner/tester/workflowName context", meta: { invoked: false, success: false, branch: null, commitHash: null, pushed: null, log: "", error: "missing committer context", }, }; } if (!tester.passed) { return { content: "committer skipped: tester not passed", meta: { invoked: false, success: false, branch: null, commitHash: null, pushed: null, log: "", error: "tester not passed", }, }; } const committed = await runHermesCommitter( workflowName, planner.userPrompt, tester.reason, dry, ); return { content: committed.success ? committed.log : `committer failed: ${committed.error ?? "unknown"}`, meta: committed, }; }, }, moderator(context) { if (context.steps.length === 0) { return "planner"; } const last = context.steps[context.steps.length - 1]; if (last.role === "planner") { if (last.meta.workflowName.trim().length > 0) return "coder"; const plannerAttempts = context.steps.filter((s) => s.role === "planner").length; return plannerAttempts < 3 ? "planner" : END; } if (last.role === "coder") { if (last.meta.lintPassed && last.meta.buildPassed) { return "tester"; } if (last.meta.attempt < 3) { return "coder"; } return END; } if (last.role === "tester") { if (last.meta.passed) { return "committer"; } if (last.meta.attempt < 3) { return "coder"; } return END; } return END; }, }; export default workflow;