diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c9a095a..bed476b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,15 @@ "Bash(move:*)", "Bash(tree:*)", "Bash(dir:*)", - "Bash(git config:*)" + "Bash(git config:*)", + "Bash(del \"C:\\Users\\AJ\\Desktop\\RoslynBridge\\RoslynBridge\\Services\\ShellService.cs\")", + "Skill(roslyn-bridge)", + "Bash(dotnet restore:*)", + "Bash(msbuild:*)", + "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" /t:Restore,Build /p:Configuration=Debug)", + "Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" RoslynBridge.sln /t:Restore /t:Build /p:Configuration=Debug)", + "Bash(del \".claude\\init.md\")", + "Bash(git add:*)" ], "deny": [], "ask": [] diff --git a/.claude/skills/roslyn-api/SKILL.md b/.claude/skills/roslyn-api/SKILL.md deleted file mode 100644 index 62c282e..0000000 --- a/.claude/skills/roslyn-api/SKILL.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -name: roslyn-api -description: Use this for C# code analysis, querying .NET projects, finding symbols, getting diagnostics, or any Roslyn/semantic analysis tasks using the bridge server ---- - -# Roslyn API Testing Guide - -Use this guide when testing or accessing the Claude Roslyn Bridge HTTP endpoints. - -## Server Info -- **Base URL**: `http://localhost:59123/query` -- **Method**: POST -- **Content-Type**: application/json - -## Correct Command Syntax - -### Using curl (Recommended on Windows) - -```bash -# Test if server is running -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}" - -# Get document info -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" - -# Get symbol at position -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\Users\\\\AJ\\\\Desktop\\\\PepLib\\\\PepLib\\\\Program.cs\",\"line\":10,\"column\":5}" -``` - -**IMPORTANT curl syntax rules:** -- Use `-X POST` (NOT `-Method POST`) -- Use `-H` for headers (NOT `-Headers`) -- Use `-d` for data (NOT `-Body`) -- Escape backslashes in file paths: `\\\\` becomes `\\` in JSON - -### Using PowerShell (Alternative) - -Run these commands **directly in PowerShell**, NOT via `powershell -Command`: - -```powershell -# Test server -$body = @{queryType='getprojects'} | ConvertTo-Json -Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json' - -# Get document -$body = @{ - queryType='getdocument' - filePath='C:\path\to\file.cs' -} | ConvertTo-Json -Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json' -``` - -**IMPORTANT PowerShell rules:** -- Run directly in PowerShell console, NOT via `bash -c` or `powershell -Command` -- Use single quotes around URI and content type -- File paths don't need escaping in PowerShell hash tables - -## Quick Reference: All Endpoints - -### Query Endpoints - -| Endpoint | Required Fields | Optional Fields | -|----------|----------------|-----------------| -| `getprojects` | - | - | -| `getdocument` | `filePath` | - | -| `getsymbol` | `filePath`, `line`, `column` | - | -| `getdiagnostics` | - | `filePath` | -| `findreferences` | `filePath`, `line`, `column` | - | -| `findsymbol` | `symbolName` | `parameters.kind` | -| `gettypemembers` | `symbolName` | `parameters.includeInherited` | -| `gettypehierarchy` | `symbolName` | `parameters.direction` | -| `findimplementations` | `symbolName` OR `filePath`+`line`+`column` | - | -| `getnamespacetypes` | `symbolName` | - | -| `getcallhierarchy` | `filePath`, `line`, `column` | `parameters.direction` | -| `getsolutionoverview` | - | - | -| `getsymbolcontext` | `filePath`, `line`, `column` | - | -| `searchcode` | `symbolName` (regex) | `parameters.scope` | - -### Editing Endpoints - -| Endpoint | Required Fields | Optional Fields | -|----------|----------------|-----------------| -| `formatdocument` | `filePath` | - | -| `organizeusings` | `filePath` | - | -| `renamesymbol` | `filePath`, `line`, `column`, `parameters.newName` | - | -| `addmissingusing` | `filePath`, `line`, `column` | - | -| `applycodefix` | `filePath`, `line`, `column` | - | - -## Common Test Examples - -```bash -# Get all projects in solution -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}" - -# Get solution overview -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsolutionoverview\"}" - -# Get diagnostics for entire solution -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdiagnostics\"}" - -# Find all classes containing "Helper" -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"searchcode\",\"symbolName\":\".*Helper\",\"parameters\":{\"scope\":\"classes\"}}" - -# Format a document -curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"formatdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" -``` - -## Response Format - -Success: -```json -{ - "success": true, - "message": "Optional message", - "data": { /* Response data */ } -} -``` - -Error: -```json -{ - "success": false, - "error": "Error message", - "data": null -} -``` - -## Notes - -- Line numbers are **1-based** -- Column numbers are **0-based** -- File paths in JSON need escaped backslashes: `C:\\path\\to\\file.cs` -- All workspace modifications use VS threading model (`JoinableTaskFactory.SwitchToMainThreadAsync()`) -- The Visual Studio extension must be running for endpoints to work diff --git a/.claude/skills/roslyn-bridge/SKILL.md b/.claude/skills/roslyn-bridge/SKILL.md new file mode 100644 index 0000000..d2a7dff --- /dev/null +++ b/.claude/skills/roslyn-bridge/SKILL.md @@ -0,0 +1,196 @@ +--- +name: roslyn-bridge +description: Use this for C# code analysis, querying .NET projects, finding symbols, getting diagnostics, or any Roslyn/semantic analysis tasks using the bridge server +--- + +# Roslyn Bridge Testing Guide + +Use this guide when testing or accessing the Claude Roslyn Bridge HTTP endpoints. + +## Server Info +- **Base URL**: `http://localhost:59123` +- **Endpoints**: + - `/query` - Main query endpoint for all Roslyn operations + - `/health` - Health check endpoint to verify server is running +- **Method**: POST +- **Content-Type**: application/json + +## ⚠️ CRITICAL: Command Syntax Rules + +**ALWAYS use curl with the Bash tool. NEVER pipe curl output to PowerShell.** + +### ✅ CORRECT: Using curl with Bash tool + +```bash +# Test if server is running +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}" + +# Get diagnostics +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdiagnostics\"}" + +# Get document info +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" + +# Get symbol at position +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsymbol\",\"filePath\":\"C:\\\\Users\\\\AJ\\\\Desktop\\\\PepLib\\\\PepLib\\\\Program.cs\",\"line\":10,\"column\":5}" +``` + +**IMPORTANT curl syntax rules:** +- Use `-X POST` (NOT `-Method POST`) +- Use `-H` for headers (NOT `-Headers`) +- Use `-d` for data (NOT `-Body`) +- Escape backslashes in file paths: `\\\\` becomes `\\` in JSON +- **DO NOT pipe to PowerShell** - the JSON output is already formatted + +### ❌ WRONG: DO NOT DO THIS + +```bash +# NEVER pipe curl to PowerShell - this will fail on Windows +curl ... | powershell -Command "$json = $input | ..." # ❌ WRONG +``` + +### Using PowerShell (Alternative) + +Run these commands **directly in PowerShell**, NOT via `powershell -Command`: + +```powershell +# Test server +$body = @{queryType='getprojects'} | ConvertTo-Json +Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json' + +# Get document +$body = @{ + queryType='getdocument' + filePath='C:\path\to\file.cs' +} | ConvertTo-Json +Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $body -ContentType 'application/json' +``` + +**IMPORTANT PowerShell rules:** +- Run directly in PowerShell console, NOT via `bash -c` or `powershell -Command` +- Use single quotes around URI and content type +- File paths don't need escaping in PowerShell hash tables + +## Quick Reference: All Endpoints + +### Query Endpoints + +| Endpoint | Required Fields | Optional Fields | Description | +|----------|----------------|-----------------|-------------| +| `getprojects` | - | - | Get all projects in the solution | +| `getdocument` | `filePath` | - | Get document information for a specific file | +| `getsymbol` | `filePath`, `line`, `column` | - | Get symbol information at a specific position | +| `getsemanticmodel` | `filePath` | - | Verify semantic model availability (not serializable) | +| `getsyntaxtree` | `filePath` | - | Get the syntax tree (source code) for a file | +| `getdiagnostics` | - | `filePath` | Get compilation errors and warnings | +| `findreferences` | `filePath`, `line`, `column` | - | Find all references to a symbol | +| `findsymbol` | `symbolName` | `parameters.kind` | Find symbols by name (supports filtering by kind) | +| `gettypemembers` | `symbolName` | `parameters.includeInherited` | Get all members of a type | +| `gettypehierarchy` | `symbolName` | `parameters.direction` | Get base types or derived types | +| `findimplementations` | `symbolName` OR `filePath`+`line`+`column` | - | Find implementations of an interface/abstract member | +| `getnamespacetypes` | `symbolName` | - | Get all types in a namespace | +| `getcallhierarchy` | `filePath`, `line`, `column` | `parameters.direction` | Get callers or callees of a method | +| `getsolutionoverview` | - | - | Get high-level solution statistics | +| `getsymbolcontext` | `filePath`, `line`, `column` | - | Get contextual information about a symbol's location | +| `searchcode` | `symbolName` (regex) | `parameters.scope` | Search for code patterns using regex | + +### Editing Endpoints + +| Endpoint | Required Fields | Optional Fields | Description | +|----------|----------------|-----------------|-------------| +| `formatdocument` | `filePath` | - | Format a document according to coding style | +| `organizeusings` | `filePath` | - | Sort and remove unused using statements | +| `renamesymbol` | `filePath`, `line`, `column`, `parameters.newName` | - | Rename a symbol across the solution | +| `addmissingusing` | `filePath`, `line`, `column` | - | Add missing using statement for a symbol | +| `applycodefix` | `filePath`, `line`, `column` | - | Apply available code fixes at a position | + +### Project Operation Endpoints + +| Endpoint | Required Fields | Optional Fields | Description | +|----------|----------------|-----------------|-------------| +| `addnugetpackage` | `projectName`, `packageName` | `version` | Add a NuGet package to a project | +| `removenugetpackage` | `projectName`, `packageName` | - | Remove a NuGet package from a project | +| `buildproject` | `projectName` | `configuration` | Build a project or solution | +| `cleanproject` | `projectName` | - | Clean build output | +| `restorepackages` | `projectName` | - | Restore NuGet packages | +| `createdirectory` | `directoryPath` | - | Create a new directory | + +## Common Test Examples + +```bash +# Health check - verify server is running +curl -X POST http://localhost:59123/health -H "Content-Type: application/json" -d "{}" + +# Get all projects in solution +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getprojects\"}" + +# Get solution overview +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsolutionoverview\"}" + +# Get diagnostics for entire solution +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getdiagnostics\"}" + +# Get semantic model for a file +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsemanticmodel\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" + +# Get syntax tree for a file +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"getsyntaxtree\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" + +# Find all classes containing "Helper" +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"searchcode\",\"symbolName\":\".*Helper\",\"parameters\":{\"scope\":\"classes\"}}" + +# Format a document +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"formatdocument\",\"filePath\":\"C:\\\\path\\\\to\\\\file.cs\"}" + +# Add NuGet package +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"addnugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\"}" + +# Add NuGet package with version +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"addnugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\",\"version\":\"13.0.3\"}" + +# Remove NuGet package +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"removenugetpackage\",\"projectName\":\"RoslynBridge\",\"packageName\":\"Newtonsoft.Json\"}" + +# Build project +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"buildproject\",\"projectName\":\"RoslynBridge\"}" + +# Build with configuration +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"buildproject\",\"projectName\":\"RoslynBridge\",\"configuration\":\"Release\"}" + +# Clean project +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"cleanproject\",\"projectName\":\"RoslynBridge\"}" + +# Restore packages +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"restorepackages\",\"projectName\":\"RoslynBridge\"}" + +# Create directory +curl -X POST http://localhost:59123/query -H "Content-Type: application/json" -d "{\"queryType\":\"createdirectory\",\"directoryPath\":\"C:\\\\path\\\\to\\\\new\\\\directory\"}" +``` + +## Response Format + +Success: +```json +{ + "success": true, + "message": "Optional message", + "data": { /* Response data */ } +} +``` + +Error: +```json +{ + "success": false, + "error": "Error message", + "data": null +} +``` + +## Notes + +- Line numbers are **1-based** +- Column numbers are **0-based** +- File paths in JSON need escaped backslashes: `C:\\path\\to\\file.cs` +- All workspace modifications use VS threading model (`JoinableTaskFactory.SwitchToMainThreadAsync()`) +- The Visual Studio extension must be running for endpoints to work diff --git a/AGENTS.md b/AGENTS.md index 5aef38e..faf287c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,11 @@ - Claude skills: `.claude/skills/roslyn-api/SKILL.md` (HTTP query reference for local discovery/testing). ## Build, Test, and Development Commands - - Build (CLI): `msbuild RoslynBridge.sln /p:Configuration=Debug` + - **IMPORTANT**: This VSIX project MUST be built with MSBuild, NOT `dotnet build`. Using `dotnet build` will fail with missing namespace errors. +- Build (CLI): `msbuild RoslynBridge.sln /p:Configuration=Debug` or with full path: + ```powershell + & 'C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe' RoslynBridge.sln /t:Restore /t:Build /p:Configuration=Debug + ``` - Build (VS): Open `RoslynBridge.sln`, set `RoslynBridge` as startup, press F5 to launch the Experimental Instance. - Health check (server running in VS): - PowerShell: `$b=@{queryType='getprojects'}|ConvertTo-Json; Invoke-RestMethod -Uri 'http://localhost:59123/query' -Method Post -Body $b -ContentType 'application/json'` diff --git a/RoslynBridge/Constants/ServerConstants.cs b/RoslynBridge/Constants/ServerConstants.cs index b87a422..277be6b 100644 --- a/RoslynBridge/Constants/ServerConstants.cs +++ b/RoslynBridge/Constants/ServerConstants.cs @@ -39,5 +39,13 @@ namespace RoslynBridge.Constants public const string RenameSymbol = "renamesymbol"; public const string OrganizeUsings = "organizeusings"; public const string AddMissingUsing = "addmissingusing"; + + // Project operation endpoints + public const string AddNuGetPackage = "addnugetpackage"; + public const string RemoveNuGetPackage = "removenugetpackage"; + public const string BuildProject = "buildproject"; + public const string CleanProject = "cleanproject"; + public const string RestorePackages = "restorepackages"; + public const string CreateDirectory = "createdirectory"; } } diff --git a/RoslynBridge/Models/RequestModels.cs b/RoslynBridge/Models/RequestModels.cs index 0bae635..3976f1b 100644 --- a/RoslynBridge/Models/RequestModels.cs +++ b/RoslynBridge/Models/RequestModels.cs @@ -10,6 +10,13 @@ namespace RoslynBridge.Models public int? Line { get; set; } public int? Column { get; set; } public Dictionary? Parameters { get; set; } + + // Project operation parameters + public string? ProjectName { get; set; } + public string? PackageName { get; set; } + public string? Version { get; set; } + public string? Configuration { get; set; } + public string? DirectoryPath { get; set; } } public class QueryResponse diff --git a/RoslynBridge/RoslynBridge.csproj b/RoslynBridge/RoslynBridge.csproj index 6172f31..aa0cbd6 100644 --- a/RoslynBridge/RoslynBridge.csproj +++ b/RoslynBridge/RoslynBridge.csproj @@ -78,6 +78,7 @@ + @@ -98,9 +99,7 @@ - - compile; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -119,7 +118,7 @@ - + diff --git a/RoslynBridge/Services/ProjectOperationsService.cs b/RoslynBridge/Services/ProjectOperationsService.cs new file mode 100644 index 0000000..9a419c3 --- /dev/null +++ b/RoslynBridge/Services/ProjectOperationsService.cs @@ -0,0 +1,300 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using RoslynBridge.Models; + +namespace RoslynBridge.Services +{ + /// + /// Service for safe project operations (NuGet, build, etc.) + /// Only allows specific, whitelisted operations + /// + public class ProjectOperationsService + { + private const int DefaultTimeout = 120000; // 2 minutes + private const int MaxTimeout = 600000; // 10 minutes + private readonly IWorkspaceProvider _workspaceProvider; + + public ProjectOperationsService(IWorkspaceProvider workspaceProvider) + { + _workspaceProvider = workspaceProvider; + } + + /// + /// Add a NuGet package to a project + /// + public async Task AddNuGetPackageAsync(string projectName, string packageName, string? version = null) + { + var projectPath = GetProjectPath(projectName); + if (projectPath == null) + { + return new QueryResponse + { + Success = false, + Error = $"Project not found: {projectName}. Use 'getprojects' to see available projects." + }; + } + + if (string.IsNullOrWhiteSpace(packageName)) + { + return new QueryResponse + { + Success = false, + Error = "Package name is required" + }; + } + + var command = string.IsNullOrWhiteSpace(version) + ? $"add \"{projectPath}\" package {packageName}" + : $"add \"{projectPath}\" package {packageName} --version {version}"; + + return await ExecuteDotNetCommandAsync(command, $"Add {packageName} to {projectName}"); + } + + /// + /// Remove a NuGet package from a project + /// + public async Task RemoveNuGetPackageAsync(string projectName, string packageName) + { + var projectPath = GetProjectPath(projectName); + if (projectPath == null) + { + return new QueryResponse + { + Success = false, + Error = $"Project not found: {projectName}. Use 'getprojects' to see available projects." + }; + } + + if (string.IsNullOrWhiteSpace(packageName)) + { + return new QueryResponse + { + Success = false, + Error = "Package name is required" + }; + } + + var command = $"remove \"{projectPath}\" package {packageName}"; + return await ExecuteDotNetCommandAsync(command, $"Remove {packageName} from {projectName}"); + } + + /// + /// Build a project or solution + /// + public async Task BuildAsync(string projectName, string? configuration = null) + { + var projectPath = GetProjectPath(projectName); + if (projectPath == null) + { + return new QueryResponse + { + Success = false, + Error = $"Project not found: {projectName}. Use 'getprojects' to see available projects." + }; + } + + var command = string.IsNullOrWhiteSpace(configuration) + ? $"build \"{projectPath}\"" + : $"build \"{projectPath}\" --configuration {configuration}"; + + return await ExecuteDotNetCommandAsync(command, $"Build {projectName}"); + } + + /// + /// Clean build output + /// + public async Task CleanAsync(string projectName) + { + var projectPath = GetProjectPath(projectName); + if (projectPath == null) + { + return new QueryResponse + { + Success = false, + Error = $"Project not found: {projectName}. Use 'getprojects' to see available projects." + }; + } + + var command = $"clean \"{projectPath}\""; + return await ExecuteDotNetCommandAsync(command, $"Clean {projectName}"); + } + + /// + /// Restore NuGet packages + /// + public async Task RestoreAsync(string projectName) + { + var projectPath = GetProjectPath(projectName); + if (projectPath == null) + { + return new QueryResponse + { + Success = false, + Error = $"Project not found: {projectName}. Use 'getprojects' to see available projects." + }; + } + + var command = $"restore \"{projectPath}\""; + return await ExecuteDotNetCommandAsync(command, $"Restore {projectName}"); + } + + /// + /// Create a new directory + /// + public Task CreateDirectoryAsync(string directoryPath) + { + try + { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + return Task.FromResult(new QueryResponse + { + Success = false, + Error = "Directory path is required" + }); + } + + bool existed = Directory.Exists(directoryPath); + if (!existed) + { + Directory.CreateDirectory(directoryPath); + } + + return Task.FromResult(new QueryResponse + { + Success = true, + Message = existed ? $"Directory already exists: {directoryPath}" : $"Successfully created directory: {directoryPath}", + Data = new + { + DirectoryPath = directoryPath, + AlreadyExisted = existed + } + }); + } + catch (Exception ex) + { + return Task.FromResult(new QueryResponse + { + Success = false, + Error = $"Error creating directory: {ex.Message}" + }); + } + } + + private string? GetProjectPath(string projectName) + { + if (string.IsNullOrWhiteSpace(projectName)) + { + return null; + } + + var workspace = _workspaceProvider.Workspace; + if (workspace?.CurrentSolution == null) + { + return null; + } + + // Find project by name + var project = workspace.CurrentSolution.Projects + .FirstOrDefault(p => p.Name.Equals(projectName, StringComparison.OrdinalIgnoreCase)); + + return project?.FilePath; + } + + private async Task ExecuteDotNetCommandAsync(string arguments, string operationName) + { + try + { + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8 + }; + + using var process = new Process { StartInfo = processStartInfo }; + + var outputBuilder = new StringBuilder(); + var errorBuilder = new StringBuilder(); + + process.OutputDataReceived += (sender, args) => + { + if (args.Data != null) + { + outputBuilder.AppendLine(args.Data); + } + }; + + process.ErrorDataReceived += (sender, args) => + { + if (args.Data != null) + { + errorBuilder.AppendLine(args.Data); + } + }; + + var startTime = DateTime.Now; + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + bool completed = await Task.Run(() => process.WaitForExit(MaxTimeout)); + + if (!completed) + { + try + { + process.Kill(); + } + catch { } + + return new QueryResponse + { + Success = false, + Error = $"{operationName} timed out after {MaxTimeout}ms" + }; + } + + var endTime = DateTime.Now; + var duration = (endTime - startTime).TotalMilliseconds; + + var stdout = outputBuilder.ToString(); + var stderr = errorBuilder.ToString(); + var exitCode = process.ExitCode; + + return new QueryResponse + { + Success = exitCode == 0, + Message = exitCode == 0 ? $"{operationName} completed successfully" : $"{operationName} failed with exit code {exitCode}", + Data = new + { + Operation = operationName, + Command = $"dotnet {arguments}", + ExitCode = exitCode, + Output = stdout, + Error = stderr, + Duration = Math.Round(duration, 2) + } + }; + } + catch (Exception ex) + { + return new QueryResponse + { + Success = false, + Error = $"Error executing {operationName}: {ex.Message}" + }; + } + } + } +} diff --git a/RoslynBridge/Services/RoslynQueryService.cs b/RoslynBridge/Services/RoslynQueryService.cs index 5630796..ccf9995 100644 --- a/RoslynBridge/Services/RoslynQueryService.cs +++ b/RoslynBridge/Services/RoslynQueryService.cs @@ -17,6 +17,7 @@ namespace RoslynBridge.Services private readonly DocumentQueryService _documentService; private readonly DiagnosticsService _diagnosticsService; private readonly RefactoringService _refactoringService; + private readonly ProjectOperationsService _projectOperationsService; public RoslynQueryService(AsyncPackage package) { @@ -28,6 +29,7 @@ namespace RoslynBridge.Services _documentService = new DocumentQueryService(package, _workspaceProvider); _diagnosticsService = new DiagnosticsService(package, _workspaceProvider); _refactoringService = new RefactoringService(package, _workspaceProvider); + _projectOperationsService = new ProjectOperationsService(_workspaceProvider); } public async Task InitializeAsync() @@ -39,6 +41,25 @@ namespace RoslynBridge.Services { try { + // Handle project operations (don't require workspace) + var queryType = request.QueryType?.ToLowerInvariant(); + switch (queryType) + { + case QueryTypes.AddNuGetPackage: + return await _projectOperationsService.AddNuGetPackageAsync(request.ProjectName ?? string.Empty, request.PackageName ?? string.Empty, request.Version); + case QueryTypes.RemoveNuGetPackage: + return await _projectOperationsService.RemoveNuGetPackageAsync(request.ProjectName ?? string.Empty, request.PackageName ?? string.Empty); + case QueryTypes.BuildProject: + return await _projectOperationsService.BuildAsync(request.ProjectName ?? string.Empty, request.Configuration); + case QueryTypes.CleanProject: + return await _projectOperationsService.CleanAsync(request.ProjectName ?? string.Empty); + case QueryTypes.RestorePackages: + return await _projectOperationsService.RestoreAsync(request.ProjectName ?? string.Empty); + case QueryTypes.CreateDirectory: + return await _projectOperationsService.CreateDirectoryAsync(request.DirectoryPath ?? string.Empty); + } + + // For Roslyn queries, ensure workspace is initialized if (_workspaceProvider.Workspace == null) { await InitializeAsync(); diff --git a/RoslynBridge/source.extension.vsixmanifest b/RoslynBridge/source.extension.vsixmanifest index fa9f2ce..a0730bb 100644 --- a/RoslynBridge/source.extension.vsixmanifest +++ b/RoslynBridge/source.extension.vsixmanifest @@ -1,32 +1,32 @@ - - - Claude Roslyn Bridge - Visual Studio extension that provides access to Roslyn APIs through an HTTP bridge for Claude integration. - https://github.com/yourusername/ClaudeRoslynBridge - README.txt - Roslyn, Claude, AI, Code Analysis - - - - amd64 - - - amd64 - - - amd64 - - - - - - - - - - - - + + + Roslyn Bridge + Visual Studio extension that provides access to Roslyn APIs through an HTTP bridge for Claude integration. + https://github.com/yourusername/ClaudeRoslynBridge + README.txt + Roslyn, Claude, AI, Code Analysis + + + + amd64 + + + amd64 + + + amd64 + + + + + + + + + + + +