diff --git a/.cursor/rules/code-generation.mdc b/.cursor/rules/code-generation.mdc index 8e3f886d..0737d786 100644 --- a/.cursor/rules/code-generation.mdc +++ b/.cursor/rules/code-generation.mdc @@ -1,12 +1,12 @@ --- -description: Code Generation Development Rules for CLI -globs: internal/bundler/*.go, cmd/*.go +description: Code Generation Development Rules for CLI and SDK +globs: internal/bundler/*.go, cmd/*.go, src/apis/**/*.ts, src/server/*.ts, src/types.ts alwaysApply: true --- # Code Generation Development Rules -> **⚠️ IMPORTANT**: These rules work in conjunction with the SDK rules. When updating these CLI rules, also update `sdk-js/.cursor/rules/code-generation.mdc` to keep them in sync. +> **⚠️ IMPORTANT**: These rules apply to both CLI and SDK development. Keep both codebases in sync when making changes. ## Core Principles @@ -21,12 +21,61 @@ alwaysApply: true 2. **Update SDK to handle generated content dynamically** (e.g., `src/apis/prompt/index.ts`) 3. **Build and test the full pipeline**: CLI generation → SDK loading → Agent usage +### Multi-File Support +- **Directory Scanning**: CLI scans `src/prompts/` and `prompts/` directories for all YAML files +- **File Processing**: Processes all `.yaml` and `.yml` files found in the prompts directory +- **Combined Output**: Merges prompts from multiple files into a single generated output +- **Legacy Support**: Still supports single `prompts.yaml` file in various locations + +### JSDoc Documentation +- **Function Comments**: Generated `system` and `prompt` functions include JSDoc comments with actual prompt content +- **IDE Support**: JSDoc comments provide better IDE hover tooltips and documentation +- **Content Preservation**: Original prompt templates are preserved exactly as written in YAML +- **Line Wrapping**: Long lines are automatically wrapped at 80 characters for readability + ### Optional Field Handling - ✅ Generated code should NEVER require optional chaining (`?.`) - ✅ Always generate both `system` and `prompt` fields, even if empty - ✅ Empty fields should return empty strings, not undefined - ❌ Never generate partial objects that require optional chaining +## Architecture Overview + +### File Structure +``` +sdk-js/src/apis/prompt/ +├── generic_types.ts # Simple utility types for CLI to use +├── generated/ +│ ├── index.d.ts # Shell TypeScript definitions (replaced by CLI) +│ ├── index.js # Shell JavaScript (replaced by CLI) +│ └── _index.js # Actual generated JavaScript (created by CLI) +├── index.ts # Main API with dynamic loading +└── signature.ts # Signature function factory +``` + +### Key Learnings from Implementation + +#### 1. Simplified Architecture +- **Use shell files**: `index.d.ts` and `index.js` are placeholders that get completely replaced +- **Less complex generics**: Avoid overly complex TypeScript generics that cause compilation issues +- **Rely on code generation**: Let the CLI do the heavy lifting instead of complex type manipulation + +#### 2. Slug-Based Naming +- **Use slugs directly**: Generate code using the original slug names (e.g., `'simple-helper'`) +- **Quote property names**: Use `'slug-name'` syntax in TypeScript interfaces +- **Bracket notation**: Use `prompts['slug-name']` in JavaScript for property access +- **CamelCase variables**: Use `strcase.ToLowerCamel(slug)` for JavaScript variable names + +#### 3. Type Safety Without Complexity +```typescript +// ✅ Simple, working approach +export type PromptsCollection = Record; +export type PromptSignature = (params: any) => string; + +// ❌ Avoid overly complex generics that cause compilation issues +export type ComplexGeneric> = ... +``` + ## CLI-Specific Rules ### Generation Target Locations @@ -38,10 +87,20 @@ sdkPath := filepath.Join(root, "node_modules", "@agentuity", "sdk", "dist", "gen sdkPath := filepath.Join(root, "src", "generated") ``` -### Path Resolution Priority -1. Try `dist/` directory first (production) -2. Fallback to `src/` directory (development) -3. Always check if SDK exists before generating +### Slug Handling in Code Generation +```go +// ✅ Correct: Use slugs directly with proper quoting +const %s = { + slug: "%s", + // ... fields +};`, strcase.ToLowerCamel(prompt.Slug), prompt.Slug + +// In TypeScript interfaces +exports = append(exports, fmt.Sprintf(" '%s': %s;", prompt.Slug, strcase.ToCamel(prompt.Slug))) + +// In JavaScript property access +bodyParts = append(bodyParts, fmt.Sprintf("const result = prompts['%s'].system(params)", prompt.Slug)) +``` ### File Generation Pattern ```go @@ -71,6 +130,104 @@ func FindSDKGeneratedDir(ctx BundleContext, projectDir string) (string, error) { } ``` +## SDK-Specific Rules + +### Path Resolution +- Use absolute paths only (relative paths don't work in bundled environments) +- Check `dist/` directory first, then `src/` directory +- Always provide fallbacks for missing generated content + +### Dynamic Loading Pattern +Generated content doesn't exist at SDK build time, so use dynamic loading patterns: + +```typescript +// ✅ Good: Dynamic loading with fallbacks +public async loadGeneratedContent(): Promise { + try { + const path = require('path'); + const fs = require('fs'); + + const possiblePaths = [ + path.join(process.cwd(), 'node_modules', '@agentuity', 'sdk', 'dist', 'generated', 'content.js'), + path.join(process.cwd(), 'node_modules', '@agentuity', 'sdk', 'src', 'generated', 'content.js') + ]; + + for (const possiblePath of possiblePaths) { + if (fs.existsSync(possiblePath)) { + const generatedModule = require(possiblePath); + this.content = generatedModule.content || defaultContent; + break; + } + } + } catch (error) { + this.content = defaultContent; + console.warn('No generated content found'); + } +} +``` + +### Context Integration +```typescript +// ✅ Good: Load generated content in context creation +export async function createServerContext(req: ServerContextRequest): Promise { + // ... other initialization + + // Load generated content dynamically + await promptAPI.loadPrompts(); + + return { + // ... other context properties + prompts: { + getPrompt: (slug: string) => promptAPI.prompts[slug], + compile: (slug: string, params: any) => { + // Use signature functions or fallback to manual compilation + const signature = promptAPI.signatures[slug]; + if (signature) { + return signature(params); + } + // Fallback logic... + } + }, + }; +} +``` + +### Type Safety +- Generate TypeScript definitions for generated content +- Use proper type annotations for dynamic imports +- Maintain type safety throughout the loading process +- Keep types simple to avoid compilation issues + +## Common Patterns + +### Generated Content API Class +```typescript +export default class GeneratedContentAPI { + public content: typeof defaultContent; + + constructor() { + this.content = defaultContent; + } + + public async loadContent(): Promise { + // Dynamic loading logic here + } +} +``` + +### Error Handling +```typescript +try { + // Try to load generated content + const generatedModule = require(possiblePath); + this.content = generatedModule.content || defaultContent; +} catch (error) { + // Fallback to default content + this.content = defaultContent; + console.warn('No generated content found'); +} +``` + ## Common Pitfalls to Avoid ### ❌ Don't Do This @@ -83,6 +240,25 @@ sdkPath := filepath.Join(root, "src", "generated") // Not checking if SDK exists os.WriteFile(path, content, 0644) // Without checking if path exists + +// Using unquoted slugs in TypeScript +optional-with-defaults: OptionalWithDefaults; // ❌ Invalid syntax +``` + +```typescript +// Hardcoding generated content in source files +export const prompts = { + copyWriter: { /* hardcoded content */ } +}; + +// Using relative imports that don't work in bundles +const generatedModule = require('./generated/_index.js'); + +// Not providing fallbacks +const generatedModule = require(possiblePath); // Will crash if file doesn't exist + +// Overly complex generics that cause compilation issues +export type ComplexGeneric> = ... ``` ### ✅ Do This Instead @@ -97,6 +273,79 @@ sdkPath := filepath.Join(root, "node_modules", "@agentuity", "sdk", "dist", "gen if err := os.MkdirAll(sdkPath, 0755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } + +// Use quoted slugs in TypeScript +'optional-with-defaults': OptionalWithDefaults; // ✅ Valid syntax ``` -Remember: The CLI's job is to generate content into the installed SDK, not modify source files. \ No newline at end of file +```typescript +// Dynamic loading with absolute paths +const possiblePaths = [ + path.join(process.cwd(), 'node_modules', '@agentuity', 'sdk', 'dist', 'generated', '_index.js') +]; + +// With proper error handling +try { + const generatedModule = require(possiblePath); + this.content = generatedModule.content || defaultContent; +} catch (error) { + this.content = defaultContent; +} + +// Simple, working types +export type PromptsCollection = Record; +export type PromptSignature = (params: any) => string; +``` + +## Build Considerations + +- Generated content is loaded at runtime, not build time +- Use `require()` for CommonJS compatibility in bundled environments +- Avoid `import()` statements for generated content +- Ensure fallbacks work when generated content is missing +- Keep TypeScript types simple to avoid compilation issues + +## Development Workflow + +### When Making Changes to Generated Content Structure: +1. **Update CLI code generation** in `cli/internal/bundler/prompts/code_generator.go` +2. **Update SDK to handle new structure** in `src/apis/prompt/index.ts` +3. **Bump SDK version** in `package.json` +4. **Build SDK**: `npm run build` +5. **Run bundle command**: `go run . bundle` to regenerate content +6. **Copy dist to node_modules**: Use `./build-and-copy.sh ` or `cp -r dist/* node_modules/@agentuity/sdk/dist/` (if needed) + +**Note**: After the first setup, you only need to run step 6 for subsequent changes to avoid reinstalling the entire package. + +### Quick SDK Update Workflow: +For faster iteration when testing SDK changes: +```bash +# In sdk-js directory +npm run build +./build-and-copy.sh /path/to/test-project/node_modules/@agentuity/sdk/dist +``` + +This copies the built SDK directly to your test project without reinstalling the package. + +### Required Exports +- ✅ Always export `interpolateTemplate` from main SDK index +- ✅ Generated content must import from `@agentuity/sdk` (not relative paths) +- ✅ Ensure all dependencies are properly exported + +## Key Implementation Notes + +### What We Learned +1. **Shell-based approach works better** than complex type manipulation +2. **Slug-based naming** is more intuitive than camelCase conversion +3. **Simple types** are more maintainable than complex generics +4. **Complete file replacement** is cleaner than partial updates +5. **Proper quoting** is essential for TypeScript interfaces with hyphens + +### Best Practices +- Use slugs directly in generated code with proper quoting +- Keep generic types simple and focused +- Rely on code generation for complex type structures +- Provide clear fallbacks for missing generated content +- Test the full pipeline regularly + +Remember: The CLI's job is to generate content into the installed SDK, and the SDK's job is to load generated content dynamically, not contain hardcoded generated content. \ No newline at end of file diff --git a/.cursor/rules/prompt-docstrings.mdc b/.cursor/rules/prompt-docstrings.mdc index df6191cd..0a5fd123 100644 --- a/.cursor/rules/prompt-docstrings.mdc +++ b/.cursor/rules/prompt-docstrings.mdc @@ -1,33 +1,74 @@ -# Prompt JSDoc Docstrings +# Prompt JSDoc Docstrings and Multi-File Support -## Goal -Add JSDoc-style docstrings to generated prompt objects that provide better IDE support and make the generated code self-documenting. +https://stackoverflow.com/questions/19230971/how-do-i-jsdoc-a-nested-objects-methods -## Requirements +## Goal +Add JSDoc-style docstrings to generated prompt functions and support scanning multiple YAML files in the prompts directory for better IDE support and organization. + +## Features Implemented + +### 1. Multi-File Support +The CLI now scans the entire `prompts` directory and processes all YAML files: +- **Directory Locations**: Checks `src/prompts/` and `prompts/` directories +- **File Types**: Processes all `.yaml` and `.yml` files +- **Combined Output**: Merges all prompts from multiple files into a single generated output +- **Legacy Support**: Still supports single `prompts.yaml` file in various locations + +### 2. JSDoc Comments on Functions and Types +Generate JSDoc comments on both JavaScript functions and TypeScript type definitions: + +**JavaScript Functions:** +```javascript +system: /** + * System prompt: + * You are a {role:senior} code reviewer specializing in {language:JavaScript}. + * Your experience level is {experience:expert} + * + */ +({ role, language, experience } = {}) => { + return interpolateTemplate("...", { role, language, experience }) +}, +``` -### 1. JSDoc Format for PromptsCollection -Generate JSDoc comments on the `PromptsCollection` type properties with the following structure: +**TypeScript Type Definitions:** ```typescript -export type PromptsCollection = { +export type Assistant = { + slug: string; /** - * [Prompt Name] - [Prompt Description] - * - * @prompt - * [Original prompt template with variables] + * System prompt: + * You are a {role:helpful assistant} specializing in {domain:programming}. + * Your experience level is {experience:intermediate} + * + */ + system: (variables?: { + role?: string | "helpful assistant"; + domain?: string | "programming"; + experience?: string | "intermediate" + }) => string; + /** + * User prompt: + * Help the user with: {task:their question} + * Use a {!tone} approach. */ - promptName: PromptName; + prompt: (variables: { + task?: string | "their question"; + tone: string + }) => string }; ``` -### 2. Content Inclusion -- **Name and Description**: Include both from YAML in format "Name - Description" -- **@prompt**: Include only the original prompt template (not system template) +### 3. Content Inclusion +- **System Prompts**: Include the actual system prompt content in JSDoc +- **User Prompts**: Include the actual user prompt content in JSDoc - **Template Preservation**: Show original templates exactly as written in YAML +- **Variable Syntax**: Preserve `{variable:default}`, `{!variable}`, `{{variable}}` syntax +- **Line Wrapping**: Automatically wrap long lines at 80 characters for readability -### 3. Template Preservation +### 4. Template Preservation - Show original templates exactly as written in YAML - Preserve variable syntax: `{variable:default}`, `{!variable}`, `{{variable}}` - Maintain line breaks and formatting +- Escape JSDoc special characters (`*/` becomes `*\/`) - Escape JSDoc comment characters (`*/` → `* /`) ### 4. IDE Integration diff --git a/internal/bundler/prompts/code_generator.go b/internal/bundler/prompts/code_generator.go index f54a630c..abccba86 100644 --- a/internal/bundler/prompts/code_generator.go +++ b/internal/bundler/prompts/code_generator.go @@ -28,7 +28,7 @@ func (cg *CodeGenerator) GenerateJavaScript() string { } return fmt.Sprintf(`// Generated prompts - do not edit manually -import { interpolateTemplate } from '@agentuity/sdk'; +import { interpolateTemplate } from '../../../index.js'; %s @@ -38,7 +38,9 @@ import { interpolateTemplate } from '@agentuity/sdk'; */ export const prompts = { %s -};`, strings.Join(objects, "\n\n"), cg.generatePromptExports()) +}; + +`, strings.Join(objects, "\n\n"), cg.generatePromptExports()) } // GenerateTypeScriptTypes generates the TypeScript definitions file @@ -46,155 +48,261 @@ func (cg *CodeGenerator) GenerateTypeScriptTypes() string { var promptTypes []string for _, prompt := range cg.prompts { - promptType := cg.generatePromptType(prompt) + promptType := cg.generateDetailedPromptType(prompt) promptTypes = append(promptTypes, promptType) } return fmt.Sprintf(`// Generated prompt types - do not edit manually -import { interpolateTemplate } from '@agentuity/sdk'; +import { interpolateTemplate, Prompt } from '@agentuity/sdk'; %s -export type PromptsCollection = { +export interface GeneratedPromptsCollection { %s -}; - -export const prompts: PromptsCollection = {} as any;`, strings.Join(promptTypes, "\n\n"), cg.generatePromptTypeExports()) } -// GenerateTypeScriptInterfaces generates the TypeScript interfaces file -func (cg *CodeGenerator) GenerateTypeScriptInterfaces() string { - var interfaces []string +export type PromptsCollection = GeneratedPromptsCollection; - for _, prompt := range cg.prompts { - interfaceDef := cg.generatePromptInterface(prompt) - interfaces = append(interfaces, interfaceDef) - } - - return fmt.Sprintf(`// Generated prompt interfaces - do not edit manually -%s`, strings.Join(interfaces, "\n\n")) +export const prompts: PromptsCollection = {} as any;`, strings.Join(promptTypes, "\n\n"), cg.generatePromptTypeExports()) } // generatePromptObject generates a single prompt object with system and prompt properties func (cg *CodeGenerator) generatePromptObject(prompt Prompt) string { - // Get variables from system template - systemVariables := cg.getSystemVariables(prompt) - var systemParams []string - if len(systemVariables) > 0 { - systemParams = append(systemParams, "variables") + // Determine if prompt has system, prompt, and variables + hasSystem := prompt.System != "" + hasPrompt := prompt.Prompt != "" + hasSystemVars := len(cg.getSystemVariables(prompt)) > 0 + hasPromptVars := len(cg.getPromptVariables(prompt)) > 0 + hasVariables := hasSystemVars || hasPromptVars + + // Generate the prompt object with conditional fields + var fields []string + fields = append(fields, fmt.Sprintf("slug: %q", prompt.Slug)) + + if hasSystem { + fields = append(fields, cg.generateSystemField(prompt)) + } + + if hasPrompt { + fields = append(fields, cg.generatePromptField(prompt)) } - systemParamStr := strings.Join(systemParams, ", ") - // Get variables from prompt template - promptVariables := cg.getPromptVariables(prompt) - var promptParams []string - if len(promptVariables) > 0 { - promptParams = append(promptParams, "variables") + if hasVariables { + fields = append(fields, cg.generateVariablesField(prompt)) } - promptParamStr := strings.Join(promptParams, ", ") return fmt.Sprintf(`const %s = { - slug: %q, - system: { - compile: (%s) => { - return %s - } - }, - prompt: { - compile: (%s) => { - return %s - } - } -};`, strcase.ToLowerCamel(prompt.Slug), prompt.Slug, systemParamStr, cg.generateTemplateValue(prompt.System), promptParamStr, cg.generateTemplateValue(prompt.Prompt)) + %s +};`, strcase.ToLowerCamel(prompt.Slug), strings.Join(fields, ",\n ")) } -// generateTemplateValue generates the value for a template (either compile function or direct interpolateTemplate call) -func (cg *CodeGenerator) generateTemplateValue(template string) string { - if template == "" { - return `""` +// generateSystemField generates the system field for a prompt +func (cg *CodeGenerator) generateSystemField(prompt Prompt) string { + systemVars := cg.getSystemVariableObjects(prompt) + + // Generate JSDoc comment with the system prompt + jsdoc := cg.generateSystemJSDoc(prompt) + + if len(systemVars) > 0 { + allOptional := cg.areAllVariablesOptional(systemVars) + + // Generate parameter destructuring for variables + var paramNames []string + for _, variable := range systemVars { + paramNames = append(paramNames, variable.Name) + } + paramStr := strings.Join(paramNames, ", ") + + if allOptional { + // Make parameters optional + return fmt.Sprintf(`system: %s({ %s } = {}) => { + return interpolateTemplate(%q, { %s }) + }`, jsdoc, paramStr, prompt.System, paramStr) + } else { + // Parameters are required + return fmt.Sprintf(`system: %s({ %s }) => { + return interpolateTemplate(%q, { %s }) + }`, jsdoc, paramStr, prompt.System, paramStr) + } } + return fmt.Sprintf(`system: %s() => { + return interpolateTemplate(%q, {}) + }`, jsdoc, prompt.System) +} + +// generatePromptField generates the prompt field for a prompt +func (cg *CodeGenerator) generatePromptField(prompt Prompt) string { + promptVars := cg.getPromptVariableObjects(prompt) + + // Generate JSDoc comment with the prompt content + jsdoc := cg.generatePromptJSDoc(prompt) + + if len(promptVars) > 0 { + allOptional := cg.areAllVariablesOptional(promptVars) - return fmt.Sprintf("interpolateTemplate(%q, variables)", template) + // Generate parameter destructuring for variables + var paramNames []string + for _, variable := range promptVars { + paramNames = append(paramNames, variable.Name) + } + paramStr := strings.Join(paramNames, ", ") + + if allOptional { + // Make parameters optional + return fmt.Sprintf(`prompt: %s({ %s } = {}) => { + return interpolateTemplate(%q, { %s }) + }`, jsdoc, paramStr, prompt.Prompt, paramStr) + } else { + // Parameters are required + return fmt.Sprintf(`prompt: %s({ %s }) => { + return interpolateTemplate(%q, { %s }) + }`, jsdoc, paramStr, prompt.Prompt, paramStr) + } + } + return fmt.Sprintf(`prompt: %s() => { + return interpolateTemplate(%q, {}) + }`, jsdoc, prompt.Prompt) } -// generatePromptType generates a TypeScript type for a prompt object -func (cg *CodeGenerator) generatePromptType(prompt Prompt) string { - // Get variables from system template - systemVariables := cg.getSystemVariableObjects(prompt) - var systemParams []string - if len(systemVariables) > 0 { - systemParams = append(systemParams, fmt.Sprintf("variables?: { %s }", cg.generateVariableTypesFromObjects(systemVariables))) +// generateVariablesField generates the variables field for a prompt +func (cg *CodeGenerator) generateVariablesField(prompt Prompt) string { + var allVars []Variable + systemVars := cg.getSystemVariableObjects(prompt) + promptVars := cg.getPromptVariableObjects(prompt) + + // Combine system and prompt variables + allVars = append(allVars, systemVars...) + allVars = append(allVars, promptVars...) + + if len(allVars) == 0 { + return "" } - systemParamStr := strings.Join(systemParams, ", ") - // Get variables from prompt template - promptVariables := cg.getPromptVariableObjects(prompt) - var promptParams []string - if len(promptVariables) > 0 { - promptParams = append(promptParams, fmt.Sprintf("variables?: { %s }", cg.generateVariableTypesFromObjects(promptVariables))) + var varDefs []string + for _, variable := range allVars { + varDefs = append(varDefs, fmt.Sprintf("%s: %q", variable.Name, "string")) } - promptParamStr := strings.Join(promptParams, ", ") - // Generate separate system and prompt types with docstrings - systemTypeName := fmt.Sprintf("%sSystem", strcase.ToCamel(prompt.Slug)) - promptTypeName := fmt.Sprintf("%sPrompt", strcase.ToCamel(prompt.Slug)) + return fmt.Sprintf("variables: { %s }", strings.Join(varDefs, ", ")) +} + +// generateDetailedPromptType generates detailed TypeScript types with specific parameter interfaces +func (cg *CodeGenerator) generateDetailedPromptType(prompt Prompt) string { + hasSystem := prompt.System != "" + hasPrompt := prompt.Prompt != "" + hasSystemVars := len(cg.getSystemVariables(prompt)) > 0 + hasPromptVars := len(cg.getPromptVariables(prompt)) > 0 + mainTypeName := strcase.ToCamel(prompt.Slug) - systemTypeWithDocstring := cg.generateTypeWithDocstring(prompt.System, systemTypeName, systemParamStr, mainTypeName) - promptTypeWithDocstring := cg.generateTypeWithDocstring(prompt.Prompt, promptTypeName, promptParamStr, mainTypeName) + // Generate parameter interfaces + var systemParams string + var promptParams string - return fmt.Sprintf(`%s + if hasSystemVars { + systemVars := cg.getSystemVariableObjects(prompt) + allSystemOptional := cg.areAllVariablesOptional(systemVars) + systemParams = cg.generateParameterInterface(systemVars, allSystemOptional) + } -%s + if hasPromptVars { + promptVars := cg.getPromptVariableObjects(prompt) + allPromptOptional := cg.areAllVariablesOptional(promptVars) + promptParams = cg.generateParameterInterface(promptVars, allPromptOptional) + } -export type %s = { - slug: string; - /** -%s - */ - system: %s; - /** -%s - */ - prompt: %s; -};`, - systemTypeWithDocstring, promptTypeWithDocstring, mainTypeName, cg.generateTemplateDocstring(prompt.System), systemTypeName, cg.generateTemplateDocstring(prompt.Prompt), promptTypeName) + // Generate the main prompt type with JSDoc comments + var fields []string + fields = append(fields, "slug: string") + + if hasSystem { + systemJSDoc := cg.generateSystemJSDocForType(prompt) + if hasSystemVars { + systemVars := cg.getSystemVariableObjects(prompt) + allSystemOptional := cg.areAllVariablesOptional(systemVars) + if allSystemOptional { + fields = append(fields, fmt.Sprintf("%s system: (variables?: %s) => string", systemJSDoc, systemParams)) + } else { + fields = append(fields, fmt.Sprintf("%s system: (variables: %s) => string", systemJSDoc, systemParams)) + } + } else { + fields = append(fields, fmt.Sprintf("%s system: () => string", systemJSDoc)) + } + } + + if hasPrompt { + promptJSDoc := cg.generatePromptJSDocForType(prompt) + if hasPromptVars { + promptVars := cg.getPromptVariableObjects(prompt) + allPromptOptional := cg.areAllVariablesOptional(promptVars) + if allPromptOptional { + fields = append(fields, fmt.Sprintf("%s prompt: (variables?: %s) => string", promptJSDoc, promptParams)) + } else { + fields = append(fields, fmt.Sprintf("%s prompt: (variables: %s) => string", promptJSDoc, promptParams)) + } + } else { + fields = append(fields, fmt.Sprintf("%s prompt: () => string", promptJSDoc)) + } + } + + fieldsStr := strings.Join(fields, ";\n ") + + // Generate parameter interface for compile function + var compileParams string + if hasSystemVars || hasPromptVars { + var paramFields []string + if hasSystemVars { + paramFields = append(paramFields, fmt.Sprintf("system: %s", systemParams)) + } + if hasPromptVars { + paramFields = append(paramFields, fmt.Sprintf("prompt: %s", promptParams)) + } + compileParams = fmt.Sprintf("{\n %s\n }", strings.Join(paramFields, ";\n ")) + } else { + compileParams = "never" + } + + // Generate JSDoc typedef for the prompt type + typedefJSDoc := cg.generateTypedefJSDoc(prompt) + + return fmt.Sprintf(`%sexport type %s = { + %s +}; + +export type %sParams = %s;`, typedefJSDoc, mainTypeName, fieldsStr, mainTypeName, compileParams) } -// generatePromptInterface generates a TypeScript interface for a prompt -func (cg *CodeGenerator) generatePromptInterface(prompt Prompt) string { - // Get variables from system template - systemVariables := cg.getSystemVariableObjects(prompt) - var systemParams []string - if len(systemVariables) > 0 { - systemParams = append(systemParams, fmt.Sprintf("variables?: { %s }", cg.generateVariableTypesFromObjects(systemVariables))) - } - systemParamStr := strings.Join(systemParams, ", ") - systemCompileType := fmt.Sprintf("(%s) => string", systemParamStr) - - // Get variables from prompt template - promptVariables := cg.getPromptVariableObjects(prompt) - var promptParams []string - if len(promptVariables) > 0 { - promptParams = append(promptParams, fmt.Sprintf("variables?: { %s }", cg.generateVariableTypesFromObjects(promptVariables))) - } - promptParamStr := strings.Join(promptParams, ", ") - promptCompileType := fmt.Sprintf("(%s) => string", promptParamStr) - - return fmt.Sprintf(`export interface %s { - slug: string; - system: { compile: %s }; - prompt: { compile: %s }; -}`, strcase.ToCamel(prompt.Slug), systemCompileType, promptCompileType) +// areAllVariablesOptional checks if all variables in a list are optional +func (cg *CodeGenerator) areAllVariablesOptional(variables []Variable) bool { + for _, variable := range variables { + if variable.IsRequired { + return false + } + } + return true } -// generateVariableTypes generates TypeScript types for variables -func (cg *CodeGenerator) generateVariableTypes(variables []string) string { - var types []string +// generateParameterInterface generates a TypeScript interface for variables +func (cg *CodeGenerator) generateParameterInterface(variables []Variable, isOptional bool) string { + if len(variables) == 0 { + return "{}" + } + + var fields []string for _, variable := range variables { - types = append(types, fmt.Sprintf("%s: string", variable)) + if variable.IsRequired { + fields = append(fields, fmt.Sprintf("%s: string", variable.Name)) + } else { + // Include default value as union type if it exists + if variable.HasDefault { + fields = append(fields, fmt.Sprintf("%s?: string | %q", variable.Name, variable.DefaultValue)) + } else { + fields = append(fields, fmt.Sprintf("%s?: string", variable.Name)) + } + } } - return strings.Join(types, "; ") + + return fmt.Sprintf("{\n %s\n }", strings.Join(fields, ";\n ")) } // generateVariableTypesFromObjects generates TypeScript types for variables with default values @@ -269,10 +377,10 @@ func (cg *CodeGenerator) generatePromptExports() string { for _, prompt := range cg.prompts { // Generate JSDoc comment for each prompt property jsdocComment := cg.generatePromptPropertyJSDoc(prompt) - exports = append(exports, jsdocComment) - exports = append(exports, fmt.Sprintf(" %s,", strcase.ToLowerCamel(prompt.Slug))) + // Combine JSDoc and property on the same line + exports = append(exports, fmt.Sprintf("%s\n '%s': %s", jsdocComment, prompt.Slug, strcase.ToLowerCamel(prompt.Slug))) } - return strings.Join(exports, "\n") + return strings.Join(exports, ",\n") } // generatePromptTypeExports generates the exports object for TypeScript types @@ -282,7 +390,7 @@ func (cg *CodeGenerator) generatePromptTypeExports() string { // Generate JSDoc comment for each prompt property jsdocComment := cg.generatePromptPropertyJSDoc(prompt) exports = append(exports, jsdocComment) - exports = append(exports, fmt.Sprintf(" %s: %s;", strcase.ToLowerCamel(prompt.Slug), strcase.ToCamel(prompt.Slug))) + exports = append(exports, fmt.Sprintf(" '%s': %s;", prompt.Slug, strcase.ToCamel(prompt.Slug))) } return strings.Join(exports, "\n") } @@ -294,22 +402,33 @@ func (cg *CodeGenerator) generatePromptPropertyJSDoc(prompt Prompt) string { // Create JSDoc comment with name, description, and templates docLines = append(docLines, " /**") - // Add name and description with separate tags - if prompt.Name != "" { - docLines = append(docLines, fmt.Sprintf(" * @name %s", prompt.Name)) + // Add name and description in the main comment + if prompt.Name != "" && prompt.Description != "" { + docLines = append(docLines, fmt.Sprintf(" * %s - %s", prompt.Name, prompt.Description)) + } else if prompt.Name != "" { + docLines = append(docLines, fmt.Sprintf(" * %s", prompt.Name)) + } else if prompt.Description != "" { + docLines = append(docLines, fmt.Sprintf(" * %s", prompt.Description)) } else { // Fallback to slug-based name - docLines = append(docLines, fmt.Sprintf(" * @name %s", strcase.ToCamel(prompt.Slug))) + docLines = append(docLines, fmt.Sprintf(" * %s", strcase.ToCamel(prompt.Slug))) } - if prompt.Description != "" { - docLines = append(docLines, fmt.Sprintf(" * @description %s", prompt.Description)) + // Add function signatures in the description + docLines = append(docLines, " *") + docLines = append(docLines, " * Functions:") + + if prompt.System != "" { + docLines = append(docLines, " * - system(): Returns the system prompt") + } + if prompt.Prompt != "" { + docLines = append(docLines, " * - prompt(): Returns the user prompt") } // Add original templates if prompt.System != "" { docLines = append(docLines, " *") - docLines = append(docLines, " * @system") + docLines = append(docLines, " * System prompt:") // Escape the template for JSDoc and add proper line breaks escapedSystem := strings.ReplaceAll(prompt.System, "*/", "* /") // Split by newlines and add proper JSDoc formatting @@ -321,7 +440,7 @@ func (cg *CodeGenerator) generatePromptPropertyJSDoc(prompt Prompt) string { if prompt.Prompt != "" { docLines = append(docLines, " *") - docLines = append(docLines, " * @prompt") + docLines = append(docLines, " * User prompt:") // Escape the template for JSDoc and add proper line breaks escapedPrompt := strings.ReplaceAll(prompt.Prompt, "*/", "* /") // Split by newlines and add proper JSDoc formatting @@ -396,7 +515,7 @@ func (cg *CodeGenerator) getPromptVariables(prompt Prompt) []string { return promptTemplate.VariableNames() } -// getSystemVariableObjects gets variable objects from the system template only +// getSystemVariableObjects gets Variable objects from the system template func (cg *CodeGenerator) getSystemVariableObjects(prompt Prompt) []Variable { // Parse system template if not already parsed systemTemplate := prompt.SystemTemplate @@ -407,7 +526,7 @@ func (cg *CodeGenerator) getSystemVariableObjects(prompt Prompt) []Variable { return systemTemplate.Variables } -// getPromptVariableObjects gets variable objects from the prompt template only +// getPromptVariableObjects gets Variable objects from the prompt template func (cg *CodeGenerator) getPromptVariableObjects(prompt Prompt) []Variable { // Parse prompt template if not already parsed promptTemplate := prompt.PromptTemplate @@ -418,25 +537,6 @@ func (cg *CodeGenerator) getPromptVariableObjects(prompt Prompt) []Variable { return promptTemplate.Variables } -// generateTypeWithDocstring generates a separate type with docstring -func (cg *CodeGenerator) generateTypeWithDocstring(template, typeName, paramStr, mainTypeName string) string { - if template == "" { - return fmt.Sprintf(`export type %s = { compile: (%s) => string };`, - typeName, paramStr) - } - - // Generate JSDoc comment for the type with @memberof - docstring := cg.generateTemplateDocstring(template) - - return fmt.Sprintf(`/** -%s - * @memberof %s - * @type {object} - */ -export type %s = { compile: (%s) => string };`, - docstring, mainTypeName, typeName, paramStr) -} - // generateTemplateDocstring generates the docstring content for any template func (cg *CodeGenerator) generateTemplateDocstring(template string) string { if template == "" { @@ -526,3 +626,184 @@ func (cg *CodeGenerator) wrapLine(line string, width int) []string { return wrapped } + +// generateSystemJSDoc generates JSDoc comment for the system function +func (cg *CodeGenerator) generateSystemJSDoc(prompt Prompt) string { + if prompt.System == "" { + return "" + } + + // Escape the system prompt for JSDoc + escapedSystem := strings.ReplaceAll(prompt.System, "*/", "*\\/") + + // Clean up the system prompt for single line display but keep variable placeholders + cleanSystem := strings.ReplaceAll(escapedSystem, "\n", " ") + cleanSystem = strings.TrimSpace(cleanSystem) + + // Get system variables for parameter documentation + systemVars := cg.getSystemVariableObjects(prompt) + allOptional := cg.areAllVariablesOptional(systemVars) + + // Build JSDoc + var jsdoc strings.Builder + jsdoc.WriteString("/**\n") + jsdoc.WriteString(fmt.Sprintf(" * System prompt: %s\n", cleanSystem)) + + // Add parameter documentation + if len(systemVars) > 0 { + jsdoc.WriteString(" * @param {Object} variables - System prompt variables\n") + for _, variable := range systemVars { + if allOptional { + jsdoc.WriteString(fmt.Sprintf(" * @param {string} [variables.%s] - System prompt variable\n", variable.Name)) + } else { + jsdoc.WriteString(fmt.Sprintf(" * @param {string} variables.%s - System prompt variable\n", variable.Name)) + } + } + } + + jsdoc.WriteString(" * @returns {string} The compiled system prompt\n") + jsdoc.WriteString(" */\n ") + + return jsdoc.String() +} + +// generatePromptJSDoc generates JSDoc comment for the prompt function +func (cg *CodeGenerator) generatePromptJSDoc(prompt Prompt) string { + if prompt.Prompt == "" { + return "" + } + + // Escape the prompt for JSDoc + escapedPrompt := strings.ReplaceAll(prompt.Prompt, "*/", "*\\/") + + // Clean up the prompt for single line display + cleanPrompt := strings.ReplaceAll(escapedPrompt, "\n", " ") + cleanPrompt = strings.TrimSpace(cleanPrompt) + + // Get prompt variables for parameter documentation + promptVars := cg.getPromptVariableObjects(prompt) + allOptional := cg.areAllVariablesOptional(promptVars) + + // Build JSDoc + var jsdoc strings.Builder + jsdoc.WriteString("/**\n") + jsdoc.WriteString(fmt.Sprintf(" * User prompt: %s\n", cleanPrompt)) + + // Add parameter documentation + if len(promptVars) > 0 { + jsdoc.WriteString(" * @param {Object} variables - User prompt variables\n") + for _, variable := range promptVars { + if allOptional { + jsdoc.WriteString(fmt.Sprintf(" * @param {string} [variables.%s] - User prompt variable\n", variable.Name)) + } else { + jsdoc.WriteString(fmt.Sprintf(" * @param {string} variables.%s - User prompt variable\n", variable.Name)) + } + } + } + + jsdoc.WriteString(" * @returns {string} The compiled user prompt\n") + jsdoc.WriteString(" */\n ") + + return jsdoc.String() +} + +// generateSystemJSDocForType generates JSDoc comment for the system function in TypeScript types +func (cg *CodeGenerator) generateSystemJSDocForType(prompt Prompt) string { + if prompt.System == "" { + return "" + } + + // Escape the system prompt for JSDoc + escapedSystem := strings.ReplaceAll(prompt.System, "*/", "*\\/") + + // Clean up the system prompt for single line display but keep variable placeholders + cleanSystem := strings.ReplaceAll(escapedSystem, "\n", " ") + cleanSystem = strings.TrimSpace(cleanSystem) + + // Convert slug to the destructured variable name pattern + variableName := strcase.ToLowerCamel(prompt.Slug) + "System" + + // Build JSDoc with actual prompt content and variable name + var jsdoc strings.Builder + jsdoc.WriteString("/**\n") + jsdoc.WriteString(fmt.Sprintf(" * %s - System prompt: %s\n", variableName, cleanSystem)) + jsdoc.WriteString(" * @param variables - System prompt variables\n") + jsdoc.WriteString(" * @returns The compiled system prompt string\n") + jsdoc.WriteString(" */\n") + + return jsdoc.String() +} + +// generatePromptJSDocForType generates JSDoc comment for the prompt function in TypeScript types +func (cg *CodeGenerator) generatePromptJSDocForType(prompt Prompt) string { + if prompt.Prompt == "" { + return "" + } + + // Escape the prompt for JSDoc + escapedPrompt := strings.ReplaceAll(prompt.Prompt, "*/", "*\\/") + + // Clean up the prompt for single line display but keep variable placeholders + cleanPrompt := strings.ReplaceAll(escapedPrompt, "\n", " ") + cleanPrompt = strings.TrimSpace(cleanPrompt) + + // Convert slug to the destructured variable name pattern + variableName := strcase.ToLowerCamel(prompt.Slug) + "Prompt" + + // Build JSDoc with actual prompt content and variable name + var jsdoc strings.Builder + jsdoc.WriteString("/**\n") + jsdoc.WriteString(fmt.Sprintf(" * %s - User prompt: %s\n", variableName, cleanPrompt)) + jsdoc.WriteString(" * @param variables - User prompt variables\n") + jsdoc.WriteString(" * @returns The compiled user prompt string\n") + jsdoc.WriteString(" */\n") + + return jsdoc.String() +} + +// generateTypedefJSDoc generates JSDoc typedef for the prompt type +func (cg *CodeGenerator) generateTypedefJSDoc(prompt Prompt) string { + var jsdoc strings.Builder + jsdoc.WriteString("/**\n") + jsdoc.WriteString(" * ") + jsdoc.WriteString(prompt.Name) + jsdoc.WriteString(" - ") + jsdoc.WriteString(prompt.Description) + jsdoc.WriteString("\n * @typedef {Object} ") + jsdoc.WriteString(strcase.ToCamel(prompt.Slug)) + jsdoc.WriteString("\n * @property {string} slug - The prompt slug\n") + + if prompt.System != "" { + jsdoc.WriteString(" * @property {Function} system - System prompt function\n") + // Add system prompt content + escapedSystem := strings.ReplaceAll(prompt.System, "*/", "*\\/") + lines := strings.Split(escapedSystem, "\n") + for _, line := range lines { + wrapped := cg.wrapLine(line, 80) + for _, wrappedLine := range wrapped { + jsdoc.WriteString(" * System: ") + jsdoc.WriteString(wrappedLine) + jsdoc.WriteString("\n") + } + } + } + + if prompt.Prompt != "" { + jsdoc.WriteString(" * @property {Function} prompt - User prompt function\n") + // Add user prompt content + escapedPrompt := strings.ReplaceAll(prompt.Prompt, "*/", "*\\/") + lines := strings.Split(escapedPrompt, "\n") + for _, line := range lines { + wrapped := cg.wrapLine(line, 80) + for _, wrappedLine := range wrapped { + jsdoc.WriteString(" * Prompt: ") + jsdoc.WriteString(wrappedLine) + jsdoc.WriteString("\n") + } + } + } + + jsdoc.WriteString(" */\n") + + return jsdoc.String() +} diff --git a/internal/bundler/prompts/code_generator_test.go b/internal/bundler/prompts/code_generator_test.go index 301f9f9a..cf569cfa 100644 --- a/internal/bundler/prompts/code_generator_test.go +++ b/internal/bundler/prompts/code_generator_test.go @@ -29,7 +29,7 @@ func TestCodeGenerator(t *testing.T) { js := codeGen.GenerateJavaScript() // Check that it contains the import - assert.Contains(t, js, "import { interpolateTemplate } from '@agentuity/sdk';") + assert.Contains(t, js, "import { interpolateTemplate } from '../../../index.js';") // Check that it contains the prompts object assert.Contains(t, js, "export const prompts = {") @@ -41,10 +41,9 @@ func TestCodeGenerator(t *testing.T) { // Check that it contains variables parameter (no TypeScript types) assert.Contains(t, js, "variables") - // Check that it contains compile functions - assert.Contains(t, js, "system: {") - assert.Contains(t, js, "prompt: {") - assert.Contains(t, js, "compile: (variables) => {") + // Check that it contains function signatures + assert.Contains(t, js, "system: /**") + assert.Contains(t, js, "prompt: /**") assert.Contains(t, js, "interpolateTemplate(") // Ensure no TypeScript syntax in JavaScript @@ -58,7 +57,7 @@ func TestCodeGenerator(t *testing.T) { types := codeGen.GenerateTypeScriptTypes() // Check that it contains the import - assert.Contains(t, types, "import { interpolateTemplate } from '@agentuity/sdk';") + assert.Contains(t, types, "import { interpolateTemplate, Prompt } from '@agentuity/sdk';") // Check that it contains the prompts object assert.Contains(t, types, "export const prompts: PromptsCollection = {} as any;") @@ -68,29 +67,10 @@ func TestCodeGenerator(t *testing.T) { assert.Contains(t, types, "TestPrompt2") // Check that it contains variable types with proper optional/default syntax - assert.Contains(t, types, "variables?: {") assert.Contains(t, types, "role?: string | \"assistant\"") assert.Contains(t, types, "domain: string") assert.Contains(t, types, "task?: string | \"their question\"") }) - - t.Run("GenerateTypeScriptInterfaces", func(t *testing.T) { - interfaces := codeGen.GenerateTypeScriptInterfaces() - - // Check that it contains both interfaces - assert.Contains(t, interfaces, "export interface TestPrompt1 {") - assert.Contains(t, interfaces, "export interface TestPrompt2 {") - - // Check that it contains variable types with proper optional/default syntax - assert.Contains(t, interfaces, "variables?: {") - assert.Contains(t, interfaces, "role?: string | \"assistant\"") - assert.Contains(t, interfaces, "domain: string") - assert.Contains(t, interfaces, "task?: string | \"their question\"") - - // Check that it contains system and prompt compile functions - assert.Contains(t, interfaces, "system: { compile:") - assert.Contains(t, interfaces, "prompt: { compile:") - }) } func TestCodeGenerator_EmptyPrompts(t *testing.T) { @@ -113,10 +93,6 @@ func TestCodeGenerator_EmptyPrompts(t *testing.T) { assert.Contains(t, types, "export const prompts: PromptsCollection = {} as any;") }) - t.Run("GenerateTypeScriptInterfaces", func(t *testing.T) { - interfaces := codeGen.GenerateTypeScriptInterfaces() - assert.Equal(t, "// Generated prompt interfaces - do not edit manually\n", interfaces) - }) } func TestCodeGenerator_SingleFieldPrompts(t *testing.T) { @@ -138,10 +114,10 @@ func TestCodeGenerator_SingleFieldPrompts(t *testing.T) { t.Run("GenerateJavaScript", func(t *testing.T) { js := codeGen.GenerateJavaScript() - // Check that it contains the correct compile functions - assert.Contains(t, js, "system: {") - assert.Contains(t, js, "prompt: {") - assert.Contains(t, js, "compile: (variables) => {") + // Check that it contains the correct function signatures + assert.Contains(t, js, "system: /**") + assert.Contains(t, js, "prompt: /**") + assert.Contains(t, js, "interpolateTemplate(") assert.Contains(t, js, "slug:") // Ensure no TypeScript syntax in JavaScript @@ -176,14 +152,14 @@ func TestCodeGenerator_ComplexPrompts(t *testing.T) { js := codeGen.GenerateJavaScript() // Check that it handles multiline templates correctly - assert.Contains(t, js, "interpolateTemplate(\"You are a {role:helpful assistant} specializing in {!domain}.\\nYour experience level is {experience:intermediate}.\", variables)") - assert.Contains(t, js, "interpolateTemplate(\"Help the user with: {task:their question}\\nUse a {approach:detailed} approach.\\nPriority: {priority:normal}\", variables)") + assert.Contains(t, js, "interpolateTemplate(\"You are a {role:helpful assistant} specializing in {!domain}.\\nYour experience level is {experience:intermediate}.\", { role, domain, experience })") + assert.Contains(t, js, "interpolateTemplate(\"Help the user with: {task:their question}\\nUse a {approach:detailed} approach.\\nPriority: {priority:normal}\", { task, approach, priority })") // Check that it contains the correct object structure assert.Contains(t, js, "const complexPrompt = {") - assert.Contains(t, js, "system: {") - assert.Contains(t, js, "prompt: {") - assert.Contains(t, js, "compile: (variables) => {") + assert.Contains(t, js, "system: /**") + assert.Contains(t, js, "prompt: /**") + assert.Contains(t, js, "interpolateTemplate(") assert.Contains(t, js, "slug:") // Ensure no TypeScript syntax in JavaScript @@ -197,8 +173,8 @@ func TestCodeGenerator_ComplexPrompts(t *testing.T) { types := codeGen.GenerateTypeScriptTypes() // Check that it has the correct object structure for complex prompts - assert.Contains(t, types, "system: ComplexPromptSystem;") - assert.Contains(t, types, "prompt: ComplexPromptPrompt;") + assert.Contains(t, types, "system: (variables:") + assert.Contains(t, types, "prompt: (variables?:") assert.Contains(t, types, "slug: string;") // Check that it includes all variables with proper optional/default syntax @@ -227,7 +203,6 @@ func TestCodeGenerator_VariableTypes(t *testing.T) { types := codeGen.GenerateTypeScriptTypes() // Check that it includes all variable types with proper optional/default syntax - assert.Contains(t, types, "variables?: {") assert.Contains(t, types, "legacy?: string") assert.Contains(t, types, "new?: string | \"default\"") assert.Contains(t, types, "required: string") diff --git a/internal/bundler/prompts/prompts.go b/internal/bundler/prompts/prompts.go index 25dd26dc..469281f2 100644 --- a/internal/bundler/prompts/prompts.go +++ b/internal/bundler/prompts/prompts.go @@ -4,31 +4,44 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/agentuity/go-common/logger" ) -// VariableInfo holds information about extracted variables -type VariableInfo struct { - Names []string -} +// FindAllPromptFiles finds all YAML files in the prompts directory +func FindAllPromptFiles(dir string) []string { + var promptFiles []string + seenFiles := make(map[string]bool) -// FindPromptsYAML finds prompts.yaml in the given directory -func FindPromptsYAML(dir string) string { - possiblePaths := []string{ - filepath.Join(dir, "src", "prompts.yaml"), - filepath.Join(dir, "src", "prompts.yml"), - filepath.Join(dir, "prompts.yaml"), - filepath.Join(dir, "prompts.yml"), + // Check for prompts directory in various locations + possibleDirs := []string{ + filepath.Join(dir, "src", "prompts"), + filepath.Join(dir, "prompts"), } - for _, path := range possiblePaths { - if _, err := os.Stat(path); err == nil { - return path + // Scan all possible directories + for _, promptDir := range possibleDirs { + if _, err := os.Stat(promptDir); err == nil { + // Found prompts directory, scan for YAML files + entries, err := os.ReadDir(promptDir) + if err != nil { + continue + } + + for _, entry := range entries { + if !entry.IsDir() && (strings.HasSuffix(entry.Name(), ".yaml") || strings.HasSuffix(entry.Name(), ".yml")) { + filePath := filepath.Join(promptDir, entry.Name()) + if !seenFiles[filePath] { + promptFiles = append(promptFiles, filePath) + seenFiles[filePath] = true + } + } + } } } - return "" + return promptFiles } // FindSDKGeneratedDir finds the SDK's generated directory in node_modules @@ -66,28 +79,34 @@ func FindSDKGeneratedDir(logger logger.Logger, projectDir string) (string, error // ProcessPrompts finds, parses, and generates prompt files into the SDK func ProcessPrompts(logger logger.Logger, projectDir string) error { - // Find prompts.yaml - promptsPath := FindPromptsYAML(projectDir) - if promptsPath == "" { - // No prompts.yaml found - this is OK, not all projects will have prompts - logger.Debug("No prompts.yaml found in project, skipping prompt generation") + // Find all prompt files + promptFiles := FindAllPromptFiles(projectDir) + if len(promptFiles) == 0 { + // No prompt files found - this is OK, not all projects will have prompts + logger.Debug("No prompt files found in project, skipping prompt generation") return nil } - logger.Debug("Found prompts.yaml at: %s", promptsPath) + logger.Debug("Found %d prompt files: %v", len(promptFiles), promptFiles) - // Read and parse prompts.yaml - data, err := os.ReadFile(promptsPath) - if err != nil { - return fmt.Errorf("failed to read prompts.yaml: %w", err) - } + // Parse all prompt files and combine prompts + var allPrompts []Prompt + for _, promptFile := range promptFiles { + data, err := os.ReadFile(promptFile) + if err != nil { + return fmt.Errorf("failed to read %s: %w", promptFile, err) + } - promptsList, err := ParsePromptsYAML(data) - if err != nil { - return fmt.Errorf("failed to parse prompts: %w", err) + promptsList, err := ParsePromptsYAML(data) + if err != nil { + return fmt.Errorf("failed to parse %s: %w", promptFile, err) + } + + allPrompts = append(allPrompts, promptsList...) + logger.Debug("Parsed %d prompts from %s", len(promptsList), promptFile) } - logger.Debug("Parsed %d prompts from YAML", len(promptsList)) + logger.Debug("Total prompts parsed: %d", len(allPrompts)) // Find SDK generated directory sdkGeneratedDir, err := FindSDKGeneratedDir(logger, projectDir) @@ -98,7 +117,7 @@ func ProcessPrompts(logger logger.Logger, projectDir string) error { logger.Debug("Found SDK generated directory: %s", sdkGeneratedDir) // Generate code using the code generator - codeGen := NewCodeGenerator(promptsList) + codeGen := NewCodeGenerator(allPrompts) // Generate index.js file (overwrite SDK's placeholder, following POC pattern) jsContent := codeGen.GenerateJavaScript()