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
This commit is contained in:
s41r4j
2025-10-26 19:42:14 +05:30
parent 260577f1f1
commit 5846c77f51
5 changed files with 102 additions and 94 deletions
+75 -6
View File
@@ -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 {
+10 -9
View File
@@ -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;