Skip to content

Commit 2bd68ea

Browse files
gfraiteurclaude
andcommitted
Add git branch display to MCP approval dialog and AI risk assessment
- Display git branch in approval dialog UI (new row in details section) - Include git branch in AI risk assessment prompt for better context - Create GitHelper class to consolidate duplicate git operations - Sync GetBranch method now wraps async GetBranchAsync to avoid code duplication Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ab74768 commit 2bd68ea

8 files changed

Lines changed: 111 additions & 114 deletions

File tree

src/PostSharp.Engineering.McpApprovalServer/Mcp/Services/CommandHistoryService.cs

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
22

33
using PostSharp.Engineering.McpApprovalServer.Mcp.Models;
4+
using PostSharp.Engineering.McpApprovalServer.Services;
45
using System;
56
using System.Collections.Generic;
67
using System.Globalization;
@@ -195,7 +196,7 @@ private static void AppendToAuditTrail( CommandRecord record )
195196
var auditFilePath = Path.Combine( _auditDirectory, $"audit-{dateStr}.log" );
196197

197198
// Get git branch for the working directory
198-
var gitBranch = GetGitBranch( record.WorkingDirectory ) ?? "N/A";
199+
var gitBranch = GitHelper.GetBranch( record.WorkingDirectory );
199200

200201
// Format: timestamp | approved/rejected | command | purpose | working_dir | branch | exit_code
201202
var status = record.Approved ? "APPROVED" : "REJECTED";
@@ -217,42 +218,4 @@ private static void AppendToAuditTrail( CommandRecord record )
217218
System.Diagnostics.Debug.WriteLine( $"Failed to append to audit trail: {ex.Message}" );
218219
}
219220
}
220-
221-
private static string? GetGitBranch( string workingDirectory )
222-
{
223-
try
224-
{
225-
if ( !Directory.Exists( workingDirectory ) )
226-
{
227-
return null;
228-
}
229-
230-
var startInfo = new System.Diagnostics.ProcessStartInfo
231-
{
232-
FileName = "git",
233-
Arguments = "rev-parse --abbrev-ref HEAD",
234-
WorkingDirectory = workingDirectory,
235-
RedirectStandardOutput = true,
236-
RedirectStandardError = true,
237-
UseShellExecute = false,
238-
CreateNoWindow = true
239-
};
240-
241-
using var process = System.Diagnostics.Process.Start( startInfo );
242-
243-
if ( process == null )
244-
{
245-
return null;
246-
}
247-
248-
var output = process.StandardOutput.ReadToEnd().Trim();
249-
process.WaitForExit();
250-
251-
return process.ExitCode == 0 ? output : null;
252-
}
253-
catch
254-
{
255-
return null;
256-
}
257-
}
258221
}

src/PostSharp.Engineering.McpApprovalServer/Mcp/Services/RiskAnalyzer.cs

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
22

33
using PostSharp.Engineering.McpApprovalServer.Mcp.Models;
4+
using PostSharp.Engineering.McpApprovalServer.Services;
45
using System;
56
using System.Collections.Generic;
67
using System.Diagnostics;
@@ -272,6 +273,14 @@ private static async Task<string> BuildAnalysisPromptAsync(
272273
sb.Append( CultureInfo.InvariantCulture, $"**Command:** `{command}`" ).AppendLine();
273274
sb.Append( CultureInfo.InvariantCulture, $"**Claimed purpose:** {claimedPurpose}" ).AppendLine();
274275
sb.Append( CultureInfo.InvariantCulture, $"**Working directory:** {workingDirectory}" ).AppendLine();
276+
277+
var gitBranch = await GitHelper.GetBranchAsync( workingDirectory, cancellationToken );
278+
279+
if ( !string.IsNullOrEmpty( gitBranch ) )
280+
{
281+
sb.Append( CultureInfo.InvariantCulture, $"**Git branch:** {gitBranch}" ).AppendLine();
282+
}
283+
275284
sb.AppendLine();
276285

277286
// For git push commands, include the commit diff for analysis
@@ -348,7 +357,7 @@ private static bool IsGitPushCommand( string command )
348357
try
349358
{
350359
// Get the list of commits that would be pushed
351-
var logOutput = await RunGitCommandAsync(
360+
var logOutput = await GitHelper.RunCommandAsync(
352361
workingDirectory,
353362
"log --oneline @{upstream}..HEAD",
354363
cancellationToken );
@@ -359,7 +368,7 @@ private static bool IsGitPushCommand( string command )
359368
}
360369

361370
// Get the diff of commits to be pushed (limit to reasonable size)
362-
var diffOutput = await RunGitCommandAsync(
371+
var diffOutput = await GitHelper.RunCommandAsync(
363372
workingDirectory,
364373
"diff @{upstream}..HEAD",
365374
cancellationToken );
@@ -394,32 +403,4 @@ private static bool IsGitPushCommand( string command )
394403
}
395404
}
396405

397-
private static async Task<string> RunGitCommandAsync(
398-
string workingDirectory,
399-
string arguments,
400-
CancellationToken cancellationToken )
401-
{
402-
var startInfo = new ProcessStartInfo
403-
{
404-
FileName = "git",
405-
Arguments = arguments,
406-
WorkingDirectory = workingDirectory,
407-
RedirectStandardOutput = true,
408-
RedirectStandardError = true,
409-
UseShellExecute = false,
410-
CreateNoWindow = true
411-
};
412-
413-
using var process = Process.Start( startInfo );
414-
415-
if ( process == null )
416-
{
417-
return string.Empty;
418-
}
419-
420-
var output = await process.StandardOutput.ReadToEndAsync( cancellationToken );
421-
await process.WaitForExitAsync( cancellationToken );
422-
423-
return output;
424-
}
425406
}

src/PostSharp.Engineering.McpApprovalServer/Services/ApprovalRequest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public sealed class ApprovalRequest
1919

2020
public required string WorkingDirectory { get; init; }
2121

22+
public required string GitBranch { get; init; }
23+
2224
public required RiskAssessment CombinedAssessment { get; init; }
2325

2426
public required RiskAssessment AiAssessment { get; init; }

src/PostSharp.Engineering.McpApprovalServer/Services/ApprovalRequestQueue.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public Task<bool> EnqueueAsync(
6060
Command = command,
6161
ClaimedPurpose = claimedPurpose,
6262
WorkingDirectory = workingDirectory,
63+
GitBranch = GitHelper.GetBranch( workingDirectory ),
6364
CombinedAssessment = combinedAssessment,
6465
AiAssessment = aiAssessment,
6566
RegexAssessment = regexAssessment,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace PostSharp.Engineering.McpApprovalServer.Services;
9+
10+
/// <summary>
11+
/// Helper class for git operations.
12+
/// </summary>
13+
internal static class GitHelper
14+
{
15+
/// <summary>
16+
/// Gets the current git branch for a directory synchronously.
17+
/// This is a synchronous wrapper around <see cref="GetBranchAsync"/>.
18+
/// </summary>
19+
public static string GetBranch( string workingDirectory )
20+
{
21+
return GetBranchAsync( workingDirectory, CancellationToken.None ).GetAwaiter().GetResult()
22+
?? "(not a git repo)";
23+
}
24+
25+
/// <summary>
26+
/// Gets the current git branch for a directory asynchronously.
27+
/// </summary>
28+
public static async Task<string?> GetBranchAsync( string workingDirectory, CancellationToken cancellationToken = default )
29+
{
30+
try
31+
{
32+
if ( !Directory.Exists( workingDirectory ) )
33+
{
34+
return "(directory not found)";
35+
}
36+
37+
var output = await RunCommandAsync( workingDirectory, "rev-parse --abbrev-ref HEAD", cancellationToken );
38+
39+
return string.IsNullOrWhiteSpace( output ) ? null : output.Trim();
40+
}
41+
catch
42+
{
43+
return null;
44+
}
45+
}
46+
47+
/// <summary>
48+
/// Runs a git command and returns its output.
49+
/// </summary>
50+
public static async Task<string> RunCommandAsync(
51+
string workingDirectory,
52+
string arguments,
53+
CancellationToken cancellationToken = default )
54+
{
55+
var startInfo = new ProcessStartInfo
56+
{
57+
FileName = "git",
58+
Arguments = arguments,
59+
WorkingDirectory = workingDirectory,
60+
RedirectStandardOutput = true,
61+
RedirectStandardError = true,
62+
UseShellExecute = false,
63+
CreateNoWindow = true
64+
};
65+
66+
using var process = Process.Start( startInfo );
67+
68+
if ( process == null )
69+
{
70+
return string.Empty;
71+
}
72+
73+
var output = await process.StandardOutput.ReadToEndAsync( cancellationToken );
74+
await process.WaitForExitAsync( cancellationToken );
75+
76+
return output;
77+
}
78+
}

src/PostSharp.Engineering.McpApprovalServer/ViewModels/ApprovalViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public ApprovalViewModel( ApprovalRequest request, ApprovalRequestQueue queue )
3535

3636
public string WorkingDirectory => this._request.WorkingDirectory;
3737

38+
public string GitBranch => this._request.GitBranch;
39+
3840
public string ReceivedAt => this._request.ReceivedAt.ToString( "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture );
3941

4042
// Assessments

src/PostSharp.Engineering.McpApprovalServer/ViewModels/HistoryViewModel.cs

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Collections.ObjectModel;
88
using System.Diagnostics;
99
using System.Globalization;
10-
using System.IO;
1110
using System.Linq;
1211
using System.Windows;
1312

@@ -125,14 +124,14 @@ public HistoryItemViewModel( CommandRecord record )
125124
{
126125
this._record = record;
127126
this._pendingRequest = null;
128-
this.GitBranch = GetGitBranch( record.WorkingDirectory );
127+
this.GitBranch = GitHelper.GetBranch( record.WorkingDirectory );
129128
}
130129

131130
public HistoryItemViewModel( ApprovalRequest pendingRequest )
132131
{
133132
this._record = null;
134133
this._pendingRequest = pendingRequest;
135-
this.GitBranch = GetGitBranch( pendingRequest.WorkingDirectory );
134+
this.GitBranch = GitHelper.GetBranch( pendingRequest.WorkingDirectory );
136135
}
137136

138137
public bool IsPending => this._pendingRequest != null;
@@ -185,42 +184,4 @@ public string ExitCodeDisplay
185184
return this._record!.ExitCode?.ToString( CultureInfo.InvariantCulture ) ?? "-";
186185
}
187186
}
188-
189-
private static string GetGitBranch( string workingDirectory )
190-
{
191-
try
192-
{
193-
if ( !Directory.Exists( workingDirectory ) )
194-
{
195-
return "N/A";
196-
}
197-
198-
var startInfo = new ProcessStartInfo
199-
{
200-
FileName = "git",
201-
Arguments = "rev-parse --abbrev-ref HEAD",
202-
WorkingDirectory = workingDirectory,
203-
RedirectStandardOutput = true,
204-
RedirectStandardError = true,
205-
UseShellExecute = false,
206-
CreateNoWindow = true
207-
};
208-
209-
using var process = Process.Start( startInfo );
210-
211-
if ( process == null )
212-
{
213-
return "N/A";
214-
}
215-
216-
var output = process.StandardOutput.ReadToEnd().Trim();
217-
process.WaitForExit();
218-
219-
return process.ExitCode == 0 ? output : "N/A";
220-
}
221-
catch
222-
{
223-
return "N/A";
224-
}
225-
}
226187
}

src/PostSharp.Engineering.McpApprovalServer/Views/ApprovalWindow.xaml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<RowDefinition Height="Auto" />
5757
<RowDefinition Height="Auto" />
5858
<RowDefinition Height="Auto" />
59+
<RowDefinition Height="Auto" />
5960
</Grid.RowDefinitions>
6061
<Grid.ColumnDefinitions>
6162
<ColumnDefinition Width="120" />
@@ -83,16 +84,24 @@
8384
Background="#F5F5F5"
8485
Margin="0,5" />
8586

86-
<TextBlock Grid.Row="2" Grid.Column="0" Text="Purpose:" FontWeight="Bold" Margin="0,5" />
87-
<TextBox Grid.Row="2" Grid.Column="1"
87+
<TextBlock Grid.Row="2" Grid.Column="0" Text="Git Branch:" FontWeight="Bold" Margin="0,5" />
88+
<TextBlock Grid.Row="2" Grid.Column="1"
89+
Text="{Binding GitBranch}"
90+
FontFamily="Consolas"
91+
FontWeight="SemiBold"
92+
Foreground="#1976D2"
93+
Margin="0,5" />
94+
95+
<TextBlock Grid.Row="3" Grid.Column="0" Text="Purpose:" FontWeight="Bold" Margin="0,5" />
96+
<TextBox Grid.Row="3" Grid.Column="1"
8897
Text="{Binding ClaimedPurpose, Mode=OneWay}"
8998
IsReadOnly="True"
9099
TextWrapping="Wrap"
91100
Background="#F5F5F5"
92101
Margin="0,5" />
93102

94-
<TextBlock Grid.Row="3" Grid.Column="0" Text="Received:" FontWeight="Bold" Margin="0,5" />
95-
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ReceivedAt}" Margin="0,5" />
103+
<TextBlock Grid.Row="4" Grid.Column="0" Text="Received:" FontWeight="Bold" Margin="0,5" />
104+
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding ReceivedAt}" Margin="0,5" />
96105
</Grid>
97106

98107
<!-- Risk assessments in tabs -->

0 commit comments

Comments
 (0)