diff --git a/packages/workflow-meta/package.json b/packages/workflow-meta/package.json new file mode 100644 index 0000000..987c79a --- /dev/null +++ b/packages/workflow-meta/package.json @@ -0,0 +1,28 @@ +{ + "name": "@uncaged/nerve-workflow-meta", + "version": "0.5.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepublishOnly": "bash ../../scripts/prepublish-check.sh", + "build": "rslib build", + "test": "vitest run --passWithNoTests" + }, + "dependencies": { + "@uncaged/nerve-core": "workspace:*", + "@uncaged/nerve-role-committer": "workspace:*", + "@uncaged/nerve-role-reviewer": "workspace:*", + "@uncaged/nerve-workflow-utils": "workspace:*", + "zod": "^4.3.6" + }, + "devDependencies": { + "@rslib/core": "^0.21.3", + "typescript": "^5.8.3", + "vitest": "^4.1.5" + } +} diff --git a/packages/workflow-meta/rslib.config.ts b/packages/workflow-meta/rslib.config.ts new file mode 100644 index 0000000..87bb8a3 --- /dev/null +++ b/packages/workflow-meta/rslib.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "@rslib/core"; + +export default defineConfig({ + lib: [ + { + format: "esm", + dts: true, + }, + ], + source: { + entry: { + index: "src/index.ts", + }, + }, + output: { + target: "node", + cleanDistPath: true, + }, +}); diff --git a/packages/workflow-meta/src/develop-sense/build.ts b/packages/workflow-meta/src/develop-sense/build.ts new file mode 100644 index 0000000..526c6a7 --- /dev/null +++ b/packages/workflow-meta/src/develop-sense/build.ts @@ -0,0 +1,42 @@ +import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; +import { createCommitterRole } from "@uncaged/nerve-role-committer"; +import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; +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 { createPlannerRole } from "./roles/planner.js"; +import { createTesterRole } from "./roles/tester.js"; + +export type CreateDevelopSenseDeps = { + defaultAdapter: AgentFn; + adapters: Partial> | null; + 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: createCommitterRole(a("committer"), extract), + }; + + return { + name: "develop-sense", + roles, + moderator, + }; +} diff --git a/packages/workflow-meta/src/develop-sense/moderator.ts b/packages/workflow-meta/src/develop-sense/moderator.ts new file mode 100644 index 0000000..f24d46e --- /dev/null +++ b/packages/workflow-meta/src/develop-sense/moderator.ts @@ -0,0 +1,65 @@ +import { END } from "@uncaged/nerve-core"; +import type { Moderator } from "@uncaged/nerve-core"; +import type { CommitterMeta } from "@uncaged/nerve-role-committer"; +import type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; +import type { CoderMeta } from "./roles/coder.js"; +import type { PlannerMeta } from "./roles/planner.js"; +import type { TesterMeta } from "./roles/tester.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/packages/workflow-meta/src/develop-sense/roles/coder.ts b/packages/workflow-meta/src/develop-sense/roles/coder.ts new file mode 100644 index 0000000..4bee332 --- /dev/null +++ b/packages/workflow-meta/src/develop-sense/roles/coder.ts @@ -0,0 +1,50 @@ +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/packages/workflow-meta/src/develop-sense/roles/planner.ts b/packages/workflow-meta/src/develop-sense/roles/planner.ts new file mode 100644 index 0000000..7c47f8c --- /dev/null +++ b/packages/workflow-meta/src/develop-sense/roles/planner.ts @@ -0,0 +1,39 @@ +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/packages/workflow-meta/src/develop-sense/roles/tester.ts b/packages/workflow-meta/src/develop-sense/roles/tester.ts new file mode 100644 index 0000000..6f8b81b --- /dev/null +++ b/packages/workflow-meta/src/develop-sense/roles/tester.ts @@ -0,0 +1,60 @@ +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/packages/workflow-meta/src/develop-workflow/build.ts b/packages/workflow-meta/src/develop-workflow/build.ts new file mode 100644 index 0000000..3659368 --- /dev/null +++ b/packages/workflow-meta/src/develop-workflow/build.ts @@ -0,0 +1,42 @@ +import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; +import { createCommitterRole } from "@uncaged/nerve-role-committer"; +import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; +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 { createPlannerRole } from "./roles/planner.js"; +import { createTesterRole } from "./roles/tester.js"; + +export type CreateDevelopWorkflowDeps = { + defaultAdapter: AgentFn; + adapters: Partial> | null; + 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: createCommitterRole(a("committer"), extract), + }; + + return { + name: "develop-workflow", + roles, + moderator, + }; +} diff --git a/packages/workflow-meta/src/develop-workflow/moderator.ts b/packages/workflow-meta/src/develop-workflow/moderator.ts new file mode 100644 index 0000000..a55fa15 --- /dev/null +++ b/packages/workflow-meta/src/develop-workflow/moderator.ts @@ -0,0 +1,67 @@ +import { END } from "@uncaged/nerve-core"; +import type { Moderator } from "@uncaged/nerve-core"; +import type { CommitterMeta } from "@uncaged/nerve-role-committer"; +import type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; +import type { CoderMeta } from "./roles/coder.js"; +import type { PlannerMeta } from "./roles/planner.js"; +import type { TesterMeta } from "./roles/tester.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/packages/workflow-meta/src/develop-workflow/roles/coder.ts b/packages/workflow-meta/src/develop-workflow/roles/coder.ts new file mode 100644 index 0000000..c09f562 --- /dev/null +++ b/packages/workflow-meta/src/develop-workflow/roles/coder.ts @@ -0,0 +1,69 @@ +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/packages/workflow-meta/src/develop-workflow/roles/planner.ts b/packages/workflow-meta/src/develop-workflow/roles/planner.ts new file mode 100644 index 0000000..20148fa --- /dev/null +++ b/packages/workflow-meta/src/develop-workflow/roles/planner.ts @@ -0,0 +1,68 @@ +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/packages/workflow-meta/src/develop-workflow/roles/tester.ts b/packages/workflow-meta/src/develop-workflow/roles/tester.ts new file mode 100644 index 0000000..766a825 --- /dev/null +++ b/packages/workflow-meta/src/develop-workflow/roles/tester.ts @@ -0,0 +1,61 @@ +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, + ); +} diff --git a/packages/workflow-meta/src/index.ts b/packages/workflow-meta/src/index.ts new file mode 100644 index 0000000..2b69cce --- /dev/null +++ b/packages/workflow-meta/src/index.ts @@ -0,0 +1,4 @@ +export { createDevelopSenseWorkflow } from "./develop-sense/build.js"; +export type { CreateDevelopSenseDeps } from "./develop-sense/build.js"; +export { createDevelopWorkflowWorkflow } from "./develop-workflow/build.js"; +export type { CreateDevelopWorkflowDeps } from "./develop-workflow/build.js"; diff --git a/packages/workflow-meta/tsconfig.json b/packages/workflow-meta/tsconfig.json new file mode 100644 index 0000000..9036088 --- /dev/null +++ b/packages/workflow-meta/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": false + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8de2ebc..2066fee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,34 @@ importers: specifier: ^4.1.5 version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) + packages/workflow-meta: + dependencies: + '@uncaged/nerve-core': + specifier: workspace:* + version: link:../core + '@uncaged/nerve-role-committer': + specifier: workspace:* + version: link:../role-committer + '@uncaged/nerve-role-reviewer': + specifier: workspace:* + version: link:../role-reviewer + '@uncaged/nerve-workflow-utils': + specifier: workspace:* + version: link:../workflow-utils + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@rslib/core': + specifier: ^0.21.3 + version: 0.21.3(typescript@5.9.3) + typescript: + specifier: ^5.8.3 + version: 5.9.3 + vitest: + specifier: ^4.1.5 + version: 4.1.5(@types/node@25.6.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.7)(yaml@2.8.3)) + packages/workflow-utils: dependencies: '@uncaged/nerve-adapter-cursor':