@litmers/cursorflow-orchestrator 0.1.20 → 0.1.26
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/CHANGELOG.md +9 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +4 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +8 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +392 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +4 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +9 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1131 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +388 -162
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +374 -2
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
package/src/utils/git.ts
CHANGED
|
@@ -195,6 +195,24 @@ export function getRepoRoot(cwd?: string): string {
|
|
|
195
195
|
return runGit(['rev-parse', '--show-toplevel'], { cwd, silent: true });
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Get main repository root directory (for worktrees)
|
|
200
|
+
*/
|
|
201
|
+
export function getMainRepoRoot(cwd?: string): string {
|
|
202
|
+
try {
|
|
203
|
+
const result = runGitResult(['worktree', 'list', '--porcelain'], { cwd });
|
|
204
|
+
if (result.success && result.stdout) {
|
|
205
|
+
const firstLine = result.stdout.split('\n')[0];
|
|
206
|
+
if (firstLine && firstLine.startsWith('worktree ')) {
|
|
207
|
+
return firstLine.slice(9).trim();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// Fallback to normal repo root
|
|
212
|
+
}
|
|
213
|
+
return getRepoRoot(cwd);
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
/**
|
|
199
217
|
* Check if directory is a git repository
|
|
200
218
|
*/
|
|
@@ -217,8 +235,17 @@ export function worktreeExists(worktreePath: string, cwd?: string): boolean {
|
|
|
217
235
|
* Create worktree
|
|
218
236
|
*/
|
|
219
237
|
export function createWorktree(worktreePath: string, branchName: string, options: { cwd?: string; baseBranch?: string } = {}): string {
|
|
220
|
-
|
|
238
|
+
let { cwd, baseBranch } = options;
|
|
221
239
|
|
|
240
|
+
if (!baseBranch) {
|
|
241
|
+
baseBranch = getCurrentBranch(cwd) || 'refs/heads/main';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Ensure baseBranch is unambiguous (branch name rather than tag)
|
|
245
|
+
const unambiguousBase = (baseBranch.startsWith('refs/') || baseBranch.includes('/'))
|
|
246
|
+
? baseBranch
|
|
247
|
+
: `refs/heads/${baseBranch}`;
|
|
248
|
+
|
|
222
249
|
// Use a file-based lock to prevent race conditions during worktree creation
|
|
223
250
|
const lockDir = safeJoin(cwd || getRepoRoot(), '_cursorflow', 'locks');
|
|
224
251
|
if (!fs.existsSync(lockDir)) {
|
|
@@ -255,7 +282,7 @@ export function createWorktree(worktreePath: string, branchName: string, options
|
|
|
255
282
|
runGit(['worktree', 'add', worktreePath, branchName], { cwd });
|
|
256
283
|
} else {
|
|
257
284
|
// Create new branch from base
|
|
258
|
-
runGit(['worktree', 'add', '-b', branchName, worktreePath,
|
|
285
|
+
runGit(['worktree', 'add', '-b', branchName, worktreePath, unambiguousBase], { cwd });
|
|
259
286
|
}
|
|
260
287
|
|
|
261
288
|
return worktreePath;
|
|
@@ -497,3 +524,348 @@ export function getLastOperationStats(cwd?: string): string {
|
|
|
497
524
|
return '';
|
|
498
525
|
}
|
|
499
526
|
}
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Enhanced Git Functions for Robustness
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Generate a unique branch name that doesn't conflict with existing branches
|
|
534
|
+
*/
|
|
535
|
+
export function generateUniqueBranchName(baseName: string, options: { cwd?: string; maxAttempts?: number } = {}): string {
|
|
536
|
+
const { cwd, maxAttempts = 10 } = options;
|
|
537
|
+
const timestamp = Date.now().toString(36);
|
|
538
|
+
const random = () => Math.random().toString(36).substring(2, 5);
|
|
539
|
+
|
|
540
|
+
// First attempt: base name with timestamp
|
|
541
|
+
let candidate = `${baseName}-${timestamp}-${random()}`;
|
|
542
|
+
|
|
543
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
544
|
+
if (!branchExists(candidate, { cwd })) {
|
|
545
|
+
return candidate;
|
|
546
|
+
}
|
|
547
|
+
// Try with new random suffix
|
|
548
|
+
candidate = `${baseName}-${timestamp}-${random()}`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Last resort: use full timestamp with random
|
|
552
|
+
return `${baseName}-${Date.now()}-${random()}`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Safe merge result
|
|
557
|
+
*/
|
|
558
|
+
export interface SafeMergeResult {
|
|
559
|
+
success: boolean;
|
|
560
|
+
conflict: boolean;
|
|
561
|
+
conflictingFiles: string[];
|
|
562
|
+
error?: string;
|
|
563
|
+
aborted: boolean;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Safely merge a branch with conflict detection and auto-abort
|
|
568
|
+
*/
|
|
569
|
+
export function safeMerge(branchName: string, options: {
|
|
570
|
+
cwd?: string;
|
|
571
|
+
noFf?: boolean;
|
|
572
|
+
message?: string | null;
|
|
573
|
+
abortOnConflict?: boolean;
|
|
574
|
+
strategy?: 'ours' | 'theirs' | null;
|
|
575
|
+
} = {}): SafeMergeResult {
|
|
576
|
+
const { cwd, noFf = false, message = null, abortOnConflict = true, strategy = null } = options;
|
|
577
|
+
|
|
578
|
+
const args = ['merge'];
|
|
579
|
+
|
|
580
|
+
if (noFf) {
|
|
581
|
+
args.push('--no-ff');
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (strategy) {
|
|
585
|
+
args.push('-X', strategy);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (message) {
|
|
589
|
+
args.push('-m', message);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
args.push(branchName);
|
|
593
|
+
|
|
594
|
+
const result = runGitResult(args, { cwd });
|
|
595
|
+
|
|
596
|
+
if (result.success) {
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
conflict: false,
|
|
600
|
+
conflictingFiles: [],
|
|
601
|
+
aborted: false,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Check for conflicts
|
|
606
|
+
const output = result.stdout + result.stderr;
|
|
607
|
+
const isConflict = output.includes('CONFLICT') || output.includes('Automatic merge failed');
|
|
608
|
+
|
|
609
|
+
if (isConflict) {
|
|
610
|
+
// Get conflicting files
|
|
611
|
+
const conflictingFiles = getConflictingFiles(cwd);
|
|
612
|
+
|
|
613
|
+
if (abortOnConflict) {
|
|
614
|
+
// Abort the merge
|
|
615
|
+
runGitResult(['merge', '--abort'], { cwd });
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
success: false,
|
|
619
|
+
conflict: true,
|
|
620
|
+
conflictingFiles,
|
|
621
|
+
error: 'Merge conflict detected and aborted',
|
|
622
|
+
aborted: true,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
success: false,
|
|
628
|
+
conflict: true,
|
|
629
|
+
conflictingFiles,
|
|
630
|
+
error: 'Merge conflict - manual resolution required',
|
|
631
|
+
aborted: false,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
success: false,
|
|
637
|
+
conflict: false,
|
|
638
|
+
conflictingFiles: [],
|
|
639
|
+
error: result.stderr || 'Merge failed',
|
|
640
|
+
aborted: false,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Get list of conflicting files
|
|
646
|
+
*/
|
|
647
|
+
export function getConflictingFiles(cwd?: string): string[] {
|
|
648
|
+
const result = runGitResult(['diff', '--name-only', '--diff-filter=U'], { cwd });
|
|
649
|
+
if (!result.success) return [];
|
|
650
|
+
|
|
651
|
+
return result.stdout.split('\n').filter(f => f.trim());
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Check if merge is in progress
|
|
656
|
+
*/
|
|
657
|
+
export function isMergeInProgress(cwd?: string): boolean {
|
|
658
|
+
const repoRoot = getRepoRoot(cwd);
|
|
659
|
+
return fs.existsSync(path.join(repoRoot, '.git', 'MERGE_HEAD'));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Abort ongoing merge
|
|
664
|
+
*/
|
|
665
|
+
export function abortMerge(cwd?: string): boolean {
|
|
666
|
+
const result = runGitResult(['merge', '--abort'], { cwd });
|
|
667
|
+
return result.success;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Get HEAD commit hash
|
|
672
|
+
*/
|
|
673
|
+
export function getHead(cwd?: string): string {
|
|
674
|
+
return runGit(['rev-parse', 'HEAD'], { cwd, silent: true });
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Get short HEAD commit hash
|
|
679
|
+
*/
|
|
680
|
+
export function getHeadShort(cwd?: string): string {
|
|
681
|
+
return runGit(['rev-parse', '--short', 'HEAD'], { cwd, silent: true });
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Stash changes with optional message
|
|
686
|
+
*/
|
|
687
|
+
export function stash(message?: string, options: { cwd?: string } = {}): boolean {
|
|
688
|
+
const args = ['stash', 'push'];
|
|
689
|
+
if (message) {
|
|
690
|
+
args.push('-m', message);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const result = runGitResult(args, { cwd: options.cwd });
|
|
694
|
+
return result.success;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Pop stashed changes
|
|
699
|
+
*/
|
|
700
|
+
export function stashPop(options: { cwd?: string } = {}): boolean {
|
|
701
|
+
const result = runGitResult(['stash', 'pop'], { cwd: options.cwd });
|
|
702
|
+
return result.success;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Clean worktree (remove untracked files)
|
|
707
|
+
*/
|
|
708
|
+
export function cleanWorktree(options: { cwd?: string; force?: boolean; directories?: boolean } = {}): void {
|
|
709
|
+
const args = ['clean'];
|
|
710
|
+
if (options.force) args.push('-f');
|
|
711
|
+
if (options.directories) args.push('-d');
|
|
712
|
+
|
|
713
|
+
runGit(args, { cwd: options.cwd });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Reset worktree to specific commit/branch
|
|
718
|
+
*/
|
|
719
|
+
export function reset(target: string, options: { cwd?: string; mode?: 'soft' | 'mixed' | 'hard' } = {}): void {
|
|
720
|
+
const args = ['reset'];
|
|
721
|
+
if (options.mode) args.push(`--${options.mode}`);
|
|
722
|
+
args.push(target);
|
|
723
|
+
|
|
724
|
+
runGit(args, { cwd: options.cwd });
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Checkout specific commit or branch
|
|
729
|
+
*/
|
|
730
|
+
export function checkout(target: string, options: { cwd?: string; force?: boolean; createBranch?: boolean } = {}): void {
|
|
731
|
+
const args = ['checkout'];
|
|
732
|
+
if (options.force) args.push('-f');
|
|
733
|
+
if (options.createBranch) args.push('-b');
|
|
734
|
+
args.push(target);
|
|
735
|
+
|
|
736
|
+
runGit(args, { cwd: options.cwd });
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Get commits between two refs
|
|
741
|
+
*/
|
|
742
|
+
export function getCommitsBetween(fromRef: string, toRef: string, options: { cwd?: string } = {}): CommitInfo[] {
|
|
743
|
+
const format = '%H|%h|%an|%ae|%at|%s';
|
|
744
|
+
const result = runGitResult(['log', '--format=' + format, `${fromRef}..${toRef}`], { cwd: options.cwd });
|
|
745
|
+
|
|
746
|
+
if (!result.success) return [];
|
|
747
|
+
|
|
748
|
+
return result.stdout.split('\n')
|
|
749
|
+
.filter(line => line.trim())
|
|
750
|
+
.map(line => {
|
|
751
|
+
const parts = line.split('|');
|
|
752
|
+
return {
|
|
753
|
+
hash: parts[0] || '',
|
|
754
|
+
shortHash: parts[1] || '',
|
|
755
|
+
author: parts[2] || '',
|
|
756
|
+
authorEmail: parts[3] || '',
|
|
757
|
+
timestamp: parseInt(parts[4] || '0'),
|
|
758
|
+
subject: parts[5] || '',
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Enhanced worktree creation with async lock
|
|
765
|
+
*/
|
|
766
|
+
export async function createWorktreeAsync(
|
|
767
|
+
worktreePath: string,
|
|
768
|
+
branchName: string,
|
|
769
|
+
options: { cwd?: string; baseBranch?: string; timeout?: number } = {}
|
|
770
|
+
): Promise<string> {
|
|
771
|
+
let { cwd, baseBranch, timeout = 30000 } = options;
|
|
772
|
+
|
|
773
|
+
if (!baseBranch) {
|
|
774
|
+
baseBranch = getCurrentBranch(cwd) || 'refs/heads/main';
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Ensure baseBranch is unambiguous
|
|
778
|
+
const unambiguousBase = (baseBranch.startsWith('refs/') || baseBranch.includes('/'))
|
|
779
|
+
? baseBranch
|
|
780
|
+
: `refs/heads/${baseBranch}`;
|
|
781
|
+
|
|
782
|
+
const { acquireLock, releaseLock } = await import('./lock');
|
|
783
|
+
const lockDir = safeJoin(cwd || getRepoRoot(), '_cursorflow', 'locks');
|
|
784
|
+
if (!fs.existsSync(lockDir)) {
|
|
785
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
786
|
+
}
|
|
787
|
+
const lockFile = safeJoin(lockDir, 'worktree.lock');
|
|
788
|
+
|
|
789
|
+
const acquired = await acquireLock(lockFile, {
|
|
790
|
+
timeoutMs: timeout,
|
|
791
|
+
operation: `createWorktree:${branchName}`,
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
if (!acquired) {
|
|
795
|
+
throw new Error('Failed to acquire worktree lock after timeout');
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
try {
|
|
799
|
+
// Check if branch already exists
|
|
800
|
+
const branchExistsLocal = runGitResult(['rev-parse', '--verify', branchName], { cwd }).success;
|
|
801
|
+
|
|
802
|
+
if (branchExistsLocal) {
|
|
803
|
+
runGit(['worktree', 'add', worktreePath, branchName], { cwd });
|
|
804
|
+
} else {
|
|
805
|
+
runGit(['worktree', 'add', '-b', branchName, worktreePath, unambiguousBase], { cwd });
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return worktreePath;
|
|
809
|
+
} finally {
|
|
810
|
+
await releaseLock(lockFile);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Prune orphaned worktrees
|
|
816
|
+
*/
|
|
817
|
+
export function pruneWorktrees(options: { cwd?: string } = {}): void {
|
|
818
|
+
runGit(['worktree', 'prune'], { cwd: options.cwd });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Get worktree for a specific path
|
|
823
|
+
*/
|
|
824
|
+
export function getWorktreeForPath(targetPath: string, cwd?: string): WorktreeInfo | null {
|
|
825
|
+
const worktrees = listWorktrees(cwd);
|
|
826
|
+
return worktrees.find(wt => wt.path === targetPath) || null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Sync branch with remote (fetch + merge or rebase)
|
|
831
|
+
*/
|
|
832
|
+
export function syncWithRemote(branch: string, options: {
|
|
833
|
+
cwd?: string;
|
|
834
|
+
strategy?: 'merge' | 'rebase';
|
|
835
|
+
createIfMissing?: boolean;
|
|
836
|
+
} = {}): { success: boolean; error?: string } {
|
|
837
|
+
const { cwd, strategy = 'merge', createIfMissing = false } = options;
|
|
838
|
+
|
|
839
|
+
// Fetch the branch
|
|
840
|
+
const fetchResult = runGitResult(['fetch', 'origin', branch], { cwd });
|
|
841
|
+
|
|
842
|
+
if (!fetchResult.success) {
|
|
843
|
+
if (createIfMissing && fetchResult.stderr.includes('not found')) {
|
|
844
|
+
// Branch doesn't exist on remote, nothing to sync
|
|
845
|
+
return { success: true };
|
|
846
|
+
}
|
|
847
|
+
return { success: false, error: fetchResult.stderr };
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Merge or rebase
|
|
851
|
+
if (strategy === 'rebase') {
|
|
852
|
+
const result = runGitResult(['rebase', `origin/${branch}`], { cwd });
|
|
853
|
+
if (!result.success) {
|
|
854
|
+
// Abort rebase on failure
|
|
855
|
+
runGitResult(['rebase', '--abort'], { cwd });
|
|
856
|
+
return { success: false, error: result.stderr };
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
const mergeResult = safeMerge(`origin/${branch}`, {
|
|
860
|
+
cwd,
|
|
861
|
+
message: `chore: sync with origin/${branch}`,
|
|
862
|
+
abortOnConflict: true,
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
if (!mergeResult.success) {
|
|
866
|
+
return { success: false, error: mergeResult.error };
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return { success: true };
|
|
871
|
+
}
|