From 5846c77f515985b7294d42bcbfe1cfc742e87b02 Mon Sep 17 00:00:00 2001 From: s41r4j Date: Sun, 26 Oct 2025 19:42:14 +0530 Subject: [PATCH] feat: improve AI commit experience and remove message length limits - Remove 72-char limit on AI-generated commit messages (no more ... truncation) - Add [Y/n] confirmation prompt when using local heuristics (no AI provider) - Default to Y (press Enter to accept) - Clearly warns user about local heuristics vs AI - Bypass with --yes flag for automation - Enhanced transparency: users now know when AI is used vs local pattern matching - Safer commits when API keys are not configured - Bumped version to 0.6.6 --- CHANGELOG.md | 16 ++++++++ QUICK_REFERENCE.md | 78 --------------------------------------- bin/gims.js | 81 ++++++++++++++++++++++++++++++++++++++--- bin/lib/ai/providers.js | 19 +++++----- package.json | 2 +- 5 files changed, 102 insertions(+), 94 deletions(-) delete mode 100644 QUICK_REFERENCE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a31417..20fe259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [0.6.6] - 2025-10-26 + +### πŸ”§ Improvements +- **Removed commit message length restriction**: AI can now generate commit messages of any length (no more `...` truncation at 72 chars) +- **Added confirmation prompt for local heuristics**: When no AI provider is configured, the tool now asks for confirmation `[Y/n]` before committing + - Default is `Y` (just press Enter to accept) + - Warning message clearly indicates "local heuristics" instead of AI + - Can be bypassed with `--yes` flag for automation +- **Better user awareness**: Users are now informed when local heuristics are used instead of AI + +### πŸ“š User Experience +- More transparent about when AI is used vs. local pattern matching +- Safer commits when API keys are not configured + +--- + ## [0.6.5] - 2025-10-20 ### πŸ”§ Command Behavior Updated diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index 58dabe8..0000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,78 +0,0 @@ -# πŸš€ GIMS Quick Reference - -## Single-Letter Workflow Commands - -```bash -g s # Status - Enhanced git status with AI insights -g i # Init - Initialize a new Git repository -g p # Preview - See what will be committed -g l # Local - AI commit locally -g o # Online - AI commit + push -g ls # List - Short commit history -g ll # Large List - Detailed commit history -g h # History - Alias for list -g a # Amend - Merge changes into previous commit (keeps message) -g u # Undo - Undo last commit -``` - -## Quick Setup - -```bash -# Choose your AI provider (one-time setup) -g setup --api-key gemini # πŸš€ Recommended: Fast & free -g setup --api-key openai # πŸ’Ž High quality -g setup --api-key groq # ⚑ Ultra fast - -# Or run full setup wizard -g setup -``` - -## Essential Workflow - -```bash -# 1. Check what's changed -g s - -# 2. Commit with AI (choose one) -g int # Interactive mode (guided) -g o # One-command: commit + push -g l # Local commit only - -# 3. View history -g ls # Recent commits (short) -g ll # Recent commits (detailed) -g h # Same as g ls -``` - -## Default AI Models - -- **Gemini**: `gemini-2.5-flash` (Fast, free, recommended) -- **OpenAI**: `gpt-5` (Latest GPT model) -- **Groq**: `groq/compound` (Ultra-fast inference) - -## Pro Tips - -```bash -g sg --multiple # Get 3 AI suggestions -g p # Preview before committing -g a # Amend: merge changes to previous commit (keeps message) -g a --edit # Amend with new AI-generated message -g sync --rebase # Smart sync with rebase -g stash # Stash with AI description -``` - -## Configuration - -```bash -g config --list # View all settings -g config --set conventional=true # Enable conventional commits -g config --set autoStage=true # Auto-stage changes -g config --set provider=gemini # Set AI provider -``` - -## Help - -```bash -g --help # All commands -g q # This quick reference -``` \ No newline at end of file diff --git a/bin/gims.js b/bin/gims.js index 02f6f76..d8e60c5 100755 --- a/bin/gims.js +++ b/bin/gims.js @@ -90,7 +90,31 @@ async function safeLog() { async function generateCommitMessage(rawDiff, options = {}) { - return await aiProvider.generateCommitMessage(rawDiff, options); + const result = await aiProvider.generateCommitMessage(rawDiff, options); + // Return both message and whether local heuristics were used + return result; +} + +async function confirmCommit(message, isLocalHeuristic) { + if (!isLocalHeuristic) return true; // No confirmation needed for AI-generated messages + + const readline = require('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + console.log(color.yellow('\n⚠️ No AI provider configured - using local heuristics')); + console.log(`Suggested commit: "${message}"`); + + return new Promise((resolve) => { + rl.question('Proceed with this commit? [Y/n]: ', (answer) => { + rl.close(); + const trimmed = answer.trim().toLowerCase(); + // Default to 'yes' if empty (just Enter pressed) + resolve(trimmed === '' || trimmed === 'y' || trimmed === 'yes'); + }); + }); } async function resolveCommit(input) { @@ -392,11 +416,19 @@ program.command('suggest').alias('sg') } } else { if (opts.progressIndicators) Progress.start('πŸ€– Analyzing changes'); - const msg = await generateCommitMessage(rawDiff, opts); + const result = await generateCommitMessage(rawDiff, opts); if (opts.progressIndicators) Progress.stop(''); + + const msg = result.message || result; // Handle both old and new format + const usedLocal = result.usedLocal || false; + + // Warn if using local heuristics + if (usedLocal) { + console.log(color.yellow('⚠️ No AI provider configured - using local heuristics')); + } if (opts.json) { - const out = { message: msg }; + const out = { message: msg, usedLocalHeuristics: usedLocal }; console.log(JSON.stringify(out)); return; } @@ -446,8 +478,11 @@ program.command('local').alias('l') } if (opts.progressIndicators) Progress.start('πŸ€– Generating commit message'); - const msg = await generateCommitMessage(rawDiff, opts); + const result = await generateCommitMessage(rawDiff, opts); if (opts.progressIndicators) Progress.stop(''); + + const msg = result.message || result; // Handle both old and new format + const usedLocal = result.usedLocal || false; if (opts.dryRun) { console.log(color.yellow('[dry-run] Would commit with message:')); @@ -455,6 +490,15 @@ program.command('local').alias('l') return; } + // Ask for confirmation if using local heuristics (unless --yes flag is set) + if (usedLocal && !opts.yes) { + const confirmed = await confirmCommit(msg, true); + if (!confirmed) { + Progress.info('Commit cancelled'); + return; + } + } + if (opts.amend) { await git.raw(['commit', '--amend', '-m', msg]); Progress.success(`Amended commit: "${msg}"`); @@ -496,8 +540,11 @@ program.command('online').alias('o') } if (opts.progressIndicators) Progress.start('πŸ€– Generating commit message'); - const msg = await generateCommitMessage(rawDiff, opts); + const result = await generateCommitMessage(rawDiff, opts); if (opts.progressIndicators) Progress.stop(''); + + const msg = result.message || result; // Handle both old and new format + const usedLocal = result.usedLocal || false; if (opts.dryRun) { console.log(color.yellow('[dry-run] Would commit & push with message:')); @@ -505,6 +552,15 @@ program.command('online').alias('o') return; } + // Ask for confirmation if using local heuristics (unless --yes flag is set) + if (usedLocal && !opts.yes) { + const confirmed = await confirmCommit(msg, true); + if (!confirmed) { + Progress.info('Commit cancelled'); + return; + } + } + Progress.info('Committing changes...'); if (opts.amend) { await git.raw(['commit', '--amend', '-m', msg]); @@ -723,10 +779,23 @@ program.command('amend').alias('a') if (cmdOptions.edit) { // Generate new message for amend + const opts = getOpts(); Progress.start('πŸ€– Generating updated commit message'); - const newMessage = await generateCommitMessage(rawDiff, getOpts()); + const result = await generateCommitMessage(rawDiff, opts); Progress.stop(''); + const newMessage = result.message || result; // Handle both old and new format + const usedLocal = result.usedLocal || false; + + // Ask for confirmation if using local heuristics (unless --yes flag is set) + if (usedLocal && !opts.yes) { + const confirmed = await confirmCommit(newMessage, true); + if (!confirmed) { + Progress.info('Amend cancelled'); + return; + } + } + await git.raw(['commit', '--amend', '-m', newMessage]); Progress.success(`Amended commit: "${newMessage}"`); } else { diff --git a/bin/lib/ai/providers.js b/bin/lib/ai/providers.js index 64c1730..c11e43d 100644 --- a/bin/lib/ai/providers.js +++ b/bin/lib/ai/providers.js @@ -46,7 +46,7 @@ class AIProviderManager { return this.cache.get(cacheKey); } - setCache(cacheKey, result) { + setCache(cacheKey, result, usedLocal = false) { if (!this.config.cacheEnabled) return; if (this.cache.size >= this.maxCacheSize) { @@ -56,6 +56,7 @@ class AIProviderManager { this.cache.set(cacheKey, { result, + usedLocal, timestamp: Date.now() }); } @@ -129,7 +130,7 @@ class AIProviderManager { const cached = this.getFromCache(cacheKey); if (cached && Date.now() - cached.timestamp < 3600000) { // 1 hour cache if (verbose) Progress.info('Using cached result'); - return cached.result; + return { message: cached.result, usedLocal: cached.usedLocal || false }; } const providerChain = this.buildProviderChain(preferredProvider); @@ -140,16 +141,16 @@ class AIProviderManager { if (provider === 'local') { const result = await this.generateLocalHeuristic(diff, options); - this.setCache(cacheKey, result); - return result; + this.setCache(cacheKey, result, true); + return { message: result, usedLocal: true }; } const prompt = this.buildPrompt(diff, { conventional, body }); const result = await this.generateWithProvider(provider, prompt, options); const cleaned = this.cleanCommitMessage(result, { body }); - this.setCache(cacheKey, cleaned); - return cleaned; + this.setCache(cacheKey, cleaned, false); + return { message: cleaned, usedLocal: false }; } catch (error) { if (verbose) Progress.warning(`${provider} failed: ${error.message}`); @@ -159,8 +160,8 @@ class AIProviderManager { // Final fallback const fallback = 'Update project files'; - this.setCache(cacheKey, fallback); - return fallback; + this.setCache(cacheKey, fallback, true); + return { message: fallback, usedLocal: true }; } buildProviderChain(preferred) { @@ -213,7 +214,7 @@ class AIProviderManager { let subject = (lines[0] || '').replace(/\s{2,}/g, ' ').replace(/[\s:,.!;]+$/g, '').trim(); if (subject.length === 0) subject = 'Update project code'; - if (subject.length > 72) subject = subject.substring(0, 69) + '...'; + // No length restriction - allow AI to generate full commit messages if (!options.body) return subject; diff --git a/package.json b/package.json index dace756..700ff94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gims", - "version": "0.6.5", + "version": "0.6.6", "description": "Git Made Simple – AI‑powered git helper using Gemini / OpenAI", "author": "S41R4J", "license": "MIT",