@eldrforge/commands-git 0.1.3 → 0.1.5

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/index.js CHANGED
@@ -3,7 +3,7 @@ import 'dotenv/config';
3
3
  import shellescape from 'shell-escape';
4
4
  import { getDryRunLogger, DEFAULT_MAX_DIFF_BYTES, DEFAULT_EXCLUDED_PATTERNS, Diff, Files, Log, DEFAULT_OUTPUT_DIRECTORY, sanitizeDirection, toAIConfig, createStorageAdapter, createLoggerAdapter, getOutputPath, getTimestampedResponseFilename, getTimestampedRequestFilename, filterContent, getTimestampedCommitFilename, improveContentWithLLM, getLogger, getTimestampedReviewNotesFilename, getTimestampedReviewFilename } from '@eldrforge/core';
5
5
  import { ValidationError, ExternalDependencyError, CommandError, createStorage, checkForFileDependencies as checkForFileDependencies$1, logFileDependencyWarning, logFileDependencySuggestions, FileOperationError } from '@eldrforge/shared';
6
- import { run, validateString, safeJsonParse, validatePackageJson, unstageAll, stageFiles, verifyStagedFiles, runSecure } from '@eldrforge/git-tools';
6
+ import { run, validateString, safeJsonParse, validatePackageJson, unstageAll, stageFiles, verifyStagedFiles, runSecure, getCurrentBranch, getGitStatusSummary } from '@eldrforge/git-tools';
7
7
  import { getRecentClosedIssuesForCommit, handleIssueCreation, getReleaseNotesContent, getIssuesContent } from '@eldrforge/github-tools';
8
8
  import { runAgenticCommit, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createCommitPrompt, createReviewPrompt, createCompletion } from '@eldrforge/ai-service';
9
9
  import path from 'path';
@@ -311,6 +311,41 @@ const saveCommitMessage = async (outputDirectory, summary, storage, logger)=>{
311
311
  }
312
312
  }
313
313
  };
314
+ /**
315
+ * Deduplicate files across splits - each file can only be in one split
316
+ * Later splits lose files that were already claimed by earlier splits
317
+ * Returns filtered splits with empty splits removed
318
+ */ function deduplicateSplits(splits, logger) {
319
+ const claimedFiles = new Set();
320
+ const result = [];
321
+ for (const split of splits){
322
+ // Find files in this split that haven't been claimed yet
323
+ const uniqueFiles = [];
324
+ const duplicates = [];
325
+ for (const file of split.files){
326
+ if (claimedFiles.has(file)) {
327
+ duplicates.push(file);
328
+ } else {
329
+ uniqueFiles.push(file);
330
+ claimedFiles.add(file);
331
+ }
332
+ }
333
+ // Log if duplicates were found
334
+ if (duplicates.length > 0) {
335
+ logger.warn(`Removing duplicate files from split "${split.message.split('\n')[0]}": ${duplicates.join(', ')}`);
336
+ }
337
+ // Only include split if it has files
338
+ if (uniqueFiles.length > 0) {
339
+ result.push({
340
+ ...split,
341
+ files: uniqueFiles
342
+ });
343
+ } else {
344
+ logger.warn(`Skipping empty split after deduplication: "${split.message.split('\n')[0]}"`);
345
+ }
346
+ }
347
+ return result;
348
+ }
314
349
  /**
315
350
  * Interactive review of a single split before committing
316
351
  */ async function reviewSplitInteractively(split, index, total, logger) {
@@ -516,7 +551,7 @@ const saveCommitMessage = async (outputDirectory, summary, storage, logger)=>{
516
551
  lines.push('═'.repeat(80));
517
552
  return lines.join('\n');
518
553
  }
519
- const executeInternal$2 = async (runConfig)=>{
554
+ const executeInternal$3 = async (runConfig)=>{
520
555
  var _ref, _runConfig_excludedPatterns;
521
556
  var _runConfig_commit, _runConfig_commit1, _runConfig_commit2, _runConfig_commit3, _runConfig_commit4, _runConfig_commit5, _runConfig_commit6, _aiConfig_commands_commit, _aiConfig_commands, _runConfig_commit7, _aiConfig_commands_commit1, _aiConfig_commands1, _runConfig_commit8, _runConfig_commit9, _runConfig_commit10, _runConfig_commit11, _runConfig_commit12, _runConfig_commit13, _runConfig_commit14;
522
557
  const isDryRun = runConfig.dryRun || false;
@@ -722,8 +757,14 @@ const executeInternal$2 = async (runConfig)=>{
722
757
  if (autoSplitEnabled) {
723
758
  var _runConfig_commit19, _runConfig_commit20;
724
759
  logger.info('\nšŸ”„ Auto-split enabled - creating separate commits...\n');
760
+ // Deduplicate files across splits to prevent staging errors
761
+ // (AI sometimes suggests the same file in multiple splits)
762
+ const deduplicatedSplits = deduplicateSplits(agenticResult.suggestedSplits, logger);
763
+ if (deduplicatedSplits.length === 0) {
764
+ throw new CommandError('All splits were empty after deduplication - no files to commit', 'SPLIT_EMPTY', false);
765
+ }
725
766
  const splitResult = await executeSplitCommits({
726
- splits: agenticResult.suggestedSplits,
767
+ splits: deduplicatedSplits,
727
768
  isDryRun,
728
769
  interactive: !!(((_runConfig_commit19 = runConfig.commit) === null || _runConfig_commit19 === void 0 ? void 0 : _runConfig_commit19.interactive) && !((_runConfig_commit20 = runConfig.commit) === null || _runConfig_commit20 === void 0 ? void 0 : _runConfig_commit20.sendit)),
729
770
  logger});
@@ -876,9 +917,9 @@ const executeInternal$2 = async (runConfig)=>{
876
917
  }
877
918
  return summary;
878
919
  };
879
- const execute$3 = async (runConfig)=>{
920
+ const execute$4 = async (runConfig)=>{
880
921
  try {
881
- return await executeInternal$2(runConfig);
922
+ return await executeInternal$3(runConfig);
882
923
  } catch (error) {
883
924
  // Import getLogger for error handling
884
925
  const { getLogger } = await import('@eldrforge/core');
@@ -1502,7 +1543,7 @@ const checkForFileDependencies = (packageJsonFiles)=>{
1502
1543
  * Execute precommit checks: lint -> build -> test
1503
1544
  * Skips clean step (clean should be run separately if needed)
1504
1545
  * Uses optimization to skip steps when unchanged
1505
- */ const execute$2 = async (runConfig)=>{
1546
+ */ const execute$3 = async (runConfig)=>{
1506
1547
  const logger = getLogger();
1507
1548
  const isDryRun = runConfig.dryRun || false;
1508
1549
  const packageDir = process.cwd();
@@ -1588,7 +1629,7 @@ const checkForFileDependencies = (packageJsonFiles)=>{
1588
1629
  }
1589
1630
  };
1590
1631
 
1591
- const executeInternal$1 = async (runConfig)=>{
1632
+ const executeInternal$2 = async (runConfig)=>{
1592
1633
  const isDryRun = runConfig.dryRun || false;
1593
1634
  const logger = getDryRunLogger(isDryRun);
1594
1635
  const storage = createStorage();
@@ -1612,9 +1653,9 @@ const executeInternal$1 = async (runConfig)=>{
1612
1653
  throw new FileOperationError('Failed to remove output directory', outputDirectory, error);
1613
1654
  }
1614
1655
  };
1615
- const execute$1 = async (runConfig)=>{
1656
+ const execute$2 = async (runConfig)=>{
1616
1657
  try {
1617
- await executeInternal$1(runConfig);
1658
+ await executeInternal$2(runConfig);
1618
1659
  } catch (error) {
1619
1660
  const logger = getLogger();
1620
1661
  if (error instanceof FileOperationError) {
@@ -2083,7 +2124,7 @@ const processSingleReview = async (reviewNote, runConfig, outputDirectory)=>{
2083
2124
  }
2084
2125
  return analysisResult;
2085
2126
  };
2086
- const executeInternal = async (runConfig)=>{
2127
+ const executeInternal$1 = async (runConfig)=>{
2087
2128
  var _runConfig_review, _runConfig_review1, _runConfig_review2, _runConfig_review3, _runConfig_review4, _runConfig_review5, _runConfig_review6, _runConfig_review7, _runConfig_review8, _runConfig_review9, _runConfig_review10, _runConfig_review11, _runConfig_review12, _runConfig_review13, _runConfig_review14, _runConfig_review15, _runConfig_review16, _runConfig_review17, _runConfig_review18;
2088
2129
  const logger = getLogger();
2089
2130
  const isDryRun = runConfig.dryRun || false;
@@ -2313,9 +2354,9 @@ const executeInternal = async (runConfig)=>{
2313
2354
  const senditMode = ((_runConfig_review18 = runConfig.review) === null || _runConfig_review18 === void 0 ? void 0 : _runConfig_review18.sendit) || false;
2314
2355
  return await handleIssueCreation(analysisResult, senditMode);
2315
2356
  };
2316
- const execute = async (runConfig)=>{
2357
+ const execute$1 = async (runConfig)=>{
2317
2358
  try {
2318
- return await executeInternal(runConfig);
2359
+ return await executeInternal$1(runConfig);
2319
2360
  } catch (error) {
2320
2361
  const logger = getLogger();
2321
2362
  if (error instanceof ValidationError) {
@@ -2342,5 +2383,585 @@ const execute = async (runConfig)=>{
2342
2383
  }
2343
2384
  };
2344
2385
 
2345
- export { PerformanceTimer, batchReadPackageJsonFiles, checkForFileDependencies, execute$1 as clean, collectAllDependencies, execute$3 as commit, findAllPackageJsonFiles, findPackagesByScope, isCleanNeeded, isTestNeeded, optimizePrecommitCommand, execute$2 as precommit, recordTestRun, execute as review, scanDirectoryForPackages };
2386
+ // Patterns for files that can be auto-resolved
2387
+ const AUTO_RESOLVABLE_PATTERNS = {
2388
+ // Package lock files - just regenerate
2389
+ packageLock: /^package-lock\.json$/,
2390
+ yarnLock: /^yarn\.lock$/,
2391
+ pnpmLock: /^pnpm-lock\.yaml$/,
2392
+ // Generated files - take theirs and regenerate
2393
+ dist: /^dist\//,
2394
+ coverage: /^coverage\//,
2395
+ nodeModules: /^node_modules\//,
2396
+ // Build artifacts
2397
+ buildOutput: /\.(js\.map|d\.ts)$/
2398
+ };
2399
+ /**
2400
+ * Check if a file can be auto-resolved
2401
+ */ function canAutoResolve(filename) {
2402
+ if (AUTO_RESOLVABLE_PATTERNS.packageLock.test(filename)) {
2403
+ return {
2404
+ canResolve: true,
2405
+ strategy: 'regenerate-lock'
2406
+ };
2407
+ }
2408
+ if (AUTO_RESOLVABLE_PATTERNS.yarnLock.test(filename)) {
2409
+ return {
2410
+ canResolve: true,
2411
+ strategy: 'regenerate-lock'
2412
+ };
2413
+ }
2414
+ if (AUTO_RESOLVABLE_PATTERNS.pnpmLock.test(filename)) {
2415
+ return {
2416
+ canResolve: true,
2417
+ strategy: 'regenerate-lock'
2418
+ };
2419
+ }
2420
+ if (AUTO_RESOLVABLE_PATTERNS.dist.test(filename)) {
2421
+ return {
2422
+ canResolve: true,
2423
+ strategy: 'take-theirs-regenerate'
2424
+ };
2425
+ }
2426
+ if (AUTO_RESOLVABLE_PATTERNS.coverage.test(filename)) {
2427
+ return {
2428
+ canResolve: true,
2429
+ strategy: 'take-theirs'
2430
+ };
2431
+ }
2432
+ if (AUTO_RESOLVABLE_PATTERNS.nodeModules.test(filename)) {
2433
+ return {
2434
+ canResolve: true,
2435
+ strategy: 'take-theirs'
2436
+ };
2437
+ }
2438
+ if (AUTO_RESOLVABLE_PATTERNS.buildOutput.test(filename)) {
2439
+ return {
2440
+ canResolve: true,
2441
+ strategy: 'take-theirs-regenerate'
2442
+ };
2443
+ }
2444
+ return {
2445
+ canResolve: false,
2446
+ strategy: 'manual'
2447
+ };
2448
+ }
2449
+ /**
2450
+ * Check if this is a package.json version conflict that can be auto-resolved
2451
+ */ async function tryResolvePackageJsonConflict(filepath, logger) {
2452
+ const storage = createStorage();
2453
+ try {
2454
+ const content = await storage.readFile(filepath, 'utf-8');
2455
+ // Check if this is actually a conflict file
2456
+ if (!content.includes('<<<<<<<') || !content.includes('>>>>>>>')) {
2457
+ return {
2458
+ resolved: false,
2459
+ error: 'Not a conflict file'
2460
+ };
2461
+ }
2462
+ // Try to parse ours and theirs versions
2463
+ const oursMatch = content.match(/<<<<<<< .*?\n([\s\S]*?)=======\n/);
2464
+ const theirsMatch = content.match(/=======\n([\s\S]*?)>>>>>>> /);
2465
+ if (!oursMatch || !theirsMatch) {
2466
+ return {
2467
+ resolved: false,
2468
+ error: 'Cannot parse conflict markers'
2469
+ };
2470
+ }
2471
+ // For package.json, if only version differs, take the higher version
2472
+ // This is a simplified heuristic - real conflicts may need more logic
2473
+ const oursPart = oursMatch[1];
2474
+ const theirsPart = theirsMatch[1];
2475
+ // Check if this is just a version conflict
2476
+ const versionPattern = /"version":\s*"([^"]+)"/;
2477
+ const oursVersion = oursPart.match(versionPattern);
2478
+ const theirsVersion = theirsPart.match(versionPattern);
2479
+ if (oursVersion && theirsVersion) {
2480
+ // Both have versions - take higher one
2481
+ const semver = await import('semver');
2482
+ const higher = semver.gt(oursVersion[1].replace(/-.*$/, ''), theirsVersion[1].replace(/-.*$/, '')) ? oursVersion[1] : theirsVersion[1];
2483
+ // Replace the conflicted version with the higher one
2484
+ let resolvedContent = content;
2485
+ // Simple approach: take theirs but use higher version
2486
+ // Remove conflict markers and use theirs as base
2487
+ resolvedContent = content.replace(/<<<<<<< .*?\n[\s\S]*?=======\n([\s\S]*?)>>>>>>> .*?\n/g, '$1');
2488
+ // Update version to higher one
2489
+ resolvedContent = resolvedContent.replace(/"version":\s*"[^"]+"/, `"version": "${higher}"`);
2490
+ await storage.writeFile(filepath, resolvedContent, 'utf-8');
2491
+ logger.info(`PULL_RESOLVED_VERSION: Auto-resolved version conflict | File: ${filepath} | Version: ${higher}`);
2492
+ return {
2493
+ resolved: true
2494
+ };
2495
+ }
2496
+ return {
2497
+ resolved: false,
2498
+ error: 'Complex conflict - not just version'
2499
+ };
2500
+ } catch (error) {
2501
+ return {
2502
+ resolved: false,
2503
+ error: error.message
2504
+ };
2505
+ }
2506
+ }
2507
+ /**
2508
+ * Resolve a single conflict file
2509
+ */ async function resolveConflict(filepath, strategy, logger, isDryRun) {
2510
+ if (isDryRun) {
2511
+ logger.info(`PULL_RESOLVE_DRY_RUN: Would resolve conflict | File: ${filepath} | Strategy: ${strategy}`);
2512
+ return {
2513
+ file: filepath,
2514
+ resolved: true,
2515
+ strategy
2516
+ };
2517
+ }
2518
+ try {
2519
+ switch(strategy){
2520
+ case 'regenerate-lock':
2521
+ {
2522
+ // Accept theirs and regenerate
2523
+ await runSecure('git', [
2524
+ 'checkout',
2525
+ '--theirs',
2526
+ filepath
2527
+ ]);
2528
+ await runSecure('git', [
2529
+ 'add',
2530
+ filepath
2531
+ ]);
2532
+ logger.info(`PULL_CONFLICT_RESOLVED: Accepted remote lock file | File: ${filepath} | Strategy: ${strategy} | Note: Will regenerate after pull`);
2533
+ return {
2534
+ file: filepath,
2535
+ resolved: true,
2536
+ strategy
2537
+ };
2538
+ }
2539
+ case 'take-theirs':
2540
+ case 'take-theirs-regenerate':
2541
+ {
2542
+ await runSecure('git', [
2543
+ 'checkout',
2544
+ '--theirs',
2545
+ filepath
2546
+ ]);
2547
+ await runSecure('git', [
2548
+ 'add',
2549
+ filepath
2550
+ ]);
2551
+ logger.info(`PULL_CONFLICT_RESOLVED: Accepted remote version | File: ${filepath} | Strategy: ${strategy}`);
2552
+ return {
2553
+ file: filepath,
2554
+ resolved: true,
2555
+ strategy
2556
+ };
2557
+ }
2558
+ case 'version-bump':
2559
+ {
2560
+ const result = await tryResolvePackageJsonConflict(filepath, logger);
2561
+ if (result.resolved) {
2562
+ await runSecure('git', [
2563
+ 'add',
2564
+ filepath
2565
+ ]);
2566
+ return {
2567
+ file: filepath,
2568
+ resolved: true,
2569
+ strategy
2570
+ };
2571
+ }
2572
+ return {
2573
+ file: filepath,
2574
+ resolved: false,
2575
+ strategy,
2576
+ error: result.error
2577
+ };
2578
+ }
2579
+ default:
2580
+ return {
2581
+ file: filepath,
2582
+ resolved: false,
2583
+ strategy,
2584
+ error: 'Unknown resolution strategy'
2585
+ };
2586
+ }
2587
+ } catch (error) {
2588
+ return {
2589
+ file: filepath,
2590
+ resolved: false,
2591
+ strategy,
2592
+ error: error.message
2593
+ };
2594
+ }
2595
+ }
2596
+ /**
2597
+ * Get list of conflicted files
2598
+ */ async function getConflictedFiles() {
2599
+ try {
2600
+ const { stdout } = await runSecure('git', [
2601
+ 'diff',
2602
+ '--name-only',
2603
+ '--diff-filter=U'
2604
+ ]);
2605
+ return stdout.trim().split('\n').filter((f)=>f.trim());
2606
+ } catch {
2607
+ return [];
2608
+ }
2609
+ }
2610
+ /**
2611
+ * Try to auto-resolve all conflicts
2612
+ */ async function autoResolveConflicts(logger, isDryRun) {
2613
+ const conflictedFiles = await getConflictedFiles();
2614
+ const resolved = [];
2615
+ const manual = [];
2616
+ for (const file of conflictedFiles){
2617
+ const { canResolve, strategy } = canAutoResolve(file);
2618
+ // Special handling for package.json
2619
+ if (file === 'package.json' || file.endsWith('/package.json')) {
2620
+ const result = await resolveConflict(file, 'version-bump', logger, isDryRun);
2621
+ if (result.resolved) {
2622
+ resolved.push(file);
2623
+ } else {
2624
+ manual.push(file);
2625
+ }
2626
+ continue;
2627
+ }
2628
+ if (canResolve) {
2629
+ const result = await resolveConflict(file, strategy, logger, isDryRun);
2630
+ if (result.resolved) {
2631
+ resolved.push(file);
2632
+ } else {
2633
+ manual.push(file);
2634
+ }
2635
+ } else {
2636
+ manual.push(file);
2637
+ }
2638
+ }
2639
+ return {
2640
+ resolved,
2641
+ manual
2642
+ };
2643
+ }
2644
+ /**
2645
+ * Stash local changes if any
2646
+ */ async function stashIfNeeded(logger, isDryRun) {
2647
+ const status = await getGitStatusSummary();
2648
+ if (status.hasUncommittedChanges || status.hasUnstagedFiles) {
2649
+ const changeCount = status.uncommittedCount + status.unstagedCount;
2650
+ logger.info(`PULL_STASHING: Stashing ${changeCount} local changes before pull | Staged: ${status.uncommittedCount} | Unstaged: ${status.unstagedCount}`);
2651
+ if (!isDryRun) {
2652
+ await runSecure('git', [
2653
+ 'stash',
2654
+ 'push',
2655
+ '-m',
2656
+ `kodrdriv-pull-auto-stash-${Date.now()}`
2657
+ ]);
2658
+ }
2659
+ return true;
2660
+ }
2661
+ return false;
2662
+ }
2663
+ /**
2664
+ * Apply stash if we created one
2665
+ */ async function applyStashIfNeeded(didStash, logger, isDryRun) {
2666
+ if (!didStash) return false;
2667
+ logger.info('PULL_STASH_POP: Restoring stashed changes');
2668
+ if (!isDryRun) {
2669
+ try {
2670
+ await runSecure('git', [
2671
+ 'stash',
2672
+ 'pop'
2673
+ ]);
2674
+ return true;
2675
+ } catch (error) {
2676
+ logger.warn(`PULL_STASH_CONFLICT: Stash pop had conflicts | Error: ${error.message} | Action: Stash preserved, manual intervention needed`);
2677
+ // Don't fail - user can manually resolve stash conflicts
2678
+ return false;
2679
+ }
2680
+ }
2681
+ return true;
2682
+ }
2683
+ /**
2684
+ * Regenerate lock files after pull
2685
+ */ async function regenerateLockFiles(resolvedFiles, logger, isDryRun) {
2686
+ const needsRegenerate = resolvedFiles.some((f)=>f === 'package-lock.json' || f.endsWith('/package-lock.json'));
2687
+ if (needsRegenerate) {
2688
+ logger.info('PULL_REGENERATE_LOCK: Regenerating package-lock.json');
2689
+ if (!isDryRun) {
2690
+ try {
2691
+ await run('npm install');
2692
+ logger.info('PULL_REGENERATE_SUCCESS: Lock file regenerated successfully');
2693
+ } catch (error) {
2694
+ logger.warn(`PULL_REGENERATE_FAILED: Failed to regenerate lock file | Error: ${error.message}`);
2695
+ }
2696
+ }
2697
+ }
2698
+ }
2699
+ /**
2700
+ * Main pull execution
2701
+ */ async function executePull(remote, branch, logger, isDryRun) {
2702
+ const currentBranch = await getCurrentBranch();
2703
+ const targetBranch = branch || currentBranch;
2704
+ logger.info(`PULL_STARTING: Pulling changes | Remote: ${remote} | Branch: ${targetBranch} | Current: ${currentBranch}`);
2705
+ // Step 1: Stash any local changes
2706
+ const didStash = await stashIfNeeded(logger, isDryRun);
2707
+ // Step 2: Fetch first to see what's coming
2708
+ logger.info(`PULL_FETCH: Fetching from ${remote}`);
2709
+ if (!isDryRun) {
2710
+ try {
2711
+ await runSecure('git', [
2712
+ 'fetch',
2713
+ remote,
2714
+ targetBranch
2715
+ ]);
2716
+ } catch (error) {
2717
+ logger.error(`PULL_FETCH_FAILED: Failed to fetch | Error: ${error.message}`);
2718
+ if (didStash) await applyStashIfNeeded(true, logger, isDryRun);
2719
+ return {
2720
+ success: false,
2721
+ hadConflicts: false,
2722
+ autoResolved: [],
2723
+ manualRequired: [],
2724
+ stashApplied: didStash,
2725
+ strategy: 'failed',
2726
+ message: `Fetch failed: ${error.message}`
2727
+ };
2728
+ }
2729
+ }
2730
+ // Step 3: Try fast-forward first
2731
+ logger.info('PULL_STRATEGY: Attempting fast-forward merge');
2732
+ if (!isDryRun) {
2733
+ try {
2734
+ await runSecure('git', [
2735
+ 'merge',
2736
+ '--ff-only',
2737
+ `${remote}/${targetBranch}`
2738
+ ]);
2739
+ await applyStashIfNeeded(didStash, logger, isDryRun);
2740
+ logger.info('PULL_SUCCESS: Fast-forward merge successful');
2741
+ return {
2742
+ success: true,
2743
+ hadConflicts: false,
2744
+ autoResolved: [],
2745
+ manualRequired: [],
2746
+ stashApplied: didStash,
2747
+ strategy: 'fast-forward',
2748
+ message: 'Fast-forward merge successful'
2749
+ };
2750
+ } catch {
2751
+ logger.info('PULL_FF_FAILED: Fast-forward not possible, trying rebase');
2752
+ }
2753
+ }
2754
+ // Step 4: Try rebase
2755
+ logger.info('PULL_STRATEGY: Attempting rebase');
2756
+ if (!isDryRun) {
2757
+ try {
2758
+ await runSecure('git', [
2759
+ 'rebase',
2760
+ `${remote}/${targetBranch}`
2761
+ ]);
2762
+ await applyStashIfNeeded(didStash, logger, isDryRun);
2763
+ logger.info('PULL_SUCCESS: Rebase successful');
2764
+ return {
2765
+ success: true,
2766
+ hadConflicts: false,
2767
+ autoResolved: [],
2768
+ manualRequired: [],
2769
+ stashApplied: didStash,
2770
+ strategy: 'rebase',
2771
+ message: 'Rebase successful'
2772
+ };
2773
+ } catch {
2774
+ // Check if rebase is in progress with conflicts
2775
+ const conflictedFiles = await getConflictedFiles();
2776
+ if (conflictedFiles.length > 0) {
2777
+ logger.info(`PULL_CONFLICTS: Rebase has ${conflictedFiles.length} conflicts, attempting auto-resolution`);
2778
+ // Step 5: Try to auto-resolve conflicts
2779
+ const { resolved, manual } = await autoResolveConflicts(logger, isDryRun);
2780
+ if (manual.length === 0) {
2781
+ // All conflicts resolved, continue rebase
2782
+ logger.info('PULL_ALL_RESOLVED: All conflicts auto-resolved, continuing rebase');
2783
+ try {
2784
+ await runSecure('git', [
2785
+ 'rebase',
2786
+ '--continue'
2787
+ ]);
2788
+ await regenerateLockFiles(resolved, logger, isDryRun);
2789
+ await applyStashIfNeeded(didStash, logger, isDryRun);
2790
+ return {
2791
+ success: true,
2792
+ hadConflicts: true,
2793
+ autoResolved: resolved,
2794
+ manualRequired: [],
2795
+ stashApplied: didStash,
2796
+ strategy: 'rebase',
2797
+ message: `Rebase successful with ${resolved.length} auto-resolved conflicts`
2798
+ };
2799
+ } catch (continueError) {
2800
+ logger.warn(`PULL_CONTINUE_FAILED: Rebase continue failed | Error: ${continueError.message}`);
2801
+ }
2802
+ } else {
2803
+ // Some conflicts need manual resolution
2804
+ logger.warn(`PULL_MANUAL_REQUIRED: ${manual.length} conflicts require manual resolution`);
2805
+ logger.warn('PULL_MANUAL_FILES: Files needing manual resolution:');
2806
+ manual.forEach((f)=>logger.warn(` - ${f}`));
2807
+ logger.info('PULL_HINT: After resolving conflicts manually, run: git rebase --continue');
2808
+ // Keep rebase in progress so user can finish
2809
+ return {
2810
+ success: false,
2811
+ hadConflicts: true,
2812
+ autoResolved: resolved,
2813
+ manualRequired: manual,
2814
+ stashApplied: false,
2815
+ strategy: 'rebase',
2816
+ message: `Rebase paused: ${manual.length} files need manual conflict resolution`
2817
+ };
2818
+ }
2819
+ } else {
2820
+ // Rebase failed for other reason, abort and try merge
2821
+ logger.info('PULL_REBASE_ABORT: Rebase failed, aborting and trying merge');
2822
+ try {
2823
+ await runSecure('git', [
2824
+ 'rebase',
2825
+ '--abort'
2826
+ ]);
2827
+ } catch {
2828
+ // Ignore abort errors
2829
+ }
2830
+ }
2831
+ }
2832
+ }
2833
+ // Step 6: Fall back to regular merge
2834
+ logger.info('PULL_STRATEGY: Attempting merge');
2835
+ if (!isDryRun) {
2836
+ try {
2837
+ await runSecure('git', [
2838
+ 'merge',
2839
+ `${remote}/${targetBranch}`
2840
+ ]);
2841
+ await applyStashIfNeeded(didStash, logger, isDryRun);
2842
+ logger.info('PULL_SUCCESS: Merge successful');
2843
+ return {
2844
+ success: true,
2845
+ hadConflicts: false,
2846
+ autoResolved: [],
2847
+ manualRequired: [],
2848
+ stashApplied: didStash,
2849
+ strategy: 'merge',
2850
+ message: 'Merge successful'
2851
+ };
2852
+ } catch {
2853
+ // Check for merge conflicts
2854
+ const conflictedFiles = await getConflictedFiles();
2855
+ if (conflictedFiles.length > 0) {
2856
+ logger.info(`PULL_CONFLICTS: Merge has ${conflictedFiles.length} conflicts, attempting auto-resolution`);
2857
+ const { resolved, manual } = await autoResolveConflicts(logger, isDryRun);
2858
+ if (manual.length === 0) {
2859
+ // All conflicts resolved, commit the merge
2860
+ logger.info('PULL_ALL_RESOLVED: All conflicts auto-resolved, completing merge');
2861
+ try {
2862
+ await runSecure('git', [
2863
+ 'commit',
2864
+ '-m',
2865
+ `Merge ${remote}/${targetBranch} (auto-resolved by kodrdriv)`
2866
+ ]);
2867
+ await regenerateLockFiles(resolved, logger, isDryRun);
2868
+ await applyStashIfNeeded(didStash, logger, isDryRun);
2869
+ return {
2870
+ success: true,
2871
+ hadConflicts: true,
2872
+ autoResolved: resolved,
2873
+ manualRequired: [],
2874
+ stashApplied: didStash,
2875
+ strategy: 'merge',
2876
+ message: `Merge successful with ${resolved.length} auto-resolved conflicts`
2877
+ };
2878
+ } catch (commitError) {
2879
+ logger.error(`PULL_COMMIT_FAILED: Merge commit failed | Error: ${commitError.message}`);
2880
+ }
2881
+ } else {
2882
+ logger.warn(`PULL_MANUAL_REQUIRED: ${manual.length} conflicts require manual resolution`);
2883
+ manual.forEach((f)=>logger.warn(` - ${f}`));
2884
+ logger.info('PULL_HINT: After resolving conflicts manually, run: git commit');
2885
+ return {
2886
+ success: false,
2887
+ hadConflicts: true,
2888
+ autoResolved: resolved,
2889
+ manualRequired: manual,
2890
+ stashApplied: false,
2891
+ strategy: 'merge',
2892
+ message: `Merge paused: ${manual.length} files need manual conflict resolution`
2893
+ };
2894
+ }
2895
+ }
2896
+ }
2897
+ }
2898
+ // If we got here, something went wrong
2899
+ if (didStash) {
2900
+ logger.warn('PULL_STASH_PRESERVED: Local changes still stashed, use "git stash pop" to restore');
2901
+ }
2902
+ return {
2903
+ success: false,
2904
+ hadConflicts: false,
2905
+ autoResolved: [],
2906
+ manualRequired: [],
2907
+ stashApplied: false,
2908
+ strategy: 'failed',
2909
+ message: 'Pull failed - unable to merge or rebase'
2910
+ };
2911
+ }
2912
+ /**
2913
+ * Internal execution
2914
+ */ const executeInternal = async (runConfig)=>{
2915
+ const isDryRun = runConfig.dryRun || false;
2916
+ const logger = getDryRunLogger(isDryRun);
2917
+ // Get pull configuration
2918
+ const pullConfig = runConfig.pull || {};
2919
+ const remote = pullConfig.remote || 'origin';
2920
+ const branch = pullConfig.branch;
2921
+ // Execute pull
2922
+ const result = await executePull(remote, branch, logger, isDryRun);
2923
+ // Format output
2924
+ const lines = [];
2925
+ lines.push('');
2926
+ lines.push('═'.repeat(60));
2927
+ lines.push(result.success ? 'āœ… PULL COMPLETE' : 'āš ļø PULL NEEDS ATTENTION');
2928
+ lines.push('═'.repeat(60));
2929
+ lines.push('');
2930
+ lines.push(`Strategy: ${result.strategy}`);
2931
+ lines.push(`Message: ${result.message}`);
2932
+ if (result.hadConflicts) {
2933
+ lines.push('');
2934
+ lines.push(`Conflicts detected: ${result.autoResolved.length + result.manualRequired.length}`);
2935
+ if (result.autoResolved.length > 0) {
2936
+ lines.push(`āœ“ Auto-resolved: ${result.autoResolved.length}`);
2937
+ result.autoResolved.forEach((f)=>lines.push(` - ${f}`));
2938
+ }
2939
+ if (result.manualRequired.length > 0) {
2940
+ lines.push(`āœ— Manual resolution needed: ${result.manualRequired.length}`);
2941
+ result.manualRequired.forEach((f)=>lines.push(` - ${f}`));
2942
+ }
2943
+ }
2944
+ if (result.stashApplied) {
2945
+ lines.push('');
2946
+ lines.push('ā„¹ļø Local changes have been restored from stash');
2947
+ }
2948
+ lines.push('');
2949
+ lines.push('═'.repeat(60));
2950
+ const output = lines.join('\n');
2951
+ logger.info(output);
2952
+ return output;
2953
+ };
2954
+ /**
2955
+ * Execute pull command
2956
+ */ const execute = async (runConfig)=>{
2957
+ try {
2958
+ return await executeInternal(runConfig);
2959
+ } catch (error) {
2960
+ const logger = getLogger();
2961
+ logger.error(`PULL_COMMAND_FAILED: Pull command failed | Error: ${error.message}`);
2962
+ throw error;
2963
+ }
2964
+ };
2965
+
2966
+ export { PerformanceTimer, batchReadPackageJsonFiles, checkForFileDependencies, execute$2 as clean, collectAllDependencies, execute$4 as commit, findAllPackageJsonFiles, findPackagesByScope, isCleanNeeded, isTestNeeded, optimizePrecommitCommand, execute$3 as precommit, execute as pull, recordTestRun, execute$1 as review, scanDirectoryForPackages };
2346
2967
  //# sourceMappingURL=index.js.map