Skip to content

[Refactor] Consolidate multi-step modules into src/generation/multi_step.rs #68

@oleander

Description

@oleander

Context

Currently have 3 separate multi-step modules that should be consolidated:

  • multi_step_analysis.rs (515 lines)
  • multi_step_integration.rs (811 lines)
  • simple_multi_step.rs (129 lines)

These overlap significantly and should be organized as a single cohesive module.

Priority

🟡 HIGH - Major simplification of code organization

Steps

1. Create module structure

mkdir -p src/generation
touch src/generation/mod.rs
touch src/generation/multi_step.rs

2. Design the unified API in src/generation/multi_step.rs

//! Multi-step commit message generation.
//!
//! Implements a sophisticated analysis pipeline:
//! 1. Parse diff into individual files
//! 2. Analyze each file (lines changed, category, impact)
//! 3. Score files by impact
//! 4. Generate message candidates
//! 5. Select best candidate

use anyhow::Result;
use async_openai::config::OpenAIConfig;
use async_openai::Client;

use crate::config::AppConfig;
use crate::diff::parser::ParsedFile;

pub mod analysis;
pub mod scoring;
pub mod candidates;
pub mod local;

// Re-export commonly used types
pub use analysis::{FileAnalysis, analyze_file, analyze_file_via_api};
pub use scoring::{calculate_impact_scores, ImpactScore};
pub use candidates::{generate_candidates, select_best_candidate};

/// Main entry point for multi-step generation with API
pub async fn generate_with_api(
    client: &Client<OpenAIConfig>,
    model: &str,
    diff: &str,
    config: &AppConfig,
) -> Result<String> {
    // High-level orchestration
    // Move from multi_step_integration.rs generate_commit_message_multi_step
}

/// Main entry point for local multi-step generation (no API)
pub fn generate_local(
    diff: &str,
    config: &AppConfig,
) -> Result<String> {
    // Move from multi_step_integration.rs generate_commit_message_local
}

3. Create src/generation/analysis.rs

//! File analysis for multi-step generation.

use anyhow::Result;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileAnalysis {
    pub lines_added: u32,
    pub lines_removed: u32,
    pub category: FileCategory,
    pub summary: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum FileCategory {
    Source, Test, Config, Docs, Binary, Build,
}

/// Analyze a file locally without API
pub fn analyze_file(
    path: &str,
    diff_content: &str,
    operation: &str,
) -> FileAnalysis {
    // Move from multi_step_analysis.rs analyze_file function
}

/// Analyze a file using OpenAI API
pub async fn analyze_file_via_api(
    client: &Client<OpenAIConfig>,
    model: &str,
    file: &ParsedFile,
) -> Result<FileAnalysis> {
    // Move from multi_step_integration.rs call_analyze_function
}

/// Helper: Categorize file by path
fn categorize_file(path: &str) -> FileCategory {
    // Move from multi_step_analysis.rs
}

#[cfg(test)]
mod tests {
    // Move from multi_step_analysis.rs tests
}

4. Create src/generation/scoring.rs

//! Impact scoring for analyzed files.

use super::analysis::FileAnalysis;

pub struct ImpactScore {
    pub file_path: String,
    pub operation: String,
    pub analysis: FileAnalysis,
    pub score: f32,
}

pub fn calculate_impact_scores(
    files: Vec<(String, String, FileAnalysis)>,
) -> Vec<ImpactScore> {
    // Move from multi_step_analysis.rs calculate_impact_scores
}

fn calculate_single_score(
    operation: &str,
    analysis: &FileAnalysis,
) -> f32 {
    // Move from multi_step_analysis.rs calculate_single_impact_score
}

5. Create src/generation/candidates.rs

//! Commit message candidate generation and selection.

use super::scoring::ImpactScore;

pub struct Candidate {
    pub message: String,
    pub style: CandidateStyle,
}

pub enum CandidateStyle {
    Action,      // "Add authentication"
    Component,   // "auth: implementation"
    Impact,      // "New feature for authentication"
}

pub fn generate_candidates(
    scored_files: &[ImpactScore],
    max_length: usize,
) -> Vec<Candidate> {
    // Move from multi_step_analysis.rs generate_commit_messages
}

pub fn select_best_candidate(
    candidates: &[Candidate],
) -> Option<String> {
    // Selection logic
}

6. Create src/generation/local.rs

//! Local generation fallback (no API required).

use anyhow::Result;
use super::analysis;
use super::scoring;
use super::candidates;

pub fn generate_simple(
    diff: &str,
    max_length: usize,
) -> Result<String> {
    // Move from simple_multi_step.rs generate_commit_message_simple_local
}

7. Create src/generation/mod.rs

//! Commit message generation strategies.
//!
//! Provides multiple approaches for generating commit messages:
//! - Multi-step analysis with API
//! - Local multi-step analysis
//! - Simple local generation

pub mod multi_step;

pub use multi_step::{generate_with_api, generate_local};

8. Update src/lib.rs

pub mod generation;

9. Update imports throughout codebase

Find and update all imports:

rg "multi_step_analysis" src/ -l
rg "multi_step_integration" src/ -l
rg "simple_multi_step" src/ -l

Change to:

use crate::generation::multi_step;

10. Delete old files

git rm src/multi_step_analysis.rs
git rm src/multi_step_integration.rs
git rm src/simple_multi_step.rs

Verification Criteria

Pass:

  • src/generation/ module created with submodules
  • All multi-step logic consolidated (no duplication)
  • Old files deleted
  • All imports updated
  • cargo build succeeds
  • cargo test passes all tests
  • Multi-step generation still works (test with commit)
  • Local fallback still works (test without API key)
  • cargo clippy shows no warnings
  • Module organization is clearer and more logical

Test manually

# Test API generation
export OPENAI_API_KEY=your-key
cd test-repo
echo "test" > file.txt
git add file.txt
git commit --no-edit

# Test local fallback
unset OPENAI_API_KEY
echo "test2" > file2.txt
git add file2.txt
git commit --no-edit

Estimated Time

6-8 hours (complex refactor)

Dependencies

Labels

  • refactor
  • module-structure
  • generation
  • breaking-change

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions