UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

203 lines • 9.06 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChecklistGenerator = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const glob_1 = require("glob"); const chalk_1 = __importDefault(require("chalk")); const llmService_1 = require("./llmService"); const securityRiskUtils_1 = require("../utils/securityRiskUtils"); class ChecklistGenerator { constructor(options = { type: 'all', format: 'json' }) { this.options = { type: options.type || 'all', // 'qa', 'security', or 'all' format: options.format || 'json' }; this.llmService = new llmService_1.LLMService(); } /** * Generate QA and security checklist from source code * @param sourcePath Path to source file or directory * @param outputDir Optional output directory to save the checklist * @returns Generated checklist result with additional metadata */ async generateChecklist(sourcePath, outputDir) { // Get all source files if sourcePath is a directory const sourceFiles = await this.getSourceFiles(sourcePath); // Initialize checklist const checklist = { title: 'Generated QA and Security Checklist', description: `Auto-generated ${this.options.type} checklist for ${path_1.default.basename(sourcePath)}`, categories: [], items: [] }; // Process each source file for (const sourceFile of sourceFiles) { const sourceCode = await promises_1.default.readFile(sourceFile, 'utf8'); const relativeSourcePath = path_1.default.relative(process.cwd(), sourceFile); // Skip files that don't need checklists if (this.shouldSkipFile(sourceFile)) { continue; } // Generate checklist with LLM const fileChecklist = await this.llmService.generateChecklist(sourceCode, relativeSourcePath, this.options.type); // Merge into main checklist this.mergeChecklists(checklist, fileChecklist); } // Save checklist to file if outputDir is provided let outputFile = ''; if (outputDir) { // Create output directory if it doesn't exist await promises_1.default.mkdir(outputDir, { recursive: true }); // Generate output filename based on source path const baseName = path_1.default.basename(sourcePath).replace(/\.(js|jsx|ts|tsx)$/, ''); outputFile = path_1.default.join(outputDir, `${baseName}-checklist.json`); // Write checklist to file await promises_1.default.writeFile(outputFile, JSON.stringify(checklist, null, 2), 'utf8'); } // Generate security risk report if security items exist const securityItems = checklist.items.filter(item => item.category === 'Security' && item.severity); // Print summary of findings console.log(chalk_1.default.bold('\nšŸ“‹ Checklist Generation Results:')); console.log(`Total items: ${chalk_1.default.cyan(checklist.items.length)}`); const securityCount = securityItems.length; if (securityCount > 0) { // Count issues by severity const criticalCount = securityItems.filter(item => item.severity === 'critical').length; const highCount = securityItems.filter(item => item.severity === 'high').length; const mediumCount = securityItems.filter(item => item.severity === 'medium').length; const lowCount = securityItems.filter(item => item.severity === 'low').length; console.log(`Security issues: ${chalk_1.default.cyan(securityCount)}`); if (criticalCount > 0) console.log(` ${(0, securityRiskUtils_1.formatSeverityBadge)('critical')} ${criticalCount}`); if (highCount > 0) console.log(` ${(0, securityRiskUtils_1.formatSeverityBadge)('high')} ${highCount}`); if (mediumCount > 0) console.log(` ${(0, securityRiskUtils_1.formatSeverityBadge)('medium')} ${mediumCount}`); if (lowCount > 0) console.log(` ${(0, securityRiskUtils_1.formatSeverityBadge)('low')} ${lowCount}`); // Display detailed security risk report if (securityItems.some(item => item.riskScore)) { console.log((0, securityRiskUtils_1.generateSecurityRiskReport)(securityItems)); } } else { console.log(chalk_1.default.green('āœ“ No security issues found')); } // Show output file location if saved if (outputFile) { console.log(chalk_1.default.blue(`\nChecklist saved to: ${outputFile}`)); } // Return checklist result with additional metadata return { items: checklist.items, itemCount: checklist.items.length, categories: checklist.categories, file: outputFile, securityItemsCount: securityItems.length }; } /** * Get all source files from a path (file or directory) */ async getSourceFiles(sourcePath) { const stats = await promises_1.default.stat(sourcePath); if (stats.isFile()) { return [sourcePath]; } if (stats.isDirectory()) { // Find all JS/TS files but exclude test files and node_modules return (0, glob_1.glob)(`${sourcePath}/**/*.{js,jsx,ts,tsx}`, { ignore: [ '**/node_modules/**', '**/*.test.{js,jsx,ts,tsx}', '**/*.spec.{js,jsx,ts,tsx}', '**/test/**', '**/tests/**', '**/dist/**', '**/build/**' ] }); } return []; } /** * Determine if a file should be skipped for checklist generation */ shouldSkipFile(filePath) { const filename = path_1.default.basename(filePath).toLowerCase(); return filename.includes('.test.') || filename.includes('.spec.') || filename.endsWith('.d.ts'); } /** * Merge file checklist into main checklist */ mergeChecklists(mainChecklist, fileChecklist) { // Merge categories if (Array.isArray(fileChecklist.categories)) { for (const category of fileChecklist.categories) { if (!mainChecklist.categories.includes(category)) { mainChecklist.categories.push(category); } } } // Merge items if (Array.isArray(fileChecklist.items)) { for (const item of fileChecklist.items) { // Ensure unique IDs by prefixing with file-specific identifier if needed if (mainChecklist.items.some(existingItem => existingItem.id === item.id)) { item.id = `${item.id}-${mainChecklist.items.length + 1}`; } mainChecklist.items.push(item); } } } /** * Convert JSON checklist to markdown format */ convertToMarkdown(checklist) { // Keep the original JSON for processing, but add a markdown property checklist.markdown = this.generateMarkdown(checklist); return checklist; } /** * Generate markdown representation of checklist */ generateMarkdown(checklist) { let markdown = `# ${checklist.title}\n\n`; markdown += `${checklist.description}\n\n`; // Group items by category const itemsByCategory = {}; for (const category of checklist.categories) { itemsByCategory[category] = []; } for (const item of checklist.items) { const category = item.category || 'Uncategorized'; if (!itemsByCategory[category]) { itemsByCategory[category] = []; } itemsByCategory[category].push(item); } // Generate markdown for each category for (const category of Object.keys(itemsByCategory)) { markdown += `## ${category}\n\n`; for (const item of itemsByCategory[category]) { markdown += `### ${item.title}\n\n`; markdown += `- **ID**: ${item.id}\n`; markdown += `- **Severity**: ${item.severity}\n`; markdown += `- **Description**: ${item.description}\n`; if (item.verification) { markdown += `- **Verification**: ${item.verification}\n`; } markdown += '\n'; } } return markdown; } } exports.ChecklistGenerator = ChecklistGenerator; //# sourceMappingURL=checklistGenerator.js.map