/** * Matrix Generator - Generate traceability matrices in various formats * Supports: CSV, Excel (TSV), Markdown, HTML */ export interface TraceabilityMatrix { requirements: string[]; code: string[]; tests: string[]; links: MatrixCell[][]; } export interface MatrixCell { requirementId: string; itemPath: string; itemType: 'code' | 'test' | 'documentation'; linked: boolean; verified: boolean; confidence: number; } export interface MatrixExportOptions { format: 'csv' | 'excel' | 'markdown' | 'html'; includeVerification?: boolean; includeConfidence?: boolean; sortBy?: 'requirement' | 'coverage' | 'type'; } /** * MatrixGenerator - Generate and export traceability matrices */ export class MatrixGenerator { /** * Generate traceability matrix from links */ generateMatrix( requirements: string[], codeFiles: string[], testFiles: string[], links: Map ): TraceabilityMatrix { const matrix: MatrixCell[][] = []; // Build matrix rows (one per requirement) for (const reqId of requirements) { const row: MatrixCell[] = []; const reqLinks = links.get(reqId) || { code: [], tests: [] }; // Add code file cells for (const codeFile of codeFiles) { row.push({ requirementId: reqId, itemPath: codeFile, itemType: 'code', linked: reqLinks.code.includes(codeFile), verified: reqLinks.code.includes(codeFile), confidence: reqLinks.code.includes(codeFile) ? 1.0 : 0.0 }); } // Add test file cells for (const testFile of testFiles) { row.push({ requirementId: reqId, itemPath: testFile, itemType: 'test', linked: reqLinks.tests.includes(testFile), verified: reqLinks.tests.includes(testFile), confidence: reqLinks.tests.includes(testFile) ? 1.0 : 0.0 }); } matrix.push(row); } return { requirements, code: codeFiles, tests: testFiles, links: matrix }; } /** * Export matrix to CSV format */ exportToCSV(matrix: TraceabilityMatrix, options: MatrixExportOptions = { format: 'csv' }): string { const lines: string[] = []; // Header row const headers = ['Requirement', 'Code Files', 'Tests', 'Documentation', 'Coverage']; if (options.includeVerification) headers.push('Verified'); if (options.includeConfidence) headers.push('Confidence'); lines.push(headers.join(',')); // Data rows for (let i = 0; i < matrix.requirements.length; i++) { const reqId = matrix.requirements[i]; const row = matrix.links[i]; // Find linked items const linkedCode = row.filter(cell => cell.itemType === 'code' && cell.linked).map(cell => cell.itemPath); const linkedTests = row.filter(cell => cell.itemType === 'test' && cell.linked).map(cell => cell.itemPath); const linkedDocs = row.filter(cell => cell.itemType === 'documentation' && cell.linked).map(cell => cell.itemPath); // Calculate coverage const hasCode = linkedCode.length > 0; const hasTests = linkedTests.length > 0; const hasDocs = linkedDocs.length > 0; const coverage = ((hasCode ? 33 : 0) + (hasTests ? 33 : 0) + (hasDocs ? 34 : 0)); const csvRow = [ this.escapeCSV(reqId), this.escapeCSV(linkedCode.join('; ') || 'NONE'), this.escapeCSV(linkedTests.join('; ') || 'NONE'), this.escapeCSV(linkedDocs.join('; ') || 'NONE'), `${coverage}%` ]; if (options.includeVerification) { const verified = row.every(cell => !cell.linked || cell.verified); csvRow.push(verified ? 'YES' : 'NO'); } if (options.includeConfidence) { const avgConfidence = row.length > 0 ? row.reduce((sum, cell) => sum + cell.confidence, 0) / row.length : 0; csvRow.push(`${(avgConfidence * 100).toFixed(1)}%`); } lines.push(csvRow.join(',')); } return lines.join('\n'); } /** * Export matrix to Excel (TSV) format */ exportToExcel(matrix: TraceabilityMatrix, options: MatrixExportOptions = { format: 'excel' }): string { // Excel export is TSV (tab-separated values) const csv = this.exportToCSV(matrix, options); return csv.replace(/,/g, '\t'); } /** * Export matrix to Markdown format */ exportToMarkdown(matrix: TraceabilityMatrix, options: MatrixExportOptions = { format: 'markdown' }): string { const lines: string[] = []; // Title lines.push('# Traceability Matrix\n'); lines.push(`Generated: ${new Date().toISOString()}\n`); lines.push(`Total Requirements: ${matrix.requirements.length}\n`); // Table header const headers = ['Requirement', 'Code Files', 'Tests', 'Documentation', 'Coverage']; if (options.includeVerification) headers.push('Verified'); if (options.includeConfidence) headers.push('Confidence'); lines.push('| ' + headers.join(' | ') + ' |'); lines.push('|' + headers.map(() => '---').join('|') + '|'); // Data rows for (let i = 0; i < matrix.requirements.length; i++) { const reqId = matrix.requirements[i]; const row = matrix.links[i]; // Find linked items const linkedCode = row.filter(cell => cell.itemType === 'code' && cell.linked).map(cell => this.formatPath(cell.itemPath)); const linkedTests = row.filter(cell => cell.itemType === 'test' && cell.linked).map(cell => this.formatPath(cell.itemPath)); const linkedDocs = row.filter(cell => cell.itemType === 'documentation' && cell.linked).map(cell => this.formatPath(cell.itemPath)); // Calculate coverage const hasCode = linkedCode.length > 0; const hasTests = linkedTests.length > 0; const hasDocs = linkedDocs.length > 0; const coverage = ((hasCode ? 33 : 0) + (hasTests ? 33 : 0) + (hasDocs ? 34 : 0)); const mdRow = [ `**${reqId}**`, linkedCode.length > 0 ? linkedCode.join('
') : '*NONE*', linkedTests.length > 0 ? linkedTests.join('
') : '*NONE*', linkedDocs.length > 0 ? linkedDocs.join('
') : '*NONE*', this.getCoverageEmoji(coverage) + ` ${coverage}%` ]; if (options.includeVerification) { const verified = row.every(cell => !cell.linked || cell.verified); mdRow.push(verified ? '✅' : '❌'); } if (options.includeConfidence) { const avgConfidence = row.length > 0 ? row.reduce((sum, cell) => sum + cell.confidence, 0) / row.length : 0; mdRow.push(`${(avgConfidence * 100).toFixed(1)}%`); } lines.push('| ' + mdRow.join(' | ') + ' |'); } return lines.join('\n'); } /** * Export matrix to HTML format */ exportToHTML(matrix: TraceabilityMatrix, options: MatrixExportOptions = { format: 'html' }): string { const lines: string[] = []; // HTML header lines.push(''); lines.push(''); lines.push(''); lines.push(' '); lines.push(' '); lines.push(' Traceability Matrix'); lines.push(' '); lines.push(''); lines.push(''); lines.push('

Traceability Matrix

'); lines.push(`

Generated: ${new Date().toISOString()}

`); lines.push(`

Total Requirements: ${matrix.requirements.length}

`); lines.push(' '); // Table header lines.push(' '); lines.push(' '); lines.push(' '); lines.push(' '); lines.push(' '); lines.push(' '); lines.push(' '); if (options.includeVerification) lines.push(' '); if (options.includeConfidence) lines.push(' '); lines.push(' '); lines.push(' '); // Table body lines.push(' '); for (let i = 0; i < matrix.requirements.length; i++) { const reqId = matrix.requirements[i]; const row = matrix.links[i]; // Find linked items const linkedCode = row.filter(cell => cell.itemType === 'code' && cell.linked).map(cell => cell.itemPath); const linkedTests = row.filter(cell => cell.itemType === 'test' && cell.linked).map(cell => cell.itemPath); const linkedDocs = row.filter(cell => cell.itemType === 'documentation' && cell.linked).map(cell => cell.itemPath); // Calculate coverage const hasCode = linkedCode.length > 0; const hasTests = linkedTests.length > 0; const hasDocs = linkedDocs.length > 0; const coverage = ((hasCode ? 33 : 0) + (hasTests ? 33 : 0) + (hasDocs ? 34 : 0)); const coverageClass = coverage >= 75 ? 'coverage-high' : coverage >= 50 ? 'coverage-medium' : 'coverage-low'; lines.push(' '); lines.push(` `); lines.push(` `); lines.push(` `); lines.push(` `); lines.push(` `); if (options.includeVerification) { const verified = row.every(cell => !cell.linked || cell.verified); lines.push(` `); } if (options.includeConfidence) { const avgConfidence = row.length > 0 ? row.reduce((sum, cell) => sum + cell.confidence, 0) / row.length : 0; lines.push(` `); } lines.push(' '); } lines.push(' '); lines.push('
RequirementCode FilesTestsDocumentationCoverageVerifiedConfidence
${this.escapeHTML(reqId)}${linkedCode.length > 0 ? linkedCode.map(f => `${this.escapeHTML(f)}`).join('
') : 'NONE'}
${linkedTests.length > 0 ? linkedTests.map(f => `${this.escapeHTML(f)}`).join('
') : 'NONE'}
${linkedDocs.length > 0 ? linkedDocs.map(f => `${this.escapeHTML(f)}`).join('
') : 'NONE'}
${coverage}%${verified ? '✅ YES' : '❌ NO'}${(avgConfidence * 100).toFixed(1)}%
'); lines.push(''); lines.push(''); return lines.join('\n'); } /** * Escape CSV values */ private escapeCSV(value: string): string { if (value.includes(',') || value.includes('"') || value.includes('\n')) { return `"${value.replace(/"/g, '""')}"`; } return value; } /** * Escape HTML entities */ private escapeHTML(value: string): string { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Format file path for display (shorten if too long) */ private formatPath(filePath: string): string { if (filePath.length <= 40) return filePath; const parts = filePath.split('/'); if (parts.length <= 2) return filePath; // Show first and last parts return `${parts[0]}/.../${parts[parts.length - 1]}`; } /** * Get coverage emoji */ private getCoverageEmoji(coverage: number): string { if (coverage >= 75) return '✅'; if (coverage >= 50) return '⚠️'; return '❌'; } }