UNPKG

cortexweaver

Version:

CortexWeaver is a command-line interface (CLI) tool that orchestrates a swarm of specialized AI agents, powered by Claude Code and Gemini CLI, to assist in software development. It transforms a high-level project plan (plan.md) into a series of coordinate

331 lines (325 loc) 14.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SpecWriter = void 0; const agent_1 = require("../agent"); const generative_ai_1 = require("@google/generative-ai"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); // SpecWriter agent for creating specifications, user stories, and BDD scenarios using Gemini API class SpecWriter extends agent_1.Agent { constructor() { super(...arguments); this.geminiClient = null; this.geminiModel = null; } // Initialize the SpecWriter agent with Gemini API configuration async initialize(config) { await super.initialize(config); const geminiApiKey = process.env.GEMINI_API_KEY; if (!geminiApiKey) { throw new Error('Gemini API key is required. Set GEMINI_API_KEY environment variable.'); } this.geminiClient = new generative_ai_1.GoogleGenerativeAI(geminiApiKey); this.geminiModel = this.geminiClient.getGenerativeModel({ model: 'gemini-pro' }); } // Execute the specification writing task async executeTask() { if (!this.currentTask || !this.taskContext) { throw new Error('No task or context available'); } if (!this.geminiModel) { throw new Error('Gemini client not initialized'); } try { await this.reportProgress('analyzing', 'Analyzing task requirements'); // Generate comprehensive specifications using Gemini const prompt = this.formatPrompt(this.getPromptTemplate(), { taskDescription: this.currentTask.description, taskTitle: this.currentTask.title, projectInfo: JSON.stringify(this.taskContext.projectInfo || {}), dependencies: JSON.stringify(this.taskContext.dependencies || []), files: JSON.stringify(this.taskContext.files || []) }); await this.reportProgress('generating', 'Generating specifications with Gemini API'); const response = await this.geminiModel.generateContent(prompt); const content = response.response.text(); // Parse and structure the generated content const userStories = await this.parseUserStories(content); const bddSpecs = await this.parseBDDSpecs(content); const nonFunctionalRequirements = this.extractNonFunctionalRequirements(content); await this.reportProgress('validating', 'Validating Gherkin syntax'); // Validate Gherkin syntax const validationResults = bddSpecs.map(spec => this.validateGherkinSyntax(spec.content)); await this.reportProgress('creating-files', 'Creating feature files'); // Create .feature files let featureFiles = []; try { featureFiles = await this.createFeatureFiles(bddSpecs); } catch (error) { throw new Error(`Failed to write feature file: ${error.message}`); } const result = { userStories, bddSpecs, featureFiles, nonFunctionalRequirements, validationResults, metadata: { totalFeatures: bddSpecs.length, totalScenarios: bddSpecs.reduce((acc, spec) => acc + spec.scenarios.length, 0), generatedAt: new Date().toISOString() } }; await this.reportProgress('completed', 'Specification generation completed'); return result; } catch (error) { await this.reportProgress('error', `Failed to generate specifications: ${error.message}`); throw new Error(`Failed to generate specifications: ${error.message}`); } } // Get the prompt template for specification writing getPromptTemplate() { return `You are an expert specification writer and business analyst. Create comprehensive specifications for software features. Task: {{taskTitle}} Description: {{taskDescription}} Project Info: {{projectInfo}} Dependencies: {{dependencies}} Files: {{files}} Generate: 1. User Stories with As a/I want/So that format, acceptance criteria, priority, story points 2. BDD Specifications in Gherkin format with Feature/Scenario/Given/When/Then 3. Non-Functional Requirements for security, performance, scalability, usability, reliability, maintainability Format: # User Stories ### User Story 1: [Title] **As a** [user] **I want to** [goal] **So that** [benefit] **Acceptance Criteria:** - [criteria] **Priority:** [high/medium/low] **Story Points:** [1-13] # BDD Specifications \`\`\`gherkin Feature: [Name] Scenario: [Name] Given [state] When [action] Then [outcome] \`\`\` # Non-Functional Requirements ## Security Requirements - [requirement] ## Performance Requirements - [requirement] ## Scalability Requirements - [requirement] ## Usability Requirements - [requirement] ## Reliability Requirements - [requirement] ## Maintainability Requirements - [requirement] Make specifications clear, testable, complete, and properly formatted.`.trim(); } // Generate user stories from task description async generateUserStories(taskDescription) { if (!this.geminiModel) throw new Error('Gemini model not initialized'); const prompt = `Generate user stories for: ${taskDescription}\nFormat: ### User Story X: [Title]\n**As a** [user] **I want to** [goal] **So that** [benefit]\n**Acceptance Criteria:** - [criteria]\n**Priority:** [high/medium/low] **Story Points:** [1-13]`; const response = await this.geminiModel.generateContent(prompt); return this.parseUserStories(response.response.text()); } // Generate BDD specifications from user stories async generateBDDSpecs(userStory) { if (!this.geminiModel) throw new Error('Gemini model not initialized'); const prompt = `Convert to BDD Gherkin: ${userStory}\nInclude Feature, Scenarios (happy path, edge cases, errors), Given-When-Then structure, tags.`; const response = await this.geminiModel.generateContent(prompt); return response.response.text(); } // Parse user stories from generated content parseUserStories(content) { const userStories = []; const storyRegex = /### User Story \d+: (.+?)\n\*\*As a\*\* (.+?)\n\*\*I want to\*\* (.+?)\n\*\*So that\*\* (.+?)\n\n\*\*Acceptance Criteria:\*\*\n((?:- .+\n?)+)/g; let match; let index = 1; while ((match = storyRegex.exec(content)) !== null) { const [, title, userType, goal, benefit, criteriaText] = match; const acceptanceCriteria = criteriaText.split('\n') .filter(line => line.trim().startsWith('- ')) .map(line => line.trim().substring(2)); // Extract priority and story points if present const storySection = content.substring(match.index, match.index + match[0].length + 200); const priorityMatch = storySection.match(/\*\*Priority:\*\* (high|medium|low)/); const pointsMatch = storySection.match(/\*\*Story Points:\*\* (\d+)/); userStories.push({ id: `story-${index}`, title: title.trim(), description: `As a ${userType}, I want to ${goal} so that ${benefit}`, acceptanceCriteria, priority: priorityMatch?.[1] || 'medium', storyPoints: pointsMatch ? parseInt(pointsMatch[1]) : undefined }); index++; } return userStories; } // Parse BDD specifications from generated content async parseBDDSpecs(content) { const bddSpecs = []; const featureRegex = /```gherkin\n(Feature: .+?)\n```/gs; let match; while ((match = featureRegex.exec(content)) !== null) { const gherkinContent = match[1]; const featureNameMatch = gherkinContent.match(/Feature: (.+)/); const featureName = featureNameMatch ? featureNameMatch[1].trim() : 'Unknown Feature'; // Extract scenarios const scenarioRegex = /(?:Scenario|Scenario Outline): (.+)/g; const scenarios = []; let scenarioMatch; while ((scenarioMatch = scenarioRegex.exec(gherkinContent)) !== null) { scenarios.push(scenarioMatch[1].trim()); } bddSpecs.push({ name: featureName, content: gherkinContent, feature: featureName, scenarios }); } return bddSpecs; } // Validate Gherkin syntax validateGherkinSyntax(gherkinContent) { const errors = []; const warnings = []; // Check for required Feature keyword if (!gherkinContent.includes('Feature:')) { errors.push('Missing Feature keyword'); } // Check for at least one scenario if (!gherkinContent.match(/(?:Scenario|Scenario Outline):/)) { errors.push('No scenarios found'); } // Check for proper Given-When-Then structure const scenarioBlocks = gherkinContent.split(/(?:Scenario|Scenario Outline):/); for (let i = 1; i < scenarioBlocks.length; i++) { const scenario = scenarioBlocks[i]; if (!scenario.includes('Given') && !scenario.includes('When') && !scenario.includes('Then')) { warnings.push(`Scenario ${i} may be missing Given-When-Then structure`); } } // Check for proper indentation (basic check) const lines = gherkinContent.split('\n'); for (const line of lines) { if (line.trim().startsWith('Given') || line.trim().startsWith('When') || line.trim().startsWith('Then') || line.trim().startsWith('And') || line.trim().startsWith('But')) { if (!line.startsWith(' ') && !line.startsWith('\t')) { warnings.push('Step may not be properly indented'); break; } } } return { isValid: errors.length === 0, errors, warnings }; } // Create .feature files in the workspace async createFeatureFiles(bddSpecs) { const featureFiles = []; const featuresDir = path.resolve(this.config.workspaceRoot, 'features'); // Ensure features directory exists if (!fs.existsSync(featuresDir)) { fs.mkdirSync(featuresDir, { recursive: true }); } for (const spec of bddSpecs) { try { // Generate filename from feature name const filename = spec.name.toLowerCase() .replace(/[^a-z0-9\s]/g, '') .replace(/\s+/g, '-') + '.feature'; const filePath = path.join(featuresDir, filename); // Write feature file fs.writeFileSync(filePath, spec.content, 'utf-8'); featureFiles.push({ filename, path: filePath, content: spec.content }); } catch (error) { throw new Error(`Failed to write feature file for ${spec.name}: ${error.message}`); } } return featureFiles; } // Extract non-functional requirements from generated content extractNonFunctionalRequirements(content) { const requirements = { security: [], performance: [], scalability: [], usability: [], reliability: [], maintainability: [] }; const sections = [ { key: 'security', patterns: ['## Security Requirements', '## Security', '### Security'] }, { key: 'performance', patterns: ['## Performance Requirements', '## Performance', '### Performance'] }, { key: 'scalability', patterns: ['## Scalability Requirements', '## Scalability', '### Scalability'] }, { key: 'usability', patterns: ['## Usability Requirements', '## Usability', '### Usability'] }, { key: 'reliability', patterns: ['## Reliability Requirements', '## Reliability', '### Reliability'] }, { key: 'maintainability', patterns: ['## Maintainability Requirements', '## Maintainability', '### Maintainability'] } ]; for (const section of sections) { for (const pattern of section.patterns) { const regex = new RegExp(`${pattern}\\n((?:- .+\\n?)+)`, 'gi'); const match = regex.exec(content); if (match) { const items = match[1].split('\n') .filter(line => line.trim().startsWith('- ')) .map(line => line.trim().substring(2)); requirements[section.key].push(...items); } } } return requirements; } } exports.SpecWriter = SpecWriter; //# sourceMappingURL=spec-writer.js.map