Skip to main content
Glama
spec-first.ts7.44 kB
/** * Code Agent - Spec-First TDD Mode * Generate tests before implementation based on specifications */ import { callLLM } from '../llm/index.js'; import { getModelsByLayer } from '../../config/models.js'; import { logger } from '../../logging/logger.js'; import * as fs from 'fs/promises'; import * as path from 'path'; export interface SpecFirstOptions { specFile?: string; testFramework?: 'vitest' | 'jest' | 'mocha'; coverageTarget?: number; generateImpl?: boolean; } export interface SpecFirstResult { specification: string; testCode: string; implementation?: string; coverageEstimate: number; testCases: TestCase[]; } export interface TestCase { name: string; description: string; input: unknown; expectedOutput: unknown; category: 'unit' | 'integration' | 'edge-case'; } /** * Generate tests from specification (TDD approach) */ export async function generateTestsFromSpec( specification: string, options: SpecFirstOptions = {}, ): Promise<SpecFirstResult> { const framework = options.testFramework || 'vitest'; const coverageTarget = options.coverageTarget || 80; logger.info('Generating tests from specification', { framework, coverageTarget, }); const models = getModelsByLayer('L2'); if (models.length === 0) { throw new Error('No L2 models available for test generation'); } const prompt = `You are a TDD expert. Generate comprehensive test cases from the following specification. SPECIFICATION: ${specification} REQUIREMENTS: - Test framework: ${framework} - Target coverage: ${coverageTarget}% - Include unit tests, integration tests, and edge cases - Follow TDD best practices Generate: 1. Test file code (${framework} format) 2. List of test cases with descriptions 3. Coverage estimate Format as JSON: { "testCode": "import { describe, it, expect } from '${framework}';\\n\\ndescribe('...', () => { ... });", "testCases": [ { "name": "should handle valid input", "description": "Tests normal case with valid data", "input": { "x": 5 }, "expectedOutput": 10, "category": "unit" } ], "coverageEstimate": 85 }`; const response = await callLLM( { prompt, maxTokens: 3000, temperature: 0.4, }, models[0], ); const jsonMatch = response.content.match(/\{[\s\S]*\}/); if (!jsonMatch) { throw new Error('Failed to parse test generation response'); } const result = JSON.parse(jsonMatch[0]); // Generate implementation if requested let implementation: string | undefined; if (options.generateImpl) { implementation = await generateImplementationFromTests( result.testCode, specification, ); } return { specification, testCode: result.testCode || '', implementation, coverageEstimate: result.coverageEstimate || 0, testCases: result.testCases || [], }; } /** * Generate implementation that passes the tests */ async function generateImplementationFromTests( testCode: string, specification: string, ): Promise<string> { const models = getModelsByLayer('L2'); const prompt = `Generate implementation code that will pass these tests. SPECIFICATION: ${specification} TESTS: \`\`\`typescript ${testCode} \`\`\` Provide clean, well-documented implementation code that: 1. Passes all tests 2. Follows TypeScript best practices 3. Includes JSDoc comments 4. Handles edge cases Return only the implementation code (no tests).`; const response = await callLLM( { prompt, maxTokens: 2000, temperature: 0.3, }, models[0], ); // Extract code block const codeMatch = response.content.match(/```(?:typescript|ts)?\n([\s\S]*?)\n```/); return codeMatch ? codeMatch[1] : response.content; } /** * TDD workflow: spec → tests → implementation → verify */ export async function tddWorkflow( specification: string, outputDir: string, options: SpecFirstOptions = {}, ): Promise<{ specFile: string; testFile: string; implFile: string; result: SpecFirstResult; }> { logger.info('Starting TDD workflow', { outputDir }); // Step 1: Generate tests from spec const result = await generateTestsFromSpec(specification, { ...options, generateImpl: true, }); // Step 2: Write spec file const specFile = path.join(outputDir, 'spec.md'); await fs.writeFile(specFile, `# Specification\n\n${specification}`, 'utf-8'); // Step 3: Write test file const testFile = path.join(outputDir, 'index.test.ts'); await fs.writeFile(testFile, result.testCode, 'utf-8'); // Step 4: Write implementation file const implFile = path.join(outputDir, 'index.ts'); await fs.writeFile(implFile, result.implementation || '', 'utf-8'); logger.info('TDD workflow completed', { specFile, testFile, implFile, }); return { specFile, testFile, implFile, result, }; } /** * Analyze test coverage gaps */ export async function analyzeTestGaps( testCode: string, implCode: string, ): Promise<{ missingTests: string[]; uncoveredEdgeCases: string[]; suggestions: string[]; }> { const models = getModelsByLayer('L1'); if (models.length === 0) { throw new Error('No L1 models available'); } const prompt = `Analyze test coverage and identify gaps. IMPLEMENTATION: \`\`\`typescript ${implCode} \`\`\` TESTS: \`\`\`typescript ${testCode} \`\`\` Identify: 1. Missing test cases 2. Uncovered edge cases 3. Suggestions for improvement Format as JSON: { "missingTests": ["Test for null input", "Test for large numbers"], "uncoveredEdgeCases": ["Empty array", "Negative numbers"], "suggestions": ["Add error handling tests", "Test async behavior"] }`; const response = await callLLM( { prompt, maxTokens: 1000, temperature: 0.3 }, models[0], ); const jsonMatch = response.content.match(/\{[\s\S]*\}/); if (!jsonMatch) { return { missingTests: [], uncoveredEdgeCases: [], suggestions: [], }; } return JSON.parse(jsonMatch[0]); } /** * Generate additional tests for uncovered code paths */ export async function generateMissingTests( implCode: string, existingTests: string, gaps: { missingTests: string[]; uncoveredEdgeCases: string[] }, framework: 'vitest' | 'jest' = 'vitest', ): Promise<string> { const models = getModelsByLayer('L1'); const prompt = `Generate additional tests to cover missing cases. IMPLEMENTATION: \`\`\`typescript ${implCode} \`\`\` EXISTING TESTS: \`\`\`typescript ${existingTests} \`\`\` MISSING COVERAGE: ${gaps.missingTests.map((t) => `- ${t}`).join('\n')} EDGE CASES: ${gaps.uncoveredEdgeCases.map((e) => `- ${e}`).join('\n')} Generate additional test cases using ${framework} that cover these gaps. Return only the new test code (not the entire test suite).`; const response = await callLLM( { prompt, maxTokens: 1500, temperature: 0.3 }, models[0], ); const codeMatch = response.content.match(/```(?:typescript|ts)?\n([\s\S]*?)\n```/); return codeMatch ? codeMatch[1] : response.content; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server