laststance / laststance/git-gpt-commit

πŸ—οΈ Refactor architecture to improve maintainability

Open
#67 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

architecture high-priority refactoring
Dominant language
JavaScript
Stars
36
Forks
3
PR merge metrics
No merged PRs in 30d

Description

Problem

The current architecture has all logic in a single 389-line index.js file with multiple responsibilities, making it difficult to maintain and test.

Current issues:

  • Single file handles: CLI routing, OpenAI API, config I/O, git operations, user prompts
  • Global mutable state (openai, model, language, apiKey)
  • gitExtension() function is 202 lines (lines 184-386)
  • gptCommit() mixes business logic with I/O operations
  • Hard to test components in isolation

Current Structure

index.js (389 lines)
  β”œβ”€ Global variables (state)
  β”œβ”€ saveConfig() / loadConfig()
  β”œβ”€ maskApiKey()
  β”œβ”€ getGitSummary()
  β”œβ”€ gptCommit()
  └─ gitExtension() (CLI setup)
utils/
  └─ sanitizeCommitMessage.js

Proposed Modular Structure

src/
  β”œβ”€ index.js                    # Entry point (~50 lines)
  β”œβ”€ cli/
  β”‚   β”œβ”€ commands/
  β”‚   β”‚   β”œβ”€ commit.js          # Commit command handler
  β”‚   β”‚   β”œβ”€ model.js           # Model selection command
  β”‚   β”‚   β”œβ”€ language.js        # Language selection command
  β”‚   β”‚   β”œβ”€ prefix.js          # Prefix toggle command
  β”‚   β”‚   β”œβ”€ apiKey.js          # API key management command
  β”‚   β”‚   └─ config.js          # Config display command
  β”‚   └─ index.js               # CLI setup with Commander
  β”œβ”€ services/
  β”‚   β”œβ”€ OpenAIService.js       # OpenAI API interactions
  β”‚   β”œβ”€ GitService.js          # Git operations
  β”‚   └─ ConfigService.js       # Configuration management
  β”œβ”€ prompts/
  β”‚   β”œβ”€ modelSelection.js      # Model selection prompt
  β”‚   β”œβ”€ languageSelection.js   # Language selection prompt
  β”‚   β”œβ”€ confirmCommit.js       # Commit confirmation prompt
  β”‚   └─ apiKeyPrompts.js       # API key prompts
  └─ utils/
      β”œβ”€ sanitizeCommitMessage.js
      └─ validators.js          # Input validation utilities
Example Refactoring

Before (index.js - mixed concerns):

const gptCommit = async () => {
  const gitSummary = await getGitSummary()
  if (!gitSummary) {
    console.log('No changes to commit. Commit canceled.')
    process.exit(0)
  }

  const messages = [ /* ... */ ]
  const parameters = { /* ... */ }
  const response = await openai.chat.completions.create(parameters)
  const message = response.choices[0].message.content.trim()
  const sanitizedMessage = sanitizeCommitMessage(message)

  const confirm = await prompts({ /* ... */ })
  if (confirm.value) {
    execSync(`git commit -m "${sanitizedMessage}"`)
    console.log('Committed with the suggested message.')
  } else {
    console.log('Commit canceled.')
  }
}

After (separation of concerns):

// src/services/OpenAIService.js
export class OpenAIService {
  constructor(apiKey, model, language) {
    this.openai = new OpenAI({ apiKey })
    this.model = model
    this.language = language
  }

  async generateCommitMessage(gitDiff, usePrefix) {
    const messages = this.buildMessages(gitDiff, usePrefix)
    const response = await this.callWithRetry({ 
      model: this.model, 
      messages 
    })
    return response.choices[0].message.content.trim()
  }

  buildMessages(gitDiff, usePrefix) { /* ... */ }
  async callWithRetry(params, retries = 3) { /* ... */ }
}

// src/services/GitService.js
export class GitService {
  async getStagedDiff() {
    const { stdout } = await exec(
      "git diff --cached -- . ':(exclude)*lock.json' ':(exclude)*lock.yaml'"
    )
    return stdout.trim() || null
  }

  async commit(message) {
    execSync(`git commit -m "${message}"`)
  }
}

// src/cli/commands/commit.js
export async function commitCommand(config) {
  const gitService = new GitService()
  const openaiService = new OpenAIService(
    config.apiKey, 
    config.model, 
    config.language
  )

  const gitDiff = await gitService.getStagedDiff()
  if (!gitDiff) {
    console.log('No changes to commit.')
    return
  }

  const message = await openaiService.generateCommitMessage(
    gitDiff, 
    config.prefixEnabled
  )
  const sanitized = sanitizeCommitMessage(message)

  if (await confirmCommitPrompt(sanitized)) {
    await gitService.commit(sanitized)
    console.log('βœ… Committed successfully.')
  } else {
    console.log('Commit canceled.')
  }
}

Benefits

  • βœ… Single Responsibility: Each module has one clear purpose
  • βœ… Testability: Easy to test components in isolation
  • βœ… Maintainability: Easier to locate and modify functionality
  • βœ… Scalability: Simple to add new commands and features
  • βœ… Reusability: Services can be reused across commands
  • βœ… State Management: Eliminate global mutable state

Implementation Plan

Phase 1: Extract Services (Week 1)
  • Create src/services/OpenAIService.js
  • Create src/services/GitService.js
  • Create src/services/ConfigService.js
  • Add tests for each service
  • Update index.js to use services
Phase 2: Extract Commands (Week 2)
  • Create src/cli/commands/ directory
  • Extract each command to separate file
  • Add tests for command handlers
  • Update CLI setup to use command files
Phase 3: Extract Prompts (Week 3)
  • Create src/prompts/ directory
  • Extract prompt logic from commands
  • Add tests for prompts
  • Update commands to use prompt modules
Phase 4: Cleanup (Week 4)
  • Remove global state from index.js
  • Update all imports and exports
  • Run full test suite
  • Update documentation
  • Verify backward compatibility

Acceptance Criteria

  • index.js reduced to < 100 lines (entry point only)
  • Each service/command file < 200 lines
  • No global mutable state
  • 100% test coverage for new services
  • All existing tests pass
  • No breaking changes to CLI interface

Priority

High - Enables future development and maintenance

Related

Quality analysis report: claudedocs/quality-analysis-report.md section 1

Migration Notes

  • Maintain backward compatibility during refactoring
  • Use feature flags if needed for gradual rollout
  • Document breaking changes clearly
  • Update CLAUDE.md with new architecture

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up β€” it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading index.js, utils/sanitizeCommitMessage.js, and section 1 of claudedocs/quality-analysis-report.md to map the current responsibilities. Compare that structure with the proposed src/ layout and implementation phases before making changes. Done means the stated acceptance criteria are met, existing tests pass, and the CLI remains backward compatible.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, javascript
Domain
cli, tooling
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.