UNPKG

@entro314labs/ai-changelog-generator

Version:

AI-powered changelog generator with MCP server support - works with most providers, online and local models

1,761 lines โ€ข 64.6 kB
import { buildCommitChangelog, summarizeFileChanges, processWorkingDirectoryChanges, formatDuration, sleep, handleUnifiedOutput, parseConventionalCommit, markdownCommitLink, markdownCommitRangeLink, processIssueReferences } from '../../shared/utils/utils.js';
import colors from '../../shared/constants/colors.js';
import path from 'path';
import { WorkspaceChangelogService } from './workspace-changelog.service.js';

export class ChangelogService {
  constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
    this.gitService = gitService;
    this.aiAnalysisService = aiAnalysisService;
    this.analysisEngine = analysisEngine;
    this.configManager = configManager;
    this.workspaceChangelogService = new WorkspaceChangelogService(aiAnalysisService, gitService);
  }

  /**
   * Generates a changelog from committed changes (git history)
   * @param {string} version - Version number for the release
   * @param {string} since - Generate changelog since this date/commit
   * @returns {Promise<string>} Complete changelog content
   */
  async generateChangelog(version = null, since = null) {
    console.log(colors.processingMessage('๐Ÿค– Analyzing changes with AI...'));

    // Get committed changes
    const commits = await this.gitService.getCommitsSince(since);

    // Get working directory changes using analysis engine
    let workingDirAnalysis = null;
    if (this.analysisEngine) {
      workingDirAnalysis = await this.analysisEngine.analyzeCurrentChanges();
    }

    if (commits.length === 0 && (!workingDirAnalysis || workingDirAnalysis.changes.length === 0)) {
      console.log(colors.infoMessage('No commits or working directory changes found.'));
      return;
    }

    const workingChangesCount = workingDirAnalysis ? workingDirAnalysis.changes.length : 0;
    console.log(colors.processingMessage(`Found ${colors.number(commits.length)} commits and ${colors.number(workingChangesCount)} working directory changes...`));

    // Extract hash strings from commit objects
    const commitHashes = commits.map(c => c.hash);
    console.log(colors.processingMessage(`Analyzing ${colors.number(commitHashes.length)} commits...`));

    // Use batch processing for large commit sets
    let analyzedCommits;
    if (commitHashes.length > 20) {
      console.log(colors.infoMessage('Using batch processing for large commit set...'));
      analyzedCommits = await this.generateChangelogBatch(commitHashes);
    } else {
      analyzedCommits = await this.processCommitsSequentially(commitHashes);
    }

    if (analyzedCommits.length === 0) {
      console.log(colors.warningMessage('No valid commits to analyze.'));
      return;
    }

    // Generate release insights including working directory changes
    const insights = await this.generateReleaseInsights(analyzedCommits, version, workingDirAnalysis);

    // Build final changelog including both committed and working changes
    const changelog = await this.buildChangelog(analyzedCommits, insights, version, workingDirAnalysis);

    // Write changelog to file using existing utility
    const filename = version && version !== 'latest' ? `CHANGELOG-${version}.md` : 'AI_CHANGELOG.md';
    const outputFile = path.join(process.cwd(), filename);
    handleUnifiedOutput(changelog, { format: 'markdown', outputFile, silent: false });

    return {
      changelog,
      insights,
      analyzedCommits,
      workingDirAnalysis
    };
  }

  async processCommitsSequentially(commitHashes) {
    const analyzedCommits = [];

    for (let i = 0; i < commitHashes.length; i++) {
      const commitHash = commitHashes[i];

      const commitAnalysis = await this.gitService.getCommitAnalysis(commitHash);
      if (commitAnalysis) {
        const selectedModel = await this.aiAnalysisService.selectOptimalModel(commitAnalysis);
        console.log(colors.processingMessage(`Processing commit ${colors.highlight(`${i + 1}/${commitHashes.length}`)}: ${colors.hash(commitHash.substring(0, 7))}`));

        const aiSummary = await this.aiAnalysisService.generateAISummary(commitAnalysis, selectedModel);

        if (aiSummary) {
          analyzedCommits.push({
            ...commitAnalysis,
            aiSummary,
            type: aiSummary.category || 'other',
            breaking: aiSummary.breaking || false
          });
        }
      }
    }

    return analyzedCommits;
  }

  async generateChangelogBatch(commitHashes) {
    const batchSize = 10;
    const results = [];

    for (let i = 0; i < commitHashes.length; i += batchSize) {
      const batch = commitHashes.slice(i, i + batchSize);
      const batchNum = Math.floor(i/batchSize) + 1;
      const totalBatches = Math.ceil(commitHashes.length/batchSize);

      console.log(colors.processingMessage(`Processing batch ${colors.highlight(`${batchNum}/${totalBatches}`)} (${colors.number(batch.length)} commits)`));
      console.log(colors.progress(batchNum, totalBatches, 'batches processed'));

      const batchPromises = batch.map(hash => this.gitService.getCommitAnalysis(hash));
      const batchResults = await Promise.allSettled(batchPromises);

      const successfulResults = batchResults
        .filter(r => r.status === 'fulfilled' && r.value)
        .map(r => r.value);

      results.push(...successfulResults);

      // Rate limiting between batches
      if (i + batchSize < commitHashes.length) {
        await sleep(1000);
      }
    }

    return results.filter(Boolean);
  }

  async generateReleaseInsights(analyzedCommits, version, workingDirAnalysis = null) {
    const insights = {
      summary: '',
      totalCommits: analyzedCommits.length,
      commitTypes: {},
      riskLevel: 'low',
      affectedAreas: new Set(),
      breaking: false,
      complexity: 'low',
      businessImpact: 'minor',
      deploymentRequirements: []
    };

    // Count commit types and assess risk
    analyzedCommits.forEach(commit => {
      insights.commitTypes[commit.type] = (insights.commitTypes[commit.type] || 0) + 1;
      if (commit.breaking) insights.breaking = true;
      if (commit.semanticAnalysis?.riskLevel === 'high') insights.riskLevel = 'high';

      commit.files.forEach(file => {
        insights.affectedAreas.add(file.category);
      });

      // Check for deployment requirements
      if (commit.semanticAnalysis?.hasDbChanges) {
        insights.deploymentRequirements.push('Database migration required');
      }
      if (commit.breaking) {
        insights.deploymentRequirements.push('Breaking changes - review migration notes above.');
      }
    });

    insights.affectedAreas = Array.from(insights.affectedAreas);

    // Include working directory changes in insights
    if (workingDirAnalysis && workingDirAnalysis.changes.length > 0) {
      insights.workingDirectoryChanges = {
        count: workingDirAnalysis.changes.length,
        summary: workingDirAnalysis.analysis ? workingDirAnalysis.analysis.summary : 'Working directory changes detected'
      };

      // Add working directory files to affected areas
      workingDirAnalysis.changes.forEach(change => {
        if (change.category) {
          insights.affectedAreas.push(change.category);
        }
      });

      // Remove duplicates
      insights.affectedAreas = Array.from(new Set(insights.affectedAreas));
    }

    // Assess overall complexity
    const totalFiles = analyzedCommits.reduce((sum, commit) => sum + commit.files.length, 0);
    const avgFilesPerCommit = totalFiles / analyzedCommits.length;

    if (avgFilesPerCommit > 20 || insights.breaking) {
      insights.complexity = 'high';
    } else if (avgFilesPerCommit > 10) {
      insights.complexity = 'medium';
    }

    // Generate summary based on insights
    insights.summary = this.generateInsightsSummary(insights, version);

    return insights;
  }

  generateInsightsSummary(insights, version) {
    const { totalCommits, commitTypes, breaking, complexity, affectedAreas } = insights;

    let summary = `Release ${version || 'latest'} includes ${totalCommits} commits`;

    if (breaking) {
      summary += ' with breaking changes';
    }

    if (Object.keys(commitTypes).length > 0) {
      const types = Object.entries(commitTypes)
        .sort(([,a], [,b]) => b - a)
        .map(([type, count]) => `${count} ${type}`)
        .join(', ');
      summary += ` (${types})`;
    }

    summary += `. Complexity: ${complexity}. Affected areas: ${affectedAreas.join(', ')}.`;

    return summary;
  }

  async buildChangelog(analyzedCommits, insights, version, workingDirAnalysis = null) {
    const timestamp = new Date().toISOString().split('T')[0];
    const commitUrl = this.configManager?.getCommitUrl();
    const commitRangeUrl = this.configManager?.getCommitRangeUrl();
    const issueUrl = this.configManager?.getIssueUrl();
    const issueRegex = this.configManager?.getIssueRegexPattern();

    let changelog = `# Changelog\n\n`;

    // Version header with commit range link
    const firstCommit = analyzedCommits[0];
    const lastCommit = analyzedCommits[analyzedCommits.length - 1];
    let versionHeader = `## [${version || 'Unreleased'}] - ${timestamp}`;

    if (firstCommit && lastCommit && commitRangeUrl) {
      const rangeLink = markdownCommitRangeLink(lastCommit.fullHash, firstCommit.fullHash, commitRangeUrl);
      versionHeader += ` (${rangeLink})`;
    }

    changelog += `${versionHeader}\n\n`;

    // Release summary
    changelog += `### ๐Ÿ“‹ Release Summary\n`;
    changelog += `${insights.summary}\n\n`;
    changelog += `**Business Impact**: ${insights.businessImpact}\n`;
    changelog += `**Complexity**: ${insights.complexity}\n\n`;

    // Group commits by conventional type
    const groupedCommits = this.groupCommitsByType(analyzedCommits);
    const sortedTypes = Object.keys(groupedCommits).sort();

    // Generate sections for each commit type
    changelog += `## Changes\n\n`;

    for (const type of sortedTypes) {
      const commits = groupedCommits[type];
      if (commits.length === 0) continue;

      changelog += `### ${this.getTypeHeader(type)}\n\n`;

      for (const commit of commits) {
        changelog += this.buildCommitEntry(commit, commitUrl, issueUrl, issueRegex);
      }

      changelog += '\n';
    }

    // Breaking changes section
    const breakingCommits = analyzedCommits.filter(commit =>
      commit.breaking || (commit.breakingChanges && commit.breakingChanges.length > 0)
    );

    if (breakingCommits.length > 0) {
      const breakingHeader = this.configManager?.getHeadlines()?.breakingChange || '๐Ÿšจ BREAKING CHANGES';
      changelog += `### ${breakingHeader}\n\n`;

      for (const commit of breakingCommits) {
        changelog += this.buildBreakingChangeEntry(commit, commitUrl, issueUrl, issueRegex);
      }

      changelog += '\n';
    }

    // Append working directory changes if present
    if (workingDirAnalysis && workingDirAnalysis.changes && workingDirAnalysis.changes.length > 0) {
      const workingDirSection = await this.buildWorkingDirectorySection(workingDirAnalysis);
      changelog += workingDirSection;
    }

    // Attribution footer
    changelog += `---\n\n`;
    changelog += `*Generated using [ai-changelog-generator](https://github.com/entro314-labs/AI-changelog-generator) - AI-powered changelog generation for Git repositories*\n`;

    return changelog;
  }

  buildCommitEntry(commit, commitUrl, issueUrl, issueRegex) {
    const commitLink = markdownCommitLink(commit.fullHash || commit.hash, commitUrl);

    // Use AI-generated summary and description if available, fallback to commit message
    let title = commit.aiSummary?.summary || commit.description || commit.subject || 'No description';
    let description = commit.aiSummary?.technicalDetails || commit.aiSummary?.description;

    // If AI description is same as title, don't repeat it
    if (description === title) {
      description = null;
    }

    // Process issue references in title
    if (issueUrl && issueRegex) {
      title = processIssueReferences(title, issueUrl, issueRegex);
    }

    // Build entry with scope and title
    let entry = '- ';
    if (commit.scope) {
      entry += `**${commit.scope}**: `;
    }

    // Add impact indicators
    if (commit.aiSummary?.impact === 'critical' || commit.aiSummary?.impact === 'high') {
      entry += '๐Ÿ”ฅ ';
    }

    entry += `**${title}**`;

    // Add detailed description if available
    if (description && description !== title) {
      entry += `: ${description}`;
    }

    entry += ` (${commitLink})`;

    // Add issue references from commit body
    if (commit.issueReferences && commit.issueReferences.length > 0) {
      const issueLinks = commit.issueReferences.map(ref =>
        issueUrl ? markdownIssueLink(ref, issueUrl) : ref
      ).join(', ');
      entry += ` - References: ${issueLinks}`;
    }

    // Add closes references
    if (commit.closesReferences && commit.closesReferences.length > 0) {
      const closesLinks = commit.closesReferences.map(ref =>
        issueUrl ? markdownIssueLink(ref, issueUrl) : ref
      ).join(', ');
      entry += ` - Closes: ${closesLinks}`;
    }

    // Add migration notes if available
    if (commit.aiSummary?.migrationNotes) {
      entry += `\n  - **Migration**: ${commit.aiSummary.migrationNotes}`;
    }

    // Add highlights as sub-bullets
    if (commit.aiSummary?.highlights && commit.aiSummary.highlights.length > 1) {
      commit.aiSummary.highlights.slice(1).forEach(highlight => {
        entry += `\n  - ${highlight}`;
      });
    }

    return `${entry}\n`;
  }

  buildBreakingChangeEntry(commit, commitUrl, issueUrl, issueRegex) {
    const commitLink = markdownCommitLink(commit.fullHash || commit.hash, commitUrl);
    let entry = '- ';

    if (commit.scope) {
      entry += `**${commit.scope}**: `;
    }

    // Use AI-generated breaking change description if available
    let breakingDescription = null;
    if (commit.aiSummary?.breakingChangeDescription) {
      breakingDescription = commit.aiSummary.breakingChangeDescription;
    } else if (commit.breakingChanges && commit.breakingChanges.length > 0) {
      breakingDescription = commit.breakingChanges[0];
    } else {
      breakingDescription = commit.aiSummary?.summary || commit.description || commit.subject || 'Breaking change';
    }

    entry += `**${breakingDescription}** (${commitLink})`;

    // Add migration notes for breaking changes
    if (commit.aiSummary?.migrationNotes) {
      entry += `\n  - **Migration**: ${commit.aiSummary.migrationNotes}`;
    }

    // Add additional breaking change details
    if (commit.breakingChanges && commit.breakingChanges.length > 1) {
      commit.breakingChanges.slice(1).forEach(change => {
        entry += `\n  - ${change}`;
      });
    }

    // Add technical details for breaking changes
    if (commit.aiSummary?.technicalDetails && commit.aiSummary.technicalDetails !== breakingDescription) {
      entry += `\n  - ${commit.aiSummary.technicalDetails}`;
    }

    return `${entry}\n\n`;
  }

  groupCommitsByType(commits) {
    const configuredTypes = this.configManager?.getChangelogCommitTypes() || ['feat', 'fix', 'refactor', 'perf', 'docs', 'build', 'chore'];
    const includeInvalid = this.configManager?.shouldIncludeInvalidCommits() ?? true;

    const types = {};

    commits.forEach(commit => {
      // ALWAYS parse conventional commit first - this is the primary source of truth for types
      const conventionalCommit = parseConventionalCommit(commit.subject || commit.message, commit.body);
      let type = conventionalCommit.type;

      // Only use AI analysis type as fallback if no conventional commit type found
      if (!type || type === 'other') {
        type = commit.aiSummary?.category || commit.type || (includeInvalid ? 'other' : null);
      }

      // Only include configured types or invalid commits if allowed
      if (type && (configuredTypes.includes(type) || (type === 'other' && includeInvalid))) {
        if (!types[type]) types[type] = [];

        // Preserve AI analysis and enhance with conventional commit data
        types[type].push({
          ...commit, // Preserves aiSummary and other AI analysis
          ...conventionalCommit, // Add conventional commit fields - this includes scope, breaking, etc.
          type: type
        });
      }
    });

    return types;
  }

  getTypeHeader(type) {
    const configuredHeadlines = this.configManager?.getHeadlines() || {};

    // Use configured headlines from YAML config or defaults
    const defaultHeaders = {
      'feat': '๐Ÿš€ Features',
      'fix': '๐Ÿ› Bug Fixes',
      'perf': 'โšก Performance Improvements',
      'refactor': 'โ™ป๏ธ Refactoring',
      'docs': '๐Ÿ“š Documentation',
      'test': '๐Ÿงช Tests',
      'build': '๐Ÿ”ง Build System',
      'ci': 'โš™๏ธ CI/CD',
      'chore': '๐Ÿ”ง Maintenance',
      'style': '๐Ÿ’„ Code Style',
      'revert': 'โช Reverts',
      'merge': '๐Ÿ”€ Merges',
      'other': '๐Ÿ“ฆ Other Changes'
    };

    return configuredHeadlines[type] || defaultHeaders[type] || `๐Ÿ“ฆ ${type.charAt(0).toUpperCase() + type.slice(1)}`;
  }

  /**
   * Generates a changelog from working directory changes (uncommitted files)
   * @param {string} version - Version number for the release
   * @returns {Promise<string>} Changelog content for working directory changes
   */
  async generateChangelogFromChanges(version = null) {
    // Use the proper WorkspaceChangelogService with optimized single AI call approach
    const result = await this.workspaceChangelogService.generateWorkspaceChangelog(version, {
      analysisMode: 'detailed' // Use detailed mode for better analysis
    });

    if (!result) {
      console.log(colors.infoMessage('No changes detected in working directory.'));
      return null;
    }

    return {
      changelog: result.changelog,
      analysis: result.context,
      changes: result.changes
    };
  }

  /**
   * Builds the working directory changes section for the changelog
   * @param {Object} workingDirAnalysis - Analysis results from analysis engine
   * @returns {Promise<string>} Formatted working directory section
   */
  async buildWorkingDirectorySection(workingDirAnalysis) {
    // Use the optimized WorkspaceChangelogService for consistent formatting
    const workspaceResult = await this.workspaceChangelogService.generateCommitStyleWorkingDirectoryEntries({
      analysisMode: 'detailed',
      workingDirAnalysis: workingDirAnalysis
    });

    if (!workspaceResult || !workspaceResult.entries || workspaceResult.entries.length === 0) {
      return `## ๐Ÿ”ง Working Directory Changes\n\nNo working directory changes detected.\n\n`;
    }

    let section = `## ๐Ÿ”ง Working Directory Changes\n\n`;

    // Add each entry in commit format
    workspaceResult.entries.forEach(entry => {
      section += `${entry}\n\n`;
    });

    return section;
  }

  async generateAIPoweredChangeEntry(change) {
    const path = change.path || change.filePath || 'unknown';
    const status = change.status || '??';
    const category = change.category || 'other';

    // Map status to change type
    const statusTypeMap = {
      '??': 'feature',
      'A': 'feature',
      'M': 'update',
      'D': 'remove',
      'R': 'refactor'
    };

    const changeType = statusTypeMap[status] || 'other';

    // Use AI to analyze the actual diff content for intelligent description
    let description = 'Working directory change';
    let confidence = 85;

    try {
      // Use AI analysis for working directory files to get better descriptions
      // especially important for architectural changes (deleted files)
      if (this.aiAnalysisService && this.aiAnalysisService.hasAI) {
        const aiSummary = await this.aiAnalysisService.generateAISummary({
          subject: `Working directory change: ${path}`,
          files: [change],
          diffStats: { files: 1, insertions: 0, deletions: 0 }
        });
        if (aiSummary && aiSummary.technicalDetails) {
          description = aiSummary.technicalDetails;
          confidence = 85;
        } else {
          description = this.analyzeDiffContentForDescription(change);
          confidence = 75;
        }
      } else {
        description = this.analyzeDiffContentForDescription(change);
        confidence = 75;
      }
    } catch (error) {
      console.warn(`AI analysis failed for ${path}, using enhanced fallback`);
      // Use enhanced analysis instead of generic
      description = this.analyzeDiffContentForDescription(change);
      confidence = 75;
    }

    // Format like commit entry
    return `- (${changeType}) ${path} - ${description} (${confidence}%)`;
  }

  generateWorkingDirectoryChangeEntry(change) {
    const path = change.path || change.filePath || 'unknown';
    const status = change.status || '??';
    const category = change.category || 'other';
    const importance = change.importance || 'medium';

    // Map status to change type
    const statusTypeMap = {
      '??': 'feature',
      'A': 'feature',
      'M': 'update',
      'D': 'remove',
      'R': 'refactor'
    };

    const changeType = statusTypeMap[status] || 'other';

    // Generate AI-style description
    const description = this.generateChangeDescription(change);

    // Calculate confidence score based on change analysis
    const confidence = this.calculateChangeConfidence(change);

    // Format like commit entry
    return `- (${changeType}) ${path} - ${description} (${confidence}%)`;
  }

  generateChangeDescription(change) {
    const path = change.path || change.filePath;
    const status = change.status;
    const diff = change.diff || '';
    const category = change.category || 'other';

    if (status === 'D') {
      return `Removed ${category} file from working directory`;
    }

    if (status === 'A' || status === '??') {
      // Handle directories differently from files
      if (path.endsWith('/')) {
        return this.analyzeDirectoryAddition(path);
      }

      // For new files, analyze the actual content from the diff
      const contentAnalysis = this.analyzeNewFileContent(diff, path);
      if (contentAnalysis) {
        return contentAnalysis;
      }
      return `Added new ${category} file to working directory`;
    }

    if (status === 'M') {
      // Analyze what actually changed in the diff - prioritize functional analysis
      const changeAnalysis = this.analyzeModifiedFileChanges(diff, path);
      if (changeAnalysis) {
        return `Modified ${category} file - ${changeAnalysis}`;
      }

      // Fallback to generic line count only if no functional analysis available
      const lines = diff.split('\n');
      const additions = lines.filter(line => line.startsWith('+') && !line.startsWith('+++')).length;
      const deletions = lines.filter(line => line.startsWith('-') && !line.startsWith('---')).length;
      return `Modified ${category} file with ${additions} additions and ${deletions} deletions`;
    }

    if (status === 'R') {
      return `Renamed ${category} file in working directory`;
    }

    return `Updated ${category} file in working directory`;
  }

  analyzeNewFileContent(diff, path) {
    if (!diff || !diff.includes('Content preview:')) {
      return null;
    }

    // Extract the content preview from our diff
    const previewMatch = diff.match(/Content preview:\n([\s\S]*?)(?:\n\.\.\. \(truncated\)|$)/);
    if (!previewMatch || !previewMatch[1]) {
      return null;
    }

    const content = previewMatch[1];
    const filename = path ? path.split('/').pop() : 'file';

    // Analyze based on file type and content
    if (path && path.endsWith('.md')) {
      return this.analyzeMarkdownContent(content, filename);
    }

    if (path && (path.endsWith('.js') || path.endsWith('.ts'))) {
      return this.analyzeJavaScriptContent(content, filename);
    }

    if (path && path.endsWith('.json')) {
      return this.analyzeJsonContent(content, filename);
    }

    if (path && path.startsWith('.')) {
      return this.analyzeConfigContent(content, filename);
    }

    // Generic content analysis
    const lines = content.split('\n').length;
    return `Added new file containing ${lines} lines of content`;
  }

  analyzeMarkdownContent(content, filename) {
    const lines = content.split('\n');
    const headings = lines.filter(line => line.startsWith('#')).length;
    const codeBlocks = (content.match(/```/g) || []).length / 2;
    const links = (content.match(/\[.*?\]\(.*?\)/g) || []).length;
    const tables = (content.match(/\|.*\|/g) || []).length;

    // Extract actual content insights
    const contentLower = content.toLowerCase();
    let documentType = '';
    let keyContent = [];

    if (filename.includes('MAPPING') || filename.includes('mapping')) {
      documentType = 'architectural mapping documentation';
      if (contentLower.includes('class')) keyContent.push('class documentation');
      if (contentLower.includes('function')) keyContent.push('function mapping');
      if (contentLower.includes('service')) keyContent.push('service architecture');
    } else if (filename.includes('ENVIRONMENT') || filename.includes('env')) {
      documentType = 'environment variable configuration guide';
      const envVars = (content.match(/[A-Z_]+=/g) || []).length;
      if (envVars > 0) keyContent.push(`${envVars} environment variables`);
    } else if (filename.includes('README') || filename.includes('readme')) {
      documentType = 'project documentation';
      if (contentLower.includes('install')) keyContent.push('installation guide');
      if (contentLower.includes('setup')) keyContent.push('setup instructions');
    } else if (filename.includes('CHANGELOG') || filename.includes('changelog')) {
      documentType = 'changelog documentation';
      const versions = (content.match(/##?\s+\[?\d+\.\d+/g) || []).length;
      if (versions > 0) keyContent.push(`${versions} version entries`);
    } else {
      documentType = 'documentation';
    }

    let analysis = `Added ${filename} ${documentType}`;

    // Add specific content details
    const details = [];
    if (keyContent.length > 0) details.push(...keyContent);
    if (headings > 0) details.push(`${headings} sections`);
    if (codeBlocks > 0) details.push(`${codeBlocks} code examples`);
    if (tables > 0) details.push(`${tables} table rows`);
    if (links > 0) details.push(`${links} links`);

    if (details.length > 0) {
      analysis += ` containing ${details.slice(0, 3).join(', ')}`;
    }

    return analysis;
  }

  analyzeJavaScriptContent(content, filename) {
    const functions = (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(|=>\s*{/g) || []).length;
    const imports = (content.match(/import\s+.*?from|require\s*\(/g) || []).length;
    const classes = (content.match(/class\s+\w+/g) || []).length;
    const exports = (content.match(/export\s+/g) || []).length;

    // Extract actual content insights
    const lines = content.split('\n');
    const comments = lines.filter(line => line.trim().startsWith('//') || line.trim().startsWith('*')).length;
    const consoleStatements = (content.match(/console\.(log|error|warn|info)/g) || []).length;

    // Look for specific patterns that indicate purpose
    let purpose = '';
    if (content.includes('verification') || content.includes('check')) {
      purpose = 'verification script';
    } else if (content.includes('test') || content.includes('spec')) {
      purpose = 'test file';
    } else if (content.includes('server') || content.includes('mcp')) {
      purpose = 'MCP server entry point';
    } else if (content.includes('setup') || content.includes('config')) {
      purpose = 'setup/configuration script';
    } else if (content.includes('import') && content.includes('update')) {
      purpose = 'import update utility';
    } else if (filename.includes('util')) {
      purpose = 'utility module';
    } else {
      purpose = 'JavaScript module';
    }

    // Extract meaningful text content for analysis
    const meaningfulContent = content
      .split('\n')
      .filter(line => line.trim() && !line.trim().startsWith('//') && !line.trim().startsWith('*'))
      .join(' ')
      .toLowerCase();

    // Look for specific functionality indicators
    let functionality = [];
    if (meaningfulContent.includes('method') && meaningfulContent.includes('analysis')) {
      functionality.push('analyzing methods');
    }
    if (meaningfulContent.includes('legacy') || meaningfulContent.includes('old')) {
      functionality.push('legacy code handling');
    }
    if (meaningfulContent.includes('repository') || meaningfulContent.includes('repo')) {
      functionality.push('repository analysis');
    }
    if (meaningfulContent.includes('investigation') || meaningfulContent.includes('check')) {
      functionality.push('investigation checklist');
    }
    if (consoleStatements > 3) {
      functionality.push('detailed logging');
    }

    let analysis = `Added ${filename.replace('.js', '')} ${purpose}`;

    // Add functionality description
    if (functionality.length > 0) {
      analysis += ` for ${functionality.slice(0, 2).join(' and ')}`;
    }

    const features = [];
    if (classes > 0) features.push(`${classes} class${classes > 1 ? 'es' : ''}`);
    if (functions > 0) features.push(`${functions} function${functions > 1 ? 's' : ''}`);
    if (imports > 0) features.push(`${imports} import${imports > 1 ? 's' : ''}`);
    if (exports > 0) features.push(`${exports} export${exports > 1 ? 's' : ''}`);

    if (features.length > 0) {
      analysis += ` with ${features.slice(0, 3).join(', ')}`;
    }

    return analysis;
  }

  analyzeJsonContent(content, filename) {
    try {
      const parsed = JSON.parse(content);

      if (filename === 'package.json') {
        const deps = Object.keys(parsed.dependencies || {}).length;
        const devDeps = Object.keys(parsed.devDependencies || {}).length;
        const scripts = Object.keys(parsed.scripts || {}).length;

        return `Added package.json with ${deps} dependencies, ${devDeps} dev dependencies, and ${scripts} scripts`;
      }

      if (filename === 'manifest.json') {
        return `Added manifest.json configuration for ${parsed.name || 'application'} with ${Object.keys(parsed).length} properties`;
      }

      const keys = Object.keys(parsed).length;
      return `Added ${filename} configuration with ${keys} settings`;
    } catch {
      return `Added ${filename} JSON configuration file`;
    }
  }

  analyzeConfigContent(content, filename) {
    const lines = content.split('\n').filter(line => line.trim()).length;

    if (filename.includes('env')) {
      const variables = content.split('\n').filter(line => line.includes('=')).length;
      return `Added environment configuration with ${variables} variables`;
    }

    if (filename.includes('ignore')) {
      const patterns = content.split('\n').filter(line => line.trim() && !line.startsWith('#')).length;
      return `Added ignore file with ${patterns} patterns`;
    }

    return `Added ${filename} configuration with ${lines} lines`;
  }

  analyzeDirectoryAddition(path) {
    const dirName = path.replace(/\/$/, '');

    // Analyze based on directory name and purpose
    if (dirName === '.claude') {
      return 'Added Claude Code configuration directory for AI coding assistant integration';
    }

    if (dirName === '.context') {
      return 'Added context directory for project context and documentation';
    }

    if (dirName === '.github') {
      return 'Added GitHub configuration directory for workflows, templates, and repository settings';
    }

    if (dirName === 'docs') {
      return 'Added documentation directory for project documentation';
    }

    if (dirName === 'test' || dirName === 'tests') {
      return 'Added test directory for unit tests and test files';
    }

    if (dirName === 'src') {
      return 'Added source code directory for main application code';
    }

    if (dirName === 'lib') {
      return 'Added library directory for shared code and utilities';
    }

    if (dirName === 'bin') {
      return 'Added binary directory for executable scripts';
    }

    if (dirName === 'config') {
      return 'Added configuration directory for application settings';
    }

    if (dirName === 'server') {
      return 'Added server directory for server-side code and configuration';
    }

    if (dirName === 'dxt') {
      return 'Added DXT directory for development tooling and utilities';
    }

    if (dirName.includes('test')) {
      return `Added ${dirName} directory for testing and test results`;
    }

    if (dirName.startsWith('.')) {
      return `Added ${dirName} configuration directory`;
    }

    return `Added ${dirName} directory`;
  }

  groupWorkingDirectoryChangesByType(changes) {
    const grouped = {
      'architectural': [],
      'feature': [],
      'refactor': [],
      'update': [],
      'remove': [],
      'config': [],
      'docs': [],
      'test': []
    };

    for (const change of changes) {
      const path = change.path || change.filePath || '';
      const status = change.status || '??';
      const category = change.category || 'other';

      // Determine the change type based on content and context
      if (this.isArchitecturalChange(path, change)) {
        grouped.architectural.push(change);
      } else if (status === 'D') {
        grouped.remove.push(change);
      } else if (status === 'M') {
        if (this.isConfigurationFile(path)) {
          grouped.config.push(change);
        } else {
          grouped.update.push(change);
        }
      } else if (status === 'A' || status === '??') {
        if (this.isTestFile(path)) {
          grouped.test.push(change);
        } else if (this.isDocumentationFile(path)) {
          grouped.docs.push(change);
        } else if (this.isConfigurationFile(path)) {
          grouped.config.push(change);
        } else {
          grouped.feature.push(change);
        }
      }
    }

    // Remove empty groups
    return Object.fromEntries(
      Object.entries(grouped).filter(([key, value]) => value.length > 0)
    );
  }

  async generateGroupedChangeEntry(changeType, files) {
    const fileCount = files.length;

    // Generate individual file descriptions using our existing perfect analysis
    const fileDescriptions = [];
    for (const file of files) {
      const description = await this.generateAIPoweredChangeEntry(file);
      if (description) {
        // Extract just the description part (remove the "- (type) path - " prefix)
        const match = description.match(/- \(\w+\) .+ - (.+) \(\d+%\)/);
        if (match) {
          fileDescriptions.push(match[1]);
        }
      }
    }

    // Intelligently summarize the individual descriptions
    const summary = this.summarizeFileDescriptions(fileDescriptions, changeType, fileCount);

    return `- (${changeType}) ${summary}`;
  }

  summarizeFileDescriptions(descriptions, changeType, fileCount) {
    if (descriptions.length === 0) {
      return `${changeType} changes affecting ${fileCount} files`;
    }

    // Extract specific, detailed functional changes and contexts
    const specificChanges = [];
    const keyFiles = [];
    const technologies = new Set();
    const purposes = new Set();

    for (const desc of descriptions) {
      // Use the existing individual file analysis instead of hardcoded patterns
      if (desc && desc.trim() && desc !== 'No description available') {
        specificChanges.push(desc);
      }
    }

    // If we have specific changes, create detailed description
    if (specificChanges.length > 0) {
      const uniqueChanges = [...new Set(specificChanges)];

      // Create a detailed, specific description
      if (uniqueChanges.length === 1) {
        const change = uniqueChanges[0];
        const techList = [...technologies];
        if (techList.length > 0) {
          return `${change} affecting ${techList.join(', ')} (${fileCount} files)`;
        }
        return `${change} (${fileCount} files)`;
      } else if (uniqueChanges.length === 2) {
        return `${uniqueChanges.join(' and ')} (${fileCount} files)`;
      } else {
        // For architectural changes affecting many files, show more detail
        if (changeType === 'architectural' && fileCount >= 10) {
          // Show first 5 changes for major architectural changes
          const primary = uniqueChanges.slice(0, 5).join(', ');
          const remaining = uniqueChanges.length - 5;
          if (remaining > 0) {
            return `${primary} and ${remaining} additional change${remaining > 1 ? 's' : ''} (${fileCount} files)`;
          } else {
            return `${primary} (${fileCount} files)`;
          }
        } else {
          // For smaller changes, show more details instead of hiding them
          if (uniqueChanges.length <= 5) {
            // Show all changes if 5 or fewer - user wants to see exactly what each feature is
            return `${uniqueChanges.join(', ')} (${fileCount} files)`;
          } else {
            // Show first 4 and remaining count for 6+ changes
            const primary = uniqueChanges.slice(0, 4).join(', ');
            const remaining = uniqueChanges.length - 4;
            return `${primary} and ${remaining} additional enhancement${remaining > 1 ? 's' : ''} (${fileCount} files)`;
          }
        }
      }
    }

    // Enhanced fallback with more context
    const techList = [...technologies];
    const purposeList = [...purposes];

    if (techList.length > 0 && purposeList.length > 0) {
      return `${changeType} changes to ${techList.slice(0, 2).join(', ')} for ${purposeList.slice(0, 2).join(', ')} (${fileCount} files)`;
    } else if (techList.length > 0) {
      return `${changeType} changes to ${techList.slice(0, 3).join(', ')} (${fileCount} files)`;
    } else if (purposeList.length > 0) {
      return `${changeType} changes for ${purposeList.slice(0, 3).join(', ')} (${fileCount} files)`;
    }

    // Final fallback
    return `${changeType} changes affecting ${fileCount} files`;
  }

  isArchitecturalChange(path, change) {
    return path === 'src/' || path.includes('lib/') ||
           path.includes('bin/') || path.includes('package.json');
  }

  isConfigurationFile(path) {
    return path.startsWith('.') || path.includes('config') ||
           path.endsWith('.json') || path.endsWith('.env') ||
           path.includes('package.json');
  }

  isTestFile(path) {
    return path.includes('test') || path.includes('spec') ||
           path.startsWith('test-') || path.includes('/test/');
  }

  isDocumentationFile(path) {
    return path.endsWith('.md') || path.includes('docs') ||
           path.includes('README') || path.includes('CHANGELOG');
  }

  analyzeModifiedFileChanges(diff, path) {
    const lines = diff.split('\n');
    const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'));
    const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'));

    const addedContent = addedLines.map(line => line.substring(1)).join('\n');
    const removedContent = removedLines.map(line => line.substring(1)).join('\n');

    // Extract actual functionality changes instead of just counting
    const functionalChanges = this.extractFunctionalityChanges(addedContent, removedContent, path);

    if (functionalChanges && functionalChanges.length > 0) {
      return functionalChanges.join(', ');
    }

    // Fallback to basic pattern analysis if no functional changes detected
    const changes = [];

    // Function changes
    const addedFunctions = (addedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length;
    const removedFunctions = (removedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length;

    if (addedFunctions > 0) changes.push(`Added ${addedFunctions} function${addedFunctions > 1 ? 's' : ''}`);
    if (removedFunctions > 0) changes.push(`removed ${removedFunctions} function${removedFunctions > 1 ? 's' : ''}`);

    // Import/export changes
    const addedImports = (addedContent.match(/import\s+.*?from|require\s*\(/g) || []).length;
    const removedImports = (removedContent.match(/import\s+.*?from|require\s*\(/g) || []).length;

    if (addedImports > 0) changes.push(`added ${addedImports} import${addedImports > 1 ? 's' : ''}`);
    if (removedImports > 0) changes.push(`removed ${removedImports} import${removedImports > 1 ? 's' : ''}`);

    // Class changes
    const addedClasses = (addedContent.match(/class\s+\w+/g) || []).length;
    const removedClasses = (removedContent.match(/class\s+\w+/g) || []).length;

    if (addedClasses > 0) changes.push(`added ${addedClasses} class${addedClasses > 1 ? 'es' : ''}`);
    if (removedClasses > 0) changes.push(`removed ${removedClasses} class${removedClasses > 1 ? 'es' : ''}`);

    // Method changes within classes
    const addedMethods = (addedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length;
    const removedMethods = (removedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length;

    if (addedMethods > 0) changes.push(`added ${addedMethods} method${addedMethods > 1 ? 's' : ''}`);
    if (removedMethods > 0) changes.push(`removed ${removedMethods} method${removedMethods > 1 ? 's' : ''}`);

    // Error handling changes
    if (addedContent.includes('try') || addedContent.includes('catch')) {
      changes.push('enhanced error handling');
    }

    // Configuration-specific changes
    if (path && path.includes('package.json')) {
      if (addedContent.includes('"dependencies"')) changes.push('updated dependencies');
      if (addedContent.includes('"scripts"')) changes.push('modified scripts');
    }

    return changes.length > 0 ? changes.slice(0, 3).join(', ') : null;
  }

  inferFileContent(path, diff) {
    if (!path) return 'content';

    const ext = path.split('.').pop()?.toLowerCase();

    if (['js', 'ts', 'jsx', 'tsx'].includes(ext)) {
      if (diff.includes('function') || diff.includes('const ') || diff.includes('class ')) {
        return 'JavaScript/TypeScript code including functions and classes';
      }
      return 'JavaScript/TypeScript code';
    }

    if (ext === 'json') {
      if (path.includes('package.json')) {
        return 'package configuration and dependencies';
      }
      return 'JSON configuration data';
    }

    if (['md', 'txt'].includes(ext)) {
      return 'documentation and text content';
    }

    if (['png', 'jpg', 'jpeg', 'gif', 'svg'].includes(ext)) {
      return 'image or asset data';
    }

    if (['css', 'scss', 'less'].includes(ext)) {
      return 'styling definitions';
    }

    return 'content';
  }

  extractSpecificChanges(diff, path) {
    if (!diff || diff.length < 50) return null;

    const addedLines = diff.split('\n').filter(line => line.startsWith('+') && !line.startsWith('+++'));
    const removedLines = diff.split('\n').filter(line => line.startsWith('-') && !line.startsWith('---'));

    const changes = [];

    // Check for function changes
    const addedFunctions = addedLines.filter(line =>
      line.includes('function ') || line.includes('const ') && line.includes('= (') || line.includes('async ')
    );

    if (addedFunctions.length > 0) {
      changes.push(`Added ${addedFunctions.length} new function${addedFunctions.length > 1 ? 's' : ''}`);
    }

    // Check for import changes
    const addedImports = addedLines.filter(line => line.includes('import ') || line.includes('require('));
    const removedImports = removedLines.filter(line => line.includes('import ') || line.includes('require('));

    if (addedImports.length > 0) {
      changes.push(`added ${addedImports.length} import${addedImports.length > 1 ? 's' : ''}`);
    }
    if (removedImports.length > 0) {
      changes.push(`removed ${removedImports.length} import${removedImports.length > 1 ? 's' : ''}`);
    }

    // Check for package.json specific changes
    if (path && path.includes('package.json')) {
      if (diff.includes('"dependencies"') || diff.includes('"devDependencies"')) {
        changes.push('updated project dependencies');
      }
      if (diff.includes('"scripts"')) {
        changes.push('modified build scripts');
      }
    }

    return changes.length > 0 ? changes.slice(0, 2).join(' and ') : null;
  }

  extractFunctionalityChanges(addedContent, removedContent, path) {
    const functionalChanges = [];

    // Analyze what functionality was actually added or removed
    if (removedContent.length > 50) { // Only analyze substantial changes

      // Connection/Testing functionality
      if (removedContent.includes('testConnection') || removedContent.includes('test.success')) {
        functionalChanges.push('removed connection testing functionality');
      }

      // Model availability checking
      if (removedContent.includes('getAvailableModels') || removedContent.includes('getAvailable') || removedContent.includes('models.length')) {
        functionalChanges.push('removed model availability checking');
      }

      // Authentication/Provider initialization
      if (removedContent.includes('new AIProvider') || removedContent.includes('provider =') || removedContent.includes('Provider(')) {
        functionalChanges.push('removed provider initialization');
      }

      // Error handling/validation
      if (removedContent.includes('if (testResult.success)') || removedContent.includes('Connection test failed')) {
        functionalChanges.push('removed error handling and validation');
      }

      // Configuration/Setup
      if (removedContent.includes('require(') && addedContent.includes('skipping test')) {
        functionalChanges.push('simplified configuration setup');
      }

      // Database/API operations
      if (removedContent.includes('await ') && removedContent.includes('database') || removedContent.includes('api')) {
        functionalChanges.push('removed database/API operations');
      }

      // UI/Display functionality
      if (removedContent.includes('forEach') && removedContent.includes('console.log')) {
        functionalChanges.push('removed dynamic model listing display');
      }

      // Authentication changes
      if (removedContent.includes('auth') || removedContent.includes('token') || removedContent.includes('key')) {
        functionalChanges.push('modified authentication handling');
      }
    }

    // Analyze what was added
    if (addedContent.length > 20) {
      if (addedContent.includes('Configuration testing requires') || addedContent.includes('skipping test')) {
        functionalChanges.push('added configuration migration notice');
      }

      if (addedContent.includes('--validate') || addedContent.includes('test your configuration')) {
        functionalChanges.push('redirected testing to CLI --validate');
      }

      // New functionality patterns
      if (addedContent.includes('async ') && addedContent.includes('function')) {
        const newFunctions = (addedContent.match(/async\\s+function\\s+([\\w]+)/g) || []).map(f => f.replace('async function ', ''));
        if (newFunctions.length > 0) {
          functionalChanges.push(`added ${newFunctions.slice(0, 2).join(', ')} async functionality`);
        }
      }
    }

    // File-specific analysis
    if (path) {
      if (path.includes('setup') && functionalChanges.length === 0) {
        if (removedContent.includes('test') && addedContent.includes('validate')) {
          functionalChanges.push('replaced test functions with validation system');
        }
      }

      if (path.includes('provider') || path.includes('ai-')) {
        if (removedContent.includes('import') && addedContent.includes('moved to')) {
          functionalChanges.push('refactored import paths for new architecture');
        }
      }
    }

    return functionalChanges.length > 0 ? functionalChanges : null;
  }

  extractFileDescriptionFromAI(aiSummary, change) {
    // Extract meaningful description from AI analysis
    if (!aiSummary || typeof aiSummary !== 'string') {
      return this.analyzeDiffContentForDescription(change);
    }

    // Clean up AI response to be suitable for changelog entry
    let description = aiSummary.trim();

    // Remove generic prefixes
    description = description.replace(/^(This change|The change|Change:|Summary:)\s*/i, '');
    description = description.replace(/^(Analysis|Description):\s*/i, '');

    // Take first sentence if it's a long response
    const sentences = description.split(/[.!?]\s+/);
    if (sentences.length > 1 && sentences[0].length > 20) {
      description = sentences[0];
    }

    // Capitalize first letter
    if (description.length > 0) {
      description = description.charAt(0).toUpperCase() + description.slice(1);
    }

    // Fallback if AI response is too generic
    if (description.length < 10 ||
        description.toLowerCase().includes('file changed') ||
        description.toLowerCase().includes('modified') ||
        description.toLowerCase().includes('updated')) {
      return this.analyzeDiffContentForDescription(change);
    }

    return description;
  }

  analyzeDiffContentForDescription(change) {
    const path = change.path || change.filePath;
    const status = change.status;
    const diff = change.diff || '';
    const category = change.category || 'other';

    if (!diff || diff === 'Analysis failed') {
      return this.generateChangeDescription(change);
    }

    // For modified files, use the enhanced functional analysis
    if (status === 'M' && this.analyzeModifiedFileChanges) {
      const functionalAnalysis = this.analyzeModifiedFileChanges(diff, path);
      if (functionalAnalysis) {
        return functionalAnalysis;
      }
    }

    // For new files, use content analysis
    if (status === 'A' || status === '??') {
      const newFileAnalysis = this.analyzeNewFileContent(diff, path);
      if (newFileAnalysis) {
        return newFileAnalysis;
      }
    }

    // For deleted files, analyze what was removed
    if (status === 'D') {
      const deletedFileAnalysis = this.analyzeDeletedFileContent(change);
      if (deletedFileAnalysis) {
        return deletedFileAnalysis;
      }
    }

    // Fallback to old pattern-based analysis for other cases
    const lines = diff.split('\n');
    const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'));
    const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'));

    const addedContent = addedLines.map(line => line.substring(1).trim()).join(' ');
    const removedContent = removedLines.map(line => line.substring(1).trim()).join(' ');

    // Look for specific patterns in the actual changes
    const changes = [];

    // Function/method additions
    const addedFunctions = this.extractFunctions(addedContent);
    const removedFunctions = this.extractFunctions(removedContent);

    if (addedFunctions.length > 0) {
      changes.push(`Added ${addedFunctions.slice(0, 2).join(', ')} method${addedFunctions.length > 1 ? 's' : ''}`);
    }
    if (removedFunctions.length > 0) {
      changes.push(`removed ${removedFunctions.slice(0, 2).join(', ')} method${removedFunctions.length > 1 ? 's' : ''}`);
    }

    // Import/export changes
    if (addedContent.includes('import ') || addedContent.includes('require(')) {
      const importCount = (addedContent.match(/import\s+/g) || []).length;
      changes.push(`added ${importCount} import${importCount > 1 ? 's' : ''}`);
    }
    if (removedContent.includes('import ') || removedContent.includes('require(')) {
      const importCount = (removedContent.match(/import\s+/g) || []).length;
      changes.push(`removed ${importCount} import${importCount > 1 ? 's' : ''}`);
    }

    // Configuration changes
    if (path && path.includes('package.json')) {
      if (addedContent.includes('"dependencies"') || addedContent.includes('"devDependencies"')) {
        changes.push('updated dependencies');
      }
      if (addedContent.includes('"scripts"')) {
        changes.push('modified scripts');
      }
    }

    // Error handling
    if (addedContent.includes('try') || addedContent.includes('catch') || addedContent.includes('throw')) {
      changes.push('enhanced error handling');
    }

    // Build meaningful description
    if (changes.length > 0) {
      const changeDesc = changes.slice(0, 3).join(', ');
      return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions. ${changeDesc}`;
    }

    // Generic fallback with line counts
    if (status === 'M' && (addedLines.length > 0 || removedLines.length > 0)) {
      return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions`;
    }

    return this.generateChangeDescription(change);
  }

  calculateChangeConfidence(change) {
    // Simple confidence calculation based on available data
    let confidence = 70; // Base confidence

    if (change.diff && change.diff.length > 50) confidence += 10;
    if (change.semanticChanges && change.semanticChanges.patterns && change.semanticChanges.patterns.length > 0) confidence += 10;
    if (change.complexity && change.complexity.score) confidence += 5;
    if (change.functionalImpact) confidence += 5;

    return Math.min(confidence, 95);
  }

  generateDiffSummary(change) {
    if (!change.diff || change.diff === 'Analysis failed') {
      return null;
    }

    const diff = change.diff;
    const status = change.status;
    const filePath = change.path || change.filePath;

    // Handle different file statuses
    if (status === 'A' || status === '??') {
      if (diff.includes('New file created with')) {
        const lineMatch = diff.match(/(\d+) lines/);
        const lines = lineMatch ? lineMatch[1] : 'unknown';
        return `New file with ${lines} lines`;
      }
      return 'New file created';
    }

    if (status === 'D') {
      return 'File deleted';
    }

    if (status === 'R') {
      return 'File renamed';
    }

    // For modified files, analyze actual diff content
    if (status === 'M' || status.includes('M')) {
      const analysis = this.analyzeDiffContent(diff, filePath);
      return analysis;
    }

    return null;
  }

  analyzeDiffContent(diff, filePath) {
    const lines = diff.split('\n');
    const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'));
    const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'));

    const changes = [];

    // Analyze what was actually changed
    const addedContent = addedLines.map(line => line.substring(1).trim()).join(' ');
    const removedContent = removedLines.map(line => line.substring(1).trim()).join(' ');

    // Function/method changes
    const addedFunctions = this.extractFunctions(addedContent);
    const removedFunctions = this.extractFunctions(removedContent);

    if (addedFunctions.length > 0) {
      changes.push(`added ${addedFunctions.slice(0, 2).join(', ')}`);
    }
    if (removedFunctions.length > 0) {
      changes.push(`removed ${removedFunctions.slice(0, 2).join(', ')}`);
    }

    // Configuration changes
    if (filePath && filePath.includes('package.json')) {
      const configChanges = this.analyzePackageJsonChanges(addedContent, removedContent);
      if (configChanges.length > 0) {
        changes.push(...configChanges);
      }
    }

    // Import/export changes
    const importChanges = this.analyzeImportChanges(addedContent, removedContent);
    if (importChanges.length > 0) {
      changes.push(...importChanges);
    }

    // Variable/constant changes
    const variableChanges = this.analyzeVariableChanges(addedContent, removedContent);
    if (variableChanges.length > 0) {
      changes.push(...variableChanges);
    }

    // Error handling changes
    const errorChanges = this.analyzeErrorHandling(addedContent, removedContent);
    if (errorChanges.length > 0) {
      changes.push(...errorChanges);
    }

    // Build summary with line counts
    let summary = `+${addedLines.length}, -${removedLines.length} lines`;
    if (changes.length > 0) {
      summary += `: ${changes.slice(0, 3).join(', ')}`;
    }

    return summary;
  }

  extractFunctions(content) {
    const functions = [];
    const patterns = [
      /function\s+(\w+)/g,
      /(\w+)\s*\(/g,
      /const\s+(\w+)\s*=\s*\(/g,
      /async\s+(\w+)/g
    ];

    patterns.forEach(pattern => {
      let match;
      while ((match = pattern.exec(content)) !== null) {
        if (match[1] && !functions.includes(match[1])) {
          functions.push(match[1]);
        }
      }
    });

    return functions.slice(0, 3);
  }

  analyzePackageJsonChanges(added, removed) {
    const changes = [];

    if (added.includes('"dependencies"') || added.includes('"devDependencies"')) {
      changes.push('updated dependencies');
    }
    if (added.includes('"scripts"')) {
      changes.push('updated scripts');
    }
    if (added.includes('"name"') || added.includes('"version"')) {
      changes.push('updated metadata');
    }
    if (added.includes('"repository"') || added.includes('"author"')) {
      changes.push('updated project info');
    }

    return changes;
  }

  analyzeImportChanges(added, removed) {
    const changes = [];

    if (added.includes('import') || added.includes('require(')) {
      changes.push('added imports');
    }
    if (removed.includes('import') || removed.includes('require(')) {
      changes.push('removed imports');
    }
    if (added.includes('export')) {
      changes.push('added exports');
    }
    if (removed.includes('export')) {
      changes.push('removed exports');
    }

    return changes;
  }

  analyzeVariableChanges(added, removed) {
    const changes = [];

    if (added.includes('const ') || added.includes('let ') || added.includes('var ')) {
      changes.push('added variables');
    }
    if (added.includes('= {') || added.includes('= [')) {
      changes.push('updated data structures');
    }

    return changes;
  }

  analyzeErrorHandling(added, removed) {
    const changes = [];

    if (added.includes('try') || added.includes('catch') || added.includes('throw')) {
      changes.push('enhanced error handling');
    }
    if (added.includes('console.error') || added.includes('console.warn')) {
      changes.push('added logging');
    }
    if (added.includes('if (') && added.includes('error')) {
      changes.push('added error checks');
    }

    return changes;
  }

  buildChangelogFromAnalysis(analysis, changes, version) {
    const timestamp = new Date().toISOString().split('T')[0];

    let changelog = `# Working Directory Changes - ${timestamp}\n\n`;

    if (version) {
      changelog += `## Version ${version}\n\n`;
    }

    changelog += `### Summary\n`;
    changelog += `${analysis.summary || 'Working directory changes detected'}\n\n`;

    changelog += `### Changes (${changes.length} files)\n`;
    changes.forEach(change => {
      changelog += `- ${change.status} ${change.path}\n`;
    });

    return changelog;
  }

  analyzeDeletedFileContent(change) {
    const filePath = change.path || change.filePath;
    const beforeContent = change.beforeContent || '';

    if (!beforeContent || beforeContent.trim() === '') {
      return `Removed ${path.basename(filePath)} file from working directory`;
    }

    // Analyze what functionality was removed based on the file content
    const language = change.language || this.detectLanguageFromPath(filePath);
    const category = change.category || 'other';

    if (language === 'javascript') {
      return this.analyzeDeletedJavaScriptFile(beforeContent, filePath, category);
    } else if (language === 'markdown') {
      return this.analyzeDeletedMarkdownFile(beforeContent, filePath);
    } else if (language === 'json') {
      return this.analyzeDeletedJsonFile(beforeContent, filePath);
    } else {
      // Generic analysis for other file types
      const lines = beforeContent.split('\n').length;
      const fileName = path.basename(filePath);
      const extension = path.extname(filePath);

      if (category === 'source') {
        return `Removed ${fileName} source file containing ${lines} lines of ${language || extension} code`;
      } else if (category === 'documentation') {
        return `Removed ${fileName} documentation file containing ${lines} lines`;
      } else if (category === 'configuration') {
        return `Removed ${fileName} configuration file`;
      } else {
        return `Removed ${fileName} file containing ${lines} lines`;
      }
    }
  }

  analyzeDeletedJavaScriptFile(content, filePath, category) {
    const fileName = path.basename(filePath);

    // Extract functions, classes, and main features
    const functions = this.extractFunctions(content);
    const classes = this.extractClasses(content);
    const imports = this.extractImports(content);
    const exports = this.extractExports(content);

    const features = [];

    if (classes.length > 0) {
      features.push(`${classes.length} class${classes.length > 1 ? 'es' : ''} (${classes.slice(0, 2).join(', ')})`);
    }

    if (functions.length > 0) {
      features.push(`${functions.length} function${functions.length > 1 ? 's' : ''} (${functions.slice(0, 3).join(', ')})`);
    }

    if (imports.length > 0) {
      features.push(`${imports.length} import${imports.length > 1 ? 's' : ''}`);
    }

    if (exports.length > 0) {
      features.push(`${exports.length} export${exports.length > 1 ? 's' : ''}`);
    }

    // Determine the purpose based on file name and content patterns
    let purpose = '';
    if (fileName.includes('generator')) {
      purpose = 'generator implementation';
    } else if (fileName.includes('provider')) {
      purpose = 'provider implementation';
    } else if (fileName.includes('config')) {
      purpose = 'configuration management';
    } else if (fileName.includes('manager') || fileName.includes('service')) {
      purpose = 'service management';
    } else if (fileName.includes('cli') || fileName.includes('bin')) {
      purpose = 'CLI functionality';
    } else if (content.includes('class ') && content.includes('constructor')) {
      purpose = 'service class';
    } else if (content.includes('module.exports') || content.includes('export')) {
      purpose = 'module';
    } else {
      purpose = 'JavaScript module';
    }

    if (features.length > 0) {
      return `Removed ${fileName} ${purpose} containing ${features.join(', ')}`;
    } else {
      const lines = content.split('\n').length;
      return `Removed ${fileName} ${purpose} containing ${lines} lines of JavaScript code`;
    }
  }

  analyzeDeletedMarkdownFile(content, filePath) {
    const fileName = path.basename(filePath);
    const lines = content.split('\n');
    const sections = lines.filter(line => line.startsWith('#')).length;

    if (fileName.toLowerCase().includes('readme')) {
      return `Removed ${fileName} project documentation containing ${sections} sections and ${lines.length} lines`;
    } else if (fileName.toLowerCase().includes('changelog')) {
      return `Removed ${fileName} changelog documentation`;
    } else if (sections > 0) {
      return `Removed ${fileName} documentation containing ${sections} sections`;
    } else {
      return `Removed ${fileName} documentation containing ${lines.length} lines`;
    }
  }

  analyzeDeletedJsonFile(content, filePath) {
    const fileName = path.basename(filePath);

    try {
      const parsed = JSON.parse(content);
      const keys = Object.keys(parsed).length;

      if (fileName === 'package.json') {
        return `Removed package.json configuration with ${keys} properties (dependencies, metadata, scripts)`;
      } else {
        return `Removed ${fileName} configuration file with ${keys} configuration properties`;
      }
    } catch {
      return `Removed ${fileName} JSON configuration file`;
    }
  }

  detectLanguageFromPath(filePath) {
    const ext = path.extname(filePath).toLowerCase();
    const languageMap = {
      '.js': 'javascript',
      '.ts': 'typescript',
      '.json': 'json',
      '.md': 'markdown',
      '.py': 'python',
      '.java': 'java',
      '.css': 'css',
      '.html': 'html',
      '.yml': 'yaml',
      '.yaml': 'yaml'
    };
    return languageMap[ext] || 'text';
  }

}