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, - ); -}