gims 0.6.7 → 0.8.1

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/bin/gims.js CHANGED
@@ -15,6 +15,7 @@ const { ConfigManager } = require('./lib/config/manager');
15
15
  const { GitAnalyzer } = require('./lib/git/analyzer');
16
16
  const { AIProviderManager } = require('./lib/ai/providers');
17
17
  const { InteractiveCommands } = require('./lib/commands/interactive');
18
+ const { Intelligence } = require('./lib/utils/intelligence');
18
19
 
19
20
  const program = new Command();
20
21
  const git = simpleGit();
@@ -64,7 +65,7 @@ async function ensureRepo() {
64
65
  function handleError(prefix, err) {
65
66
  const msg = err && err.message ? err.message : String(err);
66
67
  Progress.error(`${prefix}: ${msg}`);
67
-
68
+
68
69
  // Provide helpful suggestions based on error type
69
70
  if (msg.includes('not found') || msg.includes('does not exist')) {
70
71
  console.log(`\nTip: Check if the file/branch exists with: ${color.cyan('g status')}`);
@@ -73,7 +74,7 @@ function handleError(prefix, err) {
73
74
  } else if (msg.includes('merge') || msg.includes('conflict')) {
74
75
  console.log(`\nTip: Resolve conflicts and try again`);
75
76
  }
76
-
77
+
77
78
  process.exit(1);
78
79
  }
79
80
 
@@ -97,7 +98,7 @@ async function generateCommitMessage(rawDiff, options = {}) {
97
98
 
98
99
  async function confirmCommit(message, isLocalHeuristic) {
99
100
  if (!isLocalHeuristic) return true; // No confirmation needed for AI-generated messages
100
-
101
+
101
102
  const readline = require('readline');
102
103
  const rl = readline.createInterface({
103
104
  input: process.stdin,
@@ -106,7 +107,7 @@ async function confirmCommit(message, isLocalHeuristic) {
106
107
 
107
108
  console.log(color.yellow('\n⚠️ No AI provider configured - using local heuristics'));
108
109
  console.log(`Suggested commit: "${message}"`);
109
-
110
+
110
111
  return new Promise((resolve) => {
111
112
  rl.question('Proceed with this commit? [Y/n]: ', (answer) => {
112
113
  rl.close();
@@ -184,10 +185,10 @@ async function setupApiKey(provider) {
184
185
  });
185
186
 
186
187
  console.log(color.bold(`\n🔑 ${provider.toUpperCase()} API Key Setup\n`));
187
-
188
+
188
189
  const envVars = {
189
190
  'openai': 'OPENAI_API_KEY',
190
- 'gemini': 'GEMINI_API_KEY',
191
+ 'gemini': 'GEMINI_API_KEY',
191
192
  'groq': 'GROQ_API_KEY'
192
193
  };
193
194
 
@@ -211,7 +212,7 @@ async function setupApiKey(provider) {
211
212
  }
212
213
 
213
214
  const apiKey = await question(`\nEnter your ${provider.toUpperCase()} API key: `);
214
-
215
+
215
216
  if (!apiKey) {
216
217
  console.log(color.yellow('No API key provided. Setup cancelled.'));
217
218
  rl.close();
@@ -226,12 +227,12 @@ async function setupApiKey(provider) {
226
227
  console.log(color.cyan(`export ${envVar}="${apiKey}"`));
227
228
  console.log('\nOr add it to your shell profile (~/.bashrc, ~/.zshrc, etc.):');
228
229
  console.log(color.cyan(`echo 'export ${envVar}="${apiKey}"' >> ~/.zshrc`));
229
-
230
+
230
231
  // Set provider in config
231
232
  const config = configManager.load();
232
233
  config.provider = provider;
233
234
  configManager.save(config);
234
-
235
+
235
236
  console.log(`\n${color.green('✓')} Provider set to ${provider} in local config`);
236
237
  console.log('\nRestart your terminal and try:');
237
238
  console.log(` ${color.cyan('g sg')} - Get AI suggestions`);
@@ -245,7 +246,7 @@ program.command('status').alias('s')
245
246
  try {
246
247
  const enhancedStatus = await gitAnalyzer.getEnhancedStatus();
247
248
  console.log(gitAnalyzer.formatStatusOutput(enhancedStatus));
248
-
249
+
249
250
  // Show commit history summary
250
251
  const history = await gitAnalyzer.analyzeCommitHistory(5);
251
252
  if (history.totalCommits > 0) {
@@ -325,38 +326,41 @@ program.command('quick-help').alias('q')
325
326
  .description('Show quick reference for main commands')
326
327
  .action(() => {
327
328
  console.log(color.bold('🚀 GIMS Quick Reference\n'));
328
-
329
- console.log(color.bold('Single-Letter Workflow:'));
330
- console.log(` ${color.cyan('g s')} Status - Enhanced git status with AI insights`);
331
- console.log(` ${color.cyan('g i')} Init - Initialize a new Git repository`);
332
- console.log(` ${color.cyan('g p')} Preview - See what will be committed`);
333
- console.log(` ${color.cyan('g l')} Local - AI commit locally`);
334
- console.log(` ${color.cyan('g o')} Online - AI commit + push`);
335
- console.log(` ${color.cyan('g ls')} List - Short commit history`);
336
- console.log(` ${color.cyan('g ll')} Large List - Detailed commit history`);
337
- console.log(` ${color.cyan('g h')} History - Alias for list`);
338
- console.log(` ${color.cyan('g a')} Amend - Merge changes to previous commit (keeps message)`);
339
- console.log(` ${color.cyan('g u')} Undo - Undo last commit\n`);
340
-
341
- console.log(color.bold('Quick Setup:'));
342
- console.log(` ${color.cyan('g setup --api-key gemini')} 🚀 gemini-2.5-flash (recommended)`);
343
- console.log(` ${color.cyan('g setup --api-key openai')} 💎 gpt-5 (high quality)`);
344
- console.log(` ${color.cyan('g setup --api-key groq')} ⚡ groq/compound (ultra fast)\n`);
345
-
346
- console.log(color.bold('Essential Workflow:'));
347
- console.log(` ${color.cyan('g s')} Check what's changed`);
348
- console.log(` ${color.cyan('g int')} or ${color.cyan('g o')} Commit with AI (interactive or online)`);
349
- console.log(` ${color.cyan('g ls')} or ${color.cyan('g h')} View history\n`);
350
-
351
- console.log(`For full help: ${color.cyan('g --help')}`);
352
- console.log(`For detailed docs: See README.md`);
329
+
330
+ console.log(color.bold('Core Workflow:'));
331
+ console.log(` ${color.cyan('g s')} Status with AI insights`);
332
+ console.log(` ${color.cyan('g o')} AI commit + push`);
333
+ console.log(` ${color.cyan('g l')} AI commit locally`);
334
+ console.log(` ${color.cyan('g wip')} Quick WIP commit\n`);
335
+
336
+ console.log(color.bold('Sync & Fix:'));
337
+ console.log(` ${color.cyan('g sp')} Safe pull (stash pull → pop)`);
338
+ console.log(` ${color.cyan('g fix')} Fix branch sync issues`);
339
+ console.log(` ${color.cyan('g main')} Switch to main + pull\n`);
340
+
341
+ console.log(color.bold('Stash:'));
342
+ console.log(` ${color.cyan('g ss')} Quick stash save`);
343
+ console.log(` ${color.cyan('g pop')} Pop latest stash`);
344
+ console.log(` ${color.cyan('g us')} Unstage all files\n`);
345
+
346
+ console.log(color.bold('Smart Commands:'));
347
+ console.log(` ${color.cyan('g r')} AI code review`);
348
+ console.log(` ${color.cyan('g t')} Today's commits`);
349
+ console.log(` ${color.cyan('g last')} Last commit details\n`);
350
+
351
+ console.log(color.bold('History:'));
352
+ console.log(` ${color.cyan('g ls')} Commit history`);
353
+ console.log(` ${color.cyan('g a')} Amend last commit`);
354
+ console.log(` ${color.cyan('g u')} Undo last commit\n`);
355
+
356
+ console.log(`Full help: ${color.cyan('g --help')}`);
353
357
  });
354
358
 
355
359
  program.command('init').alias('i')
356
360
  .description('Initialize a new Git repository')
357
- .action(async () => {
358
- try {
359
- await git.init();
361
+ .action(async () => {
362
+ try {
363
+ await git.init();
360
364
  Progress.success('Initialized git repository');
361
365
  console.log(`\nNext steps:`);
362
366
  console.log(` ${color.cyan('g setup')} - Configure GIMS`);
@@ -400,17 +404,17 @@ program.command('suggest').alias('sg')
400
404
  if (opts.progressIndicators) Progress.start('🤖 Generating multiple suggestions');
401
405
  const suggestions = await aiProvider.generateMultipleSuggestions(rawDiff, opts, 3);
402
406
  if (opts.progressIndicators) Progress.stop('');
403
-
407
+
404
408
  console.log(color.bold('\n📝 Suggested commit messages:\n'));
405
409
  suggestions.forEach((msg, i) => {
406
410
  console.log(`${color.cyan((i + 1).toString())}. ${msg}`);
407
411
  });
408
-
412
+
409
413
  if (!opts.noClipboard && suggestions.length > 0) {
410
- try {
411
- clipboard.writeSync(suggestions[0]);
414
+ try {
415
+ clipboard.writeSync(suggestions[0]);
412
416
  console.log(`\n${color.green('✓')} First suggestion copied to clipboard`);
413
- } catch (_) {
417
+ } catch (_) {
414
418
  console.log(`\n${color.yellow('⚠')} Clipboard copy failed`);
415
419
  }
416
420
  }
@@ -418,10 +422,10 @@ program.command('suggest').alias('sg')
418
422
  if (opts.progressIndicators) Progress.start('🤖 Analyzing changes');
419
423
  const result = await generateCommitMessage(rawDiff, opts);
420
424
  if (opts.progressIndicators) Progress.stop('');
421
-
425
+
422
426
  const msg = result.message || result; // Handle both old and new format
423
427
  const usedLocal = result.usedLocal || false;
424
-
428
+
425
429
  // Warn if using local heuristics
426
430
  if (usedLocal) {
427
431
  console.log(color.yellow('⚠️ No AI provider configured - using local heuristics'));
@@ -434,10 +438,10 @@ program.command('suggest').alias('sg')
434
438
  }
435
439
 
436
440
  if (!opts.noClipboard) {
437
- try {
438
- clipboard.writeSync(msg);
441
+ try {
442
+ clipboard.writeSync(msg);
439
443
  Progress.success(`"${msg}" (copied to clipboard)`);
440
- } catch (_) {
444
+ } catch (_) {
441
445
  console.log(`Suggested: "${msg}" ${color.yellow('(clipboard copy failed)')}`);
442
446
  }
443
447
  } else {
@@ -471,16 +475,16 @@ program.command('local').alias('l')
471
475
  Progress.info('No staged changes found; staging all changes...');
472
476
  await git.add('.');
473
477
  rawDiff = await git.diff(['--cached', '--no-ext-diff']);
474
- if (!rawDiff.trim()) {
475
- Progress.warning('No changes to commit');
476
- return;
478
+ if (!rawDiff.trim()) {
479
+ Progress.warning('No changes to commit');
480
+ return;
477
481
  }
478
482
  }
479
483
 
480
484
  if (opts.progressIndicators) Progress.start('🤖 Generating commit message');
481
485
  const result = await generateCommitMessage(rawDiff, opts);
482
486
  if (opts.progressIndicators) Progress.stop('');
483
-
487
+
484
488
  const msg = result.message || result; // Handle both old and new format
485
489
  const usedLocal = result.usedLocal || false;
486
490
 
@@ -533,16 +537,16 @@ program.command('online').alias('o')
533
537
  Progress.info('No staged changes found; staging all changes...');
534
538
  await git.add('.');
535
539
  rawDiff = await git.diff(['--cached', '--no-ext-diff']);
536
- if (!rawDiff.trim()) {
537
- Progress.warning('No changes to commit');
538
- return;
540
+ if (!rawDiff.trim()) {
541
+ Progress.warning('No changes to commit');
542
+ return;
539
543
  }
540
544
  }
541
545
 
542
546
  if (opts.progressIndicators) Progress.start('🤖 Generating commit message');
543
547
  const result = await generateCommitMessage(rawDiff, opts);
544
548
  if (opts.progressIndicators) Progress.stop('');
545
-
549
+
546
550
  const msg = result.message || result; // Handle both old and new format
547
551
  const usedLocal = result.usedLocal || false;
548
552
 
@@ -640,9 +644,9 @@ program.command('pull')
640
644
  .description('Pull latest changes')
641
645
  .action(async () => {
642
646
  await ensureRepo();
643
- try {
647
+ try {
644
648
  Progress.info('Pulling latest changes...');
645
- await git.pull();
649
+ await git.pull();
646
650
  Progress.success('Pulled latest changes');
647
651
  }
648
652
  catch (e) { handleError('Pull error', e); }
@@ -667,27 +671,27 @@ program.command('sync')
667
671
  await ensureRepo();
668
672
  try {
669
673
  const status = await git.status();
670
-
674
+
671
675
  if (status.files.length > 0) {
672
676
  Progress.warning('You have uncommitted changes. Commit or stash them first.');
673
677
  return;
674
678
  }
675
-
679
+
676
680
  Progress.info('Fetching latest changes...');
677
681
  await git.fetch();
678
-
682
+
679
683
  const currentBranch = (await git.raw(['rev-parse', '--abbrev-ref', 'HEAD'])).trim();
680
684
  const remoteBranch = `origin/${currentBranch}`;
681
-
685
+
682
686
  try {
683
687
  const behind = await git.raw(['rev-list', '--count', `${currentBranch}..${remoteBranch}`]);
684
688
  const ahead = await git.raw(['rev-list', '--count', `${remoteBranch}..${currentBranch}`]);
685
-
689
+
686
690
  if (parseInt(behind.trim()) === 0) {
687
691
  Progress.success('Already up to date');
688
692
  return;
689
693
  }
690
-
694
+
691
695
  if (parseInt(ahead.trim()) > 0) {
692
696
  Progress.info(`Branch is ${ahead.trim()} commits ahead and ${behind.trim()} commits behind`);
693
697
  if (cmdOptions.rebase) {
@@ -713,8 +717,8 @@ program.command('sync')
713
717
  throw error;
714
718
  }
715
719
  }
716
- } catch (e) {
717
- handleError('Sync error', e);
720
+ } catch (e) {
721
+ handleError('Sync error', e);
718
722
  }
719
723
  });
720
724
 
@@ -732,7 +736,7 @@ program.command('stash')
732
736
  Progress.info('No stashes found');
733
737
  return;
734
738
  }
735
-
739
+
736
740
  console.log(color.bold('Stashes:'));
737
741
  stashes.all.forEach((stash, i) => {
738
742
  console.log(`${color.cyan((i).toString())}. ${stash.message}`);
@@ -751,15 +755,15 @@ program.command('stash')
751
755
  Progress.warning('No changes to stash');
752
756
  return;
753
757
  }
754
-
758
+
755
759
  Progress.start('🤖 Generating stash description');
756
760
  const diff = await git.diff();
757
- const description = await aiProvider.generateCommitMessage(diff, {
758
- conventional: false,
759
- body: false
761
+ const description = await aiProvider.generateCommitMessage(diff, {
762
+ conventional: false,
763
+ body: false
760
764
  });
761
765
  Progress.stop('');
762
-
766
+
763
767
  await git.stash(['push', '-m', `WIP: ${description}`]);
764
768
  Progress.success(`Stashed changes: "${description}"`);
765
769
  }
@@ -795,10 +799,10 @@ program.command('amend').alias('a')
795
799
  Progress.start('🤖 Generating updated commit message');
796
800
  const result = await generateCommitMessage(rawDiff, opts);
797
801
  Progress.stop('');
798
-
802
+
799
803
  const newMessage = result.message || result; // Handle both old and new format
800
804
  const usedLocal = result.usedLocal || false;
801
-
805
+
802
806
  // Ask for confirmation if using local heuristics (unless --yes flag is set)
803
807
  if (usedLocal && !opts.yes) {
804
808
  const confirmed = await confirmCommit(newMessage, true);
@@ -807,7 +811,7 @@ program.command('amend').alias('a')
807
811
  return;
808
812
  }
809
813
  }
810
-
814
+
811
815
  await git.raw(['commit', '--amend', '-m', newMessage]);
812
816
  Progress.success(`Amended commit: "${newMessage}"`);
813
817
  } else {
@@ -829,21 +833,21 @@ program.command('list').alias('ls')
829
833
  const limit = parseInt(cmdOptions.limit) || 20;
830
834
  const log = await git.log({ maxCount: limit });
831
835
  const commits = [...log.all].reverse();
832
-
836
+
833
837
  if (commits.length === 0) {
834
838
  Progress.info('No commits found');
835
839
  return;
836
840
  }
837
-
841
+
838
842
  commits.forEach((c, i) => {
839
- console.log(`${color.cyan((i+1).toString())}. ${color.yellow(c.hash.slice(0,7))} ${c.message}`);
843
+ console.log(`${color.cyan((i + 1).toString())}. ${color.yellow(c.hash.slice(0, 7))} ${c.message}`);
840
844
  });
841
-
845
+
842
846
  if (log.all.length >= limit) {
843
847
  console.log(color.dim(`\n... showing last ${limit} commits (use --limit to see more)`));
844
848
  }
845
- } catch (e) {
846
- handleError('List error', e);
849
+ } catch (e) {
850
+ handleError('List error', e);
847
851
  }
848
852
  });
849
853
 
@@ -856,22 +860,22 @@ program.command('largelist').alias('ll')
856
860
  const limit = parseInt(cmdOptions.limit) || 20;
857
861
  const log = await git.log({ maxCount: limit });
858
862
  const commits = [...log.all].reverse();
859
-
863
+
860
864
  if (commits.length === 0) {
861
865
  Progress.info('No commits found');
862
866
  return;
863
867
  }
864
-
868
+
865
869
  commits.forEach((c, i) => {
866
870
  const date = new Date(c.date).toLocaleString();
867
- console.log(`${color.cyan((i+1).toString())}. ${color.yellow(c.hash.slice(0,7))} | ${color.dim(date)} | ${color.green(c.author_name)} → ${c.message}`);
871
+ console.log(`${color.cyan((i + 1).toString())}. ${color.yellow(c.hash.slice(0, 7))} | ${color.dim(date)} | ${color.green(c.author_name)} → ${c.message}`);
868
872
  });
869
-
873
+
870
874
  if (log.all.length >= limit) {
871
875
  console.log(color.dim(`\n... showing last ${limit} commits (use --limit to see more)`));
872
876
  }
873
- } catch (e) {
874
- handleError('Largelist error', e);
877
+ } catch (e) {
878
+ handleError('Largelist error', e);
875
879
  }
876
880
  });
877
881
 
@@ -884,21 +888,21 @@ program.command('history').alias('h')
884
888
  const limit = parseInt(cmdOptions.limit) || 20;
885
889
  const log = await git.log({ maxCount: limit });
886
890
  const commits = [...log.all].reverse();
887
-
891
+
888
892
  if (commits.length === 0) {
889
893
  Progress.info('No commits found');
890
894
  return;
891
895
  }
892
-
896
+
893
897
  commits.forEach((c, i) => {
894
- console.log(`${color.cyan((i+1).toString())}. ${color.yellow(c.hash.slice(0,7))} ${c.message}`);
898
+ console.log(`${color.cyan((i + 1).toString())}. ${color.yellow(c.hash.slice(0, 7))} ${c.message}`);
895
899
  });
896
-
900
+
897
901
  if (log.all.length >= limit) {
898
902
  console.log(color.dim(`\n... showing last ${limit} commits (use --limit to see more)`));
899
903
  }
900
- } catch (e) {
901
- handleError('History error', e);
904
+ } catch (e) {
905
+ handleError('History error', e);
902
906
  }
903
907
  });
904
908
 
@@ -906,18 +910,18 @@ program.command('branch <c> [name]').alias('b')
906
910
  .description('Branch from commit/index')
907
911
  .action(async (c, name) => {
908
912
  await ensureRepo();
909
- try { const sha = await resolveCommit(c); const br = name || `branch-${sha.slice(0,7)}`; await git.checkout(['-b', br, sha]); console.log(`Switched to branch ${br} at ${sha}`); }
913
+ try { const sha = await resolveCommit(c); const br = name || `branch-${sha.slice(0, 7)}`; await git.checkout(['-b', br, sha]); console.log(`Switched to branch ${br} at ${sha}`); }
910
914
  catch (e) { handleError('Branch error', e); }
911
915
  });
912
916
 
913
- program.command('reset <c>').alias('r')
917
+ program.command('reset <c>').alias('rs')
914
918
  .description('Reset branch to commit/index')
915
- .option('--hard','hard reset')
919
+ .option('--hard', 'hard reset')
916
920
  .action(async (c, optsCmd) => {
917
921
  await ensureRepo();
918
922
  try {
919
923
  const sha = await resolveCommit(c);
920
- const mode = optsCmd.hard? '--hard':'--soft';
924
+ const mode = optsCmd.hard ? '--hard' : '--soft';
921
925
  const opts = getOpts();
922
926
  if (!opts.yes) {
923
927
  console.log(color.yellow(`About to run: git reset ${mode} ${sha}. Use --yes to confirm.`));
@@ -957,11 +961,11 @@ program.command('undo').alias('u')
957
961
  Progress.warning('No commits to undo');
958
962
  return;
959
963
  }
960
-
964
+
961
965
  const lastCommit = all[0];
962
966
  const mode = cmd.hard ? '--hard' : '--soft';
963
967
  const opts = getOpts();
964
-
968
+
965
969
  if (!opts.yes) {
966
970
  console.log(color.yellow(`About to undo: "${lastCommit.message}"`));
967
971
  console.log(color.yellow(`This will run: git reset ${mode} HEAD~1`));
@@ -971,16 +975,743 @@ program.command('undo').alias('u')
971
975
  console.log('Use --yes to confirm.');
972
976
  process.exit(1);
973
977
  }
974
-
978
+
975
979
  await git.raw(['reset', mode, 'HEAD~1']);
976
980
  Progress.success(`Undone commit: "${lastCommit.message}" (${mode} reset)`);
977
-
981
+
978
982
  if (mode === '--soft') {
979
983
  Progress.info('Changes are now staged. Use "g status" to see them.');
980
984
  }
981
- } catch (e) {
982
- handleError('Undo error', e);
985
+ } catch (e) {
986
+ handleError('Undo error', e);
987
+ }
988
+ });
989
+
990
+ // ===== NEW INTELLIGENT COMMANDS =====
991
+
992
+ program.command('wip')
993
+ .description('Quick work-in-progress commit')
994
+ .action(async () => {
995
+ await ensureRepo();
996
+ try {
997
+ const status = await git.status();
998
+ if (status.files.length === 0) {
999
+ Progress.warning('No changes to commit');
1000
+ return;
1001
+ }
1002
+
1003
+ Progress.info('Staging all changes...');
1004
+ await git.add('.');
1005
+
1006
+ const fileCount = status.files.length;
1007
+ const message = `WIP: ${fileCount} file${fileCount > 1 ? 's' : ''} changed`;
1008
+
1009
+ await git.commit(message);
1010
+ Progress.success(`Committed: "${message}"`);
1011
+ Progress.tip('Use `g a` to amend this commit when ready, or `g undo` to undo');
1012
+ } catch (e) {
1013
+ handleError('WIP error', e);
1014
+ }
1015
+ });
1016
+
1017
+ program.command('today').alias('t')
1018
+ .description('Show commits made today')
1019
+ .action(async () => {
1020
+ await ensureRepo();
1021
+ try {
1022
+ const commits = await gitAnalyzer.getTodayCommits();
1023
+
1024
+ if (commits.length === 0) {
1025
+ console.log(color.dim('No commits today yet.'));
1026
+ Progress.tip('Start your day with `g o` to commit and push!');
1027
+ return;
1028
+ }
1029
+
1030
+ console.log(color.bold(`📅 Today's Commits (${commits.length})\n`));
1031
+ commits.forEach((commit, i) => {
1032
+ console.log(gitAnalyzer.formatCommit(commit, i));
1033
+ });
1034
+ } catch (e) {
1035
+ handleError('Today error', e);
1036
+ }
1037
+ });
1038
+
1039
+ program.command('stats')
1040
+ .description('Your personal commit statistics')
1041
+ .option('--days <n>', 'Number of days to analyze', '30')
1042
+ .action(async (cmdOptions) => {
1043
+ await ensureRepo();
1044
+ try {
1045
+ const days = parseInt(cmdOptions.days) || 30;
1046
+ const intelligence = new Intelligence(git);
1047
+
1048
+ Progress.start('📊 Analyzing your commit history');
1049
+ const stats = await intelligence.getCommitStats(days);
1050
+ const patterns = await intelligence.analyzeCommitPatterns();
1051
+ Progress.stop('');
1052
+
1053
+ if (!stats.hasData) {
1054
+ Progress.info('Not enough commit history to analyze');
1055
+ return;
1056
+ }
1057
+
1058
+ console.log(color.bold(`\n📊 Your Git Stats (last ${days} days)\n`));
1059
+
1060
+ // Overview
1061
+ console.log(color.cyan('Overview:'));
1062
+ console.log(` Total commits: ${color.bold(stats.totalCommits.toString())}`);
1063
+ console.log(` Days active: ${stats.daysActive}`);
1064
+ console.log(` Average: ${stats.avgPerDay} commits/day`);
1065
+ console.log(` Current streak: ${color.green(stats.currentStreak + ' days')}`);
1066
+ if (stats.longestStreak > stats.currentStreak) {
1067
+ console.log(` Longest streak: ${stats.longestStreak} days`);
1068
+ }
1069
+
1070
+ // Commit types breakdown
1071
+ if (patterns.usesConventional) {
1072
+ console.log(`\n${color.cyan('Commit Types:')}`);
1073
+ const types = stats.typeBreakdown;
1074
+ const total = Object.values(types).reduce((a, b) => a + b, 0);
1075
+ Object.entries(types).forEach(([type, count]) => {
1076
+ if (count > 0) {
1077
+ const pct = Math.round(count / total * 100);
1078
+ const bar = '█'.repeat(Math.ceil(pct / 5)) + '░'.repeat(20 - Math.ceil(pct / 5));
1079
+ console.log(` ${type.padEnd(8)} ${bar} ${pct}%`);
1080
+ }
1081
+ });
1082
+ }
1083
+
1084
+ // Style insights
1085
+ if (patterns.hasHistory) {
1086
+ console.log(`\n${color.cyan('Your Style:')}`);
1087
+ console.log(` Conventional commits: ${patterns.conventionalRatio}%`);
1088
+ console.log(` Message style: ${patterns.style}`);
1089
+ console.log(` Avg message length: ${patterns.avgMessageLength} chars`);
1090
+ if (patterns.topScopes.length > 0) {
1091
+ console.log(` Common scopes: ${patterns.topScopes.join(', ')}`);
1092
+ }
1093
+ }
1094
+
1095
+ Progress.showRandomTip();
1096
+ } catch (e) {
1097
+ handleError('Stats error', e);
1098
+ }
1099
+ });
1100
+
1101
+ program.command('review').alias('r')
1102
+ .description('AI code review before committing')
1103
+ .action(async () => {
1104
+ await ensureRepo();
1105
+ const opts = getOpts();
1106
+ try {
1107
+ let diff = await git.diff(['--cached', '--no-ext-diff']);
1108
+
1109
+ if (!diff.trim()) {
1110
+ // Try unstaged changes
1111
+ diff = await git.diff(['--no-ext-diff']);
1112
+ if (!diff.trim()) {
1113
+ Progress.warning('No changes to review');
1114
+ return;
1115
+ }
1116
+ console.log(color.dim('(Reviewing unstaged changes)\n'));
1117
+ } else {
1118
+ console.log(color.dim('(Reviewing staged changes)\n'));
1119
+ }
1120
+
1121
+ const intelligence = new Intelligence(git);
1122
+
1123
+ // Analyze complexity
1124
+ const complexity = await gitAnalyzer.getChangeComplexity(diff);
1125
+ console.log(color.bold('📋 Change Summary'));
1126
+ console.log(` Complexity: ${complexity.emoji} ${complexity.complexity}`);
1127
+ console.log(` Files: ${complexity.files}`);
1128
+ console.log(` Changes: ${color.green('+' + complexity.additions)} ${color.red('-' + complexity.deletions)}`);
1129
+
1130
+ // Detect semantic changes
1131
+ const semantic = await intelligence.detectSemanticChanges(diff);
1132
+ if (semantic.labels.length > 0) {
1133
+ console.log(`\n${color.bold('🔍 Detected Patterns')}`);
1134
+ semantic.labels.forEach(label => {
1135
+ console.log(` ${label}`);
1136
+ });
1137
+ }
1138
+
1139
+ // Get AI suggestions
1140
+ if (opts.progressIndicators) Progress.start('🤖 Generating AI review');
1141
+ const message = await aiProvider.generateCommitMessage(diff, { ...opts, body: true });
1142
+ if (opts.progressIndicators) Progress.stop('');
1143
+
1144
+ console.log(`\n${color.bold('💬 Suggested Commit Message')}`);
1145
+ console.log(` ${color.green(message.message || message)}`);
1146
+
1147
+ // Actionable next steps
1148
+ console.log(`\n${color.bold('📌 Next Steps')}`);
1149
+ console.log(` ${color.cyan('g o')} Commit and push with AI message`);
1150
+ console.log(` ${color.cyan('g l')} Commit locally with AI message`);
1151
+ console.log(` ${color.cyan('g int')} Interactive commit with options`);
1152
+
1153
+ } catch (e) {
1154
+ handleError('Review error', e);
1155
+ }
1156
+ });
1157
+
1158
+ program.command('split')
1159
+ .description('Suggest how to split a large changeset')
1160
+ .action(async () => {
1161
+ await ensureRepo();
1162
+ try {
1163
+ const status = await git.status();
1164
+
1165
+ if (status.files.length === 0) {
1166
+ Progress.info('No changes to split');
1167
+ return;
1168
+ }
1169
+
1170
+ if (status.files.length < 5) {
1171
+ Progress.info('Changeset is small enough - no need to split');
1172
+ console.log(`\nYou have ${status.files.length} file${status.files.length > 1 ? 's' : ''} changed.`);
1173
+ console.log(`Use ${color.cyan('g o')} to commit them all at once.`);
1174
+ return;
1175
+ }
1176
+
1177
+ const intelligence = new Intelligence(git);
1178
+ const suggestions = await intelligence.suggestCommitSplit(status);
1179
+
1180
+ if (!suggestions) {
1181
+ Progress.info('All changes look related - commit them together');
1182
+ return;
1183
+ }
1184
+
1185
+ console.log(color.bold(`\n📦 Suggested Commit Split\n`));
1186
+ console.log(color.dim(`Your ${status.files.length} files could be split into ${suggestions.length} commits:\n`));
1187
+
1188
+ suggestions.forEach((group, i) => {
1189
+ console.log(`${color.cyan((i + 1).toString())}. ${color.bold(group.message)}`);
1190
+ group.files.slice(0, 5).forEach(file => {
1191
+ const emoji = Intelligence.getFileEmoji(file);
1192
+ console.log(` ${emoji} ${file}`);
1193
+ });
1194
+ if (group.files.length > 5) {
1195
+ console.log(color.dim(` ... and ${group.files.length - 5} more`));
1196
+ }
1197
+ console.log();
1198
+ });
1199
+
1200
+ console.log(color.bold('💡 How to split:'));
1201
+ console.log(` 1. Stage specific files: ${color.cyan('git add <file>')}`);
1202
+ console.log(` 2. Commit them: ${color.cyan('g l')} or ${color.cyan('g o')}`);
1203
+ console.log(` 3. Repeat for remaining files`);
1204
+
1205
+ } catch (e) {
1206
+ handleError('Split error', e);
1207
+ }
1208
+ });
1209
+
1210
+ // ===== WORKFLOW SHORTHAND COMMANDS =====
1211
+
1212
+ program.command('safe-pull').alias('sp')
1213
+ .description('Safe pull: stash → pull → stash pop')
1214
+ .action(async () => {
1215
+ await ensureRepo();
1216
+ try {
1217
+ const status = await git.status();
1218
+ const hasChanges = status.files.length > 0;
1219
+
1220
+ if (hasChanges) {
1221
+ Progress.info('Stashing changes...');
1222
+ await git.stash(['push', '-m', 'GIMS: auto-stash before pull']);
1223
+ }
1224
+
1225
+ Progress.info('Pulling latest changes...');
1226
+ await git.pull();
1227
+
1228
+ if (hasChanges) {
1229
+ Progress.info('Restoring stashed changes...');
1230
+ await git.stash(['pop']);
1231
+ }
1232
+
1233
+ Progress.success('Safe pull complete');
1234
+ } catch (e) {
1235
+ handleError('Safe pull error', e);
1236
+ }
1237
+ });
1238
+
1239
+ program.command('main')
1240
+ .description('Switch to main/master and pull latest')
1241
+ .action(async () => {
1242
+ await ensureRepo();
1243
+ try {
1244
+ // Detect main branch name
1245
+ let mainBranch = 'main';
1246
+ try {
1247
+ await git.raw(['rev-parse', '--verify', 'main']);
1248
+ } catch {
1249
+ try {
1250
+ await git.raw(['rev-parse', '--verify', 'master']);
1251
+ mainBranch = 'master';
1252
+ } catch {
1253
+ Progress.error('No main or master branch found');
1254
+ return;
1255
+ }
1256
+ }
1257
+
1258
+ const status = await git.status();
1259
+ if (status.files.length > 0) {
1260
+ Progress.warning('You have uncommitted changes. Commit or stash them first.');
1261
+ console.log(`Tip: Use ${color.cyan('g sp')} to safe-pull with auto-stash`);
1262
+ return;
1263
+ }
1264
+
1265
+ Progress.info(`Switching to ${mainBranch}...`);
1266
+ await git.checkout(mainBranch);
1267
+
1268
+ Progress.info('Pulling latest...');
1269
+ await git.pull();
1270
+
1271
+ Progress.success(`On ${mainBranch} with latest changes`);
1272
+ } catch (e) {
1273
+ handleError('Main error', e);
1274
+ }
1275
+ });
1276
+
1277
+ program.command('unstage').alias('us')
1278
+ .description('Unstage all staged files')
1279
+ .action(async () => {
1280
+ await ensureRepo();
1281
+ try {
1282
+ const status = await git.status();
1283
+ if (status.staged.length === 0) {
1284
+ Progress.info('Nothing is staged');
1285
+ return;
1286
+ }
1287
+
1288
+ await git.reset([]);
1289
+ Progress.success(`Unstaged ${status.staged.length} file${status.staged.length > 1 ? 's' : ''}`);
1290
+ } catch (e) {
1291
+ handleError('Unstage error', e);
1292
+ }
1293
+ });
1294
+
1295
+ program.command('discard').alias('x')
1296
+ .description('Discard all changes (with confirmation)')
1297
+ .action(async () => {
1298
+ await ensureRepo();
1299
+ const opts = getOpts();
1300
+ try {
1301
+ const status = await git.status();
1302
+ if (status.files.length === 0) {
1303
+ Progress.info('No changes to discard');
1304
+ return;
1305
+ }
1306
+
1307
+ if (!opts.yes) {
1308
+ console.log(color.yellow(`⚠️ About to discard ALL changes in ${status.files.length} file(s)`));
1309
+ console.log(color.red('This cannot be undone!'));
1310
+ console.log('Use --yes to confirm.');
1311
+ return;
1312
+ }
1313
+
1314
+ // Discard tracked file changes
1315
+ await git.checkout(['--', '.']);
1316
+
1317
+ // Remove untracked files
1318
+ if (status.not_added.length > 0) {
1319
+ await git.clean('fd');
1320
+ }
1321
+
1322
+ Progress.success('All changes discarded');
1323
+ } catch (e) {
1324
+ handleError('Discard error', e);
1325
+ }
1326
+ });
1327
+
1328
+ program.command('stash-save').alias('ss')
1329
+ .description('Quick stash: stage all and stash')
1330
+ .action(async () => {
1331
+ await ensureRepo();
1332
+ try {
1333
+ const status = await git.status();
1334
+ if (status.files.length === 0) {
1335
+ Progress.info('No changes to stash');
1336
+ return;
1337
+ }
1338
+
1339
+ await git.add('.');
1340
+
1341
+ // Generate a descriptive stash name
1342
+ const fileCount = status.files.length;
1343
+ const timestamp = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
1344
+ const message = `WIP: ${fileCount} file${fileCount > 1 ? 's' : ''} at ${timestamp}`;
1345
+
1346
+ await git.stash(['push', '-m', message]);
1347
+ Progress.success(`Stashed: "${message}"`);
1348
+ } catch (e) {
1349
+ handleError('Stash save error', e);
1350
+ }
1351
+ });
1352
+
1353
+ program.command('stash-pop').alias('pop')
1354
+ .description('Pop the latest stash')
1355
+ .action(async () => {
1356
+ await ensureRepo();
1357
+ try {
1358
+ const stashList = await git.stashList();
1359
+ if (stashList.all.length === 0) {
1360
+ Progress.info('No stashes to pop');
1361
+ return;
1362
+ }
1363
+
1364
+ const latestStash = stashList.all[0];
1365
+ await git.stash(['pop']);
1366
+ Progress.success(`Popped: "${latestStash.message}"`);
1367
+ } catch (e) {
1368
+ handleError('Stash pop error', e);
1369
+ }
1370
+ });
1371
+
1372
+ program.command('delete-branch').alias('del')
1373
+ .description('Delete branch locally and remotely')
1374
+ .argument('<branch>', 'Branch name to delete')
1375
+ .action(async (branch) => {
1376
+ await ensureRepo();
1377
+ const opts = getOpts();
1378
+ try {
1379
+ const current = (await git.branch()).current;
1380
+ if (branch === current) {
1381
+ Progress.error(`Cannot delete current branch. Switch to another branch first.`);
1382
+ return;
1383
+ }
1384
+
1385
+ if (branch === 'main' || branch === 'master') {
1386
+ Progress.error('Cannot delete main/master branch');
1387
+ return;
1388
+ }
1389
+
1390
+ if (!opts.yes) {
1391
+ console.log(color.yellow(`⚠️ About to delete branch: ${branch}`));
1392
+ console.log('This will delete both local and remote copies.');
1393
+ console.log('Use --yes to confirm.');
1394
+ return;
1395
+ }
1396
+
1397
+ // Delete local
1398
+ Progress.info('Deleting local branch...');
1399
+ try {
1400
+ await git.branch(['-D', branch]);
1401
+ } catch (e) {
1402
+ Progress.warning(`Local branch not found or already deleted`);
1403
+ }
1404
+
1405
+ // Delete remote
1406
+ Progress.info('Deleting remote branch...');
1407
+ try {
1408
+ await git.push(['origin', '--delete', branch]);
1409
+ } catch (e) {
1410
+ Progress.warning(`Remote branch not found or already deleted`);
1411
+ }
1412
+
1413
+ Progress.success(`Deleted branch: ${branch}`);
1414
+ } catch (e) {
1415
+ handleError('Delete branch error', e);
1416
+ }
1417
+ });
1418
+
1419
+ program.command('cleanup').alias('clean')
1420
+ .description('Remove local branches that no longer exist on remote')
1421
+ .action(async () => {
1422
+ await ensureRepo();
1423
+ const opts = getOpts();
1424
+ try {
1425
+ Progress.info('Fetching and pruning...');
1426
+ await git.fetch(['--prune']);
1427
+
1428
+ // Find gone branches
1429
+ const branchOutput = await git.raw(['branch', '-vv']);
1430
+ const goneBranches = branchOutput
1431
+ .split('\n')
1432
+ .filter(line => line.includes(': gone]'))
1433
+ .map(line => line.trim().split(/\s+/)[0].replace('*', '').trim())
1434
+ .filter(b => b && b !== 'main' && b !== 'master');
1435
+
1436
+ if (goneBranches.length === 0) {
1437
+ Progress.success('No dead branches to clean up');
1438
+ return;
1439
+ }
1440
+
1441
+ console.log(color.bold(`\nFound ${goneBranches.length} dead branch(es):`));
1442
+ goneBranches.forEach(b => console.log(` ${color.dim('•')} ${b}`));
1443
+
1444
+ if (!opts.yes) {
1445
+ console.log(`\nUse ${color.cyan('g clean --yes')} to delete them`);
1446
+ return;
1447
+ }
1448
+
1449
+ for (const branch of goneBranches) {
1450
+ try {
1451
+ await git.branch(['-D', branch]);
1452
+ Progress.success(`Deleted: ${branch}`);
1453
+ } catch (e) {
1454
+ Progress.warning(`Could not delete: ${branch}`);
1455
+ }
1456
+ }
1457
+
1458
+ Progress.success(`Cleaned up ${goneBranches.length} branch(es)`);
1459
+ } catch (e) {
1460
+ handleError('Cleanup error', e);
1461
+ }
1462
+ });
1463
+
1464
+ program.command('last')
1465
+ .description('Show last commit details and diff')
1466
+ .action(async () => {
1467
+ await ensureRepo();
1468
+ try {
1469
+ const log = await git.log({ maxCount: 1 });
1470
+ if (log.all.length === 0) {
1471
+ Progress.info('No commits yet');
1472
+ return;
1473
+ }
1474
+
1475
+ const commit = log.all[0];
1476
+ console.log(color.bold('\n📝 Last Commit\n'));
1477
+ console.log(` ${color.yellow(commit.hash.substring(0, 7))} ${commit.message.split('\n')[0]}`);
1478
+ console.log(` ${color.dim(`by ${commit.author_name} • ${new Date(commit.date).toLocaleString()}`)}`);
1479
+
1480
+ // Show diff stats
1481
+ const diff = await git.diff(['HEAD~1', '--stat']);
1482
+ if (diff.trim()) {
1483
+ console.log(`\n${color.bold('Changes:')}`);
1484
+ console.log(color.dim(diff));
1485
+ }
1486
+ } catch (e) {
1487
+ handleError('Last error', e);
1488
+ }
1489
+ });
1490
+
1491
+ // ===== SMART SYNC FIX COMMAND =====
1492
+
1493
+ program.command('fix')
1494
+ .description('Smart fix for branch sync issues (diverged, behind, ahead)')
1495
+ .option('--local', 'Keep local changes, force push to remote')
1496
+ .option('--remote', 'Keep remote changes, discard local')
1497
+ .option('--merge', 'Merge remote into local')
1498
+ .option('--rebase', 'Rebase local onto remote')
1499
+ .option('--ai', 'Get AI recommendation for best approach')
1500
+ .action(async (cmdOptions) => {
1501
+ await ensureRepo();
1502
+ const opts = getOpts();
1503
+
1504
+ try {
1505
+ // Fetch latest
1506
+ Progress.info('Fetching remote status...');
1507
+ await git.fetch();
1508
+
1509
+ const branch = (await git.branch()).current;
1510
+ const remoteBranch = `origin/${branch}`;
1511
+
1512
+ // Check if remote exists
1513
+ let remoteExists = true;
1514
+ try {
1515
+ await git.raw(['rev-parse', '--verify', remoteBranch]);
1516
+ } catch {
1517
+ remoteExists = false;
1518
+ }
1519
+
1520
+ if (!remoteExists) {
1521
+ console.log(color.bold(`\n📍 Branch Status: ${color.cyan(branch)}\n`));
1522
+ console.log(`Remote branch ${color.yellow(remoteBranch)} doesn't exist yet.`);
1523
+ console.log(`\nOptions:`);
1524
+ console.log(` ${color.cyan('g push --set-upstream')} Push and create remote branch`);
1525
+ return;
1526
+ }
1527
+
1528
+ // Get ahead/behind counts
1529
+ const ahead = parseInt((await git.raw(['rev-list', '--count', `${remoteBranch}..${branch}`])).trim());
1530
+ const behind = parseInt((await git.raw(['rev-list', '--count', `${branch}..${remoteBranch}`])).trim());
1531
+
1532
+ // Get local changes status
1533
+ const status = await git.status();
1534
+ const hasLocalChanges = status.files.length > 0;
1535
+
1536
+ console.log(color.bold(`\n📍 Branch Status: ${color.cyan(branch)}\n`));
1537
+
1538
+ // Determine situation
1539
+ let situation = '';
1540
+ if (ahead === 0 && behind === 0) {
1541
+ Progress.success('Branch is up to date with remote!');
1542
+ if (hasLocalChanges) {
1543
+ console.log(`\nYou have ${status.files.length} uncommitted change(s).`);
1544
+ console.log(`Use ${color.cyan('g o')} to commit and push them.`);
1545
+ }
1546
+ return;
1547
+ } else if (ahead > 0 && behind === 0) {
1548
+ situation = 'ahead';
1549
+ console.log(` ${color.green('↑')} ${ahead} commit(s) ahead of remote`);
1550
+ console.log(` ${color.dim('Your local has commits not on remote')}\n`);
1551
+ } else if (ahead === 0 && behind > 0) {
1552
+ situation = 'behind';
1553
+ console.log(` ${color.yellow('↓')} ${behind} commit(s) behind remote`);
1554
+ console.log(` ${color.dim('Remote has commits you don\'t have')}\n`);
1555
+ } else {
1556
+ situation = 'diverged';
1557
+ console.log(` ${color.red('⚡')} Branch has diverged!`);
1558
+ console.log(` ${color.green('↑')} ${ahead} commit(s) ahead`);
1559
+ console.log(` ${color.yellow('↓')} ${behind} commit(s) behind\n`);
1560
+ }
1561
+
1562
+ if (hasLocalChanges) {
1563
+ console.log(color.yellow(`⚠️ You have ${status.files.length} uncommitted file(s)`));
1564
+ console.log(`${color.dim('Commit or stash them before fixing sync issues')}\n`);
1565
+ }
1566
+
1567
+ // If specific option provided, execute it
1568
+ if (cmdOptions.local) {
1569
+ if (!opts.yes) {
1570
+ console.log(color.red('⚠️ This will FORCE PUSH and overwrite remote!'));
1571
+ console.log(`Use ${color.cyan('g fix --local --yes')} to confirm.`);
1572
+ return;
1573
+ }
1574
+ Progress.info('Force pushing local to remote...');
1575
+ await git.push(['--force']);
1576
+ Progress.success('Force pushed! Remote now matches local.');
1577
+ return;
1578
+ }
1579
+
1580
+ if (cmdOptions.remote) {
1581
+ if (!opts.yes) {
1582
+ console.log(color.red('⚠️ This will DISCARD local commits!'));
1583
+ console.log(`Use ${color.cyan('g fix --remote --yes')} to confirm.`);
1584
+ return;
1585
+ }
1586
+ Progress.info('Resetting to remote...');
1587
+ await git.reset(['--hard', remoteBranch]);
1588
+ Progress.success('Reset! Local now matches remote.');
1589
+ return;
1590
+ }
1591
+
1592
+ if (cmdOptions.merge) {
1593
+ Progress.info('Merging remote into local...');
1594
+ try {
1595
+ await git.merge([remoteBranch]);
1596
+ Progress.success('Merged successfully!');
1597
+ } catch (e) {
1598
+ Progress.error('Merge conflict! Resolve conflicts then run: g o');
1599
+ }
1600
+ return;
1601
+ }
1602
+
1603
+ if (cmdOptions.rebase) {
1604
+ Progress.info('Rebasing local onto remote...');
1605
+ try {
1606
+ await git.rebase([remoteBranch]);
1607
+ Progress.success('Rebased successfully!');
1608
+ console.log(`Now run ${color.cyan('g push --force')} to update remote.`);
1609
+ } catch (e) {
1610
+ Progress.error('Rebase conflict! Resolve conflicts then run: git rebase --continue');
1611
+ }
1612
+ return;
1613
+ }
1614
+
1615
+ // AI recommendation
1616
+ if (cmdOptions.ai) {
1617
+ Progress.info('Analyzing best approach...');
1618
+ let recommendation = '';
1619
+
1620
+ if (situation === 'ahead') {
1621
+ recommendation = `Your local is ${ahead} commits ahead. Simply push to sync.`;
1622
+ console.log(`\n${color.bold('🤖 AI Recommendation:')}`);
1623
+ console.log(` ${recommendation}`);
1624
+ console.log(`\n Run: ${color.cyan('g push')}`);
1625
+ } else if (situation === 'behind') {
1626
+ recommendation = `Remote has ${behind} new commits. Pull to get them.`;
1627
+ console.log(`\n${color.bold('🤖 AI Recommendation:')}`);
1628
+ console.log(` ${recommendation}`);
1629
+ console.log(`\n Run: ${color.cyan('g pull')} or ${color.cyan('g sp')} (if you have changes)`);
1630
+ } else {
1631
+ // Diverged - more complex
1632
+ if (ahead <= 2 && behind > ahead) {
1633
+ recommendation = `Small local changes (${ahead}), larger remote (${behind}). Rebase recommended for clean history.`;
1634
+ console.log(`\n${color.bold('🤖 AI Recommendation:')}`);
1635
+ console.log(` ${recommendation}`);
1636
+ console.log(`\n Run: ${color.cyan('g fix --rebase')}`);
1637
+ } else if (behind <= 2 && ahead > behind) {
1638
+ recommendation = `Large local changes (${ahead}), small remote (${behind}). Merge is safe.`;
1639
+ console.log(`\n${color.bold('🤖 AI Recommendation:')}`);
1640
+ console.log(` ${recommendation}`);
1641
+ console.log(`\n Run: ${color.cyan('g fix --merge')}`);
1642
+ } else {
1643
+ recommendation = `Significant divergence. Review changes first, then choose merge or rebase.`;
1644
+ console.log(`\n${color.bold('🤖 AI Recommendation:')}`);
1645
+ console.log(` ${recommendation}`);
1646
+ console.log(`\n View remote changes: ${color.cyan(`git log ${branch}..${remoteBranch} --oneline`)}`);
1647
+ console.log(` View local changes: ${color.cyan(`git log ${remoteBranch}..${branch} --oneline`)}`);
1648
+ }
1649
+ }
1650
+ return;
1651
+ }
1652
+
1653
+ // Show interactive menu
1654
+ console.log(color.bold('🔧 Fix Options:\n'));
1655
+
1656
+ if (situation === 'ahead') {
1657
+ console.log(` ${color.cyan('g push')} Push your commits to remote`);
1658
+ } else if (situation === 'behind') {
1659
+ console.log(` ${color.cyan('g pull')} Get remote commits (fast-forward)`);
1660
+ console.log(` ${color.cyan('g sp')} Safe pull (stash → pull → pop)`);
1661
+ } else {
1662
+ // Diverged
1663
+ console.log(` ${color.cyan('g fix --merge')} Merge remote into local (creates merge commit)`);
1664
+ console.log(` ${color.cyan('g fix --rebase')} Rebase local onto remote (linear history)`);
1665
+ console.log(color.dim(' ─────────────────'));
1666
+ console.log(` ${color.cyan('g fix --local')} ${color.yellow('⚠')} Force push local, overwrite remote`);
1667
+ console.log(` ${color.cyan('g fix --remote')} ${color.yellow('⚠')} Reset to remote, discard local commits`);
1668
+ }
1669
+
1670
+ console.log(color.dim(' ─────────────────'));
1671
+ console.log(` ${color.cyan('g fix --ai')} Get AI recommendation`);
1672
+
1673
+ } catch (e) {
1674
+ handleError('Fix error', e);
1675
+ }
1676
+ });
1677
+
1678
+ program.command('conflicts')
1679
+ .description('Show and help resolve merge conflicts')
1680
+ .action(async () => {
1681
+ await ensureRepo();
1682
+ try {
1683
+ const status = await git.status();
1684
+ const conflicts = status.conflicted || [];
1685
+
1686
+ if (conflicts.length === 0) {
1687
+ Progress.success('No merge conflicts!');
1688
+ return;
1689
+ }
1690
+
1691
+ console.log(color.bold(`\n⚠️ ${conflicts.length} Conflicted File(s):\n`));
1692
+ conflicts.forEach((file, i) => {
1693
+ const emoji = Intelligence.getFileEmoji(file);
1694
+ console.log(` ${i + 1}. ${emoji} ${color.red(file)}`);
1695
+ });
1696
+
1697
+ console.log(color.bold('\n🔧 How to resolve:\n'));
1698
+ console.log(` 1. Edit each file and resolve the conflict markers`);
1699
+ console.log(` ${color.dim('<<<<<<< HEAD')}`);
1700
+ console.log(` ${color.dim('your changes')}`);
1701
+ console.log(` ${color.dim('=======')}`);
1702
+ console.log(` ${color.dim('their changes')}`);
1703
+ console.log(` ${color.dim('>>>>>>> branch')}`);
1704
+ console.log(` 2. Stage resolved files: ${color.cyan('git add <file>')}`);
1705
+ console.log(` 3. Complete the merge: ${color.cyan('g o')} or ${color.cyan('git merge --continue')}`);
1706
+
1707
+ console.log(color.bold('\n⚡ Quick options:\n'));
1708
+ console.log(` ${color.cyan('git checkout --ours <file>')} Keep YOUR version`);
1709
+ console.log(` ${color.cyan('git checkout --theirs <file>')} Keep THEIR version`);
1710
+
1711
+ } catch (e) {
1712
+ handleError('Conflicts error', e);
983
1713
  }
984
1714
  });
985
1715
 
986
1716
  program.parse(process.argv);
1717
+