agileflow 2.80.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/package.json +1 -1
- package/scripts/agent-loop.js +765 -0
- package/scripts/agileflow-configure.js +3 -1
- package/scripts/agileflow-welcome.js +65 -0
- package/scripts/damage-control-bash.js +22 -115
- package/scripts/damage-control-edit.js +19 -156
- package/scripts/damage-control-write.js +19 -156
- package/scripts/lib/damage-control-utils.js +251 -0
- package/scripts/obtain-context.js +57 -2
- package/scripts/ralph-loop.js +516 -32
- package/scripts/session-manager.js +434 -20
- package/src/core/agents/configuration-visual-e2e.md +300 -0
- package/src/core/agents/orchestrator.md +301 -6
- package/src/core/commands/babysit.md +193 -15
- package/src/core/commands/batch.md +362 -0
- package/src/core/commands/choose.md +337 -0
- package/src/core/commands/configure.md +372 -100
- package/src/core/commands/session/end.md +332 -103
- package/src/core/commands/workflow.md +344 -0
- package/src/core/commands/setup/visual-e2e.md +0 -462
|
@@ -17,6 +17,7 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
17
17
|
const { c } = require('../lib/colors');
|
|
18
18
|
const { getProjectRoot } = require('../lib/paths');
|
|
19
19
|
const { safeReadJSON } = require('../lib/errors');
|
|
20
|
+
const { isValidBranchName, isValidSessionNickname } = require('../lib/validate');
|
|
20
21
|
|
|
21
22
|
const ROOT = getProjectRoot();
|
|
22
23
|
const SESSIONS_DIR = path.join(ROOT, '.agileflow', 'sessions');
|
|
@@ -225,6 +226,23 @@ function createSession(options = {}) {
|
|
|
225
226
|
const nickname = options.nickname || null;
|
|
226
227
|
const branchName = options.branch || `session-${sessionId}`;
|
|
227
228
|
const dirName = nickname || sessionId;
|
|
229
|
+
|
|
230
|
+
// SECURITY: Validate branch name to prevent command injection
|
|
231
|
+
if (!isValidBranchName(branchName)) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
error: `Invalid branch name: "${branchName}". Use only letters, numbers, hyphens, underscores, and forward slashes.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// SECURITY: Validate nickname if provided
|
|
239
|
+
if (nickname && !isValidSessionNickname(nickname)) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Invalid nickname: "${nickname}". Use only letters, numbers, hyphens, and underscores.`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
const worktreePath = path.resolve(ROOT, '..', `${projectName}-${dirName}`);
|
|
229
247
|
|
|
230
248
|
// Check if directory already exists
|
|
@@ -235,27 +253,42 @@ function createSession(options = {}) {
|
|
|
235
253
|
};
|
|
236
254
|
}
|
|
237
255
|
|
|
238
|
-
// Create branch if it doesn't exist
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
} catch (e2) {
|
|
246
|
-
return { success: false, error: `Failed to create branch: ${e2.message}` };
|
|
256
|
+
// Create branch if it doesn't exist (using spawnSync for safety)
|
|
257
|
+
const checkRef = spawnSync(
|
|
258
|
+
'git',
|
|
259
|
+
['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`],
|
|
260
|
+
{
|
|
261
|
+
cwd: ROOT,
|
|
262
|
+
encoding: 'utf8',
|
|
247
263
|
}
|
|
248
|
-
|
|
264
|
+
);
|
|
249
265
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
266
|
+
if (checkRef.status !== 0) {
|
|
267
|
+
// Branch doesn't exist, create it
|
|
268
|
+
const createBranch = spawnSync('git', ['branch', branchName], {
|
|
253
269
|
cwd: ROOT,
|
|
254
270
|
encoding: 'utf8',
|
|
255
|
-
stdio: 'pipe',
|
|
256
271
|
});
|
|
257
|
-
|
|
258
|
-
|
|
272
|
+
|
|
273
|
+
if (createBranch.status !== 0) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: `Failed to create branch: ${createBranch.stderr || 'unknown error'}`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Create worktree (using spawnSync for safety)
|
|
282
|
+
const createWorktree = spawnSync('git', ['worktree', 'add', worktreePath, branchName], {
|
|
283
|
+
cwd: ROOT,
|
|
284
|
+
encoding: 'utf8',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (createWorktree.status !== 0) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: `Failed to create worktree: ${createWorktree.stderr || 'unknown error'}`,
|
|
291
|
+
};
|
|
259
292
|
}
|
|
260
293
|
|
|
261
294
|
// Register session
|
|
@@ -346,6 +379,298 @@ function deleteSession(sessionId, removeWorktree = false) {
|
|
|
346
379
|
return { success: true };
|
|
347
380
|
}
|
|
348
381
|
|
|
382
|
+
// Get main branch name (main or master)
|
|
383
|
+
function getMainBranch() {
|
|
384
|
+
const checkMain = spawnSync('git', ['show-ref', '--verify', '--quiet', 'refs/heads/main'], {
|
|
385
|
+
cwd: ROOT,
|
|
386
|
+
encoding: 'utf8',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (checkMain.status === 0) return 'main';
|
|
390
|
+
|
|
391
|
+
const checkMaster = spawnSync('git', ['show-ref', '--verify', '--quiet', 'refs/heads/master'], {
|
|
392
|
+
cwd: ROOT,
|
|
393
|
+
encoding: 'utf8',
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (checkMaster.status === 0) return 'master';
|
|
397
|
+
|
|
398
|
+
return 'main'; // Default fallback
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Check if session branch is mergeable to main
|
|
402
|
+
function checkMergeability(sessionId) {
|
|
403
|
+
const registry = loadRegistry();
|
|
404
|
+
const session = registry.sessions[sessionId];
|
|
405
|
+
|
|
406
|
+
if (!session) {
|
|
407
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (session.is_main) {
|
|
411
|
+
return { success: false, error: 'Cannot merge main session' };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const branchName = session.branch;
|
|
415
|
+
const mainBranch = getMainBranch();
|
|
416
|
+
|
|
417
|
+
// Check for uncommitted changes in the session worktree
|
|
418
|
+
const statusResult = spawnSync('git', ['status', '--porcelain'], {
|
|
419
|
+
cwd: session.path,
|
|
420
|
+
encoding: 'utf8',
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (statusResult.stdout && statusResult.stdout.trim()) {
|
|
424
|
+
return {
|
|
425
|
+
success: true,
|
|
426
|
+
mergeable: false,
|
|
427
|
+
reason: 'uncommitted_changes',
|
|
428
|
+
details: statusResult.stdout.trim(),
|
|
429
|
+
branchName,
|
|
430
|
+
mainBranch,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check if branch has commits ahead of main
|
|
435
|
+
const aheadBehind = spawnSync(
|
|
436
|
+
'git',
|
|
437
|
+
['rev-list', '--left-right', '--count', `${mainBranch}...${branchName}`],
|
|
438
|
+
{
|
|
439
|
+
cwd: ROOT,
|
|
440
|
+
encoding: 'utf8',
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const [behind, ahead] = (aheadBehind.stdout || '0\t0').trim().split('\t').map(Number);
|
|
445
|
+
|
|
446
|
+
if (ahead === 0) {
|
|
447
|
+
return {
|
|
448
|
+
success: true,
|
|
449
|
+
mergeable: false,
|
|
450
|
+
reason: 'no_changes',
|
|
451
|
+
details: 'Branch has no commits ahead of main',
|
|
452
|
+
branchName,
|
|
453
|
+
mainBranch,
|
|
454
|
+
commitsAhead: 0,
|
|
455
|
+
commitsBehind: behind,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Try merge --no-commit --no-ff to check for conflicts (dry run)
|
|
460
|
+
// First, stash any changes in ROOT and ensure we're on main
|
|
461
|
+
const currentBranch = getCurrentBranch();
|
|
462
|
+
|
|
463
|
+
// Checkout main in ROOT for the test merge
|
|
464
|
+
const checkoutMain = spawnSync('git', ['checkout', mainBranch], {
|
|
465
|
+
cwd: ROOT,
|
|
466
|
+
encoding: 'utf8',
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (checkoutMain.status !== 0) {
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: `Failed to checkout ${mainBranch}: ${checkoutMain.stderr}`,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Try the merge
|
|
477
|
+
const testMerge = spawnSync('git', ['merge', '--no-commit', '--no-ff', branchName], {
|
|
478
|
+
cwd: ROOT,
|
|
479
|
+
encoding: 'utf8',
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const hasConflicts = testMerge.status !== 0;
|
|
483
|
+
|
|
484
|
+
// Abort the test merge
|
|
485
|
+
spawnSync('git', ['merge', '--abort'], { cwd: ROOT, encoding: 'utf8' });
|
|
486
|
+
|
|
487
|
+
// Go back to original branch if different
|
|
488
|
+
if (currentBranch && currentBranch !== mainBranch) {
|
|
489
|
+
spawnSync('git', ['checkout', currentBranch], { cwd: ROOT, encoding: 'utf8' });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
success: true,
|
|
494
|
+
mergeable: !hasConflicts,
|
|
495
|
+
branchName,
|
|
496
|
+
mainBranch,
|
|
497
|
+
commitsAhead: ahead,
|
|
498
|
+
commitsBehind: behind,
|
|
499
|
+
hasConflicts,
|
|
500
|
+
conflictDetails: hasConflicts ? testMerge.stderr : null,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Get merge preview (commits and files to be merged)
|
|
505
|
+
function getMergePreview(sessionId) {
|
|
506
|
+
const registry = loadRegistry();
|
|
507
|
+
const session = registry.sessions[sessionId];
|
|
508
|
+
|
|
509
|
+
if (!session) {
|
|
510
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (session.is_main) {
|
|
514
|
+
return { success: false, error: 'Cannot preview merge for main session' };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const branchName = session.branch;
|
|
518
|
+
const mainBranch = getMainBranch();
|
|
519
|
+
|
|
520
|
+
// Get commits that would be merged
|
|
521
|
+
const logResult = spawnSync('git', ['log', '--oneline', `${mainBranch}..${branchName}`], {
|
|
522
|
+
cwd: ROOT,
|
|
523
|
+
encoding: 'utf8',
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const commits = (logResult.stdout || '').trim().split('\n').filter(Boolean);
|
|
527
|
+
|
|
528
|
+
// Get files changed
|
|
529
|
+
const diffResult = spawnSync('git', ['diff', '--name-status', `${mainBranch}...${branchName}`], {
|
|
530
|
+
cwd: ROOT,
|
|
531
|
+
encoding: 'utf8',
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const filesChanged = (diffResult.stdout || '').trim().split('\n').filter(Boolean);
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
success: true,
|
|
538
|
+
branchName,
|
|
539
|
+
mainBranch,
|
|
540
|
+
nickname: session.nickname,
|
|
541
|
+
commits,
|
|
542
|
+
commitCount: commits.length,
|
|
543
|
+
filesChanged,
|
|
544
|
+
fileCount: filesChanged.length,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Execute merge operation
|
|
549
|
+
function integrateSession(sessionId, options = {}) {
|
|
550
|
+
const {
|
|
551
|
+
strategy = 'squash',
|
|
552
|
+
deleteBranch = true,
|
|
553
|
+
deleteWorktree = true,
|
|
554
|
+
message = null,
|
|
555
|
+
} = options;
|
|
556
|
+
|
|
557
|
+
const registry = loadRegistry();
|
|
558
|
+
const session = registry.sessions[sessionId];
|
|
559
|
+
|
|
560
|
+
if (!session) {
|
|
561
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (session.is_main) {
|
|
565
|
+
return { success: false, error: 'Cannot merge main session' };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const branchName = session.branch;
|
|
569
|
+
const mainBranch = getMainBranch();
|
|
570
|
+
|
|
571
|
+
// Ensure we're on main branch in ROOT
|
|
572
|
+
const checkoutMain = spawnSync('git', ['checkout', mainBranch], {
|
|
573
|
+
cwd: ROOT,
|
|
574
|
+
encoding: 'utf8',
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
if (checkoutMain.status !== 0) {
|
|
578
|
+
return { success: false, error: `Failed to checkout ${mainBranch}: ${checkoutMain.stderr}` };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Pull latest main (optional, for safety) - ignore errors for local-only repos
|
|
582
|
+
spawnSync('git', ['pull', '--ff-only'], { cwd: ROOT, encoding: 'utf8' });
|
|
583
|
+
|
|
584
|
+
// Build commit message
|
|
585
|
+
const commitMessage =
|
|
586
|
+
message ||
|
|
587
|
+
`Merge session ${sessionId}${session.nickname ? ` "${session.nickname}"` : ''}: ${branchName}`;
|
|
588
|
+
|
|
589
|
+
// Execute merge based on strategy
|
|
590
|
+
let mergeResult;
|
|
591
|
+
|
|
592
|
+
if (strategy === 'squash') {
|
|
593
|
+
mergeResult = spawnSync('git', ['merge', '--squash', branchName], {
|
|
594
|
+
cwd: ROOT,
|
|
595
|
+
encoding: 'utf8',
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
if (mergeResult.status === 0) {
|
|
599
|
+
// Create the squash commit
|
|
600
|
+
const commitResult = spawnSync('git', ['commit', '-m', commitMessage], {
|
|
601
|
+
cwd: ROOT,
|
|
602
|
+
encoding: 'utf8',
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
if (commitResult.status !== 0) {
|
|
606
|
+
return { success: false, error: `Failed to create squash commit: ${commitResult.stderr}` };
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
// Regular merge commit
|
|
611
|
+
mergeResult = spawnSync('git', ['merge', '--no-ff', '-m', commitMessage, branchName], {
|
|
612
|
+
cwd: ROOT,
|
|
613
|
+
encoding: 'utf8',
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (mergeResult.status !== 0) {
|
|
618
|
+
// Abort if merge failed
|
|
619
|
+
spawnSync('git', ['merge', '--abort'], { cwd: ROOT, encoding: 'utf8' });
|
|
620
|
+
return { success: false, error: `Merge failed: ${mergeResult.stderr}`, hasConflicts: true };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const result = {
|
|
624
|
+
success: true,
|
|
625
|
+
merged: true,
|
|
626
|
+
strategy,
|
|
627
|
+
branchName,
|
|
628
|
+
mainBranch,
|
|
629
|
+
commitMessage,
|
|
630
|
+
mainPath: ROOT,
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// Delete worktree first (before branch, as worktree holds ref)
|
|
634
|
+
if (deleteWorktree && session.path !== ROOT && fs.existsSync(session.path)) {
|
|
635
|
+
try {
|
|
636
|
+
execSync(`git worktree remove "${session.path}"`, { cwd: ROOT, encoding: 'utf8' });
|
|
637
|
+
result.worktreeDeleted = true;
|
|
638
|
+
} catch (e) {
|
|
639
|
+
try {
|
|
640
|
+
execSync(`git worktree remove --force "${session.path}"`, { cwd: ROOT, encoding: 'utf8' });
|
|
641
|
+
result.worktreeDeleted = true;
|
|
642
|
+
} catch (e2) {
|
|
643
|
+
result.worktreeDeleted = false;
|
|
644
|
+
result.worktreeError = e2.message;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Delete branch if requested
|
|
650
|
+
if (deleteBranch) {
|
|
651
|
+
const deleteBranchResult = spawnSync('git', ['branch', '-d', branchName], {
|
|
652
|
+
cwd: ROOT,
|
|
653
|
+
encoding: 'utf8',
|
|
654
|
+
});
|
|
655
|
+
result.branchDeleted = deleteBranchResult.status === 0;
|
|
656
|
+
if (!result.branchDeleted) {
|
|
657
|
+
// Try force delete if normal delete fails
|
|
658
|
+
const forceDelete = spawnSync('git', ['branch', '-D', branchName], {
|
|
659
|
+
cwd: ROOT,
|
|
660
|
+
encoding: 'utf8',
|
|
661
|
+
});
|
|
662
|
+
result.branchDeleted = forceDelete.status === 0;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Remove from registry
|
|
667
|
+
removeLock(sessionId);
|
|
668
|
+
delete registry.sessions[sessionId];
|
|
669
|
+
saveRegistry(registry);
|
|
670
|
+
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
673
|
+
|
|
349
674
|
// Format sessions for display
|
|
350
675
|
function formatSessionsTable(sessions) {
|
|
351
676
|
const lines = [];
|
|
@@ -394,10 +719,24 @@ function main() {
|
|
|
394
719
|
|
|
395
720
|
case 'create': {
|
|
396
721
|
const options = {};
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
722
|
+
// SECURITY: Only accept whitelisted option keys
|
|
723
|
+
const allowedKeys = ['nickname', 'branch'];
|
|
724
|
+
for (let i = 1; i < args.length; i++) {
|
|
725
|
+
const arg = args[i];
|
|
726
|
+
if (arg.startsWith('--')) {
|
|
727
|
+
const key = arg.slice(2).split('=')[0];
|
|
728
|
+
if (!allowedKeys.includes(key)) {
|
|
729
|
+
console.log(JSON.stringify({ success: false, error: `Unknown option: --${key}` }));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Handle --key=value or --key value formats
|
|
733
|
+
const eqIndex = arg.indexOf('=');
|
|
734
|
+
if (eqIndex !== -1) {
|
|
735
|
+
options[key] = arg.slice(eqIndex + 1);
|
|
736
|
+
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
737
|
+
options[key] = args[++i];
|
|
738
|
+
}
|
|
739
|
+
}
|
|
401
740
|
}
|
|
402
741
|
const result = createSession(options);
|
|
403
742
|
console.log(JSON.stringify(result));
|
|
@@ -447,6 +786,65 @@ function main() {
|
|
|
447
786
|
break;
|
|
448
787
|
}
|
|
449
788
|
|
|
789
|
+
case 'check-merge': {
|
|
790
|
+
const sessionId = args[1];
|
|
791
|
+
if (!sessionId) {
|
|
792
|
+
console.log(JSON.stringify({ success: false, error: 'Session ID required' }));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const result = checkMergeability(sessionId);
|
|
796
|
+
console.log(JSON.stringify(result));
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
case 'merge-preview': {
|
|
801
|
+
const sessionId = args[1];
|
|
802
|
+
if (!sessionId) {
|
|
803
|
+
console.log(JSON.stringify({ success: false, error: 'Session ID required' }));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const result = getMergePreview(sessionId);
|
|
807
|
+
console.log(JSON.stringify(result));
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
case 'integrate': {
|
|
812
|
+
const sessionId = args[1];
|
|
813
|
+
if (!sessionId) {
|
|
814
|
+
console.log(JSON.stringify({ success: false, error: 'Session ID required' }));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const options = {};
|
|
818
|
+
const allowedKeys = ['strategy', 'deleteBranch', 'deleteWorktree', 'message'];
|
|
819
|
+
for (let i = 2; i < args.length; i++) {
|
|
820
|
+
const arg = args[i];
|
|
821
|
+
if (arg.startsWith('--')) {
|
|
822
|
+
const eqIndex = arg.indexOf('=');
|
|
823
|
+
let key, value;
|
|
824
|
+
if (eqIndex !== -1) {
|
|
825
|
+
key = arg.slice(2, eqIndex);
|
|
826
|
+
value = arg.slice(eqIndex + 1);
|
|
827
|
+
} else {
|
|
828
|
+
key = arg.slice(2);
|
|
829
|
+
value = args[++i];
|
|
830
|
+
}
|
|
831
|
+
if (!allowedKeys.includes(key)) {
|
|
832
|
+
console.log(JSON.stringify({ success: false, error: `Unknown option: --${key}` }));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
// Convert boolean strings
|
|
836
|
+
if (key === 'deleteBranch' || key === 'deleteWorktree') {
|
|
837
|
+
options[key] = value !== 'false';
|
|
838
|
+
} else {
|
|
839
|
+
options[key] = value;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const result = integrateSession(sessionId, options);
|
|
844
|
+
console.log(JSON.stringify(result));
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
|
|
450
848
|
case 'help':
|
|
451
849
|
default:
|
|
452
850
|
console.log(`
|
|
@@ -460,13 +858,24 @@ ${c.cyan}Commands:${c.reset}
|
|
|
460
858
|
count Count other active sessions
|
|
461
859
|
delete <id> [--remove-worktree] Delete session
|
|
462
860
|
status Get current session status
|
|
861
|
+
check-merge <id> Check if session is mergeable to main
|
|
862
|
+
merge-preview <id> Preview commits/files to be merged
|
|
863
|
+
integrate <id> [opts] Merge session to main and cleanup
|
|
463
864
|
help Show this help
|
|
464
865
|
|
|
866
|
+
${c.cyan}Integrate Options:${c.reset}
|
|
867
|
+
--strategy=squash|merge Merge strategy (default: squash)
|
|
868
|
+
--deleteBranch=true|false Delete branch after merge (default: true)
|
|
869
|
+
--deleteWorktree=true|false Delete worktree after merge (default: true)
|
|
870
|
+
--message="..." Custom commit message
|
|
871
|
+
|
|
465
872
|
${c.cyan}Examples:${c.reset}
|
|
466
873
|
node session-manager.js register
|
|
467
874
|
node session-manager.js create --nickname auth
|
|
468
875
|
node session-manager.js list
|
|
469
876
|
node session-manager.js delete 2 --remove-worktree
|
|
877
|
+
node session-manager.js check-merge 2
|
|
878
|
+
node session-manager.js integrate 2 --strategy=squash
|
|
470
879
|
`);
|
|
471
880
|
}
|
|
472
881
|
}
|
|
@@ -483,6 +892,11 @@ module.exports = {
|
|
|
483
892
|
deleteSession,
|
|
484
893
|
isSessionActive,
|
|
485
894
|
cleanupStaleLocks,
|
|
895
|
+
// Merge operations
|
|
896
|
+
getMainBranch,
|
|
897
|
+
checkMergeability,
|
|
898
|
+
getMergePreview,
|
|
899
|
+
integrateSession,
|
|
486
900
|
};
|
|
487
901
|
|
|
488
902
|
// Run CLI if executed directly
|