From 60979aaa6a7245877309baa4f39c7e0bc5b612e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Wed, 29 Apr 2026 14:52:25 +0000 Subject: [PATCH] refactor: migrate develop-sense/develop-workflow to @uncaged/nerve-workflow-meta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete local roles, moderator, and build files. Workflow index.ts now imports factory from package and wires adapters/extract/cwd. Closes #21 β€” 小橘 🍊(NEKO TeamοΌ‰ --- package.json | 4 +- pnpm-lock.yaml | 10 +++ workflows/develop-sense/build.ts | 39 ----------- workflows/develop-sense/index.ts | 2 +- workflows/develop-sense/moderator.ts | 65 ----------------- workflows/develop-sense/package.json | 1 + workflows/develop-sense/roles/coder.ts | 50 -------------- workflows/develop-sense/roles/committer.ts | 5 -- workflows/develop-sense/roles/planner.ts | 36 ---------- workflows/develop-sense/roles/reviewer.ts | 3 - workflows/develop-sense/roles/tester.ts | 58 ---------------- workflows/develop-workflow/build.ts | 39 ----------- workflows/develop-workflow/index.ts | 2 +- workflows/develop-workflow/moderator.ts | 67 ------------------ workflows/develop-workflow/package.json | 1 + workflows/develop-workflow/roles/coder.ts | 69 ------------------- workflows/develop-workflow/roles/committer.ts | 5 -- workflows/develop-workflow/roles/planner.ts | 65 ----------------- workflows/develop-workflow/roles/reviewer.ts | 3 - workflows/develop-workflow/roles/tester.ts | 59 ---------------- 20 files changed, 17 insertions(+), 566 deletions(-) delete mode 100644 workflows/develop-sense/build.ts delete mode 100644 workflows/develop-sense/moderator.ts delete mode 100644 workflows/develop-sense/roles/coder.ts delete mode 100644 workflows/develop-sense/roles/committer.ts delete mode 100644 workflows/develop-sense/roles/planner.ts delete mode 100644 workflows/develop-sense/roles/reviewer.ts delete mode 100644 workflows/develop-sense/roles/tester.ts delete mode 100644 workflows/develop-workflow/build.ts delete mode 100644 workflows/develop-workflow/moderator.ts delete mode 100644 workflows/develop-workflow/roles/coder.ts delete mode 100644 workflows/develop-workflow/roles/committer.ts delete mode 100644 workflows/develop-workflow/roles/planner.ts delete mode 100644 workflows/develop-workflow/roles/reviewer.ts delete mode 100644 workflows/develop-workflow/roles/tester.ts diff --git a/package.json b/package.json index 8c9be55..e8553e5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@uncaged/nerve-daemon": "link:../repos/nerve/packages/daemon", "@uncaged/nerve-role-committer": "link:../repos/nerve/packages/role-committer", "@uncaged/nerve-role-reviewer": "link:../repos/nerve/packages/role-reviewer", + "@uncaged/nerve-workflow-meta": "link:../repos/nerve/packages/workflow-meta", "@uncaged/nerve-workflow-utils": "link:../repos/nerve/packages/workflow-utils", "drizzle-orm": "latest", "zod": "^4.3.6" @@ -30,7 +31,8 @@ "@uncaged/nerve-daemon": "link:../repos/nerve/packages/daemon", "@uncaged/nerve-core": "link:../repos/nerve/packages/core", "@uncaged/nerve-workflow-utils": "link:../repos/nerve/packages/workflow-utils", - "@uncaged/nerve-role-committer": "link:../repos/nerve/packages/role-committer" + "@uncaged/nerve-role-committer": "link:../repos/nerve/packages/role-committer", + "@uncaged/nerve-workflow-meta": "link:../repos/nerve/packages/workflow-meta" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7d2960..4ba752f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,7 @@ overrides: '@uncaged/nerve-core': link:../repos/nerve/packages/core '@uncaged/nerve-workflow-utils': link:../repos/nerve/packages/workflow-utils '@uncaged/nerve-role-committer': link:../repos/nerve/packages/role-committer + '@uncaged/nerve-workflow-meta': link:../repos/nerve/packages/workflow-meta importers: @@ -34,6 +35,9 @@ importers: '@uncaged/nerve-role-reviewer': specifier: link:../repos/nerve/packages/role-reviewer version: link:../repos/nerve/packages/role-reviewer + '@uncaged/nerve-workflow-meta': + specifier: link:../repos/nerve/packages/workflow-meta + version: link:../repos/nerve/packages/workflow-meta '@uncaged/nerve-workflow-utils': specifier: link:../repos/nerve/packages/workflow-utils version: link:../repos/nerve/packages/workflow-utils @@ -119,6 +123,9 @@ importers: '@uncaged/nerve-core': specifier: link:../../../repos/nerve/packages/core version: link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-meta': + specifier: link:../../../repos/nerve/packages/workflow-meta + version: link:../../../repos/nerve/packages/workflow-meta '@uncaged/nerve-workflow-utils': specifier: link:../../../repos/nerve/packages/workflow-utils version: link:../../../repos/nerve/packages/workflow-utils @@ -147,6 +154,9 @@ importers: '@uncaged/nerve-core': specifier: link:../../../repos/nerve/packages/core version: link:../../../repos/nerve/packages/core + '@uncaged/nerve-workflow-meta': + specifier: link:../../../repos/nerve/packages/workflow-meta + version: link:../../../repos/nerve/packages/workflow-meta '@uncaged/nerve-workflow-utils': specifier: link:../../../repos/nerve/packages/workflow-utils version: link:../../../repos/nerve/packages/workflow-utils diff --git a/workflows/develop-sense/build.ts b/workflows/develop-sense/build.ts deleted file mode 100644 index 2a1c53b..0000000 --- a/workflows/develop-sense/build.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; - -import { moderator } from "./moderator.js"; -import type { SenseMeta } from "./moderator.js"; -import { createCoderRole } from "./roles/coder.js"; -import { createWorkspaceCommitterRole } from "./roles/committer.js"; -import { createPlannerRole } from "./roles/planner.js"; -import { createReviewerRole } from "./roles/reviewer.js"; -import { createTesterRole } from "./roles/tester.js"; - -export type CreateDevelopSenseDeps = { - defaultAdapter: AgentFn; - adapters?: Partial>; - extract: LlmExtractorConfig; - cwd: string; -}; - -export function createDevelopSenseWorkflow({ - defaultAdapter, - adapters, - extract, - cwd, -}: CreateDevelopSenseDeps): WorkflowDefinition { - const a = (role: keyof SenseMeta) => adapters?.[role] ?? defaultAdapter; - const roles = { - planner: createPlannerRole(a('planner'), extract), - coder: createCoderRole(a('coder'), extract), - reviewer: createReviewerRole(a('reviewer'), extract, { cwd, conventionsPath: "CONVENTIONS.md" }), - tester: createTesterRole(a('tester'), extract, cwd), - committer: createWorkspaceCommitterRole(a('committer'), extract), - }; - - return { - name: "develop-sense", - roles, - moderator, - }; -} diff --git a/workflows/develop-sense/index.ts b/workflows/develop-sense/index.ts index 1d6e6fb..4d3720f 100644 --- a/workflows/develop-sense/index.ts +++ b/workflows/develop-sense/index.ts @@ -1,7 +1,7 @@ import { join } from "node:path"; import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor"; import { hermesAdapter } from "@uncaged/nerve-adapter-hermes"; -import { createDevelopSenseWorkflow } from "./build.js"; +import { createDevelopSenseWorkflow } from "@uncaged/nerve-workflow-meta"; const HOME = process.env.HOME ?? "/home/azureuser"; const NERVE_ROOT = join(HOME, ".uncaged-nerve"); diff --git a/workflows/develop-sense/moderator.ts b/workflows/develop-sense/moderator.ts deleted file mode 100644 index 5c5d2e5..0000000 --- a/workflows/develop-sense/moderator.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { END } from "@uncaged/nerve-core"; -import type { Moderator } from "@uncaged/nerve-core"; -import type { PlannerMeta } from "./roles/planner.js"; -import type { CoderMeta } from "./roles/coder.js"; -import type { ReviewerMeta } from "./roles/reviewer.js"; -import type { TesterMeta } from "./roles/tester.js"; -import type { CommitterMeta } from "./roles/committer.js"; - -export type SenseMeta = { - planner: PlannerMeta; - coder: CoderMeta; - reviewer: ReviewerMeta; - tester: TesterMeta; - committer: CommitterMeta; -}; - -const MAX_CODER_ROUNDS = 20; -const MAX_TOTAL_REJECTIONS = 10; - -function coderRounds(steps: { role: string }[]): number { - return steps.filter((s) => s.role === "coder").length; -} - -function totalRejections(steps: { role: string; meta: unknown }[]): number { - return steps.filter((s) => { - if (s.role === "reviewer") return !(s.meta as Record).approved; - if (s.role === "tester") return !(s.meta as Record).passed; - if (s.role === "committer") return !(s.meta as Record).committed; - return false; - }).length; -} - -function canRetryCoder(steps: { role: string; meta: unknown }[]): boolean { - return coderRounds(steps) < MAX_CODER_ROUNDS && totalRejections(steps) < MAX_TOTAL_REJECTIONS; -} - -export const moderator: Moderator = (context) => { - if (context.steps.length === 0) return "planner"; - - const last = context.steps[context.steps.length - 1]; - - if (last.role === "planner") return "coder"; - - if (last.role === "coder") { - if (last.meta.filesCreated) return "reviewer"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "reviewer") { - if (last.meta.approved) return "tester"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "tester") { - if (last.meta.passed) return "committer"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "committer") { - if (last.meta.committed) return END; - return canRetryCoder(context.steps) ? "coder" : END; - } - - return END; -}; diff --git a/workflows/develop-sense/package.json b/workflows/develop-sense/package.json index dff8549..8ff3392 100644 --- a/workflows/develop-sense/package.json +++ b/workflows/develop-sense/package.json @@ -10,6 +10,7 @@ "@uncaged/nerve-adapter-cursor": "latest", "@uncaged/nerve-adapter-hermes": "latest", "@uncaged/nerve-core": "latest", + "@uncaged/nerve-workflow-meta": "link:../../../repos/nerve/packages/workflow-meta", "@uncaged/nerve-workflow-utils": "latest", "zod": "^4.3.6" }, diff --git a/workflows/develop-sense/roles/coder.ts b/workflows/develop-sense/roles/coder.ts deleted file mode 100644 index 4bee332..0000000 --- a/workflows/develop-sense/roles/coder.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const coderMetaSchema = z.object({ - filesCreated: z.boolean().describe("true if the sense files were created"), -}); -export type CoderMeta = z.infer; - -export function coderPrompt({ threadId }: { threadId: string }): string { - return `Read the workflow thread for the planner's sense design and any tester feedback: \`nerve thread ${threadId}\` -Read the nerve-dev skill for sense file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` - -## Your task - -Implement (or fix) the sense the planner designed. If there is tester feedback in the thread, fix the issues it identified. - -## Multi-step approach - -You do NOT need to finish everything in one pass. You may return \`done: false\` to continue in the next iteration. - -## File structure for each sense - -- \`senses//src/index.ts\` β€” TypeScript compute source; import schema as \`./schema.ts\` -- \`senses//src/schema.ts\` β€” Drizzle schema (TypeScript) -- \`senses//migrations/\` β€” Drizzle migration files (at sense root, not inside src/) -- \`senses//package.json\` β€” with esbuild build script -- \`senses//index.js\` β€” bundled output generated by \`pnpm build\` (do NOT edit by hand) - -Look at existing senses for the package.json template and patterns. - -## When to return done: true - -Return \`done: true\` ONLY when ALL of the following are true: -- All required files are created -- \`pnpm install --no-cache && pnpm build\` succeeds (run it!) -- \`nerve.yaml\` is updated with the sense config - -Return \`done: false\` if you made progress but there is still work to do.`; -} - -export function createCoderRole(adapter: AgentFn, extract: LlmExtractorConfig): Role { - return createRole( - adapter, - async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }), - coderMetaSchema, - extract, - ); -} diff --git a/workflows/develop-sense/roles/committer.ts b/workflows/develop-sense/roles/committer.ts deleted file mode 100644 index 3b21db1..0000000 --- a/workflows/develop-sense/roles/committer.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { - createCommitterRole as createWorkspaceCommitterRole, - committerMetaSchema, - type CommitterMeta, -} from "@uncaged/nerve-role-committer"; diff --git a/workflows/develop-sense/roles/planner.ts b/workflows/develop-sense/roles/planner.ts deleted file mode 100644 index 3287047..0000000 --- a/workflows/develop-sense/roles/planner.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const plannerMetaSchema = z.object({ - senseName: z.string().describe("kebab-case sense name from the plan"), -}); -export type PlannerMeta = z.infer; - -export function plannerPrompt({ threadId }: { threadId: string }): string { - return `You are planning a new Nerve sense. - -Read the workflow thread for the user's request: \`nerve thread ${threadId}\` -Read the nerve-dev skill for sense conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` -Also look at existing senses in the \`senses/\` directory for patterns. - -Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdown: - -## Sense Design -### Name β€” kebab-case -### Fields β€” name, type (integer/real/text), description -### Compute Logic β€” step-by-step, specific Node.js APIs or shell commands -### Trigger Config β€” group, interval, throttle, timeout - -Output ONLY the plan. Be precise and implementation-ready.`; -} - -export function createPlannerRole(adapter: AgentFn, extract: LlmExtractorConfig): Role { - return createRole( - adapter, - async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }), - plannerMetaSchema, - extract, - ); -} diff --git a/workflows/develop-sense/roles/reviewer.ts b/workflows/develop-sense/roles/reviewer.ts deleted file mode 100644 index 472c984..0000000 --- a/workflows/develop-sense/roles/reviewer.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; -export { createReviewerRole }; -export type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; diff --git a/workflows/develop-sense/roles/tester.ts b/workflows/develop-sense/roles/tester.ts deleted file mode 100644 index 8ec7a43..0000000 --- a/workflows/develop-sense/roles/tester.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const testerMetaSchema = z.object({ - passed: z.boolean().describe("true if all e2e checks passed"), -}); -export type TesterMeta = z.infer; - -export function testerPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string { - return `You are testing a newly created Nerve sense end-to-end. - -**IMPORTANT: The Nerve workspace is at \`${nerveRoot}\`. All paths below are relative to this directory. Always \`cd ${nerveRoot}\` first.** - -Read the workflow thread for context: \`nerve thread ${threadId}\` -Read the nerve-dev skill for expected file structure: \`cat ${nerveRoot}/node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` - -Verify the full lifecycle in this order: - -1. **File check** β€” all required sense files exist: - - \`senses//src/index.ts\` - - \`senses//src/schema.ts\` - - \`senses//migrations/\` - - \`senses//package.json\` - -2. **Build** β€” run inside the sense directory: - \`\`\` - cd ${nerveRoot}/senses/ && pnpm install --no-cache && pnpm build - \`\`\` - Must produce \`index.js\` at sense root without errors. - -3. **Config check** β€” \`nerve validate\` passes, confirming nerve.yaml is valid. - -4. **Sense list** β€” \`nerve sense list\` shows the sense. - -5. **Trigger** β€” \`nerve sense trigger \` completes without error. - -6. **Query** β€” \`nerve sense query \` β€” retry up to 20s until rows appear. - -If any step fails, include the relevant error output. - -Output a clear summary: what you checked, what passed, what failed, and why.`; -} - -export function createTesterRole( - adapter: AgentFn, - extract: LlmExtractorConfig, - nerveRoot: string, -): Role { - return createRole( - adapter, - async (start: StartStep) => - testerPrompt({ threadId: start.meta.threadId, nerveRoot }), - testerMetaSchema, - extract, - ); -} diff --git a/workflows/develop-workflow/build.ts b/workflows/develop-workflow/build.ts deleted file mode 100644 index dc8ab44..0000000 --- a/workflows/develop-workflow/build.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; - -import { moderator } from "./moderator.js"; -import type { WorkflowMeta } from "./moderator.js"; -import { createCoderRole } from "./roles/coder.js"; -import { createWorkspaceCommitterRole } from "./roles/committer.js"; -import { createPlannerRole } from "./roles/planner.js"; -import { createReviewerRole } from "./roles/reviewer.js"; -import { createTesterRole } from "./roles/tester.js"; - -export type CreateDevelopWorkflowDeps = { - defaultAdapter: AgentFn; - adapters?: Partial>; - extract: LlmExtractorConfig; - nerveRoot: string; -}; - -export function createDevelopWorkflowWorkflow({ - defaultAdapter, - adapters, - extract, - nerveRoot, -}: CreateDevelopWorkflowDeps): WorkflowDefinition { - const a = (role: keyof WorkflowMeta) => adapters?.[role] ?? defaultAdapter; - const roles = { - planner: createPlannerRole(a('planner'), extract), - coder: createCoderRole(a('coder'), extract), - reviewer: createReviewerRole(a('reviewer'), extract, { cwd: nerveRoot, conventionsPath: "CONVENTIONS.md" }), - tester: createTesterRole(a('tester'), extract, nerveRoot), - committer: createWorkspaceCommitterRole(a('committer'), extract), - }; - - return { - name: "develop-workflow", - roles, - moderator, - }; -} diff --git a/workflows/develop-workflow/index.ts b/workflows/develop-workflow/index.ts index dfad6a9..83ef09a 100644 --- a/workflows/develop-workflow/index.ts +++ b/workflows/develop-workflow/index.ts @@ -1,7 +1,7 @@ import { join } from "node:path"; import { createCursorAdapter, cursorAdapter } from "@uncaged/nerve-adapter-cursor"; import { hermesAdapter } from "@uncaged/nerve-adapter-hermes"; -import { createDevelopWorkflowWorkflow } from "./build.js"; +import { createDevelopWorkflowWorkflow } from "@uncaged/nerve-workflow-meta"; const HOME = process.env.HOME ?? "/home/azureuser"; const NERVE_ROOT = join(HOME, ".uncaged-nerve"); diff --git a/workflows/develop-workflow/moderator.ts b/workflows/develop-workflow/moderator.ts deleted file mode 100644 index 97be239..0000000 --- a/workflows/develop-workflow/moderator.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { END } from "@uncaged/nerve-core"; -import type { Moderator } from "@uncaged/nerve-core"; -import type { PlannerMeta } from "./roles/planner.js"; -import type { CoderMeta } from "./roles/coder.js"; -import type { ReviewerMeta } from "./roles/reviewer.js"; -import type { TesterMeta } from "./roles/tester.js"; -import type { CommitterMeta } from "./roles/committer.js"; - -export type WorkflowMeta = { - planner: PlannerMeta; - coder: CoderMeta; - reviewer: ReviewerMeta; - tester: TesterMeta; - committer: CommitterMeta; -}; - -const MAX_CODER_ROUNDS = 20; -const MAX_TOTAL_REJECTIONS = 10; - -function coderRounds(steps: { role: string }[]): number { - return steps.filter((s) => s.role === "coder").length; -} - -function totalRejections(steps: { role: string; meta: unknown }[]): number { - return steps.filter((s) => { - if (s.role === "reviewer") return !(s.meta as Record).approved; - if (s.role === "tester") return !(s.meta as Record).passed; - if (s.role === "committer") return !(s.meta as Record).committed; - return false; - }).length; -} - -function canRetryCoder(steps: { role: string; meta: unknown }[]): boolean { - return coderRounds(steps) < MAX_CODER_ROUNDS && totalRejections(steps) < MAX_TOTAL_REJECTIONS; -} - -export const moderator: Moderator = (context) => { - if (context.steps.length === 0) return "planner"; - - const last = context.steps[context.steps.length - 1]; - - if (last.role === "planner") { - return last.meta.ready ? "coder" : END; - } - - if (last.role === "coder") { - if (last.meta.done) return "reviewer"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "reviewer") { - if (last.meta.approved) return "tester"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "tester") { - if (last.meta.passed) return "committer"; - return canRetryCoder(context.steps) ? "coder" : END; - } - - if (last.role === "committer") { - if (last.meta.committed) return END; - return canRetryCoder(context.steps) ? "coder" : END; - } - - return END; -}; diff --git a/workflows/develop-workflow/package.json b/workflows/develop-workflow/package.json index 336bcf0..4a6b93b 100644 --- a/workflows/develop-workflow/package.json +++ b/workflows/develop-workflow/package.json @@ -10,6 +10,7 @@ "@uncaged/nerve-adapter-cursor": "latest", "@uncaged/nerve-adapter-hermes": "latest", "@uncaged/nerve-core": "latest", + "@uncaged/nerve-workflow-meta": "link:../../../repos/nerve/packages/workflow-meta", "@uncaged/nerve-workflow-utils": "latest", "zod": "^4.3.6" }, diff --git a/workflows/develop-workflow/roles/coder.ts b/workflows/develop-workflow/roles/coder.ts deleted file mode 100644 index c09f562..0000000 --- a/workflows/develop-workflow/roles/coder.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const coderMetaSchema = z.object({ - done: z.boolean().describe("true if the workflow files were created and build passes"), -}); -export type CoderMeta = z.infer; - -export function coderPrompt({ threadId }: { threadId: string }): string { - return `Read the workflow thread to get the planner's design and any reviewer/tester/committer feedback: \`nerve thread ${threadId}\` -Read the nerve-dev skill for workflow file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` -Also look at existing workflows in the \`workflows/\` directory for patterns. - -## Your task - -Implement the planner's design. This may be **creating a new workflow** or **modifying an existing one**. If there is reviewer, tester, or committer feedback in the thread, fix the issues they identified. - -**IMPORTANT:** The thread contains both the **initial user prompt** (the first message) and the **planner's design**. Read both carefully: -- The **initial prompt** contains the user's specific requirements for role behavior, tools to use, and acceptance criteria -- The **planner's design** contains the architecture, file structure, and routing logic -- When writing role prompts, follow the user's behavioral requirements from the initial prompt β€” do not invent your own interpretation - -## Multi-step approach - -You do NOT need to finish everything in one pass. You may return \`done: false\` to continue in the next iteration. For example: -1. First pass: scaffold files / make structural changes -2. Second pass: implement role logic -3. Third pass: fix build/lint errors - -## Workflow file structure - -Each workflow must have: -- \`workflows//index.ts\` β€” WorkflowDefinition default export -- \`workflows//build.ts\` β€” factory function -- \`workflows//moderator.ts\` β€” moderator + meta types -- \`workflows//roles/.ts\` β€” meta schema and prompt function per role -- \`workflows//package.json\` β€” with esbuild build script -- \`workflows//tsconfig.json\` β€” TypeScript config - -For **new workflows**, also update \`nerve.yaml\` with \`workflows.\`. - -## Rules - -- Keep the WorkflowDefinition pattern -- No dynamic import() -- Use types (not interfaces) -- Meta should be simple routing signals (single boolean per role) -- Write compile-ready TypeScript - -## When to return done: true - -Return \`done: true\` ONLY when ALL of the following are true: -- All changes from the plan are implemented -- \`cd workflows/ && pnpm install --no-cache && pnpm build\` succeeds (run it!) -- No lint or type errors remain - -Return \`done: false\` if you made progress but there is still work to do, or if build/lint has errors you plan to fix in the next iteration.`; -} - -export function createCoderRole(adapter: AgentFn, extract: LlmExtractorConfig): Role { - return createRole( - adapter, - async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }), - coderMetaSchema, - extract, - ); -} diff --git a/workflows/develop-workflow/roles/committer.ts b/workflows/develop-workflow/roles/committer.ts deleted file mode 100644 index 3b21db1..0000000 --- a/workflows/develop-workflow/roles/committer.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { - createCommitterRole as createWorkspaceCommitterRole, - committerMetaSchema, - type CommitterMeta, -} from "@uncaged/nerve-role-committer"; diff --git a/workflows/develop-workflow/roles/planner.ts b/workflows/develop-workflow/roles/planner.ts deleted file mode 100644 index ce2bdb0..0000000 --- a/workflows/develop-workflow/roles/planner.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const plannerMetaSchema = z.object({ - ready: z.boolean().describe("true if requirements are clear and a workflow can be implemented"), -}); -export type PlannerMeta = z.infer; - -export function plannerPrompt({ threadId }: { threadId: string }): string { - return `You are a Nerve workflow planner. You can **create new workflows** or **modify existing ones**. - -Read the workflow thread for the user's request: \`nerve thread ${threadId}\` -Read the nerve-dev skill for workflow conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` -List existing workflows: \`ls workflows/\` - -## Determine the task type - -1. If the user wants to **modify an existing workflow** β€” read its current code (\`cat workflows//moderator.ts\`, \`cat workflows//build.ts\`, \`ls workflows//roles/\`, etc.) and understand its current structure before planning changes. -2. If the user wants to **create a new workflow** β€” look at existing workflows in \`workflows/\` for patterns to follow. - -## Produce a PLAN (not code) in markdown - -For **new workflows**: -- Workflow name (kebab-case) -- Roles list (name, purpose, tool) -- Flow transitions / moderator routing logic -- Validation loops design -- External dependencies -- Data flow between roles - -For **modifications to existing workflows**: -- Workflow name (existing) -- What changes are needed and why -- Files to add/modify/delete -- Impact on moderator routing logic (this workflow's typical order is planner β†’ coder β†’ reviewer β†’ tester β†’ committer) -- Backward compatibility considerations (if any) - -**For every role (new or modified)**, include a **Role Behavior** section that describes: -- What the role should do, check, or produce -- What tools or commands it should use -- What criteria determine its meta output (e.g. approved/passed/done) -- Preserve the user's specific requirements verbatim β€” do NOT summarize away details - -If requirements are NOT clear, describe what is missing or ambiguous. - -End your response with a JSON block: -\`\`\`json -{ "ready": true } -\`\`\` -or -\`\`\`json -{ "ready": false } -\`\`\``; -} - -export function createPlannerRole(adapter: AgentFn, extract: LlmExtractorConfig): Role { - return createRole( - adapter, - async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }), - plannerMetaSchema, - extract, - ); -} diff --git a/workflows/develop-workflow/roles/reviewer.ts b/workflows/develop-workflow/roles/reviewer.ts deleted file mode 100644 index 472c984..0000000 --- a/workflows/develop-workflow/roles/reviewer.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; -export { createReviewerRole }; -export type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; diff --git a/workflows/develop-workflow/roles/tester.ts b/workflows/develop-workflow/roles/tester.ts deleted file mode 100644 index f740e2a..0000000 --- a/workflows/develop-workflow/roles/tester.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { AgentFn, Role, StartStep } from "@uncaged/nerve-core"; -import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; -import { createRole } from "@uncaged/nerve-workflow-utils"; -import { z } from "zod"; - -export const testerMetaSchema = z.object({ - passed: z.boolean().describe("true if all validation checks passed"), -}); -export type TesterMeta = z.infer; - -export function testerPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string { - return `You are testing a Nerve workflow β€” either newly created or recently modified. - -**IMPORTANT: The Nerve workspace is at \`${nerveRoot}\`. All paths below are relative to this directory. Always \`cd ${nerveRoot}\` first.** - -Read the workflow thread for context: \`nerve thread ${threadId}\` -Read the nerve-dev skill for expected file structure: \`cat ${nerveRoot}/node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\` - -Get the workflow name from the thread (the planner's output). - -Verify the full lifecycle in this order: - -1. **File check** β€” all required workflow files exist (under \`${nerveRoot}/\`): - - \`workflows//index.ts\` - - \`workflows//build.ts\` - - \`workflows//moderator.ts\` - - \`workflows//roles/\` with one \`.ts\` file per role - - \`workflows//package.json\` - -2. **Build** β€” run inside the workflow directory: - \`\`\` - cd ${nerveRoot}/workflows/ && pnpm install --no-cache && pnpm build - \`\`\` - Must produce \`dist/index.js\` without errors. - -3. **Config check** β€” \`cd ${nerveRoot} && nerve validate\` passes, confirming nerve.yaml is valid. - -4. **Workflow list** β€” \`nerve workflow list\` shows the workflow. - -5. **Trigger test** β€” \`nerve workflow trigger --dry-run\` if available, otherwise just confirm the workflow appears in \`nerve workflow status\`. - -If any step fails, include the relevant error output. - -Output a clear summary: what you checked, what passed, what failed, and why.`; -} - -export function createTesterRole( - adapter: AgentFn, - extract: LlmExtractorConfig, - nerveRoot: string, -): Role { - return createRole( - adapter, - async (start: StartStep) => - testerPrompt({ threadId: start.meta.threadId, nerveRoot }), - testerMetaSchema, - extract, - ); -}