chore: gitignore dist/ for workflow-generator

This commit is contained in:
小橘 2026-04-28 08:50:47 +00:00
parent a469f30b42
commit bf77e3452a
2 changed files with 1 additions and 846 deletions

View File

@ -0,0 +1 @@
dist/

View File

@ -1,846 +0,0 @@
// index.ts
import { join as join4 } from "node:path";
// build.ts
import { join as join3 } from "node:path";
// roles/planner/index.ts
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { isDryRun, llmExtract, nerveAgentContext, readNerveYaml } from "@uncaged/nerve-workflow-utils";
import { z } from "zod";
// roles/planner/prompt.ts
function plannerPrompt({
nerveAgentContext: nerveAgentContext2,
userPrompt,
nerveRoot,
workflowsDir,
senseGeneratorReference,
nerveYaml
}) {
const content = `Design a Nerve workflow plan from this request.
${nerveAgentContext2}
User request:
${userPrompt}
Target root: ${nerveRoot}
Workflow dir root: ${workflowsDir}
Reference structure:
\`\`\`ts
${senseGeneratorReference.slice(0, 18e3)}
\`\`\`
Current nerve.yaml:
\`\`\`yaml
${nerveYaml}
\`\`\`
Produce a complete markdown plan that includes:
- workflow name
- roles list
- flow/transitions
- validation loops design
- external deps
- data flow`;
return [{ role: "user", content }];
}
// roles/planner/index.ts
var roleSchema = z.object({
name: z.string().default(""),
goal: z.string().default(""),
io: z.string().default("")
}).default({ name: "", goal: "", io: "" });
var plannerMetaSchema = z.object({
userPrompt: z.string().default(""),
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(nerveRoot) {
const result = readNerveYaml({ nerveRoot });
return result.ok ? result.value : "# nerve.yaml unavailable";
}
function getSenseGeneratorReference(workflowsDir) {
const p = join(workflowsDir, "sense-generator", "index.ts");
if (!existsSync(p)) {
return "(missing workflows/sense-generator/index.ts)";
}
return readFileSync(p, "utf-8");
}
function buildPlannerRole({
provider,
nerveRoot,
workflowsDir
}) {
return async (start, _messages) => {
const dry = isDryRun(start);
const userPrompt = start.content;
const messages = plannerPrompt({
nerveAgentContext,
userPrompt,
nerveRoot,
workflowsDir,
senseGeneratorReference: getSenseGeneratorReference(workflowsDir),
nerveYaml: getNerveYaml(nerveRoot)
});
const extracted = await llmExtract({
text: messages.map((m) => m.content).join("\n"),
schema: plannerMetaSchema,
provider,
dryRun: dry
});
const emptyMeta = {
userPrompt,
workflowName: "",
roles: [],
flowTransitions: "",
validationLoopsDesign: "",
externalDeps: "",
dataFlow: "",
planMarkdown: ""
};
if (!extracted.ok) {
return {
content: `[planner] llmExtract failed: ${JSON.stringify(extracted.error)}`,
meta: emptyMeta
};
}
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
}
};
};
}
// roles/coder/index.ts
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
import { join as join2 } from "node:path";
import { cursorAgent, isDryRun as isDryRun2, spawnSafe } from "@uncaged/nerve-workflow-utils";
import { z as z2 } from "zod";
// roles/coder/prompt.ts
function coderPrompt({
workflowsDir,
wfName,
planMarkdown,
plannerStructured,
feedback,
nerveRoot
}) {
return `Implement a Nerve workflow package under ${workflowsDir}/${wfName}/.
Planner output:
${planMarkdown}
Structured planner fields:
${JSON.stringify(plannerStructured, null, 2)}
${feedback}
Required files:
1) ${workflowsDir}/${wfName}/index.ts
2) ${workflowsDir}/${wfName}/package.json
3) ${workflowsDir}/${wfName}/tsconfig.json
4) update ${nerveRoot}/nerve.yaml with workflows.${wfName}
Rules:
- keep WorkflowDefinition<WorkflowMeta> pattern
- no dynamic import()
- use types (not interfaces)
- include retry-aware moderator routing
- write compile-ready TypeScript`;
}
// roles/coder/index.ts
var coderMetaSchema = z2.object({
workflowName: z2.string().default(""),
attempt: z2.number().default(1),
files: z2.object({
indexTs: z2.boolean().default(false),
packageJson: z2.boolean().default(false),
tsconfigJson: z2.boolean().default(false)
}).default({ indexTs: false, packageJson: false, tsconfigJson: false }),
lintPassed: z2.boolean().default(false),
buildPassed: z2.boolean().default(false),
lintLog: z2.string().default(""),
buildLog: z2.string().default(""),
cursorOutput: z2.string().default(""),
reason: z2.string().nullable().default(null)
});
function formatSpawnFailure(error) {
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)}`;
}
function scanGeneratedCodePitfalls(source) {
const issues = [];
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;
}
async function runLintAndBuild(workflowDir, dry) {
const lintRun = await spawnSafe("pnpm", ["run", "check"], {
cwd: workflowDir,
env: null,
timeoutMs: 3e5,
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: 3e5,
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 };
}
function lastMetaForRole(messages, role) {
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === role) {
return messages[i].meta;
}
}
return null;
}
function buildCoderRole({ nerveRoot, workflowsDir }) {
return async (start, messages) => {
const dry = isDryRun2(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 ? `
Previous tester failure to fix:
${previousTester.reason}
${previousTester.dryRunLog}
` : "";
const prompt = coderPrompt({
workflowsDir,
wfName,
planMarkdown: plannerMeta.planMarkdown,
plannerStructured: {
workflowName: plannerMeta.workflowName,
roles: plannerMeta.roles,
flowTransitions: plannerMeta.flowTransitions,
validationLoopsDesign: plannerMeta.validationLoopsDesign,
externalDeps: plannerMeta.externalDeps,
dataFlow: plannerMeta.dataFlow
},
feedback,
nerveRoot
});
const agentRun = await cursorAgent({
prompt,
mode: "default",
cwd: nerveRoot,
env: null,
timeoutMs: null,
dryRun: dry
});
const workflowDir = join2(workflowsDir, wfName);
const files = {
indexTs: existsSync2(join2(workflowDir, "index.ts")),
packageJson: existsSync2(join2(workflowDir, "package.json")),
tsconfigJson: existsSync2(join2(workflowDir, "tsconfig.json"))
};
const missing = [
files.indexTs ? null : "index.ts",
files.packageJson ? null : "package.json",
files.tsconfigJson ? null : "tsconfig.json"
].filter((x) => x !== null);
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 = readFileSync2(join2(workflowDir, "index.ts"), "utf-8");
const pitfalls = scanGeneratedCodePitfalls(source);
if (pitfalls.length > 0) {
return {
content: `coder static check failed:
${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
${check.lintLog}
${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
}
};
};
}
// roles/tester/index.ts
import { cursorAgent as cursorAgent2, isDryRun as isDryRun3 } from "@uncaged/nerve-workflow-utils";
import { z as z3 } from "zod";
// roles/tester/prompt.ts
function testerPrompt({
workflowName,
plannerSpec,
coderOutput,
nerveRoot: _nerveRoot
}) {
return `You are testing a generated Nerve workflow by doing a dry-run review.
Workflow: ${workflowName}
Planner specification:
${JSON.stringify(plannerSpec, null, 2)}
Coder output summary:
${coderOutput.slice(0, 6e3)}
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|<reason>|<compact markdown log>
or
FAIL|<reason>|<compact markdown log>`;
}
// roles/tester/index.ts
var testerMetaSchema = z3.object({
workflowName: z3.string().default(""),
attempt: z3.number().default(1),
passed: z3.boolean().default(false),
dryRunLog: z3.string().default(""),
reason: z3.string().default("")
});
function formatSpawnFailure2(error) {
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)}`;
}
function lastMetaForRole2(messages, role) {
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === role) {
return messages[i].meta;
}
}
return null;
}
function buildTesterRole({ nerveRoot }) {
return async (start, messages) => {
const dry = isDryRun3(start);
const plannerMeta = lastMetaForRole2(messages, "planner");
const coderMeta = lastMetaForRole2(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}
${coderMeta.buildLog}`,
reason: "coder did not pass lint+build"
}
};
}
if (dry) {
return {
content: "PASS \u2014 dry-run mode",
meta: {
workflowName: coderMeta.workflowName,
attempt,
passed: true,
dryRunLog: "[dry-run] tester skipped external checks",
reason: "dry-run mode"
}
};
}
const prompt = testerPrompt({
workflowName: coderMeta.workflowName,
plannerSpec: {
roles: plannerMeta.roles,
flowTransitions: plannerMeta.flowTransitions,
validationLoopsDesign: plannerMeta.validationLoopsDesign,
externalDeps: plannerMeta.externalDeps,
dataFlow: plannerMeta.dataFlow
},
coderOutput: coderMeta.cursorOutput,
nerveRoot
});
const run = await cursorAgent2({
prompt,
mode: "ask",
cwd: nerveRoot,
env: null,
timeoutMs: null,
dryRun: false
});
if (!run.ok) {
return {
content: "tester agent failed",
meta: {
workflowName: coderMeta.workflowName,
attempt,
passed: false,
dryRunLog: "",
reason: `tester agent failed: ${formatSpawnFailure2(run.error)}`
}
};
}
const text = run.value.trim();
const pass = text.startsWith("PASS|");
const fail = text.startsWith("FAIL|");
if (!pass && !fail) {
return {
content: "tester format invalid",
meta: {
workflowName: coderMeta.workflowName,
attempt,
passed: false,
dryRunLog: text,
reason: "tester format invalid"
}
};
}
const parts = text.split("|");
const reason = parts[1] ?? "no reason";
const log = parts.slice(2).join("|").trim();
return {
content: `${pass ? "PASS" : "FAIL"} \u2014 ${reason}`,
meta: {
workflowName: coderMeta.workflowName,
attempt,
passed: pass,
dryRunLog: log,
reason
}
};
};
}
// roles/committer/index.ts
import { cursorAgent as cursorAgent3, isDryRun as isDryRun4, spawnSafe as spawnSafe2 } from "@uncaged/nerve-workflow-utils";
import { z as z4 } from "zod";
// roles/committer/prompt.ts
function committerPrompt({
nerveRoot,
workflowName,
userPrompt,
testerReason
}) {
return `You are a git committer subagent for Nerve workflow generation.
Repository root: ${nerveRoot}
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=<branch-or-empty>
COMMIT=<hash-or-empty>
PUSHED=<true|false|unknown>
LOG_START
<details>
LOG_END`;
}
// roles/committer/index.ts
var committerMetaSchema = z4.object({
invoked: z4.boolean().default(false),
success: z4.boolean().default(false),
branch: z4.string().nullable().default(null),
commitHash: z4.string().nullable().default(null),
pushed: z4.boolean().nullable().default(null),
log: z4.string().default(""),
error: z4.string().nullable().default(null)
});
function formatSpawnFailure3(error) {
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)}`;
}
function lastMetaForRole3(messages, role) {
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === role) {
return messages[i].meta;
}
}
return null;
}
function inferWorkflowName(messages) {
const tester = lastMetaForRole3(messages, "tester");
if (tester !== null && tester.workflowName.trim().length > 0) {
return tester.workflowName.trim();
}
const coder = lastMetaForRole3(messages, "coder");
if (coder !== null && coder.workflowName.trim().length > 0) {
return coder.workflowName.trim();
}
const planner = lastMetaForRole3(messages, "planner");
if (planner !== null && planner.workflowName.trim().length > 0) {
return planner.workflowName.trim();
}
return "";
}
async function runHermesCommitter(task, nerveRoot) {
const commandAttempts = [
{ cmd: "hermes-agent", args: ["--cwd", nerveRoot, "--task", task] },
{ cmd: "hermes", args: ["agent", "--cwd", nerveRoot, "--task", task] }
];
for (const candidate of commandAttempts) {
const run = await spawnSafe2(candidate.cmd, candidate.args, {
cwd: nerveRoot,
env: null,
timeoutMs: 6e5,
dryRun: false
});
if (!run.ok) {
continue;
}
const text = `${run.value.stdout}
${run.value.stderr}`;
const branch2 = text.match(/^BRANCH=(.*)$/m)?.[1]?.trim() ?? null;
const commitHash2 = 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: branch2 && branch2.length > 0 ? branch2 : null,
commitHash: commitHash2 && commitHash2.length > 0 ? commitHash2 : null,
pushed,
log: text.slice(0, 2e4),
error: null
};
}
const fallback = await cursorAgent3({
prompt: `Run this git committer task in repository ${nerveRoot}:
${task}`,
mode: "default",
cwd: nerveRoot,
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: ${formatSpawnFailure3(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, 2e4),
error: null
};
}
function buildCommitterRole({ nerveRoot }) {
return async (start, messages) => {
const dry = isDryRun4(start);
const planner = lastMetaForRole3(messages, "planner");
const tester = lastMetaForRole3(messages, "tester");
const workflowName = inferWorkflowName(messages);
const skipMeta = {
invoked: false,
success: false,
branch: null,
commitHash: null,
pushed: null,
log: "",
error: null
};
if (planner === null || tester === null || workflowName.length === 0) {
return {
content: "committer skipped: missing planner/tester/workflowName context",
meta: { ...skipMeta, error: "missing committer context" }
};
}
if (!tester.passed) {
return {
content: "committer skipped: tester not passed",
meta: { ...skipMeta, error: "tester not passed" }
};
}
if (dry) {
return {
content: "[dry-run] skipped hermes committer",
meta: {
invoked: true,
success: true,
branch: "wf/dry-run",
commitHash: null,
pushed: null,
log: "[dry-run] skipped hermes committer",
error: null
}
};
}
const task = committerPrompt({
nerveRoot,
workflowName,
userPrompt: planner.userPrompt,
testerReason: tester.reason
});
const committed = await runHermesCommitter(task, nerveRoot);
return {
content: committed.success ? committed.log : `committer failed: ${committed.error ?? "unknown"}`,
meta: committed
};
};
}
// moderator.ts
import { END } from "@uncaged/nerve-core";
var 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;
};
// build.ts
function buildWorkflowGenerator({
provider,
nerveRoot
}) {
const workflowsDir = join3(nerveRoot, "workflows");
return {
name: "workflow-generator",
roles: {
planner: buildPlannerRole({ provider, nerveRoot, workflowsDir }),
coder: buildCoderRole({ nerveRoot, workflowsDir }),
tester: buildTesterRole({ nerveRoot }),
committer: buildCommitterRole({ nerveRoot })
},
moderator
};
}
// index.ts
var HOME = process.env.HOME ?? "/home/azureuser";
var NERVE_ROOT = join4(HOME, ".uncaged-nerve");
var apiKey = process.env.DASHSCOPE_API_KEY;
var baseUrl = process.env.DASHSCOPE_BASE_URL;
var model = process.env.DASHSCOPE_MODEL ?? "qwen-plus";
if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
}
var workflow = buildWorkflowGenerator({
provider: { apiKey, baseUrl, model },
nerveRoot: NERVE_ROOT
});
var index_default = workflow;
export {
index_default as default
};