UNPKG

@fromsvenwithlove/devops-issues-cli

Version:

AI-powered CLI tool and library for Azure DevOps work item management with Claude agents

173 lines (138 loc) • 5.27 kB
import { readFileSync } from 'fs'; import chalk from 'chalk'; import Ajv from 'ajv'; import addFormats from 'ajv-formats'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load schema const schemaPath = join(__dirname, '..', 'schemas', 'work-item-hierarchy.json'); const schema = JSON.parse(readFileSync(schemaPath, 'utf-8')); // Configure AJV const ajv = new Ajv({ allErrors: true, verbose: true }); addFormats(ajv); const validate = ajv.compile(schema); export default async function validateCommand(file, options = {}) { try { console.log(chalk.blue(`šŸ” Validating: ${file}`)); // Read and parse the file const content = readFileSync(file, 'utf-8'); let workItemData; try { workItemData = JSON.parse(content); } catch (parseError) { console.log(chalk.red('āœ— Invalid JSON format')); console.log(chalk.gray(`Parse error: ${parseError.message}`)); process.exit(1); } // Validate against schema const isValid = validate(workItemData); if (isValid) { console.log(chalk.green('āœ“ Validation passed')); // Additional checks const stats = analyzeWorkItems(workItemData); console.log(chalk.blue('\nšŸ“Š Work Item Analysis:')); console.log(` Total work items: ${chalk.yellow(stats.totalItems)}`); console.log(` Work item types: ${chalk.yellow(Object.keys(stats.typeCount).join(', '))}`); if (stats.typeCount) { Object.entries(stats.typeCount).forEach(([type, count]) => { console.log(` ${type}: ${chalk.yellow(count)}`); }); } if (stats.maxDepth > 0) { console.log(` Maximum nesting depth: ${chalk.yellow(stats.maxDepth)}`); } if (stats.warnings.length > 0) { console.log(chalk.yellow('\nāš ļø Warnings:')); stats.warnings.forEach(warning => { console.log(chalk.yellow(` • ${warning}`)); }); } if (options.verbose) { console.log(chalk.blue('\nšŸ“‹ Detailed Structure:')); printStructure(workItemData.workItems, 0); } } else { console.log(chalk.red('āœ— Validation failed')); console.log(chalk.red('\nErrors:')); validate.errors.forEach((error, index) => { console.log(chalk.red(` ${index + 1}. ${error.instancePath || 'root'}: ${error.message}`)); if (error.data !== undefined) { console.log(chalk.gray(` Current value: ${JSON.stringify(error.data)}`)); } if (error.params) { const params = Object.entries(error.params) .map(([key, value]) => `${key}: ${value}`) .join(', '); console.log(chalk.gray(` ${params}`)); } }); process.exit(1); } } catch (error) { console.log(chalk.red(`Error reading file: ${error.message}`)); process.exit(1); } } function analyzeWorkItems(data) { const stats = { totalItems: 0, typeCount: {}, maxDepth: 0, warnings: [] }; function analyzeItems(items, depth = 0) { if (!items || !Array.isArray(items)) return; stats.maxDepth = Math.max(stats.maxDepth, depth); items.forEach(item => { stats.totalItems++; // Count types if (item.type) { stats.typeCount[item.type] = (stats.typeCount[item.type] || 0) + 1; } // Check for potential issues if (!item.title || item.title.trim().length === 0) { stats.warnings.push(`Empty title found for ${item.type || 'unknown'} work item`); } if (item.title && item.title.length > 200) { stats.warnings.push(`Very long title (${item.title.length} chars) for work item: "${item.title.substring(0, 50)}..."`); } if (item.type === 'User Story' && !item.acceptanceCriteria) { stats.warnings.push(`User Story missing acceptance criteria: "${item.title}"`); } if (item.type === 'Bug' && !item.reproSteps) { stats.warnings.push(`Bug missing reproduction steps: "${item.title}"`); } // Analyze children recursively if (item.children) { analyzeItems(item.children, depth + 1); } }); } analyzeItems(data.workItems); return stats; } function printStructure(items, depth = 0) { if (!items || !Array.isArray(items)) return; const indent = ' '.repeat(depth); items.forEach((item, index) => { const typeColor = getTypeColor(item.type); const title = item.title ? item.title.substring(0, 60) : 'No title'; const truncated = item.title && item.title.length > 60 ? '...' : ''; console.log(`${indent}${chalk.gray(`${index + 1}.`)} ${typeColor(item.type)}: ${chalk.white(title)}${chalk.gray(truncated)}`); if (item.children && item.children.length > 0) { printStructure(item.children, depth + 1); } }); } function getTypeColor(type) { const colors = { 'Epic': chalk.magenta, 'Feature': chalk.blue, 'User Story': chalk.green, 'Task': chalk.yellow, 'Bug': chalk.red }; return colors[type] || chalk.white; }