| 1 | using CascadeIDE.Services; |
| 2 | |
| 3 | namespace CascadeIDE.Features.Agent.Environment; |
| 4 | |
| 5 | /// <summary>Git worktree profile (ADR 0148 W6). Best-effort: requires clean repo.</summary> |
| 6 | public sealed class AgentWorktreeSandbox |
| 7 | { |
| 8 | private readonly IGitCommandRunner _git; |
| 9 | |
| 10 | public AgentWorktreeSandbox(IGitCommandRunner git) => _git = git; |
| 11 | |
| 12 | public async Task<(bool Success, string? WorktreePath, string? Error)> TryCreateAsync( |
| 13 | string workspaceRoot, |
| 14 | string runId, |
| 15 | CancellationToken cancellationToken = default) |
| 16 | { |
| 17 | if (string.IsNullOrWhiteSpace(workspaceRoot) || !Directory.Exists(workspaceRoot)) |
| 18 | return (false, null, "Workspace root is missing."); |
| 19 | |
| 20 | var parent = Path.Combine(workspaceRoot, ".cascade-agent-worktrees"); |
| 21 | Directory.CreateDirectory(parent); |
| 22 | var target = Path.Combine(parent, runId); |
| 23 | |
| 24 | if (Directory.Exists(target)) |
| 25 | { |
| 26 | try |
| 27 | { |
| 28 | Directory.Delete(target, recursive: true); |
| 29 | } |
| 30 | catch (IOException ex) |
| 31 | { |
| 32 | return (false, null, ex.Message); |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | var args = new[] { "worktree", "add", target, "-b", $"agent/{runId[..8]}" }; |
| 37 | var (ok, _, output) = await _git.RunAsync(args, workspaceRoot, cancellationToken).ConfigureAwait(false); |
| 38 | if (!ok) |
| 39 | return (false, null, string.IsNullOrWhiteSpace(output) ? "git worktree add failed." : output.Trim()); |
| 40 | |
| 41 | return (true, target, null); |
| 42 | } |
| 43 | } |
| 44 | |