sequant 2.0.1 â 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bin/cli.js +2 -1
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +6 -0
- package/dist/marketplace/external_plugins/sequant/README.md +58 -8
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +19 -8
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +36 -49
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +158 -48
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +354 -352
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +1155 -33
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +35 -4
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +2157 -104
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +386 -0
- package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +38 -664
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +505 -120
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +246 -1
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +138 -1
- package/dist/src/commands/dashboard.js +1 -1
- package/dist/src/commands/doctor.js +1 -1
- package/dist/src/commands/init.js +10 -10
- package/dist/src/commands/logs.js +1 -1
- package/dist/src/commands/run.js +49 -39
- package/dist/src/commands/state.js +3 -3
- package/dist/src/commands/status.js +5 -5
- package/dist/src/commands/sync.js +8 -8
- package/dist/src/commands/update.js +16 -16
- package/dist/src/lib/cli-ui.js +20 -19
- package/dist/src/lib/merge-check/index.js +2 -2
- package/dist/src/lib/settings.d.ts +8 -0
- package/dist/src/lib/settings.js +1 -0
- package/dist/src/lib/shutdown.js +1 -1
- package/dist/src/lib/templates.js +2 -0
- package/dist/src/lib/wizard.js +6 -4
- package/dist/src/lib/workflow/batch-executor.js +1 -1
- package/dist/src/lib/workflow/log-writer.js +6 -6
- package/dist/src/lib/workflow/metrics-writer.js +5 -3
- package/dist/src/lib/workflow/phase-executor.js +5 -1
- package/dist/src/lib/workflow/platforms/github.js +5 -1
- package/dist/src/lib/workflow/state-cleanup.js +1 -1
- package/dist/src/lib/workflow/state-manager.js +15 -13
- package/dist/src/lib/workflow/state-rebuild.js +2 -2
- package/dist/src/lib/workflow/types.d.ts +11 -0
- package/dist/src/lib/workflow/worktree-manager.js +40 -41
- package/dist/src/lib/worktree-isolation.d.ts +130 -0
- package/dist/src/lib/worktree-isolation.js +310 -0
- package/package.json +8 -8
- package/templates/agents/sequant-explorer.md +23 -0
- package/templates/agents/sequant-implementer.md +18 -0
- package/templates/agents/sequant-qa-checker.md +24 -0
- package/templates/agents/sequant-testgen.md +25 -0
- package/templates/scripts/cleanup-worktree.sh +18 -0
- package/templates/skills/_shared/references/subagent-types.md +158 -48
- package/templates/skills/exec/SKILL.md +72 -6
- package/templates/skills/qa/SKILL.md +8 -217
- package/templates/skills/spec/SKILL.md +446 -120
- 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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
713
|
+
console.log(chalk.yellow(` ! Dependency reinstall failed: ${error.trim()}`));
|
|
715
714
|
return false;
|
|
716
715
|
}
|
|
717
|
-
console.log(chalk.green(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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;
|