@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.
@@ -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 (error) {
300
- if (error.message.includes('no configured push destination')) {
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(`⚠️ Push failed: ${error.message}\n`));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapcommit/cli",
3
- "version": "3.3.1",
3
+ "version": "3.5.0",
4
4
  "description": "Instant AI commits. Beautiful progress tracking. Never write commit messages again.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {