Skip to content

Commit 79d1965

Browse files
authored
AI Advisor (#67)
* Added CLAUDE.md for the MCP task * Added stub for MCP server * Settings for AI Endpoint * Added AI client * AI Advidor Dialog * Markdown * Cleanup * Update readme * Prompt improvements, hopefully * Unify settings
1 parent 1fdb517 commit 79d1965

20 files changed

+898
-84
lines changed

.editorconfig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ dotnet_analyzer_diagnostic.severity = none
88
# Microsoft .NET properties
99
csharp_new_line_before_members_in_object_initializers = false
1010
csharp_preferred_modifier_order = internal, public, private, protected, file, new, static, override, abstract, sealed, virtual, async, extern, unsafe, volatile, readonly, required:suggestion
11+
csharp_prefer_braces = true:none
1112
csharp_preserve_single_line_blocks = true
1213

1314
# ReSharper properties
1415
resharper_accessor_owner_body = accessors_with_expression_body
1516
resharper_blank_lines_around_block_case_section = 1
1617
resharper_braces_for_foreach = required
17-
resharper_braces_for_ifelse = not_required
18+
resharper_braces_for_ifelse = required
1819
resharper_braces_for_while = required
1920
resharper_braces_redundant = false
2021
resharper_csharp_keep_blank_lines_in_code = 100
@@ -23,7 +24,7 @@ resharper_csharp_keep_existing_enum_arrangement = false
2324
resharper_csharp_max_line_length = 199
2425
resharper_csharp_remove_blank_lines_near_braces_in_declarations = false
2526
resharper_indent_preprocessor_other = do_not_change
26-
resharper_instance_members_qualify_declared_in =
27+
resharper_instance_members_qualify_declared_in =
2728
resharper_keep_existing_declaration_block_arrangement = false
2829
resharper_parentheses_redundancy_style = remove
2930
resharper_place_accessorholder_attribute_on_same_line = true

CSharpCodeAnalyst.sln

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 18
4-
VisualStudioVersion = 18.0.11205.157 d18.0
4+
VisualStudioVersion = 18.0.11205.157
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpCodeAnalyst", "CSharpCodeAnalyst\CSharpCodeAnalyst.csproj", "{E445DF38-70F2-4972-9567-6591E1E3C833}"
77
ProjectSection(ProjectDependencies) = postProject
@@ -31,29 +31,85 @@ EndProject
3131
Global
3232
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3333
Debug|Any CPU = Debug|Any CPU
34+
Debug|x64 = Debug|x64
35+
Debug|x86 = Debug|x86
3436
Release|Any CPU = Release|Any CPU
37+
Release|x64 = Release|x64
38+
Release|x86 = Release|x86
3539
EndGlobalSection
3640
GlobalSection(ProjectConfigurationPlatforms) = postSolution
3741
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3842
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|Any CPU.Build.0 = Debug|Any CPU
43+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|x64.ActiveCfg = Debug|Any CPU
44+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|x64.Build.0 = Debug|Any CPU
45+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|x86.ActiveCfg = Debug|Any CPU
46+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Debug|x86.Build.0 = Debug|Any CPU
3947
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|Any CPU.ActiveCfg = Release|Any CPU
4048
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|Any CPU.Build.0 = Release|Any CPU
49+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|x64.ActiveCfg = Release|Any CPU
50+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|x64.Build.0 = Release|Any CPU
51+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|x86.ActiveCfg = Release|Any CPU
52+
{E445DF38-70F2-4972-9567-6591E1E3C833}.Release|x86.Build.0 = Release|Any CPU
4153
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4254
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|Any CPU.Build.0 = Debug|Any CPU
55+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|x64.ActiveCfg = Debug|Any CPU
56+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|x64.Build.0 = Debug|Any CPU
57+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|x86.ActiveCfg = Debug|Any CPU
58+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Debug|x86.Build.0 = Debug|Any CPU
4359
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|Any CPU.ActiveCfg = Release|Any CPU
4460
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|Any CPU.Build.0 = Release|Any CPU
61+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|x64.ActiveCfg = Release|Any CPU
62+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|x64.Build.0 = Release|Any CPU
63+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|x86.ActiveCfg = Release|Any CPU
64+
{8D89C41C-F2EB-4E54-8297-1A552BEE2396}.Release|x86.Build.0 = Release|Any CPU
4565
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4666
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|x64.ActiveCfg = Debug|Any CPU
68+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|x64.Build.0 = Debug|Any CPU
69+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|x86.ActiveCfg = Debug|Any CPU
70+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Debug|x86.Build.0 = Debug|Any CPU
4771
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
4872
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|Any CPU.Build.0 = Release|Any CPU
73+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|x64.ActiveCfg = Release|Any CPU
74+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|x64.Build.0 = Release|Any CPU
75+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|x86.ActiveCfg = Release|Any CPU
76+
{F91511F4-5523-4886-BB60-7A8864A621C9}.Release|x86.Build.0 = Release|Any CPU
4977
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5078
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|x64.ActiveCfg = Debug|Any CPU
80+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|x64.Build.0 = Debug|Any CPU
81+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|x86.ActiveCfg = Debug|Any CPU
82+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Debug|x86.Build.0 = Debug|Any CPU
5183
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
5284
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|Any CPU.Build.0 = Release|Any CPU
85+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|x64.ActiveCfg = Release|Any CPU
86+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|x64.Build.0 = Release|Any CPU
87+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|x86.ActiveCfg = Release|Any CPU
88+
{7D5EA751-84A3-43F1-BCCE-C9D10536AB1B}.Release|x86.Build.0 = Release|Any CPU
5389
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5490
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|Any CPU.Build.0 = Debug|Any CPU
91+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|x64.ActiveCfg = Debug|Any CPU
92+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|x64.Build.0 = Debug|Any CPU
93+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|x86.ActiveCfg = Debug|Any CPU
94+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Debug|x86.Build.0 = Debug|Any CPU
5595
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|Any CPU.ActiveCfg = Release|Any CPU
5696
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|Any CPU.Build.0 = Release|Any CPU
97+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|x64.ActiveCfg = Release|Any CPU
98+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|x64.Build.0 = Release|Any CPU
99+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|x86.ActiveCfg = Release|Any CPU
100+
{767539BE-FBE3-4B46-9A5E-21D60E1B278B}.Release|x86.Build.0 = Release|Any CPU
101+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
102+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
103+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|x64.ActiveCfg = Debug|Any CPU
104+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|x64.Build.0 = Debug|Any CPU
105+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|x86.ActiveCfg = Debug|Any CPU
106+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Debug|x86.Build.0 = Debug|Any CPU
107+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
108+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|Any CPU.Build.0 = Release|Any CPU
109+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|x64.ActiveCfg = Release|Any CPU
110+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|x64.Build.0 = Release|Any CPU
111+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|x86.ActiveCfg = Release|Any CPU
112+
{391D7263-AFC7-4FC7-BED1-105930871AC9}.Release|x86.Build.0 = Release|Any CPU
57113
EndGlobalSection
58114
GlobalSection(SolutionProperties) = preSolution
59115
HideSolutionNode = FALSE
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using CodeGraph.Algorithms.Cycles;
2+
using CodeGraph.Export;
3+
using CodeGraph.Graph;
4+
5+
namespace CSharpCodeAnalyst.Ai;
6+
7+
/// <summary>
8+
/// Orchestrates AI-assisted cycle analysis: builds the prompt, calls the LLM, returns the Markdown response.
9+
/// </summary>
10+
public class AiAdvisorService
11+
{
12+
private readonly AiClient _client = new();
13+
14+
public async Task<string> GetCycleAdviceAsync(
15+
CycleGroup cycleGroup,
16+
string endpoint,
17+
string apiKey,
18+
string model,
19+
CancellationToken cancellationToken = default)
20+
{
21+
var level = GetCycleLevel(cycleGroup.CodeGraph);
22+
var serialized = CodeGraphSerializer.Serialize(cycleGroup.CodeGraph);
23+
var prompt = BuildCyclePrompt(level, serialized);
24+
return await _client.SendAsync(endpoint, apiKey, model, prompt, cancellationToken);
25+
}
26+
27+
private static string GetCycleLevel(CodeGraph.Graph.CodeGraph cycleGraph)
28+
{
29+
var types = cycleGraph.Nodes.Values
30+
.Where(n => !n.IsExternal)
31+
.Select(n => n.ElementType)
32+
.ToList();
33+
34+
if (types.Any(t => t is CodeElementType.Method or CodeElementType.Property or CodeElementType.Field))
35+
return "method";
36+
37+
if (types.Any(t => t is CodeElementType.Class or CodeElementType.Interface
38+
or CodeElementType.Struct or CodeElementType.Record or CodeElementType.Enum))
39+
return "class";
40+
41+
return "namespace";
42+
}
43+
44+
private static string BuildCyclePrompt(string level, string serializedGraph)
45+
{
46+
return $"""
47+
You are a software architect analyzing a C# dependency cycle.
48+
49+
The cycle exists at the **{level}** level and is represented as a strongly connected component (SCC)
50+
— every element in the group can reach every other element through the dependency graph.
51+
52+
## Your task
53+
54+
1. **Trace the cycles.** Identify the concrete dependency paths that form the loop(s).
55+
Name the specific elements involved (use their `name` or `full` attributes).
56+
2. **Characterize the coupling.** For each path, explain what kind of dependency it is
57+
(e.g. type reference, method call chain, inheritance, instantiation).
58+
3. **Propose targeted refactorings.** Each suggestion must reference the specific elements
59+
and relationships from this graph by name. Do not give generic software-engineering advice.
60+
Instead, explain exactly which dependency to cut or redirect, and how.
61+
62+
## Relationship types
63+
64+
- `Calls` — a method or property invokes another method or property
65+
- `Uses` — a type references another type (field type, parameter type, return type, local variable)
66+
- `Inherits` — class inherits from a base class
67+
- `Implements` — class or struct implements an interface
68+
- `Instantiates` — a type creates an instance of another type
69+
70+
## Graph format
71+
72+
```
73+
CodeElementType Id [name=Name] [full=FullName] [parent=ParentId] [external]
74+
[loc=File:Line,Col]
75+
SourceId RelationshipType TargetId
76+
[loc=File:Line,Col]
77+
```
78+
79+
## Cycle group
80+
81+
{serializedGraph}
82+
""";
83+
}
84+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Window x:Class="CSharpCodeAnalyst.Ai.AiAdvisorWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mdXaml="clr-namespace:MdXaml;assembly=MdXaml"
5+
xmlns:resources="clr-namespace:CSharpCodeAnalyst.Resources"
6+
Title="{x:Static resources:Strings.AiAdvisorWindow_Title}"
7+
Height="600" Width="700"
8+
MinHeight="300" MinWidth="400"
9+
WindowStartupLocation="Manual">
10+
11+
12+
<Grid Margin="8">
13+
<Grid.RowDefinitions>
14+
<RowDefinition Height="*" />
15+
<RowDefinition Height="Auto" />
16+
</Grid.RowDefinitions>
17+
18+
<mdXaml:MarkdownScrollViewer x:Name="MarkdownViewer"
19+
Grid.Row="0"
20+
MarkdownStyleName="GithubLike"
21+
VerticalScrollBarVisibility="Auto"/>
22+
23+
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,6,0,0">
24+
<Button Content="{x:Static resources:Strings.AiAdvisorWindow_Save}" Width="75" Click="SaveButton_Click" Margin="0,0,8,0" />
25+
<Button Content="{x:Static resources:Strings.AiAdvisorWindow_Close}" Width="75" Click="CloseButton_Click" IsDefault="True" />
26+
</StackPanel>
27+
</Grid>
28+
</Window>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.IO;
2+
using System.Windows;
3+
using System.Windows.Input;
4+
using CSharpCodeAnalyst.Resources;
5+
using Microsoft.Win32;
6+
7+
namespace CSharpCodeAnalyst.Ai;
8+
9+
public partial class AiAdvisorWindow
10+
{
11+
private static AiAdvisorWindow? _instance;
12+
13+
private AiAdvisorWindow()
14+
{
15+
InitializeComponent();
16+
}
17+
18+
/// <summary>
19+
/// Shows (or updates) the singleton advisor window with new content.
20+
/// The window is owned by the main window so it closes together with the app.
21+
/// </summary>
22+
public static void ShowAdvice(string markdownText)
23+
{
24+
if (_instance == null || !_instance.IsLoaded)
25+
{
26+
_instance = new AiAdvisorWindow();
27+
28+
// Let it float behind the main window
29+
//_instance.Owner = Application.Current.MainWindow;
30+
31+
_instance.Left = 0;
32+
_instance.Top = 0;
33+
34+
_instance.Closed += (_, _) => _instance = null;
35+
_instance.Show();
36+
}
37+
else
38+
{
39+
_instance.Activate();
40+
}
41+
42+
_instance.SetContent(markdownText);
43+
}
44+
45+
private void SetContent(string markdownText)
46+
{
47+
MarkdownViewer.Markdown = markdownText;
48+
}
49+
50+
private void SaveButton_Click(object sender, RoutedEventArgs e)
51+
{
52+
var dialog = new SaveFileDialog
53+
{
54+
Title = Strings.AiAdvisorWindow_SaveDialog_Title,
55+
Filter = Strings.AiAdvisorWindow_SaveDialog_Filter,
56+
DefaultExt = ".md",
57+
FileName = Strings.AiAdvisorWindow_SaveDialog_DefaultFileName
58+
};
59+
60+
if (dialog.ShowDialog() == true)
61+
{
62+
File.WriteAllText(dialog.FileName, MarkdownViewer.Markdown);
63+
}
64+
}
65+
66+
private void CloseButton_Click(object sender, RoutedEventArgs e)
67+
{
68+
Close();
69+
}
70+
}

0 commit comments

Comments
 (0)