sequant 2.0.0 → 2.1.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.
Files changed (61) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +7 -6
  4. package/dist/bin/cli.js +2 -1
  5. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  6. package/dist/marketplace/external_plugins/sequant/.mcp.json +6 -0
  7. package/dist/marketplace/external_plugins/sequant/README.md +58 -8
  8. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +19 -8
  9. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +36 -49
  10. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +158 -48
  11. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +354 -352
  12. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +1155 -33
  13. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +35 -4
  14. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +2157 -104
  15. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +1 -1
  16. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +386 -0
  17. package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +38 -664
  18. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +505 -120
  19. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +246 -1
  20. package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +138 -1
  21. package/dist/src/commands/dashboard.js +1 -1
  22. package/dist/src/commands/doctor.js +1 -1
  23. package/dist/src/commands/init.js +10 -10
  24. package/dist/src/commands/logs.js +1 -1
  25. package/dist/src/commands/run.js +49 -39
  26. package/dist/src/commands/state.js +3 -3
  27. package/dist/src/commands/status.js +5 -5
  28. package/dist/src/commands/sync.js +8 -8
  29. package/dist/src/commands/update.js +16 -16
  30. package/dist/src/lib/cli-ui.js +20 -19
  31. package/dist/src/lib/merge-check/index.js +2 -2
  32. package/dist/src/lib/settings.d.ts +8 -0
  33. package/dist/src/lib/settings.js +1 -0
  34. package/dist/src/lib/shutdown.js +1 -1
  35. package/dist/src/lib/templates.js +2 -0
  36. package/dist/src/lib/wizard.js +6 -4
  37. package/dist/src/lib/workflow/batch-executor.d.ts +9 -1
  38. package/dist/src/lib/workflow/batch-executor.js +39 -2
  39. package/dist/src/lib/workflow/log-writer.js +6 -6
  40. package/dist/src/lib/workflow/metrics-writer.js +5 -3
  41. package/dist/src/lib/workflow/phase-executor.d.ts +1 -1
  42. package/dist/src/lib/workflow/phase-executor.js +52 -22
  43. package/dist/src/lib/workflow/platforms/github.js +5 -1
  44. package/dist/src/lib/workflow/state-cleanup.js +1 -1
  45. package/dist/src/lib/workflow/state-manager.js +15 -13
  46. package/dist/src/lib/workflow/state-rebuild.js +2 -2
  47. package/dist/src/lib/workflow/types.d.ts +27 -0
  48. package/dist/src/lib/workflow/worktree-manager.js +40 -41
  49. package/dist/src/lib/worktree-isolation.d.ts +130 -0
  50. package/dist/src/lib/worktree-isolation.js +310 -0
  51. package/package.json +24 -14
  52. package/templates/agents/sequant-explorer.md +23 -0
  53. package/templates/agents/sequant-implementer.md +18 -0
  54. package/templates/agents/sequant-qa-checker.md +24 -0
  55. package/templates/agents/sequant-testgen.md +25 -0
  56. package/templates/scripts/cleanup-worktree.sh +18 -0
  57. package/templates/skills/_shared/references/subagent-types.md +158 -48
  58. package/templates/skills/exec/SKILL.md +72 -6
  59. package/templates/skills/qa/SKILL.md +8 -217
  60. package/templates/skills/spec/SKILL.md +446 -120
  61. package/templates/skills/testgen/SKILL.md +138 -1
@@ -166,7 +166,7 @@ export function checkWorktreeFreshness(worktreePath, verbose, baseBranch = "main
166
166
  unpushedResult.stdout.toString().trim().length > 0;
167
167
  }
168
168
  if (verbose && result.isStale) {
169
- console.log(chalk.gray(` 📊 Worktree is ${result.commitsBehind} commits behind origin/${baseBranch}`));
169
+ console.log(chalk.gray(` Worktree is ${result.commitsBehind} commits behind origin/${baseBranch}`));
170
170
  }
171
171
  return result;
172
172
  }
@@ -186,7 +186,7 @@ export function removeStaleWorktree(existingPath, branch, verbose) {
186
186
  const removeResult = spawnSync("git", ["worktree", "remove", "--force", existingPath], { stdio: "pipe" });
187
187
  if (removeResult.status !== 0) {
188
188
  const error = removeResult.stderr.toString();
189
- console.log(chalk.yellow(` ⚠️ Could not remove worktree: ${error}`));
189
+ console.log(chalk.yellow(` ! Could not remove worktree: ${error}`));
190
190
  return false;
191
191
  }
192
192
  // Delete the branch so it can be recreated fresh
@@ -211,7 +211,7 @@ export function listWorktrees() {
211
211
  const lines = output.split("\n");
212
212
  const worktrees = [];
213
213
  let currentPath = "";
214
- let currentBranch = "";
214
+ let currentBranch;
215
215
  for (const line of lines) {
216
216
  if (line.startsWith("worktree ")) {
217
217
  currentPath = line.substring(9);
@@ -223,7 +223,6 @@ export function listWorktrees() {
223
223
  const issue = issueMatch ? parseInt(issueMatch[1], 10) : null;
224
224
  worktrees.push({ path: currentPath, branch: currentBranch, issue });
225
225
  currentPath = "";
226
- currentBranch = "";
227
226
  }
228
227
  }
229
228
  return worktrees;
@@ -339,18 +338,18 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
339
338
  if (freshness.isStale) {
340
339
  // AC-3: Handle stale worktrees - check for work in progress
341
340
  if (freshness.hasUncommittedChanges) {
342
- console.log(chalk.yellow(` ⚠️ Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} but has uncommitted changes`));
341
+ console.log(chalk.yellow(` ! Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} but has uncommitted changes`));
343
342
  console.log(chalk.yellow(` ℹ️ Keeping existing worktree. Commit or stash changes, then re-run.`));
344
343
  // Continue with existing worktree
345
344
  }
346
345
  else if (freshness.hasUnpushedCommits) {
347
- console.log(chalk.yellow(` ⚠️ Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} but has unpushed commits`));
346
+ console.log(chalk.yellow(` ! Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} but has unpushed commits`));
348
347
  console.log(chalk.yellow(` ℹ️ Keeping existing worktree with WIP commits.`));
349
348
  // Continue with existing worktree
350
349
  }
351
350
  else {
352
351
  // Safe to recreate - no uncommitted/unpushed work
353
- console.log(chalk.yellow(` ⚠️ Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} — recreating fresh`));
352
+ console.log(chalk.yellow(` ! Worktree is ${freshness.commitsBehind} commits behind ${detectedBase} — recreating fresh`));
354
353
  if (removeStaleWorktree(existingPath, branch, verbose)) {
355
354
  existingPath = null; // Will fall through to create new worktree
356
355
  }
@@ -359,12 +358,12 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
359
358
  }
360
359
  if (existingPath) {
361
360
  if (verbose) {
362
- console.log(chalk.gray(` 📂 Reusing existing worktree: ${existingPath}`));
361
+ console.log(chalk.gray(` Reusing existing worktree: ${existingPath}`));
363
362
  }
364
363
  // In chain mode, rebase existing worktree onto previous chain link
365
364
  if (chainMode && baseBranch) {
366
365
  if (verbose) {
367
- console.log(chalk.gray(` 🔄 Rebasing existing worktree onto chain base (${baseBranch})...`));
366
+ console.log(chalk.gray(` Rebasing existing worktree onto chain base (${baseBranch})...`));
368
367
  }
369
368
  const rebaseResult = spawnSync("git", ["-C", existingPath, "rebase", baseBranch], { stdio: "pipe" });
370
369
  if (rebaseResult.status !== 0) {
@@ -372,7 +371,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
372
371
  // Check if it's a conflict
373
372
  if (rebaseError.includes("CONFLICT") ||
374
373
  rebaseError.includes("could not apply")) {
375
- console.log(chalk.yellow(` ⚠️ Rebase conflict detected. Aborting rebase and keeping original branch state.`));
374
+ console.log(chalk.yellow(` ! Rebase conflict detected. Aborting rebase and keeping original branch state.`));
376
375
  console.log(chalk.yellow(` ℹ️ Branch ${branch} is not properly chained. Manual rebase may be required.`));
377
376
  // Abort the rebase to restore branch state
378
377
  spawnSync("git", ["-C", existingPath, "rebase", "--abort"], {
@@ -380,7 +379,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
380
379
  });
381
380
  }
382
381
  else {
383
- console.log(chalk.yellow(` ⚠️ Rebase failed: ${rebaseError.trim()}`));
382
+ console.log(chalk.yellow(` ! Rebase failed: ${rebaseError.trim()}`));
384
383
  console.log(chalk.yellow(` ℹ️ Continuing with branch in its original state.`));
385
384
  }
386
385
  return {
@@ -392,7 +391,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
392
391
  };
393
392
  }
394
393
  if (verbose) {
395
- console.log(chalk.green(` Existing worktree rebased onto ${baseBranch}`));
394
+ console.log(chalk.green(` Existing worktree rebased onto ${baseBranch}`));
396
395
  }
397
396
  return {
398
397
  issue: issueNumber,
@@ -435,13 +434,13 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
435
434
  const branchToFetch = effectiveBase.replace(/^origin\//, "");
436
435
  if (!isLocalBranch) {
437
436
  if (verbose) {
438
- console.log(chalk.gray(` 🔄 Fetching latest ${branchToFetch}...`));
437
+ console.log(chalk.gray(` Fetching latest ${branchToFetch}...`));
439
438
  }
440
439
  const fetchResult = spawnSync("git", ["fetch", "origin", branchToFetch], {
441
440
  stdio: "pipe",
442
441
  });
443
442
  if (fetchResult.status !== 0 && verbose) {
444
- console.log(chalk.yellow(` ⚠️ Could not fetch origin/${branchToFetch}, using local state`));
443
+ console.log(chalk.yellow(` ! Could not fetch origin/${branchToFetch}, using local state`));
445
444
  }
446
445
  }
447
446
  else if (verbose) {
@@ -477,7 +476,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
477
476
  let rebased = false;
478
477
  if (needsRebase) {
479
478
  if (verbose) {
480
- console.log(chalk.gray(` 🔄 Rebasing existing branch onto previous chain link (${baseRef})...`));
479
+ console.log(chalk.gray(` Rebasing existing branch onto previous chain link (${baseRef})...`));
481
480
  }
482
481
  const rebaseResult = spawnSync("git", ["-C", worktreePath, "rebase", baseRef], {
483
482
  stdio: "pipe",
@@ -487,7 +486,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
487
486
  // Check if it's a conflict
488
487
  if (rebaseError.includes("CONFLICT") ||
489
488
  rebaseError.includes("could not apply")) {
490
- console.log(chalk.yellow(` ⚠️ Rebase conflict detected. Aborting rebase and keeping original branch state.`));
489
+ console.log(chalk.yellow(` ! Rebase conflict detected. Aborting rebase and keeping original branch state.`));
491
490
  console.log(chalk.yellow(` ℹ️ Branch ${branch} is not properly chained. Manual rebase may be required.`));
492
491
  // Abort the rebase to restore branch state
493
492
  spawnSync("git", ["-C", worktreePath, "rebase", "--abort"], {
@@ -495,14 +494,14 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
495
494
  });
496
495
  }
497
496
  else {
498
- console.log(chalk.yellow(` ⚠️ Rebase failed: ${rebaseError.trim()}`));
497
+ console.log(chalk.yellow(` ! Rebase failed: ${rebaseError.trim()}`));
499
498
  console.log(chalk.yellow(` ℹ️ Continuing with branch in its original state.`));
500
499
  }
501
500
  }
502
501
  else {
503
502
  rebased = true;
504
503
  if (verbose) {
505
- console.log(chalk.green(` Branch rebased onto ${baseRef}`));
504
+ console.log(chalk.green(` Branch rebased onto ${baseRef}`));
506
505
  }
507
506
  }
508
507
  }
@@ -525,7 +524,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
525
524
  const nodeModulesPath = path.join(worktreePath, "node_modules");
526
525
  if (!existsSync(nodeModulesPath)) {
527
526
  if (verbose) {
528
- console.log(chalk.gray(` 📦 Installing dependencies...`));
527
+ console.log(chalk.gray(` Installing dependencies...`));
529
528
  }
530
529
  // Use detected package manager or default to npm
531
530
  const pm = packageManager || "npm";
@@ -537,7 +536,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
537
536
  });
538
537
  }
539
538
  if (verbose) {
540
- console.log(chalk.green(` Worktree ready: ${worktreePath}`));
539
+ console.log(chalk.green(` Worktree ready: ${worktreePath}`));
541
540
  }
542
541
  return {
543
542
  issue: issueNumber,
@@ -554,7 +553,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
554
553
  export async function ensureWorktrees(issues, verbose, packageManager, baseBranch) {
555
554
  const worktrees = new Map();
556
555
  const baseDisplay = baseBranch || detectDefaultBranch(verbose);
557
- console.log(chalk.blue(`\n 📂 Preparing worktrees from ${baseDisplay}...`));
556
+ console.log(chalk.blue(`\n Preparing worktrees from ${baseDisplay}...`));
558
557
  for (const issue of issues) {
559
558
  const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, baseBranch, false);
560
559
  if (worktree) {
@@ -576,7 +575,7 @@ export async function ensureWorktrees(issues, verbose, packageManager, baseBranc
576
575
  export async function ensureWorktreesChain(issues, verbose, packageManager, baseBranch) {
577
576
  const worktrees = new Map();
578
577
  const baseDisplay = baseBranch || detectDefaultBranch(verbose);
579
- console.log(chalk.blue(`\n 🔗 Preparing chained worktrees from ${baseDisplay}...`));
578
+ console.log(chalk.blue(`\n Preparing chained worktrees from ${baseDisplay}...`));
580
579
  // First issue starts from the specified base branch (or main)
581
580
  let previousBranch = baseBranch;
582
581
  for (const issue of issues) {
@@ -622,7 +621,7 @@ export function createCheckpointCommit(worktreePath, issueNumber, verbose) {
622
621
  const statusResult = spawnSync("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" });
623
622
  if (statusResult.status !== 0) {
624
623
  if (verbose) {
625
- console.log(chalk.yellow(` ⚠️ Could not check git status for checkpoint`));
624
+ console.log(chalk.yellow(` ! Could not check git status for checkpoint`));
626
625
  }
627
626
  return false;
628
627
  }
@@ -639,7 +638,7 @@ export function createCheckpointCommit(worktreePath, issueNumber, verbose) {
639
638
  });
640
639
  if (addResult.status !== 0) {
641
640
  if (verbose) {
642
- console.log(chalk.yellow(` ⚠️ Could not stage changes for checkpoint`));
641
+ console.log(chalk.yellow(` ! Could not stage changes for checkpoint`));
643
642
  }
644
643
  return false;
645
644
  }
@@ -652,7 +651,7 @@ passed QA in chain mode. It serves as a recovery point if later issues fail.`;
652
651
  if (commitResult.status !== 0) {
653
652
  const error = commitResult.stderr.toString();
654
653
  if (verbose) {
655
- console.log(chalk.yellow(` ⚠️ Could not create checkpoint commit: ${error}`));
654
+ console.log(chalk.yellow(` ! Could not create checkpoint commit: ${error}`));
656
655
  }
657
656
  return false;
658
657
  }
@@ -689,19 +688,19 @@ export function reinstallIfLockfileChanged(worktreePath, packageManager, verbose
689
688
  if (result.status === 0 && result.stdout.toString().trim().length > 0) {
690
689
  lockfileChanged = true;
691
690
  if (verbose) {
692
- console.log(chalk.gray(` 📦 Lockfile changed: ${lockfile}`));
691
+ console.log(chalk.gray(` Lockfile changed: ${lockfile}`));
693
692
  }
694
693
  break;
695
694
  }
696
695
  }
697
696
  if (!lockfileChanged) {
698
697
  if (verbose) {
699
- console.log(chalk.gray(` 📦 No lockfile changes detected`));
698
+ console.log(chalk.gray(` No lockfile changes detected`));
700
699
  }
701
700
  return false;
702
701
  }
703
702
  // Re-run install to sync node_modules with updated lockfile
704
- console.log(chalk.blue(` 📦 Reinstalling dependencies (lockfile changed)...`));
703
+ console.log(chalk.blue(` Reinstalling dependencies (lockfile changed)...`));
705
704
  const pm = packageManager || "npm";
706
705
  const pmConfig = PM_CONFIG[pm];
707
706
  const [cmd, ...args] = pmConfig.installSilent.split(" ");
@@ -711,10 +710,10 @@ export function reinstallIfLockfileChanged(worktreePath, packageManager, verbose
711
710
  });
712
711
  if (installResult.status !== 0) {
713
712
  const error = installResult.stderr.toString();
714
- console.log(chalk.yellow(` ⚠️ Dependency reinstall failed: ${error.trim()}`));
713
+ console.log(chalk.yellow(` ! Dependency reinstall failed: ${error.trim()}`));
715
714
  return false;
716
715
  }
717
- console.log(chalk.green(` Dependencies reinstalled`));
716
+ console.log(chalk.green(` Dependencies reinstalled`));
718
717
  return true;
719
718
  }
720
719
  /**
@@ -732,7 +731,7 @@ export function reinstallIfLockfileChanged(worktreePath, packageManager, verbose
732
731
  export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbose, baseBranch = "main") {
733
732
  const baseRef = `origin/${baseBranch}`;
734
733
  if (verbose) {
735
- console.log(chalk.gray(` 🔄 Rebasing #${issueNumber} onto ${baseRef} before PR...`));
734
+ console.log(chalk.gray(` Rebasing #${issueNumber} onto ${baseRef} before PR...`));
736
735
  }
737
736
  // Fetch latest base branch to ensure we're rebasing onto fresh state
738
737
  const fetchResult = spawnSync("git", ["-C", worktreePath, "fetch", "origin", baseBranch], {
@@ -740,7 +739,7 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
740
739
  });
741
740
  if (fetchResult.status !== 0) {
742
741
  const error = fetchResult.stderr.toString();
743
- console.log(chalk.yellow(` ⚠️ Could not fetch ${baseRef}: ${error.trim()}`));
742
+ console.log(chalk.yellow(` ! Could not fetch ${baseRef}: ${error.trim()}`));
744
743
  // Continue anyway - might work with local state
745
744
  }
746
745
  // Perform the rebase
@@ -750,7 +749,7 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
750
749
  // Check if it's a conflict
751
750
  if (rebaseError.includes("CONFLICT") ||
752
751
  rebaseError.includes("could not apply")) {
753
- console.log(chalk.yellow(` ⚠️ Rebase conflict detected. Aborting rebase and keeping original branch state.`));
752
+ console.log(chalk.yellow(` ! Rebase conflict detected. Aborting rebase and keeping original branch state.`));
754
753
  console.log(chalk.yellow(` ℹ️ PR will be created without rebase. Manual rebase may be required before merge.`));
755
754
  // Abort the rebase to restore branch state
756
755
  spawnSync("git", ["-C", worktreePath, "rebase", "--abort"], {
@@ -764,7 +763,7 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
764
763
  };
765
764
  }
766
765
  else {
767
- console.log(chalk.yellow(` ⚠️ Rebase failed: ${rebaseError.trim()}`));
766
+ console.log(chalk.yellow(` ! Rebase failed: ${rebaseError.trim()}`));
768
767
  console.log(chalk.yellow(` ℹ️ Continuing with branch in its original state.`));
769
768
  return {
770
769
  performed: true,
@@ -774,7 +773,7 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
774
773
  };
775
774
  }
776
775
  }
777
- console.log(chalk.green(` Branch rebased onto ${baseRef}`));
776
+ console.log(chalk.green(` Branch rebased onto ${baseRef}`));
778
777
  // Check if lockfile changed and reinstall if needed
779
778
  const reinstalled = reinstallIfLockfileChanged(worktreePath, packageManager, verbose);
780
779
  return {
@@ -819,7 +818,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
819
818
  const pushResult = spawnSync("git", ["-C", worktreePath, "push", "-u", "origin", branch], { stdio: "pipe", timeout: 60000 });
820
819
  if (pushResult.status !== 0) {
821
820
  const pushError = pushResult.stderr?.toString().trim() ?? "Unknown error";
822
- console.log(chalk.yellow(` ⚠️ git push failed: ${pushError}`));
821
+ console.log(chalk.yellow(` ! git push failed: ${pushError}`));
823
822
  return {
824
823
  attempted: true,
825
824
  success: false,
@@ -828,7 +827,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
828
827
  }
829
828
  // Step 3: Create PR
830
829
  if (verbose) {
831
- console.log(chalk.gray(` 📝 Creating PR for #${issueNumber}...`));
830
+ console.log(chalk.gray(` Creating PR for #${issueNumber}...`));
832
831
  }
833
832
  const isBug = labels?.some((l) => /^bug/i.test(l));
834
833
  const prefix = isBug ? "fix" : "feat";
@@ -858,7 +857,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
858
857
  };
859
858
  }
860
859
  }
861
- console.log(chalk.yellow(` ⚠️ PR creation failed: ${prError}`));
860
+ console.log(chalk.yellow(` ! PR creation failed: ${prError}`));
862
861
  return {
863
862
  attempted: true,
864
863
  success: false,
@@ -871,7 +870,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
871
870
  if (prUrlMatch) {
872
871
  const prNumber = parseInt(prUrlMatch[1], 10);
873
872
  const prUrl = prUrlMatch[0];
874
- console.log(chalk.green(` PR #${prNumber} created: ${prUrl}`));
873
+ console.log(chalk.green(` PR #${prNumber} created: ${prUrl}`));
875
874
  return {
876
875
  attempted: true,
877
876
  success: true,
@@ -882,7 +881,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
882
881
  // Fallback: try gh pr view to get details
883
882
  const viewInfo = github.viewPRByBranchSync(branch, worktreePath);
884
883
  if (viewInfo) {
885
- console.log(chalk.green(` PR #${viewInfo.number} created: ${viewInfo.url}`));
884
+ console.log(chalk.green(` PR #${viewInfo.number} created: ${viewInfo.url}`));
886
885
  return {
887
886
  attempted: true,
888
887
  success: true,
@@ -891,7 +890,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
891
890
  };
892
891
  }
893
892
  // PR was created but we couldn't parse the URL
894
- console.log(chalk.yellow(` ⚠️ PR created but could not extract URL from output: ${prOutput}`));
893
+ console.log(chalk.yellow(` ! PR created but could not extract URL from output: ${prOutput}`));
895
894
  return {
896
895
  attempted: true,
897
896
  success: true,
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Worktree isolation for parallel /exec agent groups.
3
+ *
4
+ * Provides sub-worktree creation, merge-back, and cleanup for
5
+ * parallel agent execution, eliminating file conflicts structurally.
6
+ *
7
+ * @see https://github.com/sequant-io/sequant/issues/485
8
+ */
9
+ /** Result of creating a sub-worktree */
10
+ export interface SubWorktreeInfo {
11
+ /** Absolute path to the sub-worktree */
12
+ path: string;
13
+ /** Branch name used for the sub-worktree */
14
+ branch: string;
15
+ /** Agent index within the parallel group */
16
+ agentIndex: number;
17
+ }
18
+ /** Result of merging a single sub-worktree back */
19
+ export interface MergeResult {
20
+ /** Whether the merge succeeded */
21
+ success: boolean;
22
+ /** Branch that was merged */
23
+ branch: string;
24
+ /** Agent index */
25
+ agentIndex: number;
26
+ /** Files that conflicted (empty if success) */
27
+ conflictFiles: string[];
28
+ /** Error message if merge failed */
29
+ error?: string;
30
+ }
31
+ /** Aggregate result of merging all sub-worktrees */
32
+ export interface MergeBackResult {
33
+ /** Number of agents that merged successfully */
34
+ merged: number;
35
+ /** Number of agents with conflicts */
36
+ conflicts: number;
37
+ /** Per-agent merge results */
38
+ results: MergeResult[];
39
+ }
40
+ /**
41
+ * Directory name for sub-worktrees inside the issue worktree.
42
+ * This directory is nested inside the issue worktree for locality.
43
+ */
44
+ export declare const SUB_WORKTREE_DIR = ".exec-agents";
45
+ /** Name of the worktree include file */
46
+ export declare const WORKTREE_INCLUDE_FILE = ".worktreeinclude";
47
+ /**
48
+ * Read the list of files to copy into sub-worktrees.
49
+ *
50
+ * Reads from .worktreeinclude if it exists (one path per line, # comments),
51
+ * otherwise returns the hardcoded default list.
52
+ */
53
+ export declare function getIncludeFiles(worktreePath: string): string[];
54
+ /**
55
+ * Generate a branch name for a sub-worktree agent.
56
+ *
57
+ * @param issueWorktreePath - Path to the issue worktree
58
+ * @param agentIndex - Zero-based agent index
59
+ * @returns Branch name like `exec-agent-485-0`
60
+ */
61
+ export declare function agentBranchName(issueWorktreePath: string, agentIndex: number): string;
62
+ /**
63
+ * Create a sub-worktree for a parallel agent.
64
+ *
65
+ * The sub-worktree is created inside the issue worktree at
66
+ * `.exec-agents/agent-<N>/` and branches from the issue branch HEAD.
67
+ *
68
+ * node_modules is symlinked from the issue worktree for speed.
69
+ * Environment files are copied per new-feature.sh convention.
70
+ *
71
+ * @param issueWorktreePath - Absolute path to the issue worktree
72
+ * @param agentIndex - Zero-based agent index
73
+ * @returns Sub-worktree info, or null if creation failed
74
+ */
75
+ export declare function createSubWorktree(issueWorktreePath: string, agentIndex: number): SubWorktreeInfo | null;
76
+ /**
77
+ * Merge a single sub-worktree's changes back into the issue worktree.
78
+ *
79
+ * Uses `git merge --no-ff` for built-in conflict detection.
80
+ *
81
+ * @param issueWorktreePath - Absolute path to the issue worktree
82
+ * @param subWorktree - Sub-worktree info
83
+ * @returns Merge result with conflict details if any
84
+ */
85
+ export declare function mergeBackSubWorktree(issueWorktreePath: string, subWorktree: SubWorktreeInfo): MergeResult;
86
+ /**
87
+ * Merge all sub-worktrees back into the issue worktree.
88
+ *
89
+ * Merges are attempted sequentially. If one conflicts, the merge is
90
+ * aborted and subsequent agents are still attempted. Non-conflicting
91
+ * changes are preserved in the issue worktree.
92
+ *
93
+ * @param issueWorktreePath - Absolute path to the issue worktree
94
+ * @param subWorktrees - Array of sub-worktree info
95
+ * @returns Aggregate merge result
96
+ */
97
+ export declare function mergeAllSubWorktrees(issueWorktreePath: string, subWorktrees: SubWorktreeInfo[]): MergeBackResult;
98
+ /**
99
+ * Remove a single sub-worktree and its branch.
100
+ *
101
+ * @param issueWorktreePath - Absolute path to the issue worktree
102
+ * @param subWorktree - Sub-worktree info to clean up
103
+ */
104
+ export declare function cleanupSubWorktree(issueWorktreePath: string, subWorktree: SubWorktreeInfo): void;
105
+ /**
106
+ * Clean up all sub-worktrees for an issue worktree.
107
+ *
108
+ * Handles both successful cleanup and orphaned worktrees from
109
+ * interrupted sessions.
110
+ *
111
+ * @param issueWorktreePath - Absolute path to the issue worktree
112
+ * @param subWorktrees - Known sub-worktrees to clean (if provided)
113
+ */
114
+ export declare function cleanupAllSubWorktrees(issueWorktreePath: string, subWorktrees?: SubWorktreeInfo[]): void;
115
+ /**
116
+ * Detect and clean up orphaned sub-worktrees from interrupted sessions.
117
+ *
118
+ * Scans the `.exec-agents/` directory for any remaining worktrees and
119
+ * removes them. Also prunes stale worktree entries from git.
120
+ *
121
+ * @param issueWorktreePath - Absolute path to the issue worktree
122
+ */
123
+ export declare function cleanupOrphanedSubWorktrees(issueWorktreePath: string): void;
124
+ /**
125
+ * Format merge-back results for logging.
126
+ *
127
+ * @param result - Aggregate merge result
128
+ * @returns Human-readable summary
129
+ */
130
+ export declare function formatMergeResult(result: MergeBackResult): string;