Forge
csharp3407750f
1using AgentForge.Data;
2using AgentForge.Options;
3using AgentForge.Plugin.Ci.Data;
4using AgentForge.Services;
5using Microsoft.EntityFrameworkCore;
6using Microsoft.Extensions.DependencyInjection;
7using Microsoft.Extensions.Options;
8
9namespace AgentForge.Plugin.Ci.Execution;
10
11public sealed class ForgeCiRunExecutor(IServiceScopeFactory scopeFactory)
12{
13 public async Task ExecuteAsync(string runId, CancellationToken cancellationToken = default)
14 {
15 using var scope = scopeFactory.CreateScope();
16 var services = scope.ServiceProvider;
17 var store = services.GetRequiredService<ForgeCiRunStore>();
18 var repository = services.GetRequiredService<ForgeRepository>();
19 var git = services.GetRequiredService<GitRepoService>();
20 var options = services.GetRequiredService<IOptions<ForgeOptions>>();
21 var db = services.GetRequiredService<ForgeDbContext>();
22
23 var pipeline = db.CiPipelineRuns.AsNoTracking().FirstOrDefault(r => r.Id == runId);
24 if (pipeline is null)
25 return;
26
27 var run = store.GetRun(pipeline.RepoId, runId);
28 if (run is null)
29 return;
30
31 var repo = db.Repos.AsNoTracking().FirstOrDefault(r => r.Id == run.RepoId);
32 if (repo is null)
33 return;
34
35 store.MarkRunRunning(runId);
36 ForgeCiKernelBridge.SyncAggregate(repository, run, repo.Name, ForgeCiRunStatusSlugs.Running, options);
37
38 var plan = store.LoadPlan(runId);
39 var steps = store.ListSteps(runId);
40 var workspace = ForgeCiRunExecutorWorkspace.PrepareWorkspace(git, repo.Name, runId);
41 var failed = false;
42
43 for (var i = 0; i < steps.Count; i++)
44 {
45 cancellationToken.ThrowIfCancellationRequested();
46 if (failed)
47 break;
48
49 var stepRecord = steps[i];
50 var planStep = plan[i];
51 store.MarkStepRunning(stepRecord.Id);
52
53 var result = await ForgeCiTaskRunner.ExecuteAsync(planStep, workspace, cancellationToken)
54 .ConfigureAwait(false);
55
56 var stepStatus = result.Success ? ForgeCiRunStatusSlugs.Success : ForgeCiRunStatusSlugs.Failure;
57 store.MarkStepFinished(stepRecord.Id, stepStatus, result.Log);
58 if (!result.Success)
59 failed = true;
60 }
61
62 var finalStatus = failed ? ForgeCiRunStatusSlugs.Failure : ForgeCiRunStatusSlugs.Success;
63 store.MarkRunFinished(runId, finalStatus);
64
65 var finished = store.GetRun(run.RepoId, runId)!;
66 ForgeCiKernelBridge.SyncAggregate(repository, finished, repo.Name, finalStatus, options);
67
68 try
69 {
70 if (Directory.Exists(workspace))
71 Directory.Delete(workspace, recursive: true);
72 }
73 catch
74 {
75 // Best-effort cleanup.
76 }
77 }
78}
79
80internal static class ForgeCiRunExecutorWorkspace
81{
82 internal static string PrepareWorkspace(GitRepoService git, string repoName, string runId)
83 {
84 var root = Path.Combine(Path.GetTempPath(), "forge-ci-runs", runId);
85 Directory.CreateDirectory(root);
86
87 if (!git.BareRepoExists(repoName))
88 return root;
89
90 try
91 {
92 var barePath = git.GetBareRepoPath(repoName);
93 CloneBareToWorkspace(barePath, root);
94 }
95 catch
96 {
97 // Fall back to empty workspace.
98 }
99
100 return root;
101 }
102
103 private static void CloneBareToWorkspace(string barePath, string workspace)
104 {
105 var psi = new System.Diagnostics.ProcessStartInfo
106 {
107 FileName = "git",
108 Arguments = $"clone \"{barePath}\" \"{workspace}\"",
109 RedirectStandardOutput = true,
110 RedirectStandardError = true,
111 UseShellExecute = false,
112 CreateNoWindow = true,
113 };
114
115 using var process = System.Diagnostics.Process.Start(psi)
116 ?? throw new InvalidOperationException("Failed to start git clone.");
117 process.WaitForExit();
118 if (process.ExitCode != 0)
119 throw new InvalidOperationException("git clone failed.");
120 }
121}
122
View only · write via MCP/CIDE