Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ast-visual-studio-extension/CxExtension/CxWindowPackage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ast_visual_studio_extension.CxExtension.Commands;
using ast_visual_studio_extension.CxExtension.DevAssist.Commands;
using log4net;
using log4net.Appender;
using log4net.Config;
Expand Down Expand Up @@ -69,6 +70,12 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke

// Command to create Checkmarx extension main window
await CxWindowCommand.InitializeAsync(this);

// Initialize AI Chat Command (Fix with Checkmarx One Assist)
await AIChatCommand.InitializeAsync(this);

// Set package on CopilotIntegration so ASCA "Fix with AI" can run extension-manager checks
DevAssist.Services.CopilotIntegration.Package = this;
}
catch (Exception ex)
{
Expand Down
9 changes: 9 additions & 0 deletions ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
<ButtonText>Checkmarx</ButtonText>
</Strings>
</Button>
<Button guid="guidCxWindowPackageCmdSet" id="AIChatCommandId" priority="0x0102" type="Button">
<Parent guid="guidCxWindowPackageCmdSet" id="TWToolbarGroup"/>
<Icon guid="CxImages" id="cxlogo" />
<Strings>
<ButtonText>Fix with AI</ButtonText>
<CommandName>Fix with Checkmarx One Assist</CommandName>
</Strings>
</Button>
</Buttons>

<Bitmaps>
Expand All @@ -46,6 +54,7 @@

<GuidSymbol name="guidCxWindowPackageCmdSet" value="{e46cd6d8-268d-4e77-9074-071e72a25f39}">
<IDSymbol name="CxWindowCommandId" value="0x0100" />
<IDSymbol name="AIChatCommandId" value="0x0300" />
<IDSymbol name="MyMenuGroup" value="0x1020" />
<IDSymbol name="CxWindowCmdId" value="0x0101" />
<IDSymbol name="TWToolbar" value="0x1000" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using Microsoft.VisualStudio.Shell;
using System;
using System.ComponentModel.Design;
using System.Threading.Tasks;
using ast_visual_studio_extension.CxExtension.DevAssist.Services;
using ast_visual_studio_extension.CxWrapper.Models;

namespace ast_visual_studio_extension.CxExtension.DevAssist.Commands
{
public class AIChatCommand
{
public const int CommandId = 0x0300;
public static readonly Guid CommandSet = new Guid("e46cd6d8-268d-4e77-9074-071e72a25f39");

private readonly AsyncPackage _package;
private readonly CopilotIntegration _copilotService;
private readonly PromptBuilderService _promptBuilder;

private AIChatCommand(AsyncPackage package)
{
_package = package ?? throw new ArgumentNullException(nameof(package));
_copilotService = new CopilotIntegration();
_promptBuilder = new PromptBuilderService();
}

public static async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService != null)
{
var command = new AIChatCommand(package);
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(command.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
}

/// <summary>
/// Execute AI Chat command with vulnerability data
/// </summary>
private async void Execute(object sender, EventArgs e)
{
try
{
// Get vulnerability data from command parameter or context
var vulnerabilityData = GetVulnerabilityDataFromContext(e);

if (vulnerabilityData == null)
{
System.Windows.MessageBox.Show("No vulnerability data available for AI assistance.",
"AI Chat", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
return;
}

// Build appropriate prompt based on vulnerability type
string prompt = BuildPromptForVulnerability(vulnerabilityData);

// Open Copilot with the prompt
bool success = await _copilotService.OpenCopilotChatAsync(prompt, _package);

if (!success)
{
System.Windows.MessageBox.Show("Failed to open GitHub Copilot. Please ensure it's installed and try again.",
"AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}",
"Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}

/// <summary>
/// Public method to execute AI chat with specific vulnerability data
/// </summary>
public async Task ExecuteWithDataAsync(CxAscaDetail ascaData)
{
try
{
string prompt = _promptBuilder.BuildASCAPrompt(
ascaData.RuleName,
ascaData.RemediationAdvise,
ascaData.Severity,
ascaData.FileName,
ascaData.Line
);

await _copilotService.OpenCopilotChatAsync(prompt, _package);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}",
"AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}

/// <summary>
/// Public method to execute AI chat with SCA vulnerability data
/// </summary>
public async Task ExecuteWithSCADataAsync(string packageName, string version, string severity, string packageManager = null)
{
try
{
string prompt = _promptBuilder.BuildSCAPrompt(packageName, version, severity, packageManager);
await _copilotService.OpenCopilotChatAsync(prompt, _package);
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}",
"AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
}
}

private object GetVulnerabilityDataFromContext(EventArgs e)
{
// This would extract vulnerability data from the command context
// Implementation depends on how data is passed to the command
// For now, return null - will be implemented based on UI integration
return null;
}

private string BuildPromptForVulnerability(object vulnerabilityData)
{
// Build prompt based on vulnerability data type
if (vulnerabilityData is CxAscaDetail ascaData)
{
return _promptBuilder.BuildASCAPrompt(
ascaData.RuleName,
ascaData.RemediationAdvise,
ascaData.Severity,
ascaData.FileName,
ascaData.Line
);
}

// Add other vulnerability types as needed
return _promptBuilder.BuildGenericPrompt("Unknown vulnerability", "Unknown");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using ast_visual_studio_extension.CxExtension.Utils;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.ExtensionManager;
using Microsoft.VisualStudio.Shell;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace ast_visual_studio_extension.CxExtension.DevAssist.Services
{
public class CopilotIntegration
{
/// <summary>
/// Optional package set by the hosting VS package (e.g. CxWindowPackage) so that
/// OpenCopilotChatAsync can run extension-manager checks when called without an explicit package (e.g. from ASCA marker).
/// </summary>
public static AsyncPackage Package { get; set; }

private DTE2 _dte;
private OutputWindowPane _outputPane;

public CopilotIntegration()
{
ThreadHelper.ThrowIfNotOnUIThread();
_dte = Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(EnvDTE.DTE)) as DTE2;
}

private void Log(string message)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if (_dte == null)
{
System.Diagnostics.Debug.WriteLine($"{DateTime.Now:HH:mm:ss.fff} [Copilot] {message}");
return;
}
if (_outputPane == null)
_outputPane = OutputPaneUtils.InitializeOutputPane(_dte.ToolWindows.OutputWindow, CxConstants.EXTENSION_TITLE);
_outputPane?.OutputString($"{DateTime.Now:HH:mm:ss.fff} [Copilot] {message}\n");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CopilotIntegration] Log failed: {ex.Message}");
}
}

/// <summary>
/// Detect if Copilot Chat extension is installed (GitHub.CopilotChat VSIX).
/// Returns true if the Copilot Chat extension is in the list of installed extensions.
/// </summary>
public async Task<bool> IsCopilotChatEnabledAsync(AsyncPackage package)
{
if (package == null)
{
Log("[IsCopilotChatEnabled] package is null -> false");
return false;
}

await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

var extensionManager = await package.GetServiceAsync(typeof(SVsExtensionManager)) as IVsExtensionManager;
if (extensionManager == null)
{
Log("[IsCopilotChatEnabled] IVsExtensionManager is null -> false");
return false;
}

const string CopilotChatVsixId = "Component.VisualStudio.GitHub.Copilot";

bool found = extensionManager.GetInstalledExtensions()
.Any(e => e.Header.Identifier.Equals(CopilotChatVsixId, StringComparison.OrdinalIgnoreCase));

Log(found ? "[IsCopilotChatEnabled] Copilot Chat extension installed -> true" : "[IsCopilotChatEnabled] Copilot Chat extension not found -> false");
return found;
}

/// <summary>
/// Check if GitHub Copilot for Visual Studio is installed and Chat is available (legacy/alias).

public async Task<bool> IsCopilotInstalledAsync(AsyncPackage package)
{
return await IsCopilotChatEnabledAsync(package);
}

/// Open GitHub Copilot Chat with optional prompt.
public async Task<bool> OpenCopilotChatAsync(string prompt = null, AsyncPackage package = null)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
AsyncPackage packageToUse = package ?? Package;
if (packageToUse == null)
Log("[OpenCopilotChatAsync] WARNING: package is null and CopilotIntegration.Package was not set (e.g. CxWindowPackage should set it)");

try
{
bool success = await TryOpenCopilotChatAsync(packageToUse);

if (success && !string.IsNullOrEmpty(prompt))
{
await Task.Delay(1000);
await SendPromptToCopilotAsync(prompt);
}
else
{
if (string.IsNullOrEmpty(prompt)) Log("[OpenCopilotChatAsync] Skipping prompt: no prompt");
}
return success;
}
catch (Exception ex)
{
ShowError($"Failed to open GitHub Copilot: {ex.Message}");
return false;
}
}
private async Task<bool> TryOpenCopilotChatAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

// default keyboard shortcut (Ctrl+Backslash then C) to open GitHub Copilot Chat
try
{
System.Windows.Forms.SendKeys.SendWait("^\\c");
Log("[TryOpenCopilotChatAsync] Default shortcut (Ctrl+\\ then C) sent");
return true;
}
catch (Exception ex)
{
Log($"[TryOpenCopilotChatAsync] Shortcut fallback failed: {ex.Message}");
}
return false;
}

/// <summary>
/// Enumerates DTE commands whose name contains "Copilot" or "GitHub" and logs them to Output.
/// Use this to find the exact command name that opens Copilot Chat on your machine.
/// </summary>

private async Task SendPromptToCopilotAsync(string prompt)
{
Log("[SendPromptToCopilotAsync] Enter");
Log($"[SendPromptToCopilotAsync] prompt length={prompt?.Length ?? 0}");
try
{
Log("[SendPromptToCopilotAsync] Method 1: clipboard + paste");
Clipboard.SetText(prompt);
Log("[SendPromptToCopilotAsync] Clipboard.SetText done, waiting 100ms");
await Task.Delay(100);
System.Windows.Forms.SendKeys.SendWait("^v{ENTER}");
Log("[SendPromptToCopilotAsync] SendKeys ^v{ENTER} sent");
}
catch (Exception ex)
{
Log($"[SendPromptToCopilotAsync] Method 1 failed: {ex.Message}");
Log("[SendPromptToCopilotAsync] Method 2: direct SendKeys (fallback)");
try
{
System.Windows.Forms.SendKeys.SendWait(prompt.Replace("{", "{{").Replace("}", "}}"));
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Log("[SendPromptToCopilotAsync] Method 2 completed");
}
catch (Exception ex2)
{
Log($"[SendPromptToCopilotAsync] Method 2 failed: {ex2.Message}");
}
}
}
private void ShowError(string message)
{
Log($"[ShowError] {message}");
MessageBox.Show(message, "Copilot Integration Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Loading
Loading