Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 257 additions & 8 deletions .cursor/rules/code-generation.mdc
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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

Comment on lines 36 to 41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Rule vs implementation mismatch: always emit system and prompt fields

The generator currently omits missing fields, which reintroduces optional chaining. Update generator to always emit both with empty-string returns when absent. As per coding guidelines.

🤖 Prompt for AI Agents
.cursor/rules/code-generation.mdc lines 36-41: the rule states generated code
must always emit both 'system' and 'prompt' fields (never require optional
chaining) but the implementation currently omits missing fields; update the
generator so that whenever an output object is created it always includes both
'system' and 'prompt' keys and returns empty strings for any that are absent,
ensuring no partial objects are produced and callers never need 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<string, any>;
export type PromptSignature<T> = (params: any) => string;

// ❌ Avoid overly complex generics that cause compilation issues
export type ComplexGeneric<T extends Prompt<infer A, infer B, infer C, infer D, infer E>> = ...
```

## CLI-Specific Rules

### Generation Target Locations
Expand All @@ -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
Expand Down Expand Up @@ -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<void> {
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<AgentContext> {
// ... 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<void> {
// 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
Expand All @@ -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<T extends Prompt<infer A, infer B, infer C, infer D, infer E>> = ...
```

### ✅ Do This Instead
Expand All @@ -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.
```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<string, any>;
export type PromptSignature<T> = (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 <target-directory>` 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.
Loading
Loading