diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc index faee8ef68..2e7cd867f 100644 --- a/.cursor/rules/global.mdc +++ b/.cursor/rules/global.mdc @@ -1,8 +1,7 @@ --- -description: -globs: alwaysApply: true --- + # CLAUDE.md This file provides guidance to Claude Code when working with code in this monorepo. @@ -54,6 +53,58 @@ When writing code, follow this workflow to ensure code quality: - Keep business logic separate from infrastructure concerns - Use proper error handling at each level +## Environment Variables + +This project uses a centralized environment variable system: + +1. **All environment variables are defined at the root level** in a single `.env` file +2. **Turbo passes these variables** to all packages via the `globalEnv` configuration in `turbo.json` +3. **Individual packages validate** their required environment variables using the shared `@buster/env-utils` package + +### Setting Up Environment Variables + +1. Copy `.env.example` to `.env` at the project root: + ```bash + cp .env.example .env + ``` + +2. Fill in the required values in `.env` + +3. All packages will automatically have access to these variables through Turbo + +### Adding New Environment Variables + +When adding new environment variables: + +1. Add the variable to `.env.example` with a descriptive comment +2. Add the variable name to the `globalEnv` array in `turbo.json` +3. Update the package's `validate-env.js` script to include the new variable +4. Update the package's `env.d.ts` file with the TypeScript type definition + +### Migrating Packages to Centralized Env + +To migrate an existing package to use the centralized environment system: + +1. Remove any local `.env` files from the package +2. Add `@buster/env-utils` as a dependency: + ```json + "@buster/env-utils": "workspace:*" + ``` +3. Update the package's `scripts/validate-env.js` to use the shared utilities: + ```javascript + import { loadRootEnv, validateEnv } from '@buster/env-utils'; + + loadRootEnv(); + + const requiredEnv = { + DATABASE_URL: process.env.DATABASE_URL, + // ... other required variables + }; + + const { hasErrors } = validateEnv(requiredEnv); + if (hasErrors) process.exit(1); + ``` + ### 3. Ensure Type Safety ```bash # Build entire monorepo to check types @@ -202,6 +253,13 @@ export async function getWorkspaceSettingsHandler( ## Database Operations +### Query Organization +- **All database queries must be created as helper functions** in `@packages/database/src/queries/` +- **Organize by table** - Each table should have its own subdirectory (e.g., `assets/`, `chats/`, `users/`) +- **Type all queries** - Every query function must have properly typed parameters and return types +- **Export from index** - Each subdirectory should have an `index.ts` that exports all queries for that table +- **Reusable and composable** - Write queries as small, focused functions that can be composed together + ### Soft Delete and Upsert Practices - In our database, we never hard delete, we always use soft deletes with the `deleted_at` field - For update operations, we should almost always perform an upsert unless otherwise specified @@ -211,9 +269,271 @@ export async function getWorkspaceSettingsHandler( - When running tests, use the following Turbo commands: - `turbo test:unit` for unit tests - `turbo test:integration` for integration tests - - `turbo test` for running all tests -# important-instruction-reminders -Do what has been asked; nothing more, nothing less. -NEVER create files unless they're absolutely necessary for achieving your goal. -ALWAYS prefer editing an existing file to creating a new one. -NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. \ No newline at end of file + - `turbo test` for running all tests# CLAUDE.md + +This file provides guidance to Claude Code when working with code in this monorepo. + +**Note**: Many packages and apps have their own CLAUDE.md files with specific implementation details and patterns. Always check for a local CLAUDE.md when working in a specific directory. + +## Monorepo Structure + +This is a pnpm-based monorepo using Turborepo with the following structure: + +### Apps (`@buster-app/*`) +- `apps/web` - Next.js frontend application +- `apps/server` - Node.js/Hono backend server +- `apps/trigger` - Background job processing with Trigger.dev v3 +- `apps/electric-server` - Electric SQL sync server +- `apps/api` - Rust backend API (legacy) +- `apps/cli` - Command-line tools (Rust) + +### Packages (`@buster/*`) +- `packages/ai` - AI agents, tools, and workflows using Mastra framework +- `packages/database` - Database schema, migrations, and utilities (Drizzle ORM) +- `packages/data-source` - Data source adapters (PostgreSQL, MySQL, BigQuery, Snowflake, etc.) +- `packages/access-controls` - Permission and access control logic +- `packages/stored-values` - Stored values management +- `packages/rerank` - Document reranking functionality +- `packages/server-shared` - Shared server types and utilities +- `packages/test-utils` - Shared testing utilities +- `packages/vitest-config` - Shared Vitest configuration +- `packages/typescript-config` - Shared TypeScript configuration +- `packages/web-tools` - Web scraping and research tools +- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels) +- `packages/supabase` - Supabase setup and configuration +- `packages/sandbox` - Sandboxed code execution using Daytona SDK + +## Development Workflow + +When writing code, follow this workflow to ensure code quality: + +### 1. Write Modular, Testable Functions +- Create small, focused functions with single responsibilities +- Design functions to be easily testable with clear inputs/outputs +- Use dependency injection for external dependencies +- **IMPORTANT: Write functional, composable code - avoid classes** +- All features should be composed of testable functions +- Follow existing patterns in the codebase + +### 2. Build Features by Composing Functions +- Combine modular functions to create complete features +- Keep business logic separate from infrastructure concerns +- Use proper error handling at each level + +## Environment Variables + +This project uses a centralized environment variable system: + +1. **All environment variables are defined at the root level** in a single `.env` file +2. **Turbo passes these variables** to all packages via the `globalEnv` configuration in `turbo.json` +3. **Individual packages validate** their required environment variables using the shared `@buster/env-utils` package + +### Setting Up Environment Variables + +1. Copy `.env.example` to `.env` at the project root: + ```bash + cp .env.example .env + ``` + +2. Fill in the required values in `.env` + +3. All packages will automatically have access to these variables through Turbo + +### Adding New Environment Variables + +When adding new environment variables: + +1. Add the variable to `.env.example` with a descriptive comment +2. Add the variable name to the `globalEnv` array in `turbo.json` +3. Update the package's `validate-env.js` script to include the new variable +4. Update the package's `env.d.ts` file with the TypeScript type definition + +### Migrating Packages to Centralized Env + +To migrate an existing package to use the centralized environment system: + +1. Remove any local `.env` files from the package +2. Add `@buster/env-utils` as a dependency: + ```json + "@buster/env-utils": "workspace:*" + ``` +3. Update the package's `scripts/validate-env.js` to use the shared utilities: + ```javascript + import { loadRootEnv, validateEnv } from '@buster/env-utils'; + + loadRootEnv(); + + const requiredEnv = { + DATABASE_URL: process.env.DATABASE_URL, + // ... other required variables + }; + + const { hasErrors } = validateEnv(requiredEnv); + if (hasErrors) process.exit(1); + ``` + +### 3. Ensure Type Safety +```bash +# Build entire monorepo to check types +turbo run build + +# Build specific package/app +turbo run build --filter=@buster/ai +turbo run build --filter=@buster-app/web + +# Type check without building +turbo run typecheck +turbo run typecheck --filter=@buster/database +``` + +### 4. Run Biome for Linting & Formatting +```bash +# Check files with Biome +pnpm run check path/to/file.ts +pnpm run check packages/ai + +# Auto-fix linting, formatting, and import organization +pnpm run check:fix path/to/file.ts +pnpm run check:fix packages/ai +``` + +### 5. Run Tests with Vitest + +**Important**: Always run unit tests before completing any task to ensure code changes don't break existing functionality. + +```bash +# Run unit tests (always run these when working locally) +turbo run test:unit + +# Run unit tests for specific package +turbo run test:unit --filter=@buster/ai + +# Run integration tests ONLY for specific features/packages you're working on +turbo run test:integration --filter=@buster/database + +# Run specific test file +pnpm run test path/to/file.test.ts + +# Watch mode for development +pnpm run test:watch +``` + +### 6. Pre-Completion Checklist +**IMPORTANT: Before finishing any task or creating PRs, always run:** +```bash +# 1. Run unit tests for the entire monorepo +turbo run test:unit + +# 2. Build the entire monorepo to ensure everything compiles +turbo run build + +# 3. Run linting for the entire monorepo +turbo run lint +``` + +**Key Testing Guidelines:** +- **Always run unit tests, build, and lint** when working locally before considering a task complete +- **Unit tests** should be run for the entire monorepo to catch any breaking changes +- **Build** must pass for the entire monorepo to ensure type safety +- **Integration tests** should only be run for specific packages/features you're working on (NOT the entire monorepo) +- **Fix all failing tests, build errors, and lint errors** before completing any task +- **Heavily bias toward unit tests** - they are faster and cheaper to run +- **Mock everything you can** in unit tests for isolation and speed + +## Code Quality Standards + +### TypeScript Configuration +- **Strict mode enabled** - All strict checks are on +- **No implicit any** - Always use specific types +- **Strict null checks** - Handle null/undefined explicitly +- **No implicit returns** - All code paths must return +- **Consistent file casing** - Enforced by TypeScript + +### Type Safety and Zod Best Practices +- We care deeply about type safety and we use Zod schemas and then export them as types +- We prefer using type abstractions over `.parse()` method calls +- Always export Zod schemas as TypeScript types to leverage static type checking +- Avoid runtime type checking when compile-time type checks are sufficient + +### Biome Rules (Key Enforcements) +- **`useImportType: "warn"`** - Use type-only imports when possible +- **`noExplicitAny: "error"`** - Never use `any` type +- **`noUnusedVariables: "error"`** - Remove unused code +- **`noNonNullAssertion: "error"`** - No `!` assertions +- **`noConsoleLog: "warn"`** - Avoid console.log in production +- **`useNodejsImportProtocol: "error"`** - Use `node:` prefix for Node.js imports + +### Logging Guidelines +- **Never use `console.log`** +- **Use appropriate console methods**: + - `console.info` for general information + - `console.warn` for warning messages + - `console.error` for error messages + +## Error Handling and Logging Philosophy +- We care deeply about error handling and logging +- Key principles for error management: + - Catch errors effectively and thoughtfully + - Consider the state errors put the system into + - Implement comprehensive unit tests for error scenarios + - Log errors strategically for effective debugging + - Avoid over-logging while ensuring sufficient context for troubleshooting + +## Hono API Development Guidelines + +### API Structure and Organization +- **Version-based organization** - APIs are organized under `/api/v2/` directory +- **Feature-based folders** - Each feature gets its own folder (e.g., `chats/`, `security/`) +- **Separate handler files** - Each endpoint handler must be in its own file +- **Functional handlers** - All handlers should be pure functions that accept request data and return response data + +### Request/Response Type Safety +- **Use shared types** - All request and response types must be defined in `@buster/server-shared` +- **Zod schemas** - Define schemas in server-shared and export both the schema and inferred types +- **zValidator middleware** - Always use `zValidator` from `@hono/zod-validator` for request validation +- **Type imports** - Import types from server-shared packages for consistency + +### Handler Pattern +```typescript +// Handler file (e.g., get-workspace-settings.ts) +import type { GetWorkspaceSettingsResponse } from '@buster/server-shared/security'; +import type { User } from '@buster/database'; + +export async function getWorkspaceSettingsHandler( + user: User +): Promise { + // Implementation +} + +// Route definition (index.ts) +.get('/workspace-settings', async (c) => { + const user = c.get('busterUser'); + const response = await getWorkspaceSettingsHandler(user); + return c.json(response); +}) +``` + +### Authentication and User Context +- **Use requireAuth middleware** - Apply to all protected routes +- **Extract user context** - Use `c.get('busterUser')` to get the authenticated user +- **Type as User** - Import `User` type from `@buster/database` for handler parameters + +## Database Operations + +### Query Organization +- **All database queries must be created as helper functions** in `@packages/database/src/queries/` +- **Organize by table** - Each table should have its own subdirectory (e.g., `assets/`, `chats/`, `users/`) +- **Type all queries** - Every query function must have properly typed parameters and return types +- **Export from index** - Each subdirectory should have an `index.ts` that exports all queries for that table +- **Reusable and composable** - Write queries as small, focused functions that can be composed together + +### Soft Delete and Upsert Practices +- In our database, we never hard delete, we always use soft deletes with the `deleted_at` field +- For update operations, we should almost always perform an upsert unless otherwise specified +``` + +**Test Running Guidelines**: +- When running tests, use the following Turbo commands: + - `turbo test:unit` for unit tests + - `turbo test:integration` for integration tests + - `turbo test` for running all tests \ No newline at end of file diff --git a/packages/ai/src/steps/docs-agent/create-docs-todos-step.ts b/packages/ai/src/steps/docs-agent/create-docs-todos-step.ts new file mode 100644 index 000000000..da0806237 --- /dev/null +++ b/packages/ai/src/steps/docs-agent/create-docs-todos-step.ts @@ -0,0 +1,87 @@ +import type { Sandbox } from '@buster/sandbox'; +import { createStep } from '@mastra/core'; +import type { RuntimeContext } from '@mastra/core/runtime-context'; +import { z } from 'zod'; +import { DocsAgentContextKey } from '../../context/docs-agent-context'; + +const createDocsTodosStepInputSchema = z.object({ + message: z.string(), + organizationId: z.string(), + contextInitialized: z.boolean(), + context: z.object({ + sandbox: z.any(), + todoList: z.string(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + }), +}); + +const createDocsTodosStepOutputSchema = z.object({ + todos: z.array(z.string()), + todoList: z.string(), + // Pass through other fields + message: z.string(), + organizationId: z.string(), + context: z.object({ + sandbox: z.any(), + todoList: z.string(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + }), +}); + +const createDocsTodosExecution = async ({ + inputData, + runtimeContext, +}: { + inputData: z.infer; + runtimeContext: RuntimeContext; +}): Promise> => { + // Get the sandbox from runtime context (it was set by initializeContextStep) + const sandbox = runtimeContext.get(DocsAgentContextKey.Sandbox) as Sandbox; + const currentTodoList = runtimeContext.get(DocsAgentContextKey.TodoListFile) as string; + + // TODO: Implement the logic to create documentation todos + // This step should analyze the message and create a list of documentation tasks + // Now you have access to the sandbox from runtime context! + // The sandbox and currentTodoList will be used in the actual implementation + + // For now, log to show they're available + console.info('Sandbox available:', !!sandbox); + console.info('Current todo list:', currentTodoList); + + const todos: string[] = []; + + // Update the runtime context with the new todo list + const updatedTodoList = todos.join('\n'); + runtimeContext.set(DocsAgentContextKey.TodoListFile, updatedTodoList); + + // Return the data with todos + return { + ...inputData, + todos, + todoList: updatedTodoList, + context: { + ...inputData.context, + todoList: updatedTodoList, + }, + }; +}; + +export const createDocsTodosStep = createStep({ + id: 'create-docs-todos', + description: 'Creates a list of documentation todos based on the user message', + inputSchema: createDocsTodosStepInputSchema, + outputSchema: createDocsTodosStepOutputSchema, + execute: createDocsTodosExecution, +}); diff --git a/packages/ai/src/steps/docs-agent/docs-agent-step.ts b/packages/ai/src/steps/docs-agent/docs-agent-step.ts new file mode 100644 index 000000000..c2258827d --- /dev/null +++ b/packages/ai/src/steps/docs-agent/docs-agent-step.ts @@ -0,0 +1,104 @@ +import type { Sandbox } from '@buster/sandbox'; +import { createStep } from '@mastra/core'; +import type { RuntimeContext } from '@mastra/core/runtime-context'; +import { z } from 'zod'; +import { DocsAgentContextKey } from '../../context/docs-agent-context'; +import type { MessageUserClarifyingQuestion } from '../../context/docs-agent-context'; + +const docsAgentStepInputSchema = z.object({ + todos: z.array(z.string()), + todoList: z.string(), + message: z.string(), + organizationId: z.string(), + context: z.object({ + sandbox: z.any(), + todoList: z.string(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + }), +}); + +const docsAgentStepOutputSchema = z.object({ + todos: z.array(z.string()).optional(), + todoList: z.string().optional(), + documentationCreated: z.boolean().optional(), + clarificationNeeded: z.boolean().optional(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + finished: z.boolean().optional(), + metadata: z + .object({ + filesCreated: z.number().optional(), + toolsUsed: z.array(z.string()).optional(), + }) + .optional(), +}); + +const docsAgentExecution = async ({ + inputData, + runtimeContext, +}: { + inputData: z.infer; + runtimeContext: RuntimeContext; +}): Promise> => { + // Access values from runtime context + const sandbox = runtimeContext.get(DocsAgentContextKey.Sandbox) as Sandbox; + const todoList = runtimeContext.get(DocsAgentContextKey.TodoListFile) as string; + const clarificationQuestion = runtimeContext.get(DocsAgentContextKey.ClarificationFile) as + | MessageUserClarifyingQuestion + | undefined; + + // Also access standard workflow context + const organizationId = runtimeContext.get('organizationId') as string; + const workflowStartTime = runtimeContext.get('workflowStartTime') as number; + + console.info('[DocsAgent] Runtime context values:', { + hasSandbox: !!sandbox, + todoList, + hasClarificationQuestion: !!clarificationQuestion, + organizationId, + workflowStartTime, + }); + + // TODO: Implement the docs agent logic + // This step should: + // 1. Process the todos using the sandbox + // 2. Create documentation files + // 3. Handle clarification questions if needed + + // Example of how to use sandbox (when implemented): + // const result = await sandbox.execute('console.log("Hello from sandbox")'); + + // If you need to update clarification question: + // runtimeContext.set(DocsAgentContextKey.ClarificationFile, newClarificationQuestion); + + return { + todos: inputData.todos, + todoList: inputData.todoList, + documentationCreated: false, + clarificationNeeded: false, + finished: true, + metadata: { + filesCreated: 0, + toolsUsed: [], + }, + }; +}; + +export const docsAgentStep = createStep({ + id: 'docs-agent', + description: 'Main documentation agent that processes todos and creates documentation', + inputSchema: docsAgentStepInputSchema, + outputSchema: docsAgentStepOutputSchema, + execute: docsAgentExecution, +}); diff --git a/packages/ai/src/steps/docs-agent/initialize-context-step.ts b/packages/ai/src/steps/docs-agent/initialize-context-step.ts new file mode 100644 index 000000000..362706761 --- /dev/null +++ b/packages/ai/src/steps/docs-agent/initialize-context-step.ts @@ -0,0 +1,87 @@ +import type { Sandbox } from '@buster/sandbox'; +import { createStep } from '@mastra/core'; +import type { RuntimeContext } from '@mastra/core/runtime-context'; +import { z } from 'zod'; +import { ClarifyingQuestionSchema, DocsAgentContextKey } from '../../context/docs-agent-context'; + +// Input schema includes the context values we need to set +const initializeContextStepInputSchema = z.object({ + message: z.string(), + organizationId: z.string(), + // Context values that will be set in runtime context + sandbox: z.custom( + (val) => { + // Validate it's a sandbox instance with required methods + return ( + val && + typeof val === 'object' && + typeof val.execute === 'function' && + typeof val.cleanup === 'function' + ); + }, + { + message: 'Invalid Sandbox instance', + } + ), + todoList: z.string().optional().default(''), + clarificationQuestion: ClarifyingQuestionSchema.optional(), +}); + +// Output schema passes through all input data plus confirms context is initialized +const initializeContextStepOutputSchema = z.object({ + message: z.string(), + organizationId: z.string(), + contextInitialized: z.boolean(), + // Pass through for subsequent steps (but they'll use runtime context) + context: z.object({ + sandbox: z.any(), // Will be in runtime context + todoList: z.string(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + }), +}); + +const initializeContextExecution = async ({ + inputData, + runtimeContext, +}: { + inputData: z.infer; + runtimeContext: RuntimeContext; +}): Promise> => { + // Set runtime context values + runtimeContext.set(DocsAgentContextKey.Sandbox, inputData.sandbox); + runtimeContext.set(DocsAgentContextKey.TodoListFile, inputData.todoList); + + if (inputData.clarificationQuestion) { + runtimeContext.set(DocsAgentContextKey.ClarificationFile, inputData.clarificationQuestion); + } + + // Also set standard workflow context + runtimeContext.set('organizationId', inputData.organizationId); + runtimeContext.set('workflowStartTime', Date.now()); + + // Return data for next steps with context initialized + return { + message: inputData.message, + organizationId: inputData.organizationId, + contextInitialized: true, + context: { + sandbox: inputData.sandbox, + todoList: inputData.todoList, + clarificationQuestion: inputData.clarificationQuestion, + }, + }; +}; + +export const initializeContextStep = createStep({ + id: 'initialize-context', + description: 'Initializes the runtime context with DocsAgent specific values', + inputSchema: initializeContextStepInputSchema, + outputSchema: initializeContextStepOutputSchema, + execute: initializeContextExecution, +}); diff --git a/packages/ai/src/tools/file-tools/bash-tool/bash-execute-tool.test.ts b/packages/ai/src/tools/file-tools/bash-tool/bash-execute-tool.test.ts index 588ffdd96..c883af8a0 100644 --- a/packages/ai/src/tools/file-tools/bash-tool/bash-execute-tool.test.ts +++ b/packages/ai/src/tools/file-tools/bash-tool/bash-execute-tool.test.ts @@ -34,7 +34,7 @@ describe('bash-execute-tool', () => { describe('bashExecute tool', () => { it('should have correct tool configuration', () => { - expect(executeBash.id).toBe('bash_execute'); + expect(executeBash.id).toBe('execute-bash'); expect(executeBash.description).toContain('Executes bash commands'); expect(executeBash.inputSchema).toBeDefined(); expect(executeBash.outputSchema).toBeDefined(); diff --git a/packages/ai/src/utils/sql-permissions/permission-validator.test.ts b/packages/ai/src/utils/sql-permissions/permission-validator.test.ts index 1dce6de07..569f2247b 100644 --- a/packages/ai/src/utils/sql-permissions/permission-validator.test.ts +++ b/packages/ai/src/utils/sql-permissions/permission-validator.test.ts @@ -51,7 +51,10 @@ describe('Permission Validator', () => { }, ] as any); - const result = await validateSqlPermissions('SELECT id, user_id FROM public.orders', 'user123'); + const result = await validateSqlPermissions( + 'SELECT id, user_id FROM public.orders', + 'user123' + ); expect(result).toEqual({ isAuthorized: false, diff --git a/packages/ai/src/workflows/docs-agent/docs-agent-workflow.ts b/packages/ai/src/workflows/docs-agent/docs-agent-workflow.ts new file mode 100644 index 000000000..a50b06c9c --- /dev/null +++ b/packages/ai/src/workflows/docs-agent/docs-agent-workflow.ts @@ -0,0 +1,66 @@ +import type { Sandbox } from '@buster/sandbox'; +import { createWorkflow } from '@mastra/core'; +import { z } from 'zod'; +import { ClarifyingQuestionSchema } from '../../context/docs-agent-context'; +import { createDocsTodosStep } from '../../steps/docs-agent/create-docs-todos-step'; +import { docsAgentStep } from '../../steps/docs-agent/docs-agent-step'; +import { initializeContextStep } from '../../steps/docs-agent/initialize-context-step'; + +// Input schema for the workflow - now accepts context values directly +const docsAgentWorkflowInputSchema = z.object({ + message: z.string(), + organizationId: z.string(), + // Direct context values instead of nested object + sandbox: z.custom( + (val) => { + return ( + val && + typeof val === 'object' && + typeof val.execute === 'function' && + typeof val.cleanup === 'function' + ); + }, + { + message: 'Invalid Sandbox instance', + } + ), + todoList: z.string().optional().default(''), + clarificationQuestion: ClarifyingQuestionSchema.optional(), +}); + +// Output schema for the workflow +const docsAgentWorkflowOutputSchema = z.object({ + todos: z.array(z.string()).optional(), + todoList: z.string().optional(), + documentationCreated: z.boolean().optional(), + clarificationNeeded: z.boolean().optional(), + clarificationQuestion: z + .object({ + issue: z.string(), + context: z.string(), + clarificationQuestion: z.string(), + }) + .optional(), + finished: z.boolean().optional(), + metadata: z + .object({ + filesCreated: z.number().optional(), + toolsUsed: z.array(z.string()).optional(), + }) + .optional(), +}); + +// Create the workflow with initialization step first +const docsAgentWorkflow = createWorkflow({ + id: 'docs-agent-workflow', + inputSchema: docsAgentWorkflowInputSchema, + outputSchema: docsAgentWorkflowOutputSchema, + steps: [initializeContextStep, createDocsTodosStep, docsAgentStep], +}) + .then(initializeContextStep) // First step: initialize runtime context + .then(createDocsTodosStep) // Then create todos + .then(docsAgentStep) // Finally run the agent + .commit(); + +export default docsAgentWorkflow; +export { docsAgentWorkflowInputSchema, docsAgentWorkflowOutputSchema };