ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
203 lines ⢠9.06 kB
JavaScript
;
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