opencastle 0.33.6 → 0.33.7

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 (33) hide show
  1. package/dist/cli/convoy/engine.d.ts +5 -0
  2. package/dist/cli/convoy/engine.d.ts.map +1 -1
  3. package/dist/cli/convoy/engine.js +85 -16
  4. package/dist/cli/convoy/engine.js.map +1 -1
  5. package/dist/cli/convoy/engine.test.js +10 -12
  6. package/dist/cli/convoy/engine.test.js.map +1 -1
  7. package/dist/cli/convoy/pipeline.d.ts +3 -0
  8. package/dist/cli/convoy/pipeline.d.ts.map +1 -1
  9. package/dist/cli/convoy/pipeline.js +88 -18
  10. package/dist/cli/convoy/pipeline.js.map +1 -1
  11. package/dist/cli/run.d.ts.map +1 -1
  12. package/dist/cli/run.js +1 -123
  13. package/dist/cli/run.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/cli/convoy/engine.test.ts +10 -12
  16. package/src/cli/convoy/engine.ts +84 -16
  17. package/src/cli/convoy/pipeline.ts +81 -19
  18. package/src/cli/run.ts +0 -118
  19. package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
  20. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +4 -4
  21. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +6 -6
  22. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
  23. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
  24. package/src/dashboard/dist/data/convoys/demo-docs-update.json +3 -3
  25. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +4 -4
  26. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  27. package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
  28. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +4 -4
  29. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +6 -6
  30. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
  31. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
  32. package/src/dashboard/public/data/convoys/demo-docs-update.json +3 -3
  33. package/src/dashboard/public/data/convoys/demo-perf-opt.json +4 -4
@@ -16,6 +16,11 @@ export interface ConvoyEngineOptions {
16
16
  _mergeQueue?: MergeQueue;
17
17
  /** Override for test injection. Pass `ensureBranch` for real behavior, or a mock. */
18
18
  _ensureBranch?: (branchName: string, basePath: string) => Promise<void>;
19
+ /** Pass `null` to skip convoy-level worktree creation (test mode).
20
+ * Pass a string path to inject a specific worktree directory.
21
+ * Omit (undefined) for real worktree creation.
22
+ * Also skipped when `_ensureBranch` is provided (backward-compat for tests). */
23
+ _convoyWorktreeDir?: string | null;
19
24
  /** Injectable for test injection of the review pipeline. */
20
25
  _reviewRunner?: (task: TaskRecord, level: ReviewLevel, reviewerModel: string) => Promise<ReviewResult>;
21
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/engine.ts"],"names":[],"mappings":"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"}
@@ -2612,11 +2612,36 @@ export function createConvoyEngine(options) {
2612
2612
  const convoyId = `convoy-${startTime}`;
2613
2613
  const specHash = createHash('sha256').update(specYaml).digest('hex');
2614
2614
  const baseBranch = spec.branch ?? (await getCurrentBranch());
2615
- // Ensure target branch exists before acquiring any locks.
2616
- // Uses _ensureBranch injection so callers/tests can override.
2615
+ // Create convoy-level worktree for branch isolation.
2616
+ // Skipped when _convoyWorktreeDir is null or _ensureBranch is injected (test mode).
2617
+ let effectiveBasePath = basePath;
2618
+ let convoyWorktreeDir;
2617
2619
  if (spec.branch !== undefined) {
2618
- const branchFn = options._ensureBranch ?? ensureBranch;
2619
- await branchFn(spec.branch, basePath);
2620
+ const skipWorktree = options._convoyWorktreeDir === null || options._ensureBranch !== undefined;
2621
+ if (!skipWorktree) {
2622
+ if (typeof options._convoyWorktreeDir === 'string') {
2623
+ effectiveBasePath = options._convoyWorktreeDir;
2624
+ convoyWorktreeDir = options._convoyWorktreeDir;
2625
+ }
2626
+ else {
2627
+ const worktreeId = `convoy-root-${Date.now()}`;
2628
+ convoyWorktreeDir = join(basePath, '.opencastle', 'worktrees', worktreeId);
2629
+ mkdirSync(dirname(convoyWorktreeDir), { recursive: true });
2630
+ let branchExists = false;
2631
+ try {
2632
+ await execFile('git', ['rev-parse', '--verify', spec.branch], { cwd: basePath });
2633
+ branchExists = true;
2634
+ }
2635
+ catch { /* branch doesn't exist */ }
2636
+ if (branchExists) {
2637
+ await execFile('git', ['worktree', 'add', convoyWorktreeDir, spec.branch], { cwd: basePath });
2638
+ }
2639
+ else {
2640
+ await execFile('git', ['worktree', 'add', '-b', spec.branch, convoyWorktreeDir], { cwd: basePath });
2641
+ }
2642
+ effectiveBasePath = convoyWorktreeDir;
2643
+ }
2644
+ }
2620
2645
  }
2621
2646
  mkdirSync(dirname(dbPath), { recursive: true });
2622
2647
  const lockDb = new DatabaseSync(dbPath);
@@ -2648,10 +2673,10 @@ export function createConvoyEngine(options) {
2648
2673
  const store = createConvoyStore(dbPath);
2649
2674
  const ndjsonPath = options.logsDir
2650
2675
  ? join(options.logsDir, 'convoys', `${convoyId}.ndjson`)
2651
- : ndjsonPathForConvoy(convoyId, basePath);
2676
+ : ndjsonPathForConvoy(convoyId, effectiveBasePath);
2652
2677
  const events = createEventEmitter(store, { ndjsonPath });
2653
- const wtManager = options._worktreeManager ?? createWorktreeManager(basePath);
2654
- const mergeQueue = options._mergeQueue ?? createMergeQueue(basePath);
2678
+ const wtManager = options._worktreeManager ?? createWorktreeManager(effectiveBasePath);
2679
+ const mergeQueue = options._mergeQueue ?? createMergeQueue(effectiveBasePath);
2655
2680
  let result;
2656
2681
  try {
2657
2682
  store.insertConvoy({
@@ -2699,13 +2724,19 @@ export function createConvoyEngine(options) {
2699
2724
  }
2700
2725
  store.updateConvoyStatus(convoyId, 'running', { started_at: new Date().toISOString() });
2701
2726
  events.emit('convoy_started', { name: spec.name }, { convoy_id: convoyId });
2702
- result = await runConvoy(convoyId, spec, adapter, store, events, wtManager, mergeQueue, basePath, baseBranch, verbose, startTime, ndjsonPath, options._reviewRunner);
2727
+ result = await runConvoy(convoyId, spec, adapter, store, events, wtManager, mergeQueue, effectiveBasePath, baseBranch, verbose, startTime, ndjsonPath, options._reviewRunner);
2703
2728
  }
2704
2729
  finally {
2705
2730
  events.close();
2706
2731
  store.close();
2707
2732
  lock.release();
2708
2733
  lockDb.close();
2734
+ if (convoyWorktreeDir) {
2735
+ try {
2736
+ await execFile('git', ['worktree', 'remove', convoyWorktreeDir, '--force'], { cwd: basePath });
2737
+ }
2738
+ catch { /* ignore cleanup errors */ }
2739
+ }
2709
2740
  }
2710
2741
  return result;
2711
2742
  }
@@ -2739,12 +2770,9 @@ export function createConvoyEngine(options) {
2739
2770
  }
2740
2771
  lock.startHeartbeat();
2741
2772
  const store = createConvoyStore(dbPath);
2742
- const ndjsonPath = options.logsDir
2743
- ? join(options.logsDir, 'convoys', `${convoyId}.ndjson`)
2744
- : ndjsonPathForConvoy(convoyId, basePath);
2745
- const events = createEventEmitter(store, { ndjsonPath });
2746
- const wtManager = options._worktreeManager ?? createWorktreeManager(basePath);
2747
- const mergeQueue = options._mergeQueue ?? createMergeQueue(basePath);
2773
+ let effectiveBasePath = basePath;
2774
+ let convoyWorktreeDir;
2775
+ let events;
2748
2776
  let result;
2749
2777
  try {
2750
2778
  const convoy = store.getConvoy(convoyId);
@@ -2752,6 +2780,41 @@ export function createConvoyEngine(options) {
2752
2780
  throw new Error(`Convoy "${convoyId}" not found in store`);
2753
2781
  }
2754
2782
  const baseBranch = convoy.branch ?? spec.branch ?? (await getCurrentBranch());
2783
+ const convoyBranch = convoy.branch ?? spec.branch;
2784
+ // Create convoy-level worktree for branch isolation.
2785
+ if (convoyBranch !== undefined) {
2786
+ const skipWorktree = options._convoyWorktreeDir === null || options._ensureBranch !== undefined;
2787
+ if (!skipWorktree) {
2788
+ if (typeof options._convoyWorktreeDir === 'string') {
2789
+ effectiveBasePath = options._convoyWorktreeDir;
2790
+ convoyWorktreeDir = options._convoyWorktreeDir;
2791
+ }
2792
+ else {
2793
+ const worktreeId = `convoy-root-${Date.now()}`;
2794
+ convoyWorktreeDir = join(basePath, '.opencastle', 'worktrees', worktreeId);
2795
+ mkdirSync(dirname(convoyWorktreeDir), { recursive: true });
2796
+ let branchExists = false;
2797
+ try {
2798
+ await execFile('git', ['rev-parse', '--verify', convoyBranch], { cwd: basePath });
2799
+ branchExists = true;
2800
+ }
2801
+ catch { /* branch doesn't exist */ }
2802
+ if (branchExists) {
2803
+ await execFile('git', ['worktree', 'add', convoyWorktreeDir, convoyBranch], { cwd: basePath });
2804
+ }
2805
+ else {
2806
+ await execFile('git', ['worktree', 'add', '-b', convoyBranch, convoyWorktreeDir], { cwd: basePath });
2807
+ }
2808
+ effectiveBasePath = convoyWorktreeDir;
2809
+ }
2810
+ }
2811
+ }
2812
+ const ndjsonPath = options.logsDir
2813
+ ? join(options.logsDir, 'convoys', `${convoyId}.ndjson`)
2814
+ : ndjsonPathForConvoy(convoyId, effectiveBasePath);
2815
+ events = createEventEmitter(store, { ndjsonPath });
2816
+ const wtManager = options._worktreeManager ?? createWorktreeManager(effectiveBasePath);
2817
+ const mergeQueue = options._mergeQueue ?? createMergeQueue(effectiveBasePath);
2755
2818
  // Reset interrupted tasks and mark their workers as killed
2756
2819
  const allTasks = store.getTasksByConvoy(convoyId);
2757
2820
  for (const task of allTasks) {
@@ -2779,13 +2842,19 @@ export function createConvoyEngine(options) {
2779
2842
  // NDJSON recovery: truncate partial lines, replay missing events
2780
2843
  recoverNdjson(store, convoyId, ndjsonPath);
2781
2844
  events.emit('convoy_resumed', { original_created_at: convoy.created_at }, { convoy_id: convoyId });
2782
- result = await runConvoy(convoyId, spec, adapter, store, events, wtManager, mergeQueue, basePath, baseBranch, verbose, startTime, ndjsonPath, options._reviewRunner);
2845
+ result = await runConvoy(convoyId, spec, adapter, store, events, wtManager, mergeQueue, effectiveBasePath, baseBranch, verbose, startTime, ndjsonPath, options._reviewRunner);
2783
2846
  }
2784
2847
  finally {
2785
- events.close();
2848
+ events?.close();
2786
2849
  store.close();
2787
2850
  lock.release();
2788
2851
  lockDb.close();
2852
+ if (convoyWorktreeDir) {
2853
+ try {
2854
+ await execFile('git', ['worktree', 'remove', convoyWorktreeDir, '--force'], { cwd: basePath });
2855
+ }
2856
+ catch { /* ignore cleanup errors */ }
2857
+ }
2789
2858
  }
2790
2859
  return result;
2791
2860
  }