@codebakers/cli 3.9.14 โ 3.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp/server.js +361 -5
- package/package.json +1 -1
- package/src/mcp/server.ts +388 -5
package/dist/mcp/server.js
CHANGED
|
@@ -798,6 +798,28 @@ class CodeBakersServer {
|
|
|
798
798
|
},
|
|
799
799
|
},
|
|
800
800
|
},
|
|
801
|
+
{
|
|
802
|
+
name: 'generate_tests',
|
|
803
|
+
description: 'Generate test stubs for a file or feature. Creates a test file with happy path and error case templates based on the source code. Reduces friction for adding tests. Use when user needs help writing tests.',
|
|
804
|
+
inputSchema: {
|
|
805
|
+
type: 'object',
|
|
806
|
+
properties: {
|
|
807
|
+
file: {
|
|
808
|
+
type: 'string',
|
|
809
|
+
description: 'Source file to generate tests for (e.g., "src/components/LoginForm.tsx", "src/app/api/users/route.ts")',
|
|
810
|
+
},
|
|
811
|
+
feature: {
|
|
812
|
+
type: 'string',
|
|
813
|
+
description: 'Feature name if generating tests for a feature rather than a specific file',
|
|
814
|
+
},
|
|
815
|
+
testType: {
|
|
816
|
+
type: 'string',
|
|
817
|
+
enum: ['unit', 'integration', 'e2e'],
|
|
818
|
+
description: 'Type of test to generate (default: unit for components/functions, integration for API routes)',
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
},
|
|
801
823
|
{
|
|
802
824
|
name: 'validate_complete',
|
|
803
825
|
description: 'MANDATORY: Call this BEFORE saying "done" or "complete" on any feature. Validates that tests exist, tests pass, and TypeScript compiles. Returns { valid: true } or { valid: false, missing: [...] }. You are NOT ALLOWED to complete a feature without calling this first.',
|
|
@@ -1516,6 +1538,8 @@ class CodeBakersServer {
|
|
|
1516
1538
|
return this.handleProjectStatus();
|
|
1517
1539
|
case 'run_tests':
|
|
1518
1540
|
return this.handleRunTests(args);
|
|
1541
|
+
case 'generate_tests':
|
|
1542
|
+
return this.handleGenerateTests(args);
|
|
1519
1543
|
case 'validate_complete':
|
|
1520
1544
|
return this.handleValidateComplete(args);
|
|
1521
1545
|
case 'discover_patterns':
|
|
@@ -3629,6 +3653,270 @@ Just describe what you want to build! I'll automatically:
|
|
|
3629
3653
|
}],
|
|
3630
3654
|
};
|
|
3631
3655
|
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Generate test stubs for a file or feature
|
|
3658
|
+
* Analyzes source code and creates appropriate test templates
|
|
3659
|
+
*/
|
|
3660
|
+
handleGenerateTests(args) {
|
|
3661
|
+
const { file, feature, testType } = args;
|
|
3662
|
+
const cwd = process.cwd();
|
|
3663
|
+
if (!file && !feature) {
|
|
3664
|
+
return {
|
|
3665
|
+
content: [{
|
|
3666
|
+
type: 'text',
|
|
3667
|
+
text: 'โ Please provide either a file path or feature name to generate tests for.',
|
|
3668
|
+
}],
|
|
3669
|
+
isError: true,
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
3672
|
+
// Detect test framework
|
|
3673
|
+
let testFramework = 'vitest';
|
|
3674
|
+
try {
|
|
3675
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
3676
|
+
if (fs.existsSync(pkgPath)) {
|
|
3677
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
3678
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3679
|
+
if (deps['jest'])
|
|
3680
|
+
testFramework = 'jest';
|
|
3681
|
+
else if (deps['@playwright/test'])
|
|
3682
|
+
testFramework = 'playwright';
|
|
3683
|
+
else if (deps['vitest'])
|
|
3684
|
+
testFramework = 'vitest';
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
catch {
|
|
3688
|
+
// Use default
|
|
3689
|
+
}
|
|
3690
|
+
let response = `# ๐งช Test Stub Generator\n\n`;
|
|
3691
|
+
if (file) {
|
|
3692
|
+
// Generate tests for a specific file
|
|
3693
|
+
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
|
3694
|
+
if (!fs.existsSync(filePath)) {
|
|
3695
|
+
return {
|
|
3696
|
+
content: [{
|
|
3697
|
+
type: 'text',
|
|
3698
|
+
text: `โ File not found: ${file}`,
|
|
3699
|
+
}],
|
|
3700
|
+
isError: true,
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
3704
|
+
const fileName = path.basename(file);
|
|
3705
|
+
const fileExt = path.extname(file);
|
|
3706
|
+
const isApiRoute = file.includes('/api/') || file.includes('\\api\\');
|
|
3707
|
+
const isComponent = fileExt === '.tsx' && !isApiRoute;
|
|
3708
|
+
const isService = file.includes('/services/') || file.includes('\\services\\') || file.includes('/lib/') || file.includes('\\lib\\');
|
|
3709
|
+
// Detect exported functions/components
|
|
3710
|
+
const exportedItems = [];
|
|
3711
|
+
const exportDefaultMatch = content.match(/export\s+default\s+(function\s+)?(\w+)/);
|
|
3712
|
+
const exportMatches = content.matchAll(/export\s+(async\s+)?(function|const)\s+(\w+)/g);
|
|
3713
|
+
if (exportDefaultMatch && exportDefaultMatch[2]) {
|
|
3714
|
+
exportedItems.push(exportDefaultMatch[2]);
|
|
3715
|
+
}
|
|
3716
|
+
for (const match of exportMatches) {
|
|
3717
|
+
if (match[3])
|
|
3718
|
+
exportedItems.push(match[3]);
|
|
3719
|
+
}
|
|
3720
|
+
// HTTP methods for API routes
|
|
3721
|
+
const httpMethods = [];
|
|
3722
|
+
if (isApiRoute) {
|
|
3723
|
+
if (content.includes('export async function GET') || content.includes('export function GET'))
|
|
3724
|
+
httpMethods.push('GET');
|
|
3725
|
+
if (content.includes('export async function POST') || content.includes('export function POST'))
|
|
3726
|
+
httpMethods.push('POST');
|
|
3727
|
+
if (content.includes('export async function PUT') || content.includes('export function PUT'))
|
|
3728
|
+
httpMethods.push('PUT');
|
|
3729
|
+
if (content.includes('export async function PATCH') || content.includes('export function PATCH'))
|
|
3730
|
+
httpMethods.push('PATCH');
|
|
3731
|
+
if (content.includes('export async function DELETE') || content.includes('export function DELETE'))
|
|
3732
|
+
httpMethods.push('DELETE');
|
|
3733
|
+
}
|
|
3734
|
+
response += `**Source:** \`${file}\`\n`;
|
|
3735
|
+
response += `**Type:** ${isApiRoute ? 'API Route' : isComponent ? 'React Component' : isService ? 'Service/Utility' : 'Module'}\n`;
|
|
3736
|
+
response += `**Test Framework:** ${testFramework}\n\n`;
|
|
3737
|
+
// Determine test file path
|
|
3738
|
+
const testFileName = fileName.replace(/\.(ts|tsx)$/, '.test$1');
|
|
3739
|
+
let testFilePath;
|
|
3740
|
+
if (isApiRoute) {
|
|
3741
|
+
// API routes: tests/api/[route].test.ts
|
|
3742
|
+
const routePath = file.replace(/.*\/api\//, '').replace(/\/route\.(ts|tsx)$/, '').replace(/\\/g, '/');
|
|
3743
|
+
testFilePath = `tests/api/${routePath}.test.ts`;
|
|
3744
|
+
}
|
|
3745
|
+
else if (isComponent) {
|
|
3746
|
+
// Components: alongside the file
|
|
3747
|
+
testFilePath = file.replace(/\.(tsx)$/, '.test.tsx');
|
|
3748
|
+
}
|
|
3749
|
+
else {
|
|
3750
|
+
// Services/utils: tests/services/
|
|
3751
|
+
testFilePath = `tests/${fileName.replace(/\.(ts|tsx)$/, '.test.ts')}`;
|
|
3752
|
+
}
|
|
3753
|
+
response += `**Test File:** \`${testFilePath}\`\n\n`;
|
|
3754
|
+
response += `---\n\n`;
|
|
3755
|
+
// Generate test stub based on file type
|
|
3756
|
+
if (isApiRoute && httpMethods.length > 0) {
|
|
3757
|
+
response += `## API Route Test Stub\n\n`;
|
|
3758
|
+
response += '```typescript\n';
|
|
3759
|
+
response += `import { describe, it, expect, beforeEach } from '${testFramework}';\n\n`;
|
|
3760
|
+
response += `describe('${file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|tsx)$/, '')}', () => {\n`;
|
|
3761
|
+
for (const method of httpMethods) {
|
|
3762
|
+
response += ` describe('${method}', () => {\n`;
|
|
3763
|
+
response += ` it('should handle successful request', async () => {\n`;
|
|
3764
|
+
response += ` // Arrange: Set up test data\n`;
|
|
3765
|
+
response += ` const request = new Request('http://localhost/api/...', {\n`;
|
|
3766
|
+
response += ` method: '${method}',\n`;
|
|
3767
|
+
if (method !== 'GET' && method !== 'DELETE') {
|
|
3768
|
+
response += ` body: JSON.stringify({ /* test data */ }),\n`;
|
|
3769
|
+
response += ` headers: { 'Content-Type': 'application/json' },\n`;
|
|
3770
|
+
}
|
|
3771
|
+
response += ` });\n\n`;
|
|
3772
|
+
response += ` // Act: Call the handler\n`;
|
|
3773
|
+
response += ` // const response = await ${method}(request);\n`;
|
|
3774
|
+
response += ` // const data = await response.json();\n\n`;
|
|
3775
|
+
response += ` // Assert: Check response\n`;
|
|
3776
|
+
response += ` // expect(response.status).toBe(200);\n`;
|
|
3777
|
+
response += ` // expect(data).toMatchObject({ /* expected */ });\n`;
|
|
3778
|
+
response += ` });\n\n`;
|
|
3779
|
+
response += ` it('should handle validation errors', async () => {\n`;
|
|
3780
|
+
response += ` // Test with invalid input\n`;
|
|
3781
|
+
response += ` // expect(response.status).toBe(400);\n`;
|
|
3782
|
+
response += ` });\n\n`;
|
|
3783
|
+
response += ` it('should handle unauthorized access', async () => {\n`;
|
|
3784
|
+
response += ` // Test without auth\n`;
|
|
3785
|
+
response += ` // expect(response.status).toBe(401);\n`;
|
|
3786
|
+
response += ` });\n`;
|
|
3787
|
+
response += ` });\n\n`;
|
|
3788
|
+
}
|
|
3789
|
+
response += `});\n`;
|
|
3790
|
+
response += '```\n\n';
|
|
3791
|
+
}
|
|
3792
|
+
else if (isComponent) {
|
|
3793
|
+
const componentName = exportedItems[0] || fileName.replace(/\.(tsx)$/, '');
|
|
3794
|
+
response += `## Component Test Stub\n\n`;
|
|
3795
|
+
response += '```typescript\n';
|
|
3796
|
+
response += `import { render, screen, fireEvent } from '@testing-library/react';\n`;
|
|
3797
|
+
response += `import { describe, it, expect } from '${testFramework}';\n`;
|
|
3798
|
+
response += `import { ${componentName} } from './${fileName.replace(/\.tsx$/, '')}';\n\n`;
|
|
3799
|
+
response += `describe('${componentName}', () => {\n`;
|
|
3800
|
+
response += ` it('renders correctly', () => {\n`;
|
|
3801
|
+
response += ` render(<${componentName} />);\n`;
|
|
3802
|
+
response += ` // expect(screen.getByRole('...')).toBeInTheDocument();\n`;
|
|
3803
|
+
response += ` });\n\n`;
|
|
3804
|
+
response += ` it('handles user interaction', async () => {\n`;
|
|
3805
|
+
response += ` render(<${componentName} />);\n`;
|
|
3806
|
+
response += ` // const button = screen.getByRole('button', { name: /.../ });\n`;
|
|
3807
|
+
response += ` // await fireEvent.click(button);\n`;
|
|
3808
|
+
response += ` // expect(screen.getByText('...')).toBeInTheDocument();\n`;
|
|
3809
|
+
response += ` });\n\n`;
|
|
3810
|
+
response += ` it('displays loading state', () => {\n`;
|
|
3811
|
+
response += ` // Test loading state\n`;
|
|
3812
|
+
response += ` });\n\n`;
|
|
3813
|
+
response += ` it('handles errors gracefully', () => {\n`;
|
|
3814
|
+
response += ` // Test error state\n`;
|
|
3815
|
+
response += ` });\n`;
|
|
3816
|
+
response += `});\n`;
|
|
3817
|
+
response += '```\n\n';
|
|
3818
|
+
}
|
|
3819
|
+
else {
|
|
3820
|
+
// Generic function/service tests
|
|
3821
|
+
response += `## Unit Test Stub\n\n`;
|
|
3822
|
+
response += '```typescript\n';
|
|
3823
|
+
response += `import { describe, it, expect, vi } from '${testFramework}';\n`;
|
|
3824
|
+
if (exportedItems.length > 0) {
|
|
3825
|
+
response += `import { ${exportedItems.join(', ')} } from '${file.replace(/\.(ts|tsx)$/, '')}';\n`;
|
|
3826
|
+
}
|
|
3827
|
+
response += `\n`;
|
|
3828
|
+
response += `describe('${fileName.replace(/\.(ts|tsx)$/, '')}', () => {\n`;
|
|
3829
|
+
for (const item of exportedItems.slice(0, 5)) { // Limit to first 5
|
|
3830
|
+
response += ` describe('${item}', () => {\n`;
|
|
3831
|
+
response += ` it('should work correctly with valid input', async () => {\n`;
|
|
3832
|
+
response += ` // Arrange\n`;
|
|
3833
|
+
response += ` const input = { /* test data */ };\n\n`;
|
|
3834
|
+
response += ` // Act\n`;
|
|
3835
|
+
response += ` // const result = await ${item}(input);\n\n`;
|
|
3836
|
+
response += ` // Assert\n`;
|
|
3837
|
+
response += ` // expect(result).toBe(/* expected */);\n`;
|
|
3838
|
+
response += ` });\n\n`;
|
|
3839
|
+
response += ` it('should handle edge cases', async () => {\n`;
|
|
3840
|
+
response += ` // Test with empty/null/undefined inputs\n`;
|
|
3841
|
+
response += ` });\n\n`;
|
|
3842
|
+
response += ` it('should throw on invalid input', async () => {\n`;
|
|
3843
|
+
response += ` // expect(() => ${item}(invalid)).toThrow();\n`;
|
|
3844
|
+
response += ` });\n`;
|
|
3845
|
+
response += ` });\n\n`;
|
|
3846
|
+
}
|
|
3847
|
+
response += `});\n`;
|
|
3848
|
+
response += '```\n\n';
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
else if (feature) {
|
|
3852
|
+
// Generate feature-based test structure
|
|
3853
|
+
response += `**Feature:** ${feature}\n`;
|
|
3854
|
+
response += `**Test Framework:** ${testFramework}\n\n`;
|
|
3855
|
+
response += `---\n\n`;
|
|
3856
|
+
response += `## Feature Test Structure\n\n`;
|
|
3857
|
+
response += `For feature "${feature}", create the following test files:\n\n`;
|
|
3858
|
+
response += `### 1. Unit Tests (\`tests/unit/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
|
|
3859
|
+
response += '```typescript\n';
|
|
3860
|
+
response += `import { describe, it, expect } from '${testFramework}';\n\n`;
|
|
3861
|
+
response += `describe('${feature} - Unit Tests', () => {\n`;
|
|
3862
|
+
response += ` describe('Core Logic', () => {\n`;
|
|
3863
|
+
response += ` it('should handle happy path', () => {\n`;
|
|
3864
|
+
response += ` // Test the main success scenario\n`;
|
|
3865
|
+
response += ` });\n\n`;
|
|
3866
|
+
response += ` it('should validate input', () => {\n`;
|
|
3867
|
+
response += ` // Test input validation\n`;
|
|
3868
|
+
response += ` });\n\n`;
|
|
3869
|
+
response += ` it('should handle errors', () => {\n`;
|
|
3870
|
+
response += ` // Test error handling\n`;
|
|
3871
|
+
response += ` });\n`;
|
|
3872
|
+
response += ` });\n`;
|
|
3873
|
+
response += `});\n`;
|
|
3874
|
+
response += '```\n\n';
|
|
3875
|
+
response += `### 2. Integration Tests (\`tests/integration/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
|
|
3876
|
+
response += '```typescript\n';
|
|
3877
|
+
response += `import { describe, it, expect, beforeAll, afterAll } from '${testFramework}';\n\n`;
|
|
3878
|
+
response += `describe('${feature} - Integration Tests', () => {\n`;
|
|
3879
|
+
response += ` beforeAll(async () => {\n`;
|
|
3880
|
+
response += ` // Set up test database, mock services, etc.\n`;
|
|
3881
|
+
response += ` });\n\n`;
|
|
3882
|
+
response += ` afterAll(async () => {\n`;
|
|
3883
|
+
response += ` // Clean up\n`;
|
|
3884
|
+
response += ` });\n\n`;
|
|
3885
|
+
response += ` it('should complete the full flow', async () => {\n`;
|
|
3886
|
+
response += ` // Test the complete feature flow\n`;
|
|
3887
|
+
response += ` });\n`;
|
|
3888
|
+
response += `});\n`;
|
|
3889
|
+
response += '```\n\n';
|
|
3890
|
+
if (testType === 'e2e' || testFramework === 'playwright') {
|
|
3891
|
+
response += `### 3. E2E Tests (\`e2e/${feature.toLowerCase().replace(/\s+/g, '-')}.spec.ts\`)\n\n`;
|
|
3892
|
+
response += '```typescript\n';
|
|
3893
|
+
response += `import { test, expect } from '@playwright/test';\n\n`;
|
|
3894
|
+
response += `test.describe('${feature}', () => {\n`;
|
|
3895
|
+
response += ` test('user can complete the flow', async ({ page }) => {\n`;
|
|
3896
|
+
response += ` // Navigate to the feature\n`;
|
|
3897
|
+
response += ` await page.goto('/...');\n\n`;
|
|
3898
|
+
response += ` // Interact with the UI\n`;
|
|
3899
|
+
response += ` // await page.click('button');\n`;
|
|
3900
|
+
response += ` // await page.fill('input', 'value');\n\n`;
|
|
3901
|
+
response += ` // Verify the result\n`;
|
|
3902
|
+
response += ` // await expect(page.getByText('...')).toBeVisible();\n`;
|
|
3903
|
+
response += ` });\n`;
|
|
3904
|
+
response += `});\n`;
|
|
3905
|
+
response += '```\n\n';
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
response += `---\n\n`;
|
|
3909
|
+
response += `**Next Steps:**\n`;
|
|
3910
|
+
response += `1. Create the test file at the suggested path\n`;
|
|
3911
|
+
response += `2. Uncomment and fill in the test implementations\n`;
|
|
3912
|
+
response += `3. Run tests: \`npm test\`\n`;
|
|
3913
|
+
return {
|
|
3914
|
+
content: [{
|
|
3915
|
+
type: 'text',
|
|
3916
|
+
text: response,
|
|
3917
|
+
}],
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3632
3920
|
/**
|
|
3633
3921
|
* MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
|
|
3634
3922
|
* Runs local checks (tests, TypeScript), then validates with server
|
|
@@ -4185,15 +4473,21 @@ Just describe what you want to build! I'll automatically:
|
|
|
4185
4473
|
responseText += `**Follow these established patterns for consistency.**\n\n`;
|
|
4186
4474
|
}
|
|
4187
4475
|
responseText += `---\n\n`;
|
|
4188
|
-
// Section 1: Patterns from server
|
|
4476
|
+
// Section 1: Patterns from server (CONDENSED - not full content)
|
|
4189
4477
|
if (result.patterns && result.patterns.length > 0) {
|
|
4190
4478
|
responseText += `## ๐ฆ MANDATORY PATTERNS\n\n`;
|
|
4191
|
-
responseText += `
|
|
4479
|
+
responseText += `The following patterns apply to this task. Key rules are shown below:\n\n`;
|
|
4192
4480
|
for (const pattern of result.patterns) {
|
|
4193
|
-
responseText += `### ${pattern.name}\n\n`;
|
|
4194
|
-
|
|
4195
|
-
|
|
4481
|
+
responseText += `### ${pattern.name} (${pattern.relevance} relevance)\n\n`;
|
|
4482
|
+
// Extract only KEY RULES (first ~100 lines or critical sections)
|
|
4483
|
+
// This prevents 100K+ character responses
|
|
4484
|
+
const content = pattern.content || '';
|
|
4485
|
+
const condensed = this.extractKeyRules(content, pattern.name);
|
|
4486
|
+
if (condensed) {
|
|
4487
|
+
responseText += condensed + '\n\n';
|
|
4488
|
+
}
|
|
4196
4489
|
}
|
|
4490
|
+
responseText += `> **Note:** These are condensed key rules. The full patterns are enforced server-side.\n\n`;
|
|
4197
4491
|
}
|
|
4198
4492
|
// Section 2: Test Requirements (ALL from server, not local file)
|
|
4199
4493
|
responseText += `---\n\n`;
|
|
@@ -4313,6 +4607,68 @@ Just describe what you want to build! I'll automatically:
|
|
|
4313
4607
|
const stopWords = ['the', 'and', 'for', 'add', 'fix', 'create', 'make', 'build', 'implement', 'update', 'modify', 'change', 'new', 'with', 'from', 'this', 'that'];
|
|
4314
4608
|
return words.filter(w => !stopWords.includes(w));
|
|
4315
4609
|
}
|
|
4610
|
+
/**
|
|
4611
|
+
* Extract key rules from pattern content (CONDENSED - max ~2000 chars per pattern)
|
|
4612
|
+
* This prevents 100K+ character responses that exceed token limits
|
|
4613
|
+
*/
|
|
4614
|
+
extractKeyRules(content, patternName) {
|
|
4615
|
+
if (!content)
|
|
4616
|
+
return '';
|
|
4617
|
+
const MAX_CHARS = 2000; // ~50 lines max per pattern
|
|
4618
|
+
const lines = content.split('\n');
|
|
4619
|
+
// Strategy: Extract headers, key rules, and first code example
|
|
4620
|
+
const keyParts = [];
|
|
4621
|
+
let inCodeBlock = false;
|
|
4622
|
+
let codeBlockCount = 0;
|
|
4623
|
+
let currentSize = 0;
|
|
4624
|
+
for (const line of lines) {
|
|
4625
|
+
// Track code blocks
|
|
4626
|
+
if (line.trim().startsWith('```')) {
|
|
4627
|
+
inCodeBlock = !inCodeBlock;
|
|
4628
|
+
if (!inCodeBlock)
|
|
4629
|
+
codeBlockCount++;
|
|
4630
|
+
}
|
|
4631
|
+
// Skip if we've hit the limit
|
|
4632
|
+
if (currentSize > MAX_CHARS) {
|
|
4633
|
+
keyParts.push('...(truncated - server enforces full pattern)');
|
|
4634
|
+
break;
|
|
4635
|
+
}
|
|
4636
|
+
// Always include headers
|
|
4637
|
+
if (line.startsWith('#')) {
|
|
4638
|
+
keyParts.push(line);
|
|
4639
|
+
currentSize += line.length;
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
// Include lines with key indicators
|
|
4643
|
+
const isKeyLine = line.includes('MUST') ||
|
|
4644
|
+
line.includes('NEVER') ||
|
|
4645
|
+
line.includes('ALWAYS') ||
|
|
4646
|
+
line.includes('REQUIRED') ||
|
|
4647
|
+
line.includes('MANDATORY') ||
|
|
4648
|
+
line.includes('DO NOT') ||
|
|
4649
|
+
line.includes('โ
') ||
|
|
4650
|
+
line.includes('โ') ||
|
|
4651
|
+
line.includes('โ ๏ธ') ||
|
|
4652
|
+
line.startsWith('- ') ||
|
|
4653
|
+
line.startsWith('* ') ||
|
|
4654
|
+
line.startsWith('|'); // Tables
|
|
4655
|
+
if (isKeyLine && !inCodeBlock) {
|
|
4656
|
+
keyParts.push(line);
|
|
4657
|
+
currentSize += line.length;
|
|
4658
|
+
continue;
|
|
4659
|
+
}
|
|
4660
|
+
// Include first code example only
|
|
4661
|
+
if (inCodeBlock && codeBlockCount === 0) {
|
|
4662
|
+
keyParts.push(line);
|
|
4663
|
+
currentSize += line.length;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
// If we got nothing useful, return first N characters
|
|
4667
|
+
if (keyParts.length < 5) {
|
|
4668
|
+
return content.substring(0, MAX_CHARS) + '\n...(truncated)';
|
|
4669
|
+
}
|
|
4670
|
+
return keyParts.join('\n');
|
|
4671
|
+
}
|
|
4316
4672
|
/**
|
|
4317
4673
|
* Find files recursively with given extensions
|
|
4318
4674
|
*/
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -878,6 +878,29 @@ class CodeBakersServer {
|
|
|
878
878
|
},
|
|
879
879
|
},
|
|
880
880
|
},
|
|
881
|
+
{
|
|
882
|
+
name: 'generate_tests',
|
|
883
|
+
description:
|
|
884
|
+
'Generate test stubs for a file or feature. Creates a test file with happy path and error case templates based on the source code. Reduces friction for adding tests. Use when user needs help writing tests.',
|
|
885
|
+
inputSchema: {
|
|
886
|
+
type: 'object' as const,
|
|
887
|
+
properties: {
|
|
888
|
+
file: {
|
|
889
|
+
type: 'string',
|
|
890
|
+
description: 'Source file to generate tests for (e.g., "src/components/LoginForm.tsx", "src/app/api/users/route.ts")',
|
|
891
|
+
},
|
|
892
|
+
feature: {
|
|
893
|
+
type: 'string',
|
|
894
|
+
description: 'Feature name if generating tests for a feature rather than a specific file',
|
|
895
|
+
},
|
|
896
|
+
testType: {
|
|
897
|
+
type: 'string',
|
|
898
|
+
enum: ['unit', 'integration', 'e2e'],
|
|
899
|
+
description: 'Type of test to generate (default: unit for components/functions, integration for API routes)',
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
},
|
|
881
904
|
{
|
|
882
905
|
name: 'validate_complete',
|
|
883
906
|
description:
|
|
@@ -1659,6 +1682,9 @@ class CodeBakersServer {
|
|
|
1659
1682
|
case 'run_tests':
|
|
1660
1683
|
return this.handleRunTests(args as { filter?: string; watch?: boolean });
|
|
1661
1684
|
|
|
1685
|
+
case 'generate_tests':
|
|
1686
|
+
return this.handleGenerateTests(args as { file?: string; feature?: string; testType?: 'unit' | 'integration' | 'e2e' });
|
|
1687
|
+
|
|
1662
1688
|
case 'validate_complete':
|
|
1663
1689
|
return this.handleValidateComplete(args as { feature: string; files?: string[] });
|
|
1664
1690
|
|
|
@@ -4079,6 +4105,281 @@ Just describe what you want to build! I'll automatically:
|
|
|
4079
4105
|
};
|
|
4080
4106
|
}
|
|
4081
4107
|
|
|
4108
|
+
/**
|
|
4109
|
+
* Generate test stubs for a file or feature
|
|
4110
|
+
* Analyzes source code and creates appropriate test templates
|
|
4111
|
+
*/
|
|
4112
|
+
private handleGenerateTests(args: { file?: string; feature?: string; testType?: 'unit' | 'integration' | 'e2e' }) {
|
|
4113
|
+
const { file, feature, testType } = args;
|
|
4114
|
+
const cwd = process.cwd();
|
|
4115
|
+
|
|
4116
|
+
if (!file && !feature) {
|
|
4117
|
+
return {
|
|
4118
|
+
content: [{
|
|
4119
|
+
type: 'text' as const,
|
|
4120
|
+
text: 'โ Please provide either a file path or feature name to generate tests for.',
|
|
4121
|
+
}],
|
|
4122
|
+
isError: true,
|
|
4123
|
+
};
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
// Detect test framework
|
|
4127
|
+
let testFramework = 'vitest';
|
|
4128
|
+
try {
|
|
4129
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
4130
|
+
if (fs.existsSync(pkgPath)) {
|
|
4131
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
4132
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4133
|
+
if (deps['jest']) testFramework = 'jest';
|
|
4134
|
+
else if (deps['@playwright/test']) testFramework = 'playwright';
|
|
4135
|
+
else if (deps['vitest']) testFramework = 'vitest';
|
|
4136
|
+
}
|
|
4137
|
+
} catch {
|
|
4138
|
+
// Use default
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
let response = `# ๐งช Test Stub Generator\n\n`;
|
|
4142
|
+
|
|
4143
|
+
if (file) {
|
|
4144
|
+
// Generate tests for a specific file
|
|
4145
|
+
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
|
4146
|
+
|
|
4147
|
+
if (!fs.existsSync(filePath)) {
|
|
4148
|
+
return {
|
|
4149
|
+
content: [{
|
|
4150
|
+
type: 'text' as const,
|
|
4151
|
+
text: `โ File not found: ${file}`,
|
|
4152
|
+
}],
|
|
4153
|
+
isError: true,
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
4158
|
+
const fileName = path.basename(file);
|
|
4159
|
+
const fileExt = path.extname(file);
|
|
4160
|
+
const isApiRoute = file.includes('/api/') || file.includes('\\api\\');
|
|
4161
|
+
const isComponent = fileExt === '.tsx' && !isApiRoute;
|
|
4162
|
+
const isService = file.includes('/services/') || file.includes('\\services\\') || file.includes('/lib/') || file.includes('\\lib\\');
|
|
4163
|
+
|
|
4164
|
+
// Detect exported functions/components
|
|
4165
|
+
const exportedItems: string[] = [];
|
|
4166
|
+
const exportDefaultMatch = content.match(/export\s+default\s+(function\s+)?(\w+)/);
|
|
4167
|
+
const exportMatches = content.matchAll(/export\s+(async\s+)?(function|const)\s+(\w+)/g);
|
|
4168
|
+
|
|
4169
|
+
if (exportDefaultMatch && exportDefaultMatch[2]) {
|
|
4170
|
+
exportedItems.push(exportDefaultMatch[2]);
|
|
4171
|
+
}
|
|
4172
|
+
for (const match of exportMatches) {
|
|
4173
|
+
if (match[3]) exportedItems.push(match[3]);
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
// HTTP methods for API routes
|
|
4177
|
+
const httpMethods: string[] = [];
|
|
4178
|
+
if (isApiRoute) {
|
|
4179
|
+
if (content.includes('export async function GET') || content.includes('export function GET')) httpMethods.push('GET');
|
|
4180
|
+
if (content.includes('export async function POST') || content.includes('export function POST')) httpMethods.push('POST');
|
|
4181
|
+
if (content.includes('export async function PUT') || content.includes('export function PUT')) httpMethods.push('PUT');
|
|
4182
|
+
if (content.includes('export async function PATCH') || content.includes('export function PATCH')) httpMethods.push('PATCH');
|
|
4183
|
+
if (content.includes('export async function DELETE') || content.includes('export function DELETE')) httpMethods.push('DELETE');
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
response += `**Source:** \`${file}\`\n`;
|
|
4187
|
+
response += `**Type:** ${isApiRoute ? 'API Route' : isComponent ? 'React Component' : isService ? 'Service/Utility' : 'Module'}\n`;
|
|
4188
|
+
response += `**Test Framework:** ${testFramework}\n\n`;
|
|
4189
|
+
|
|
4190
|
+
// Determine test file path
|
|
4191
|
+
const testFileName = fileName.replace(/\.(ts|tsx)$/, '.test$1');
|
|
4192
|
+
let testFilePath: string;
|
|
4193
|
+
|
|
4194
|
+
if (isApiRoute) {
|
|
4195
|
+
// API routes: tests/api/[route].test.ts
|
|
4196
|
+
const routePath = file.replace(/.*\/api\//, '').replace(/\/route\.(ts|tsx)$/, '').replace(/\\/g, '/');
|
|
4197
|
+
testFilePath = `tests/api/${routePath}.test.ts`;
|
|
4198
|
+
} else if (isComponent) {
|
|
4199
|
+
// Components: alongside the file
|
|
4200
|
+
testFilePath = file.replace(/\.(tsx)$/, '.test.tsx');
|
|
4201
|
+
} else {
|
|
4202
|
+
// Services/utils: tests/services/
|
|
4203
|
+
testFilePath = `tests/${fileName.replace(/\.(ts|tsx)$/, '.test.ts')}`;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
response += `**Test File:** \`${testFilePath}\`\n\n`;
|
|
4207
|
+
response += `---\n\n`;
|
|
4208
|
+
|
|
4209
|
+
// Generate test stub based on file type
|
|
4210
|
+
if (isApiRoute && httpMethods.length > 0) {
|
|
4211
|
+
response += `## API Route Test Stub\n\n`;
|
|
4212
|
+
response += '```typescript\n';
|
|
4213
|
+
response += `import { describe, it, expect, beforeEach } from '${testFramework}';\n\n`;
|
|
4214
|
+
response += `describe('${file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|tsx)$/, '')}', () => {\n`;
|
|
4215
|
+
|
|
4216
|
+
for (const method of httpMethods) {
|
|
4217
|
+
response += ` describe('${method}', () => {\n`;
|
|
4218
|
+
response += ` it('should handle successful request', async () => {\n`;
|
|
4219
|
+
response += ` // Arrange: Set up test data\n`;
|
|
4220
|
+
response += ` const request = new Request('http://localhost/api/...', {\n`;
|
|
4221
|
+
response += ` method: '${method}',\n`;
|
|
4222
|
+
if (method !== 'GET' && method !== 'DELETE') {
|
|
4223
|
+
response += ` body: JSON.stringify({ /* test data */ }),\n`;
|
|
4224
|
+
response += ` headers: { 'Content-Type': 'application/json' },\n`;
|
|
4225
|
+
}
|
|
4226
|
+
response += ` });\n\n`;
|
|
4227
|
+
response += ` // Act: Call the handler\n`;
|
|
4228
|
+
response += ` // const response = await ${method}(request);\n`;
|
|
4229
|
+
response += ` // const data = await response.json();\n\n`;
|
|
4230
|
+
response += ` // Assert: Check response\n`;
|
|
4231
|
+
response += ` // expect(response.status).toBe(200);\n`;
|
|
4232
|
+
response += ` // expect(data).toMatchObject({ /* expected */ });\n`;
|
|
4233
|
+
response += ` });\n\n`;
|
|
4234
|
+
response += ` it('should handle validation errors', async () => {\n`;
|
|
4235
|
+
response += ` // Test with invalid input\n`;
|
|
4236
|
+
response += ` // expect(response.status).toBe(400);\n`;
|
|
4237
|
+
response += ` });\n\n`;
|
|
4238
|
+
response += ` it('should handle unauthorized access', async () => {\n`;
|
|
4239
|
+
response += ` // Test without auth\n`;
|
|
4240
|
+
response += ` // expect(response.status).toBe(401);\n`;
|
|
4241
|
+
response += ` });\n`;
|
|
4242
|
+
response += ` });\n\n`;
|
|
4243
|
+
}
|
|
4244
|
+
response += `});\n`;
|
|
4245
|
+
response += '```\n\n';
|
|
4246
|
+
|
|
4247
|
+
} else if (isComponent) {
|
|
4248
|
+
const componentName = exportedItems[0] || fileName.replace(/\.(tsx)$/, '');
|
|
4249
|
+
response += `## Component Test Stub\n\n`;
|
|
4250
|
+
response += '```typescript\n';
|
|
4251
|
+
response += `import { render, screen, fireEvent } from '@testing-library/react';\n`;
|
|
4252
|
+
response += `import { describe, it, expect } from '${testFramework}';\n`;
|
|
4253
|
+
response += `import { ${componentName} } from './${fileName.replace(/\.tsx$/, '')}';\n\n`;
|
|
4254
|
+
response += `describe('${componentName}', () => {\n`;
|
|
4255
|
+
response += ` it('renders correctly', () => {\n`;
|
|
4256
|
+
response += ` render(<${componentName} />);\n`;
|
|
4257
|
+
response += ` // expect(screen.getByRole('...')).toBeInTheDocument();\n`;
|
|
4258
|
+
response += ` });\n\n`;
|
|
4259
|
+
response += ` it('handles user interaction', async () => {\n`;
|
|
4260
|
+
response += ` render(<${componentName} />);\n`;
|
|
4261
|
+
response += ` // const button = screen.getByRole('button', { name: /.../ });\n`;
|
|
4262
|
+
response += ` // await fireEvent.click(button);\n`;
|
|
4263
|
+
response += ` // expect(screen.getByText('...')).toBeInTheDocument();\n`;
|
|
4264
|
+
response += ` });\n\n`;
|
|
4265
|
+
response += ` it('displays loading state', () => {\n`;
|
|
4266
|
+
response += ` // Test loading state\n`;
|
|
4267
|
+
response += ` });\n\n`;
|
|
4268
|
+
response += ` it('handles errors gracefully', () => {\n`;
|
|
4269
|
+
response += ` // Test error state\n`;
|
|
4270
|
+
response += ` });\n`;
|
|
4271
|
+
response += `});\n`;
|
|
4272
|
+
response += '```\n\n';
|
|
4273
|
+
|
|
4274
|
+
} else {
|
|
4275
|
+
// Generic function/service tests
|
|
4276
|
+
response += `## Unit Test Stub\n\n`;
|
|
4277
|
+
response += '```typescript\n';
|
|
4278
|
+
response += `import { describe, it, expect, vi } from '${testFramework}';\n`;
|
|
4279
|
+
if (exportedItems.length > 0) {
|
|
4280
|
+
response += `import { ${exportedItems.join(', ')} } from '${file.replace(/\.(ts|tsx)$/, '')}';\n`;
|
|
4281
|
+
}
|
|
4282
|
+
response += `\n`;
|
|
4283
|
+
response += `describe('${fileName.replace(/\.(ts|tsx)$/, '')}', () => {\n`;
|
|
4284
|
+
|
|
4285
|
+
for (const item of exportedItems.slice(0, 5)) { // Limit to first 5
|
|
4286
|
+
response += ` describe('${item}', () => {\n`;
|
|
4287
|
+
response += ` it('should work correctly with valid input', async () => {\n`;
|
|
4288
|
+
response += ` // Arrange\n`;
|
|
4289
|
+
response += ` const input = { /* test data */ };\n\n`;
|
|
4290
|
+
response += ` // Act\n`;
|
|
4291
|
+
response += ` // const result = await ${item}(input);\n\n`;
|
|
4292
|
+
response += ` // Assert\n`;
|
|
4293
|
+
response += ` // expect(result).toBe(/* expected */);\n`;
|
|
4294
|
+
response += ` });\n\n`;
|
|
4295
|
+
response += ` it('should handle edge cases', async () => {\n`;
|
|
4296
|
+
response += ` // Test with empty/null/undefined inputs\n`;
|
|
4297
|
+
response += ` });\n\n`;
|
|
4298
|
+
response += ` it('should throw on invalid input', async () => {\n`;
|
|
4299
|
+
response += ` // expect(() => ${item}(invalid)).toThrow();\n`;
|
|
4300
|
+
response += ` });\n`;
|
|
4301
|
+
response += ` });\n\n`;
|
|
4302
|
+
}
|
|
4303
|
+
response += `});\n`;
|
|
4304
|
+
response += '```\n\n';
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
} else if (feature) {
|
|
4308
|
+
// Generate feature-based test structure
|
|
4309
|
+
response += `**Feature:** ${feature}\n`;
|
|
4310
|
+
response += `**Test Framework:** ${testFramework}\n\n`;
|
|
4311
|
+
response += `---\n\n`;
|
|
4312
|
+
|
|
4313
|
+
response += `## Feature Test Structure\n\n`;
|
|
4314
|
+
response += `For feature "${feature}", create the following test files:\n\n`;
|
|
4315
|
+
|
|
4316
|
+
response += `### 1. Unit Tests (\`tests/unit/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
|
|
4317
|
+
response += '```typescript\n';
|
|
4318
|
+
response += `import { describe, it, expect } from '${testFramework}';\n\n`;
|
|
4319
|
+
response += `describe('${feature} - Unit Tests', () => {\n`;
|
|
4320
|
+
response += ` describe('Core Logic', () => {\n`;
|
|
4321
|
+
response += ` it('should handle happy path', () => {\n`;
|
|
4322
|
+
response += ` // Test the main success scenario\n`;
|
|
4323
|
+
response += ` });\n\n`;
|
|
4324
|
+
response += ` it('should validate input', () => {\n`;
|
|
4325
|
+
response += ` // Test input validation\n`;
|
|
4326
|
+
response += ` });\n\n`;
|
|
4327
|
+
response += ` it('should handle errors', () => {\n`;
|
|
4328
|
+
response += ` // Test error handling\n`;
|
|
4329
|
+
response += ` });\n`;
|
|
4330
|
+
response += ` });\n`;
|
|
4331
|
+
response += `});\n`;
|
|
4332
|
+
response += '```\n\n';
|
|
4333
|
+
|
|
4334
|
+
response += `### 2. Integration Tests (\`tests/integration/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
|
|
4335
|
+
response += '```typescript\n';
|
|
4336
|
+
response += `import { describe, it, expect, beforeAll, afterAll } from '${testFramework}';\n\n`;
|
|
4337
|
+
response += `describe('${feature} - Integration Tests', () => {\n`;
|
|
4338
|
+
response += ` beforeAll(async () => {\n`;
|
|
4339
|
+
response += ` // Set up test database, mock services, etc.\n`;
|
|
4340
|
+
response += ` });\n\n`;
|
|
4341
|
+
response += ` afterAll(async () => {\n`;
|
|
4342
|
+
response += ` // Clean up\n`;
|
|
4343
|
+
response += ` });\n\n`;
|
|
4344
|
+
response += ` it('should complete the full flow', async () => {\n`;
|
|
4345
|
+
response += ` // Test the complete feature flow\n`;
|
|
4346
|
+
response += ` });\n`;
|
|
4347
|
+
response += `});\n`;
|
|
4348
|
+
response += '```\n\n';
|
|
4349
|
+
|
|
4350
|
+
if (testType === 'e2e' || testFramework === 'playwright') {
|
|
4351
|
+
response += `### 3. E2E Tests (\`e2e/${feature.toLowerCase().replace(/\s+/g, '-')}.spec.ts\`)\n\n`;
|
|
4352
|
+
response += '```typescript\n';
|
|
4353
|
+
response += `import { test, expect } from '@playwright/test';\n\n`;
|
|
4354
|
+
response += `test.describe('${feature}', () => {\n`;
|
|
4355
|
+
response += ` test('user can complete the flow', async ({ page }) => {\n`;
|
|
4356
|
+
response += ` // Navigate to the feature\n`;
|
|
4357
|
+
response += ` await page.goto('/...');\n\n`;
|
|
4358
|
+
response += ` // Interact with the UI\n`;
|
|
4359
|
+
response += ` // await page.click('button');\n`;
|
|
4360
|
+
response += ` // await page.fill('input', 'value');\n\n`;
|
|
4361
|
+
response += ` // Verify the result\n`;
|
|
4362
|
+
response += ` // await expect(page.getByText('...')).toBeVisible();\n`;
|
|
4363
|
+
response += ` });\n`;
|
|
4364
|
+
response += `});\n`;
|
|
4365
|
+
response += '```\n\n';
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
response += `---\n\n`;
|
|
4370
|
+
response += `**Next Steps:**\n`;
|
|
4371
|
+
response += `1. Create the test file at the suggested path\n`;
|
|
4372
|
+
response += `2. Uncomment and fill in the test implementations\n`;
|
|
4373
|
+
response += `3. Run tests: \`npm test\`\n`;
|
|
4374
|
+
|
|
4375
|
+
return {
|
|
4376
|
+
content: [{
|
|
4377
|
+
type: 'text' as const,
|
|
4378
|
+
text: response,
|
|
4379
|
+
}],
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4082
4383
|
/**
|
|
4083
4384
|
* MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
|
|
4084
4385
|
* Runs local checks (tests, TypeScript), then validates with server
|
|
@@ -4652,15 +4953,25 @@ Just describe what you want to build! I'll automatically:
|
|
|
4652
4953
|
|
|
4653
4954
|
responseText += `---\n\n`;
|
|
4654
4955
|
|
|
4655
|
-
// Section 1: Patterns from server
|
|
4956
|
+
// Section 1: Patterns from server (CONDENSED - not full content)
|
|
4656
4957
|
if (result.patterns && result.patterns.length > 0) {
|
|
4657
4958
|
responseText += `## ๐ฆ MANDATORY PATTERNS\n\n`;
|
|
4658
|
-
responseText += `
|
|
4959
|
+
responseText += `The following patterns apply to this task. Key rules are shown below:\n\n`;
|
|
4960
|
+
|
|
4659
4961
|
for (const pattern of result.patterns) {
|
|
4660
|
-
responseText += `### ${pattern.name}\n\n`;
|
|
4661
|
-
|
|
4662
|
-
|
|
4962
|
+
responseText += `### ${pattern.name} (${pattern.relevance} relevance)\n\n`;
|
|
4963
|
+
|
|
4964
|
+
// Extract only KEY RULES (first ~100 lines or critical sections)
|
|
4965
|
+
// This prevents 100K+ character responses
|
|
4966
|
+
const content = pattern.content || '';
|
|
4967
|
+
const condensed = this.extractKeyRules(content, pattern.name);
|
|
4968
|
+
|
|
4969
|
+
if (condensed) {
|
|
4970
|
+
responseText += condensed + '\n\n';
|
|
4971
|
+
}
|
|
4663
4972
|
}
|
|
4973
|
+
|
|
4974
|
+
responseText += `> **Note:** These are condensed key rules. The full patterns are enforced server-side.\n\n`;
|
|
4664
4975
|
}
|
|
4665
4976
|
|
|
4666
4977
|
// Section 2: Test Requirements (ALL from server, not local file)
|
|
@@ -4791,6 +5102,78 @@ Just describe what you want to build! I'll automatically:
|
|
|
4791
5102
|
return words.filter(w => !stopWords.includes(w));
|
|
4792
5103
|
}
|
|
4793
5104
|
|
|
5105
|
+
/**
|
|
5106
|
+
* Extract key rules from pattern content (CONDENSED - max ~2000 chars per pattern)
|
|
5107
|
+
* This prevents 100K+ character responses that exceed token limits
|
|
5108
|
+
*/
|
|
5109
|
+
private extractKeyRules(content: string, patternName: string): string {
|
|
5110
|
+
if (!content) return '';
|
|
5111
|
+
|
|
5112
|
+
const MAX_CHARS = 2000; // ~50 lines max per pattern
|
|
5113
|
+
const lines = content.split('\n');
|
|
5114
|
+
|
|
5115
|
+
// Strategy: Extract headers, key rules, and first code example
|
|
5116
|
+
const keyParts: string[] = [];
|
|
5117
|
+
let inCodeBlock = false;
|
|
5118
|
+
let codeBlockCount = 0;
|
|
5119
|
+
let currentSize = 0;
|
|
5120
|
+
|
|
5121
|
+
for (const line of lines) {
|
|
5122
|
+
// Track code blocks
|
|
5123
|
+
if (line.trim().startsWith('```')) {
|
|
5124
|
+
inCodeBlock = !inCodeBlock;
|
|
5125
|
+
if (!inCodeBlock) codeBlockCount++;
|
|
5126
|
+
}
|
|
5127
|
+
|
|
5128
|
+
// Skip if we've hit the limit
|
|
5129
|
+
if (currentSize > MAX_CHARS) {
|
|
5130
|
+
keyParts.push('...(truncated - server enforces full pattern)');
|
|
5131
|
+
break;
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
// Always include headers
|
|
5135
|
+
if (line.startsWith('#')) {
|
|
5136
|
+
keyParts.push(line);
|
|
5137
|
+
currentSize += line.length;
|
|
5138
|
+
continue;
|
|
5139
|
+
}
|
|
5140
|
+
|
|
5141
|
+
// Include lines with key indicators
|
|
5142
|
+
const isKeyLine =
|
|
5143
|
+
line.includes('MUST') ||
|
|
5144
|
+
line.includes('NEVER') ||
|
|
5145
|
+
line.includes('ALWAYS') ||
|
|
5146
|
+
line.includes('REQUIRED') ||
|
|
5147
|
+
line.includes('MANDATORY') ||
|
|
5148
|
+
line.includes('DO NOT') ||
|
|
5149
|
+
line.includes('โ
') ||
|
|
5150
|
+
line.includes('โ') ||
|
|
5151
|
+
line.includes('โ ๏ธ') ||
|
|
5152
|
+
line.startsWith('- ') ||
|
|
5153
|
+
line.startsWith('* ') ||
|
|
5154
|
+
line.startsWith('|'); // Tables
|
|
5155
|
+
|
|
5156
|
+
if (isKeyLine && !inCodeBlock) {
|
|
5157
|
+
keyParts.push(line);
|
|
5158
|
+
currentSize += line.length;
|
|
5159
|
+
continue;
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5162
|
+
// Include first code example only
|
|
5163
|
+
if (inCodeBlock && codeBlockCount === 0) {
|
|
5164
|
+
keyParts.push(line);
|
|
5165
|
+
currentSize += line.length;
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
// If we got nothing useful, return first N characters
|
|
5170
|
+
if (keyParts.length < 5) {
|
|
5171
|
+
return content.substring(0, MAX_CHARS) + '\n...(truncated)';
|
|
5172
|
+
}
|
|
5173
|
+
|
|
5174
|
+
return keyParts.join('\n');
|
|
5175
|
+
}
|
|
5176
|
+
|
|
4794
5177
|
/**
|
|
4795
5178
|
* Find files recursively with given extensions
|
|
4796
5179
|
*/
|