@snapcommit/cli 3.3.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/cursor-style.js +223 -3
- package/package.json +1 -1
|
@@ -292,16 +292,90 @@ async function executeCommitWithAI(intent) {
|
|
|
292
292
|
// Push if requested
|
|
293
293
|
const shouldPush = intent.shouldPush || intent.gitCommands?.some((cmd) => cmd.includes('push'));
|
|
294
294
|
if (shouldPush) {
|
|
295
|
+
// STEP 1: Pull first to avoid conflicts (like Cursor does!)
|
|
296
|
+
try {
|
|
297
|
+
const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
298
|
+
const hasRemote = (0, child_process_1.execSync)(`git ls-remote --heads origin ${currentBranch}`, { encoding: 'utf-8' }).trim();
|
|
299
|
+
if (hasRemote) {
|
|
300
|
+
console.log(chalk_1.default.blue('⚡ Syncing with remote...'));
|
|
301
|
+
try {
|
|
302
|
+
(0, child_process_1.execSync)('git pull --rebase', { encoding: 'utf-8', stdio: 'pipe' });
|
|
303
|
+
console.log(chalk_1.default.green('✓ Synced'));
|
|
304
|
+
}
|
|
305
|
+
catch (pullError) {
|
|
306
|
+
// Handle rebase conflicts
|
|
307
|
+
if (pullError.message.includes('conflict')) {
|
|
308
|
+
console.log(chalk_1.default.yellow('\n⚠️ Merge conflicts detected - resolving...'));
|
|
309
|
+
// Try to auto-resolve conflicts
|
|
310
|
+
const resolved = await tryAdvancedConflictResolution();
|
|
311
|
+
if (resolved) {
|
|
312
|
+
console.log(chalk_1.default.green('✓ Conflicts resolved'));
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
console.log(chalk_1.default.red('\n❌ Cannot auto-resolve conflicts'));
|
|
316
|
+
console.log(chalk_1.default.white('\nConflicted files:'));
|
|
317
|
+
const conflicts = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' });
|
|
318
|
+
conflicts.split('\n').filter(f => f.trim()).forEach(file => {
|
|
319
|
+
console.log(chalk_1.default.red(` ✗ ${file}`));
|
|
320
|
+
});
|
|
321
|
+
console.log(chalk_1.default.cyan('\nTo resolve manually:'));
|
|
322
|
+
console.log(chalk_1.default.gray(' 1. Fix conflicts in the files above'));
|
|
323
|
+
console.log(chalk_1.default.gray(' 2. Run: ') + chalk_1.default.cyan('git add <files>'));
|
|
324
|
+
console.log(chalk_1.default.gray(' 3. Run: ') + chalk_1.default.cyan('git rebase --continue'));
|
|
325
|
+
console.log(chalk_1.default.gray(' 4. Then try: ') + chalk_1.default.cyan('snap') + chalk_1.default.gray(' and ') + chalk_1.default.cyan('push changes\n'));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// Other pull errors
|
|
331
|
+
console.log(chalk_1.default.yellow(`⚠️ Sync issue: ${pullError.message}`));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// No remote or other issues - continue with push
|
|
338
|
+
}
|
|
339
|
+
// STEP 2: Now push
|
|
295
340
|
try {
|
|
296
341
|
(0, child_process_1.execSync)('git push', { encoding: 'utf-8', stdio: 'pipe' });
|
|
297
342
|
console.log(chalk_1.default.green('✓ Pushed\n'));
|
|
298
343
|
}
|
|
299
|
-
catch (
|
|
300
|
-
|
|
344
|
+
catch (pushError) {
|
|
345
|
+
const errorMsg = pushError.message.toLowerCase();
|
|
346
|
+
if (errorMsg.includes('no configured push destination')) {
|
|
301
347
|
console.log(chalk_1.default.gray('✓ Committed locally (no remote)\n'));
|
|
302
348
|
}
|
|
349
|
+
else if (errorMsg.includes('rejected') && errorMsg.includes('non-fast-forward')) {
|
|
350
|
+
// Need force push - but be safe!
|
|
351
|
+
console.log(chalk_1.default.yellow('\n⚠️ Remote has changes that would be overwritten'));
|
|
352
|
+
console.log(chalk_1.default.white('This requires force push, which can be dangerous.\n'));
|
|
353
|
+
const rlForce = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
354
|
+
const rlForcePush = rlForce.createInterface({
|
|
355
|
+
input: process.stdin,
|
|
356
|
+
output: process.stdout,
|
|
357
|
+
});
|
|
358
|
+
const confirmForce = await new Promise((resolve) => {
|
|
359
|
+
rlForcePush.question(chalk_1.default.cyan('Force push? ') + chalk_1.default.gray('(yes/no): '), (ans) => {
|
|
360
|
+
setImmediate(() => rlForcePush.close());
|
|
361
|
+
resolve(ans.trim().toLowerCase());
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
if (confirmForce === 'yes') {
|
|
365
|
+
try {
|
|
366
|
+
(0, child_process_1.execSync)('git push --force-with-lease', { encoding: 'utf-8', stdio: 'pipe' });
|
|
367
|
+
console.log(chalk_1.default.green('✓ Force pushed (safely)\n'));
|
|
368
|
+
}
|
|
369
|
+
catch (forceError) {
|
|
370
|
+
console.log(chalk_1.default.red(`\n❌ Force push failed: ${forceError.message}\n`));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
console.log(chalk_1.default.gray('\n✗ Push cancelled\n'));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
303
377
|
else {
|
|
304
|
-
console.log(chalk_1.default.yellow(
|
|
378
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Push failed: ${pushError.message}\n`));
|
|
305
379
|
}
|
|
306
380
|
}
|
|
307
381
|
}
|
|
@@ -309,8 +383,154 @@ async function executeCommitWithAI(intent) {
|
|
|
309
383
|
console.log();
|
|
310
384
|
}
|
|
311
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* AI-POWERED conflict resolution - intelligently resolves merge conflicts!
|
|
388
|
+
*/
|
|
389
|
+
async function tryAdvancedConflictResolution() {
|
|
390
|
+
try {
|
|
391
|
+
// Get conflicted files
|
|
392
|
+
const conflictedFiles = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' })
|
|
393
|
+
.split('\n')
|
|
394
|
+
.filter(f => f.trim());
|
|
395
|
+
if (conflictedFiles.length === 0)
|
|
396
|
+
return false;
|
|
397
|
+
console.log(chalk_1.default.white(`\n📋 Analyzing ${conflictedFiles.length} conflicted file(s)...\n`));
|
|
398
|
+
// For each conflicted file, use AI to resolve
|
|
399
|
+
for (const file of conflictedFiles) {
|
|
400
|
+
try {
|
|
401
|
+
// Read the conflicted file
|
|
402
|
+
const conflictContent = (0, child_process_1.execSync)(`git show :1:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
403
|
+
const oursContent = (0, child_process_1.execSync)(`git show :2:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
404
|
+
const theirsContent = (0, child_process_1.execSync)(`git show :3:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
405
|
+
// Ask AI to resolve the conflict
|
|
406
|
+
console.log(chalk_1.default.blue(`🤖 AI analyzing: ${file}...`));
|
|
407
|
+
const resolution = await resolveConflictWithAI(file, conflictContent, oursContent, theirsContent);
|
|
408
|
+
if (resolution) {
|
|
409
|
+
console.log(chalk_1.default.green(`✓ AI resolved: ${file}`));
|
|
410
|
+
// Write the resolved content
|
|
411
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
412
|
+
fs.writeFileSync(file, resolution);
|
|
413
|
+
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
// Fallback: ask user which version to keep
|
|
417
|
+
console.log(chalk_1.default.yellow(`\n⚠️ ${file} - Choose resolution:`));
|
|
418
|
+
console.log(chalk_1.default.gray(' 1. Keep your changes (ours)'));
|
|
419
|
+
console.log(chalk_1.default.gray(' 2. Keep their changes (theirs)'));
|
|
420
|
+
console.log(chalk_1.default.gray(' 3. Skip (resolve manually later)\n'));
|
|
421
|
+
const rlConflict = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
422
|
+
const rlChoice = rlConflict.createInterface({
|
|
423
|
+
input: process.stdin,
|
|
424
|
+
output: process.stdout,
|
|
425
|
+
});
|
|
426
|
+
const choice = await new Promise((resolve) => {
|
|
427
|
+
rlChoice.question(chalk_1.default.cyan('Choice (1/2/3): '), (ans) => {
|
|
428
|
+
setImmediate(() => rlChoice.close());
|
|
429
|
+
resolve(ans.trim());
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
if (choice === '1') {
|
|
433
|
+
(0, child_process_1.execSync)(`git checkout --ours ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
434
|
+
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
435
|
+
console.log(chalk_1.default.green(`✓ Kept your changes: ${file}`));
|
|
436
|
+
}
|
|
437
|
+
else if (choice === '2') {
|
|
438
|
+
(0, child_process_1.execSync)(`git checkout --theirs ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
439
|
+
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
440
|
+
console.log(chalk_1.default.green(`✓ Kept their changes: ${file}`));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
console.log(chalk_1.default.gray(`⊘ Skipped: ${file}`));
|
|
444
|
+
return false; // User wants to resolve manually
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (fileError) {
|
|
449
|
+
console.log(chalk_1.default.yellow(`⚠️ Couldn't auto-resolve ${file}`));
|
|
450
|
+
// Fallback: ask user
|
|
451
|
+
const rlFallback = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
452
|
+
const rlFallbackChoice = rlFallback.createInterface({
|
|
453
|
+
input: process.stdin,
|
|
454
|
+
output: process.stdout,
|
|
455
|
+
});
|
|
456
|
+
const fallbackChoice = await new Promise((resolve) => {
|
|
457
|
+
rlFallbackChoice.question(chalk_1.default.cyan(`Keep (1) yours or (2) theirs? [1/2]: `), (ans) => {
|
|
458
|
+
setImmediate(() => rlFallbackChoice.close());
|
|
459
|
+
resolve(ans.trim());
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
if (fallbackChoice === '1') {
|
|
463
|
+
(0, child_process_1.execSync)(`git checkout --ours ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
464
|
+
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
465
|
+
}
|
|
466
|
+
else if (fallbackChoice === '2') {
|
|
467
|
+
(0, child_process_1.execSync)(`git checkout --theirs ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
468
|
+
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Continue the rebase
|
|
476
|
+
try {
|
|
477
|
+
(0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
catch (continueError) {
|
|
481
|
+
// If rebase fails, might need to commit first
|
|
482
|
+
try {
|
|
483
|
+
(0, child_process_1.execSync)('git commit --no-edit', { encoding: 'utf-8', stdio: 'pipe' });
|
|
484
|
+
(0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
console.log(chalk_1.default.red(`❌ Conflict resolution failed: ${error.message}`));
|
|
494
|
+
try {
|
|
495
|
+
(0, child_process_1.execSync)('git rebase --abort', { encoding: 'utf-8', stdio: 'pipe' });
|
|
496
|
+
}
|
|
497
|
+
catch { }
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Use AI to intelligently resolve merge conflicts
|
|
503
|
+
*/
|
|
504
|
+
async function resolveConflictWithAI(filename, base, ours, theirs) {
|
|
505
|
+
try {
|
|
506
|
+
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
507
|
+
// Get the backend URL from env or default
|
|
508
|
+
const backendUrl = process.env.SNAPCOMMIT_BACKEND_URL || 'https://snapcommit.dev';
|
|
509
|
+
const response = await axios.post(`${backendUrl}/api/ai/resolve-conflict`, {
|
|
510
|
+
filename,
|
|
511
|
+
base,
|
|
512
|
+
ours,
|
|
513
|
+
theirs,
|
|
514
|
+
}, {
|
|
515
|
+
headers: {
|
|
516
|
+
'Content-Type': 'application/json',
|
|
517
|
+
},
|
|
518
|
+
timeout: 30000, // 30 second timeout
|
|
519
|
+
});
|
|
520
|
+
if (response.data?.resolution) {
|
|
521
|
+
return response.data.resolution;
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
// AI resolution failed - fall back to manual choice
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
312
530
|
/**
|
|
313
531
|
* Execute Git commands - Cursor-style (clean, fast, auto-fix)
|
|
532
|
+
* NOW SUPPORTS: commits, pushes, pulls, merges, rebases, cherry-picks, stash,
|
|
533
|
+
* resets, reverts, blame, bisect, tags, reflog, and MORE!
|
|
314
534
|
*/
|
|
315
535
|
async function executeGitCommands(commands) {
|
|
316
536
|
for (const cmd of commands) {
|