Module: Aidp::Worktree

Defined in:
lib/aidp/worktree.rb

Overview

Manages git worktree operations for parallel workstreams. Each workstream gets an isolated git worktree with its own branch, allowing multiple agents to work concurrently without conflicts.

Defined Under Namespace

Classes: Error, NotInGitRepo, WorktreeExists, WorktreeNotFound

Class Method Summary collapse

Class Method Details

.create(slug:, project_dir: Dir.pwd, branch: nil, base_branch: nil, task: nil) ⇒ Hash

Create a new git worktree for a workstream

Parameters:

  • slug (String)

    Short identifier for this workstream (e.g., “iss-123-fix-login”)

  • project_dir (String) (defaults to: Dir.pwd)

    Project root directory

  • branch (String, nil) (defaults to: nil)

    Branch name (defaults to “aidp/#slug”)

  • base_branch (String) (defaults to: nil)

    Branch to create from (defaults to current branch)

Returns:

  • (Hash)

    Worktree info: { path:, branch:, slug: }



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/aidp/worktree.rb', line 26

def create(slug:, project_dir: Dir.pwd, branch: nil, base_branch: nil, task: nil)
  ensure_git_repo!(project_dir)

  branch ||= "aidp/#{slug}"
  worktree_path = worktree_path_for(slug, project_dir)

  if Dir.exist?(worktree_path)
    raise WorktreeExists, "Worktree already exists at #{worktree_path}"
  end

  branch_exists = branch_exists?(project_dir, branch)
  run_worktree_add!(
    project_dir: project_dir,
    branch: branch,
    branch_exists: branch_exists,
    worktree_path: worktree_path,
    base_branch: base_branch
  )

  # Initialize .aidp directory in the worktree
  ensure_aidp_dir(worktree_path)

  # Register the worktree
  register_worktree(slug, worktree_path, branch, project_dir)

  # Initialize per-workstream state (task, counters, events)
  Aidp::WorkstreamState.init(slug: slug, project_dir: project_dir, task: task)

  {
    slug: slug,
    path: worktree_path,
    branch: branch
  }
end

.exists?(slug:, project_dir: Dir.pwd) ⇒ Boolean

Check if a worktree exists

Parameters:

  • slug (String)

    Workstream identifier

  • project_dir (String) (defaults to: Dir.pwd)

    Project root directory

Returns:

  • (Boolean)


140
141
142
# File 'lib/aidp/worktree.rb', line 140

def exists?(slug:, project_dir: Dir.pwd)
  !info(slug: slug, project_dir: project_dir).nil?
end

.info(slug:, project_dir: Dir.pwd) ⇒ Hash?

Get info for a specific worktree

Parameters:

  • slug (String)

    Workstream identifier

  • project_dir (String) (defaults to: Dir.pwd)

    Project root directory

Returns:

  • (Hash, nil)

    Worktree info or nil if not found



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/aidp/worktree.rb', line 121

def info(slug:, project_dir: Dir.pwd)
  registry = load_registry(project_dir)
  data = registry[slug]
  return nil unless data

  {
    slug: slug,
    path: data["path"],
    branch: data["branch"],
    created_at: data["created_at"],
    active: Dir.exist?(data["path"])
  }
end

.list(project_dir: Dir.pwd) ⇒ Array<Hash>

List all active worktrees for this project

Parameters:

  • project_dir (String) (defaults to: Dir.pwd)

    Project root directory

Returns:

  • (Array<Hash>)

    Array of worktree info hashes



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/aidp/worktree.rb', line 65

def list(project_dir: Dir.pwd)
  registry = load_registry(project_dir)
  registry.map do |slug, info|
    {
      slug: slug,
      path: info["path"],
      branch: info["branch"],
      created_at: info["created_at"],
      active: Dir.exist?(info["path"])
    }
  end
end

.remove(slug:, project_dir: Dir.pwd, delete_branch: false) ⇒ Object

Remove a worktree and optionally its branch

Parameters:

  • slug (String)

    Workstream identifier

  • project_dir (String) (defaults to: Dir.pwd)

    Project root directory

  • delete_branch (Boolean) (defaults to: false)

    Whether to delete the git branch

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/aidp/worktree.rb', line 83

def remove(slug:, project_dir: Dir.pwd, delete_branch: false)
  registry = load_registry(project_dir)
  info = registry[slug]

  raise WorktreeNotFound, "Worktree '#{slug}' not found" unless info

  worktree_path = info["path"]
  branch = info["branch"]

  # Remove the git worktree
  if Dir.exist?(worktree_path)
    Dir.chdir(project_dir) do
      system("git", "worktree", "remove", worktree_path, "--force", out: File::NULL, err: File::NULL)
    end
  end

  # Remove the branch if requested
  if delete_branch
    Dir.chdir(project_dir) do
      system("git", "branch", "-D", branch, out: File::NULL, err: File::NULL)
    end
  end

  # Mark state removed (if exists) then unregister
  if Aidp::WorkstreamState.read(slug: slug, project_dir: project_dir)
    Aidp::WorkstreamState.mark_removed(slug: slug, project_dir: project_dir)
  end
  # Unregister the worktree
  unregister_worktree(slug, project_dir)

  true
end