diff --git a/src/Aspire.Cli/Agents/AgentEnvironmentDetector.cs b/src/Aspire.Cli/Agents/AgentEnvironmentDetector.cs index 1d9bb246615..4a326191428 100644 --- a/src/Aspire.Cli/Agents/AgentEnvironmentDetector.cs +++ b/src/Aspire.Cli/Agents/AgentEnvironmentDetector.cs @@ -9,11 +9,12 @@ namespace Aspire.Cli.Agents; internal sealed class AgentEnvironmentDetector(IEnumerable scanners) : IAgentEnvironmentDetector { /// - public async Task DetectAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken) + public async Task DetectAsync(DirectoryInfo workingDirectory, DirectoryInfo repositoryRoot, CancellationToken cancellationToken) { var context = new AgentEnvironmentScanContext { - WorkingDirectory = workingDirectory + WorkingDirectory = workingDirectory, + RepositoryRoot = repositoryRoot }; foreach (var scanner in scanners) diff --git a/src/Aspire.Cli/Agents/AgentEnvironmentScanContext.cs b/src/Aspire.Cli/Agents/AgentEnvironmentScanContext.cs index f9721c2f622..8d569bf0cd8 100644 --- a/src/Aspire.Cli/Agents/AgentEnvironmentScanContext.cs +++ b/src/Aspire.Cli/Agents/AgentEnvironmentScanContext.cs @@ -15,6 +15,13 @@ internal sealed class AgentEnvironmentScanContext /// public required DirectoryInfo WorkingDirectory { get; init; } + /// + /// Gets the root directory of the repository/workspace. + /// This is typically the git repository root if available, otherwise the working directory. + /// Scanners should use this as the boundary for searches instead of searching up the directory tree. + /// + public required DirectoryInfo RepositoryRoot { get; init; } + /// /// Adds an applicator to the collection of detected agent environments. /// diff --git a/src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs b/src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs index 630f0fb5223..04138a16c51 100644 --- a/src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs +++ b/src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs @@ -3,7 +3,6 @@ using System.Text.Json; using System.Text.Json.Nodes; -using Aspire.Cli.Git; using Aspire.Cli.Resources; using Microsoft.Extensions.Logging; @@ -18,22 +17,18 @@ internal sealed class ClaudeCodeAgentEnvironmentScanner : IAgentEnvironmentScann private const string McpConfigFileName = ".mcp.json"; private const string AspireServerName = "aspire"; - private readonly IGitRepository _gitRepository; private readonly IClaudeCodeCliRunner _claudeCodeCliRunner; private readonly ILogger _logger; /// /// Initializes a new instance of . /// - /// The Git repository service for finding repository boundaries. /// The Claude Code CLI runner for checking if Claude Code is installed. /// The logger for diagnostic output. - public ClaudeCodeAgentEnvironmentScanner(IGitRepository gitRepository, IClaudeCodeCliRunner claudeCodeCliRunner, ILogger logger) + public ClaudeCodeAgentEnvironmentScanner(IClaudeCodeCliRunner claudeCodeCliRunner, ILogger logger) { - ArgumentNullException.ThrowIfNull(gitRepository); ArgumentNullException.ThrowIfNull(claudeCodeCliRunner); ArgumentNullException.ThrowIfNull(logger); - _gitRepository = gitRepository; _claudeCodeCliRunner = claudeCodeCliRunner; _logger = logger; } @@ -42,47 +37,35 @@ public ClaudeCodeAgentEnvironmentScanner(IGitRepository gitRepository, IClaudeCo public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationToken cancellationToken) { _logger.LogDebug("Starting Claude Code environment scan in directory: {WorkingDirectory}", context.WorkingDirectory.FullName); - - // Get the git root to use as a boundary for searching - _logger.LogDebug("Finding git repository root..."); - var gitRoot = await _gitRepository.GetRootAsync(cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Git root: {GitRoot}", gitRoot?.FullName ?? "(none)"); + _logger.LogDebug("Workspace root: {RepositoryRoot}", context.RepositoryRoot.FullName); // Find the .claude folder to determine if Claude Code is being used in this project _logger.LogDebug("Searching for .claude folder..."); - var claudeCodeFolder = FindClaudeCodeFolder(context.WorkingDirectory, gitRoot); + var claudeCodeFolder = FindClaudeCodeFolder(context.WorkingDirectory, context.RepositoryRoot); - // Determine the repo root - use git root, or infer from .claude folder location, or fall back to working directory - DirectoryInfo? repoRoot = gitRoot; - if (repoRoot is null && claudeCodeFolder is not null) + if (claudeCodeFolder is not null) { - // .claude folder's parent is the repo root - repoRoot = claudeCodeFolder.Parent; - _logger.LogDebug("Inferred repo root from .claude folder parent: {RepoRoot}", repoRoot?.FullName ?? "(none)"); - } - - if (claudeCodeFolder is not null || repoRoot is not null) - { - var targetRepoRoot = repoRoot ?? context.WorkingDirectory; - _logger.LogDebug("Found .claude folder or repo root at: {RepoRoot}", targetRepoRoot.FullName); + // If .claude folder is found, override the workspace root with its parent directory + var workspaceRoot = claudeCodeFolder.Parent ?? context.RepositoryRoot; + _logger.LogDebug("Inferred workspace root from .claude folder parent: {WorkspaceRoot}", workspaceRoot.FullName); // Check if the aspire server is already configured in .mcp.json _logger.LogDebug("Checking if Aspire MCP server is already configured in .mcp.json..."); - if (HasAspireServerConfigured(targetRepoRoot)) + if (HasAspireServerConfigured(workspaceRoot)) { _logger.LogDebug("Aspire MCP server is already configured - skipping"); // Already configured, no need to offer an applicator return; } - // Found a .claude folder or git repo - add an applicator to configure MCP - _logger.LogDebug("Adding Claude Code applicator for .mcp.json at: {RepoRoot}", targetRepoRoot.FullName); - context.AddApplicator(CreateApplicator(targetRepoRoot)); + // Found a .claude folder - add an applicator to configure MCP + _logger.LogDebug("Adding Claude Code applicator for .mcp.json at: {WorkspaceRoot}", workspaceRoot.FullName); + context.AddApplicator(CreateApplicator(workspaceRoot)); } else { - // No .claude folder or git repo found - check if Claude Code CLI is installed - _logger.LogDebug("No .claude folder or git repo found, checking for Claude Code CLI installation..."); + // No .claude folder found - check if Claude Code CLI is installed + _logger.LogDebug("No .claude folder found, checking for Claude Code CLI installation..."); var claudeCodeVersion = await _claudeCodeCliRunner.GetVersionAsync(cancellationToken).ConfigureAwait(false); if (claudeCodeVersion is not null) @@ -90,16 +73,16 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok _logger.LogDebug("Found Claude Code CLI version: {Version}", claudeCodeVersion); // Check if the aspire server is already configured in .mcp.json - if (HasAspireServerConfigured(context.WorkingDirectory)) + if (HasAspireServerConfigured(context.RepositoryRoot)) { _logger.LogDebug("Aspire MCP server is already configured - skipping"); // Already configured, no need to offer an applicator return; } - // Claude Code is installed - offer to create config at working directory - _logger.LogDebug("Adding Claude Code applicator for .mcp.json at working directory: {WorkingDirectory}", context.WorkingDirectory.FullName); - context.AddApplicator(CreateApplicator(context.WorkingDirectory)); + // Claude Code is installed - offer to create config at workspace root + _logger.LogDebug("Adding Claude Code applicator for .mcp.json at workspace root: {WorkspaceRoot}", context.RepositoryRoot.FullName); + context.AddApplicator(CreateApplicator(context.RepositoryRoot)); } else { @@ -110,12 +93,12 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok /// /// Walks up the directory tree to find a .claude folder. - /// Stops if we go above the git root (if provided). + /// Stops if we go above the workspace root. /// Ignores the .claude folder in the user's home directory. /// /// The directory to start searching from. - /// The git repository root, or null if not in a git repository. - private static DirectoryInfo? FindClaudeCodeFolder(DirectoryInfo startDirectory, DirectoryInfo? gitRoot) + /// The workspace root to use as the boundary for searches. + private static DirectoryInfo? FindClaudeCodeFolder(DirectoryInfo startDirectory, DirectoryInfo repositoryRoot) { var currentDirectory = startDirectory; var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -130,9 +113,9 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok return new DirectoryInfo(claudeCodePath); } - // Stop if we've reached the git root without finding .claude - // (don't search above the repository boundary) - if (gitRoot is not null && string.Equals(currentDirectory.FullName, gitRoot.FullName, StringComparison.OrdinalIgnoreCase)) + // Stop if we've reached the workspace root without finding .claude + // (don't search above the workspace boundary) + if (string.Equals(currentDirectory.FullName, repositoryRoot.FullName, StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/src/Aspire.Cli/Agents/IAgentEnvironmentDetector.cs b/src/Aspire.Cli/Agents/IAgentEnvironmentDetector.cs index a250f067313..8f47de528c8 100644 --- a/src/Aspire.Cli/Agents/IAgentEnvironmentDetector.cs +++ b/src/Aspire.Cli/Agents/IAgentEnvironmentDetector.cs @@ -12,7 +12,8 @@ internal interface IAgentEnvironmentDetector /// Detects available agent environments by running all registered scanners. /// /// The working directory to scan. + /// The root directory of the repository/workspace. Scanners use this as the boundary for searches. /// A token to cancel the operation. /// An array of applicators for detected agent environments. - Task DetectAsync(DirectoryInfo workingDirectory, CancellationToken cancellationToken); + Task DetectAsync(DirectoryInfo workingDirectory, DirectoryInfo repositoryRoot, CancellationToken cancellationToken); } diff --git a/src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs b/src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs index 5618da902ea..4e78c6340d4 100644 --- a/src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs +++ b/src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs @@ -3,7 +3,6 @@ using System.Text.Json; using System.Text.Json.Nodes; -using Aspire.Cli.Git; using Aspire.Cli.Resources; using Microsoft.Extensions.Logging; @@ -17,22 +16,18 @@ internal sealed class OpenCodeAgentEnvironmentScanner : IAgentEnvironmentScanner private const string OpenCodeConfigFileName = "opencode.jsonc"; private const string AspireServerName = "aspire"; - private readonly IGitRepository _gitRepository; private readonly IOpenCodeCliRunner _openCodeCliRunner; private readonly ILogger _logger; /// /// Initializes a new instance of . /// - /// The Git repository service for finding repository boundaries. /// The OpenCode CLI runner for checking if OpenCode is installed. /// The logger for diagnostic output. - public OpenCodeAgentEnvironmentScanner(IGitRepository gitRepository, IOpenCodeCliRunner openCodeCliRunner, ILogger logger) + public OpenCodeAgentEnvironmentScanner(IOpenCodeCliRunner openCodeCliRunner, ILogger logger) { - ArgumentNullException.ThrowIfNull(gitRepository); ArgumentNullException.ThrowIfNull(openCodeCliRunner); ArgumentNullException.ThrowIfNull(logger); - _gitRepository = gitRepository; _openCodeCliRunner = openCodeCliRunner; _logger = logger; } @@ -41,14 +36,10 @@ public OpenCodeAgentEnvironmentScanner(IGitRepository gitRepository, IOpenCodeCl public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationToken cancellationToken) { _logger.LogDebug("Starting OpenCode environment scan in directory: {WorkingDirectory}", context.WorkingDirectory.FullName); - - // Get the git root - OpenCode config should be at the repo root - _logger.LogDebug("Finding git repository root..."); - var gitRoot = await _gitRepository.GetRootAsync(cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Git root: {GitRoot}", gitRoot?.FullName ?? "(none)"); - - // Look for existing opencode.jsonc file at git root or working directory - var configDirectory = gitRoot ?? context.WorkingDirectory; + _logger.LogDebug("Workspace root: {RepositoryRoot}", context.RepositoryRoot.FullName); + + // Look for existing opencode.jsonc file at workspace root + var configDirectory = context.RepositoryRoot; var configFilePath = Path.Combine(configDirectory.FullName, OpenCodeConfigFileName); var configFileExists = File.Exists(configFilePath); diff --git a/src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs b/src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs index 3cfa62b2781..441e137a12f 100644 --- a/src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs +++ b/src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs @@ -3,7 +3,6 @@ using System.Text.Json; using System.Text.Json.Nodes; -using Aspire.Cli.Git; using Aspire.Cli.Resources; using Microsoft.Extensions.Logging; @@ -19,22 +18,18 @@ internal sealed class VsCodeAgentEnvironmentScanner : IAgentEnvironmentScanner private const string VsCodeEnvironmentVariablePrefix = "VSCODE_"; private const string AspireServerName = "aspire"; - private readonly IGitRepository _gitRepository; private readonly IVsCodeCliRunner _vsCodeCliRunner; private readonly ILogger _logger; /// /// Initializes a new instance of . /// - /// The Git repository service for finding repository boundaries. /// The VS Code CLI runner for checking if VS Code is installed. /// The logger for diagnostic output. - public VsCodeAgentEnvironmentScanner(IGitRepository gitRepository, IVsCodeCliRunner vsCodeCliRunner, ILogger logger) + public VsCodeAgentEnvironmentScanner(IVsCodeCliRunner vsCodeCliRunner, ILogger logger) { - ArgumentNullException.ThrowIfNull(gitRepository); ArgumentNullException.ThrowIfNull(vsCodeCliRunner); ArgumentNullException.ThrowIfNull(logger); - _gitRepository = gitRepository; _vsCodeCliRunner = vsCodeCliRunner; _logger = logger; } @@ -43,14 +38,10 @@ public VsCodeAgentEnvironmentScanner(IGitRepository gitRepository, IVsCodeCliRun public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationToken cancellationToken) { _logger.LogDebug("Starting VS Code environment scan in directory: {WorkingDirectory}", context.WorkingDirectory.FullName); - - // Get the git root to use as a boundary for searching - _logger.LogDebug("Finding git repository root..."); - var gitRoot = await _gitRepository.GetRootAsync(cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Git root: {GitRoot}", gitRoot?.FullName ?? "(none)"); + _logger.LogDebug("Workspace root: {RepositoryRoot}", context.RepositoryRoot.FullName); _logger.LogDebug("Searching for .vscode folder..."); - var vsCodeFolder = FindVsCodeFolder(context.WorkingDirectory, gitRoot); + var vsCodeFolder = FindVsCodeFolder(context.WorkingDirectory, context.RepositoryRoot); if (vsCodeFolder is not null) { @@ -72,9 +63,8 @@ public async Task ScanAsync(AgentEnvironmentScanContext context, CancellationTok { _logger.LogDebug("No .vscode folder found, but VS Code is available on the system"); // No .vscode folder found, but VS Code is available - // Use git root if available, otherwise fall back to current working directory - var targetDirectory = gitRoot ?? context.WorkingDirectory; - var targetVsCodeFolder = new DirectoryInfo(Path.Combine(targetDirectory.FullName, VsCodeFolderName)); + // Use workspace root for new .vscode folder + var targetVsCodeFolder = new DirectoryInfo(Path.Combine(context.RepositoryRoot.FullName, VsCodeFolderName)); _logger.LogDebug("Adding VS Code applicator for new .vscode folder at: {VsCodeFolder}", targetVsCodeFolder.FullName); context.AddApplicator(CreateApplicator(targetVsCodeFolder)); } @@ -125,12 +115,12 @@ private async Task IsVsCodeAvailableAsync(CancellationToken cancellationTo /// /// Walks up the directory tree to find a .vscode folder. - /// Stops if we go above the git root (if provided). + /// Stops if we go above the workspace root. /// Ignores the .vscode folder in the user's home directory (used for user settings, not workspace config). /// /// The directory to start searching from. - /// The git repository root, or null if not in a git repository. - private static DirectoryInfo? FindVsCodeFolder(DirectoryInfo startDirectory, DirectoryInfo? gitRoot) + /// The workspace root to use as the boundary for searches. + private static DirectoryInfo? FindVsCodeFolder(DirectoryInfo startDirectory, DirectoryInfo repositoryRoot) { var currentDirectory = startDirectory; var homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -145,9 +135,9 @@ private async Task IsVsCodeAvailableAsync(CancellationToken cancellationTo return new DirectoryInfo(vsCodePath); } - // Stop if we've reached the git root without finding .vscode - // (don't search above the repository boundary) - if (gitRoot is not null && string.Equals(currentDirectory.FullName, gitRoot.FullName, StringComparison.OrdinalIgnoreCase)) + // Stop if we've reached the workspace root without finding .vscode + // (don't search above the workspace boundary) + if (string.Equals(currentDirectory.FullName, repositoryRoot.FullName, StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/src/Aspire.Cli/Commands/McpCommand.cs b/src/Aspire.Cli/Commands/McpCommand.cs index d39039760e7..189a8e36720 100644 --- a/src/Aspire.Cli/Commands/McpCommand.cs +++ b/src/Aspire.Cli/Commands/McpCommand.cs @@ -6,6 +6,7 @@ using Aspire.Cli.Agents; using Aspire.Cli.Backchannel; using Aspire.Cli.Configuration; +using Aspire.Cli.Git; using Aspire.Cli.Interaction; using Aspire.Cli.Resources; using Aspire.Cli.Utils; @@ -23,7 +24,8 @@ public McpCommand( IAuxiliaryBackchannelMonitor auxiliaryBackchannelMonitor, ILoggerFactory loggerFactory, ILogger logger, - IAgentEnvironmentDetector agentEnvironmentDetector) + IAgentEnvironmentDetector agentEnvironmentDetector, + IGitRepository gitRepository) : base("mcp", McpCommandStrings.Description, features, updateNotifier, executionContext, interactionService) { ArgumentNullException.ThrowIfNull(interactionService); @@ -31,7 +33,7 @@ public McpCommand( var startCommand = new McpStartCommand(interactionService, features, updateNotifier, executionContext, auxiliaryBackchannelMonitor, loggerFactory, logger); Subcommands.Add(startCommand); - var initCommand = new McpInitCommand(interactionService, features, updateNotifier, executionContext, agentEnvironmentDetector); + var initCommand = new McpInitCommand(interactionService, features, updateNotifier, executionContext, agentEnvironmentDetector, gitRepository); Subcommands.Add(initCommand); } diff --git a/src/Aspire.Cli/Commands/McpInitCommand.cs b/src/Aspire.Cli/Commands/McpInitCommand.cs index b7e61decdf9..2ee058c140c 100644 --- a/src/Aspire.Cli/Commands/McpInitCommand.cs +++ b/src/Aspire.Cli/Commands/McpInitCommand.cs @@ -2,12 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using System.Globalization; using Aspire.Cli.Agents; using Aspire.Cli.Configuration; +using Aspire.Cli.Git; using Aspire.Cli.Interaction; using Aspire.Cli.NuGet; using Aspire.Cli.Resources; using Aspire.Cli.Utils; +using Spectre.Console; namespace Aspire.Cli.Commands; @@ -18,6 +21,7 @@ internal sealed class McpInitCommand : BaseCommand, IPackageMetaPrefetchingComma { private readonly IInteractionService _interactionService; private readonly IAgentEnvironmentDetector _agentEnvironmentDetector; + private readonly IGitRepository _gitRepository; /// /// McpInitCommand does not need template package metadata prefetching. @@ -34,23 +38,52 @@ public McpInitCommand( IFeatures features, ICliUpdateNotifier updateNotifier, CliExecutionContext executionContext, - IAgentEnvironmentDetector agentEnvironmentDetector) + IAgentEnvironmentDetector agentEnvironmentDetector, + IGitRepository gitRepository) : base("init", McpCommandStrings.InitCommand_Description, features, updateNotifier, executionContext, interactionService) { ArgumentNullException.ThrowIfNull(interactionService); ArgumentNullException.ThrowIfNull(agentEnvironmentDetector); + ArgumentNullException.ThrowIfNull(gitRepository); _interactionService = interactionService; _agentEnvironmentDetector = agentEnvironmentDetector; + _gitRepository = gitRepository; } protected override bool UpdateNotificationsEnabled => false; protected override async Task ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { + // Try to discover the git repository root to use as the default workspace root + var gitRoot = await _gitRepository.GetRootAsync(cancellationToken); + var defaultWorkspaceRoot = gitRoot ?? ExecutionContext.WorkingDirectory; + + // Prompt the user for the workspace root + var workspaceRootPath = await _interactionService.PromptForStringAsync( + McpCommandStrings.InitCommand_WorkspaceRootPrompt, + defaultValue: defaultWorkspaceRoot.FullName, + validator: path => + { + if (string.IsNullOrWhiteSpace(path)) + { + return ValidationResult.Error(McpCommandStrings.InitCommand_WorkspaceRootRequired); + } + + if (!Directory.Exists(path)) + { + return ValidationResult.Error(string.Format(CultureInfo.InvariantCulture, McpCommandStrings.InitCommand_WorkspaceRootNotFound, path)); + } + + return ValidationResult.Success(); + }, + cancellationToken: cancellationToken); + + var workspaceRoot = new DirectoryInfo(workspaceRootPath); + var applicators = await _interactionService.ShowStatusAsync( McpCommandStrings.InitCommand_DetectingAgentEnvironments, - async () => await _agentEnvironmentDetector.DetectAsync(ExecutionContext.WorkingDirectory, cancellationToken)); + async () => await _agentEnvironmentDetector.DetectAsync(ExecutionContext.WorkingDirectory, workspaceRoot, cancellationToken)); if (applicators.Length == 0) { diff --git a/src/Aspire.Cli/Resources/McpCommandStrings.Designer.cs b/src/Aspire.Cli/Resources/McpCommandStrings.Designer.cs index c30c8f5c02a..29da36ea141 100644 --- a/src/Aspire.Cli/Resources/McpCommandStrings.Designer.cs +++ b/src/Aspire.Cli/Resources/McpCommandStrings.Designer.cs @@ -122,5 +122,32 @@ internal static string InitCommand_DetectingAgentEnvironments { return ResourceManager.GetString("InitCommand_DetectingAgentEnvironments", resourceCulture); } } + + /// + /// Looks up a localized string similar to Enter the path to the root of your workspace:. + /// + internal static string InitCommand_WorkspaceRootPrompt { + get { + return ResourceManager.GetString("InitCommand_WorkspaceRootPrompt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Workspace root path is required.. + /// + internal static string InitCommand_WorkspaceRootRequired { + get { + return ResourceManager.GetString("InitCommand_WorkspaceRootRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Directory not found: {0}. + /// + internal static string InitCommand_WorkspaceRootNotFound { + get { + return ResourceManager.GetString("InitCommand_WorkspaceRootNotFound", resourceCulture); + } + } } } diff --git a/src/Aspire.Cli/Resources/McpCommandStrings.resx b/src/Aspire.Cli/Resources/McpCommandStrings.resx index 2bf0ad861c4..df67202b866 100644 --- a/src/Aspire.Cli/Resources/McpCommandStrings.resx +++ b/src/Aspire.Cli/Resources/McpCommandStrings.resx @@ -81,4 +81,13 @@ Detecting agent environments... + + Enter the path to the root of your workspace: + + + Workspace root path is required. + + + Directory not found: {0} + diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.cs.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.cs.xlf index 170f104ef89..3449359aaa9 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.cs.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.cs.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Spusťte server MCP (Model Context Protocol). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.de.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.de.xlf index c5a8e420090..8d84bbeb4f0 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.de.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.de.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Starten Sie den MCP-Server (Model Context Protocol). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.es.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.es.xlf index f8f7ebe29c2..3ec38d4d2ca 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.es.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.es.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Inicie el servidor MCP (protocolo de contexto de modelo). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.fr.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.fr.xlf index 9648bcb15cc..944d03b08fd 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.fr.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.fr.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Démarrez le serveur MCP (Model Context Protocol). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.it.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.it.xlf index 6e68722684b..b5b53cfef1d 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.it.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.it.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Avviare il server MCP (Model Context Protocol). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ja.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ja.xlf index b5147d79864..9e6be134137 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ja.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ja.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. MCP (モデル コンテキスト プロトコル) サーバーを起動します。 diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ko.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ko.xlf index 5f05740979e..54a96e8a5da 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ko.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ko.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. MCP(모델 컨텍스트 프로토콜) 서버를 시작합니다. diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pl.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pl.xlf index 9171fb98aaf..5bea4efdb24 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pl.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pl.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Uruchom serwer MCP (Model Context Protocol). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pt-BR.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pt-BR.xlf index 47ce23447e0..d6427485e66 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pt-BR.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.pt-BR.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Inicie o servidor MCP (Protocolo de Contexto de Modelo). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ru.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ru.xlf index 1b71ac11975..41401dfa6d3 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ru.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.ru.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. Запуск сервера MCP (протокол контекста модели). diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.tr.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.tr.xlf index 762814edeea..f379eead8c1 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.tr.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.tr.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. MCP (Model Bağlam Protokolü) sunucusunu başlat. diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hans.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hans.xlf index b0e3b1aaa3d..5519f7b6ec8 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hans.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. 启动 MCP (模型上下文协议)服务器。 diff --git a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hant.xlf b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hant.xlf index 25bc228bdf4..921af8d083d 100644 --- a/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Cli/Resources/xlf/McpCommandStrings.zh-Hant.xlf @@ -32,6 +32,21 @@ No agent environments were detected. + + Directory not found: {0} + Directory not found: {0} + + + + Enter the path to the root of your workspace: + Enter the path to the root of your workspace: + + + + Workspace root path is required. + Workspace root path is required. + + Start the MCP (Model Context Protocol) server. 啟動 MCP (模型內容通訊協定) 伺服器。 diff --git a/tests/Aspire.Cli.Tests/Agents/AgentEnvironmentDetectorTests.cs b/tests/Aspire.Cli.Tests/Agents/AgentEnvironmentDetectorTests.cs index 37c81d9e3aa..5e1623632e7 100644 --- a/tests/Aspire.Cli.Tests/Agents/AgentEnvironmentDetectorTests.cs +++ b/tests/Aspire.Cli.Tests/Agents/AgentEnvironmentDetectorTests.cs @@ -14,7 +14,7 @@ public async Task DetectAsync_WithNoScanners_ReturnsEmptyArray() using var workspace = TemporaryWorkspace.Create(outputHelper); var detector = new AgentEnvironmentDetector([]); - var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, CancellationToken.None); + var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, workspace.WorkspaceRoot, CancellationToken.None); Assert.Empty(applicators); } @@ -26,10 +26,11 @@ public async Task DetectAsync_WithScanner_RunsScannerWithCorrectContext() var scanner = new TestAgentEnvironmentScanner(); var detector = new AgentEnvironmentDetector([scanner]); - var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, CancellationToken.None); + var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, workspace.WorkspaceRoot, CancellationToken.None); Assert.True(scanner.WasScanned); Assert.Equal(workspace.WorkspaceRoot.FullName, scanner.ScanContext?.WorkingDirectory.FullName); + Assert.Equal(workspace.WorkspaceRoot.FullName, scanner.ScanContext?.RepositoryRoot.FullName); } [Fact] @@ -44,7 +45,7 @@ public async Task DetectAsync_WithScannerThatAddsApplicator_ReturnsApplicator() }; var detector = new AgentEnvironmentDetector([scanner]); - var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, CancellationToken.None); + var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, workspace.WorkspaceRoot, CancellationToken.None); Assert.Single(applicators); Assert.Equal("Test Environment", applicators[0].Description); @@ -68,7 +69,7 @@ public async Task DetectAsync_WithMultipleScanners_RunsAllScanners() }; var detector = new AgentEnvironmentDetector([scanner1, scanner2]); - var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, CancellationToken.None); + var applicators = await detector.DetectAsync(workspace.WorkspaceRoot, workspace.WorkspaceRoot, CancellationToken.None); Assert.True(scanner1.WasScanned); Assert.True(scanner2.WasScanned); diff --git a/tests/Aspire.Cli.Tests/Agents/CopilotCliAgentEnvironmentScannerTests.cs b/tests/Aspire.Cli.Tests/Agents/CopilotCliAgentEnvironmentScannerTests.cs index 5d1ba6392e4..ae65e23ef97 100644 --- a/tests/Aspire.Cli.Tests/Agents/CopilotCliAgentEnvironmentScannerTests.cs +++ b/tests/Aspire.Cli.Tests/Agents/CopilotCliAgentEnvironmentScannerTests.cs @@ -19,7 +19,11 @@ public async Task ScanAsync_WhenCopilotCliInstalled_ReturnsApplicator() using var workspace = TemporaryWorkspace.Create(outputHelper); var copilotCliRunner = new FakeCopilotCliRunner(new SemVersion(1, 0, 0)); var scanner = new CopilotCliAgentEnvironmentScanner(copilotCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -33,7 +37,11 @@ public async Task ScanAsync_WhenCopilotCliNotInstalled_ReturnsNoApplicator() using var workspace = TemporaryWorkspace.Create(outputHelper); var copilotCliRunner = new FakeCopilotCliRunner(null); var scanner = new CopilotCliAgentEnvironmentScanner(copilotCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -51,7 +59,11 @@ public async Task ApplyAsync_CreatesMcpConfigJsonWithCorrectConfiguration() // Create a scanner that writes to a known test location var copilotCliRunner = new FakeCopilotCliRunner(new SemVersion(1, 0, 0)); var scanner = new TestCopilotCliAgentEnvironmentScanner(copilotCliRunner, copilotFolder.FullName); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -117,7 +129,11 @@ public async Task ApplyAsync_PreservesExistingMcpConfigContent() var copilotCliRunner = new FakeCopilotCliRunner(new SemVersion(1, 0, 0)); var scanner = new TestCopilotCliAgentEnvironmentScanner(copilotCliRunner, copilotFolder.FullName); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); await context.Applicators[0].ApplyAsync(CancellationToken.None); @@ -156,7 +172,11 @@ public async Task ScanAsync_WhenAspireAlreadyConfigured_ReturnsNoApplicator() var copilotCliRunner = new FakeCopilotCliRunner(new SemVersion(1, 0, 0)); var scanner = new TestCopilotCliAgentEnvironmentScanner(copilotCliRunner, copilotFolder.FullName); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); diff --git a/tests/Aspire.Cli.Tests/Agents/VsCodeAgentEnvironmentScannerTests.cs b/tests/Aspire.Cli.Tests/Agents/VsCodeAgentEnvironmentScannerTests.cs index ffc8c42542f..601e4250fe4 100644 --- a/tests/Aspire.Cli.Tests/Agents/VsCodeAgentEnvironmentScannerTests.cs +++ b/tests/Aspire.Cli.Tests/Agents/VsCodeAgentEnvironmentScannerTests.cs @@ -4,7 +4,6 @@ using System.Text.Json.Nodes; using Aspire.Cli.Agents; using Aspire.Cli.Agents.VsCode; -using Aspire.Cli.Git; using Aspire.Cli.Tests.Utils; using Microsoft.Extensions.Logging.Abstractions; using Semver; @@ -18,10 +17,13 @@ public async Task ScanAsync_WhenVsCodeFolderExists_ReturnsApplicator() { using var workspace = TemporaryWorkspace.Create(outputHelper); var vsCodeFolder = workspace.CreateDirectory(".vscode"); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -35,10 +37,13 @@ public async Task ScanAsync_WhenVsCodeFolderExistsInParent_ReturnsApplicatorForP using var workspace = TemporaryWorkspace.Create(outputHelper); var vsCodeFolder = workspace.CreateDirectory(".vscode"); var childDir = workspace.CreateDirectory("subdir"); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = childDir }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = childDir, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -46,15 +51,18 @@ public async Task ScanAsync_WhenVsCodeFolderExistsInParent_ReturnsApplicatorForP } [Fact] - public async Task ScanAsync_WhenGitRootFoundBeforeVsCode_AndNoCliAvailable_ReturnsNoApplicator() + public async Task ScanAsync_WhenRepositoryRootReachedBeforeVsCode_AndNoCliAvailable_ReturnsNoApplicator() { using var workspace = TemporaryWorkspace.Create(outputHelper); var childDir = workspace.CreateDirectory("subdir"); - // Git root is the workspace root, so search should stop there - var gitRepository = new FakeGitRepository(workspace.WorkspaceRoot); + // Repository root is the workspace root, so search should stop there var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = childDir }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = childDir, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -65,10 +73,13 @@ public async Task ScanAsync_WhenGitRootFoundBeforeVsCode_AndNoCliAvailable_Retur public async Task ScanAsync_WhenNoVsCodeFolder_AndVsCodeCliAvailable_ReturnsApplicator() { using var workspace = TemporaryWorkspace.Create(outputHelper); - var gitRepository = new FakeGitRepository(workspace.WorkspaceRoot); var vsCodeCliRunner = new FakeVsCodeCliRunner(new SemVersion(1, 85, 0)); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -76,13 +87,16 @@ public async Task ScanAsync_WhenNoVsCodeFolder_AndVsCodeCliAvailable_ReturnsAppl } [Fact] - public async Task ScanAsync_WhenNoVsCodeOrGitFolder_AndNoCliAvailable_ReturnsNoApplicator() + public async Task ScanAsync_WhenNoVsCodeFolder_AndNoCliAvailable_ReturnsNoApplicator() { using var workspace = TemporaryWorkspace.Create(outputHelper); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; // This test assumes no VSCODE_* environment variables are set // With no CLI available and no env vars, no applicator should be returned @@ -97,13 +111,16 @@ public async Task ApplyAsync_CreatesVsCodeFolderIfNotExists() { using var workspace = TemporaryWorkspace.Create(outputHelper); var vsCodePath = Path.Combine(workspace.WorkspaceRoot.FullName, ".vscode"); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); // First, make the scanner find a parent .vscode folder to get an applicator var parentVsCode = workspace.CreateDirectory(".vscode"); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -122,10 +139,13 @@ public async Task ApplyAsync_CreatesMcpJsonWithCorrectConfiguration() { using var workspace = TemporaryWorkspace.Create(outputHelper); var vsCodeFolder = workspace.CreateDirectory(".vscode"); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); await context.Applicators[0].ApplyAsync(CancellationToken.None); @@ -178,10 +198,13 @@ public async Task ApplyAsync_PreservesExistingMcpJsonContent() var mcpJsonPath = Path.Combine(vsCodeFolder.FullName, "mcp.json"); await File.WriteAllTextAsync(mcpJsonPath, existingConfig.ToJsonString()); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); await context.Applicators[0].ApplyAsync(CancellationToken.None); @@ -219,10 +242,13 @@ public async Task ApplyAsync_UpdatesExistingAspireServerConfig() var mcpJsonPath = Path.Combine(vsCodeFolder.FullName, "mcp.json"); await File.WriteAllTextAsync(mcpJsonPath, existingConfig.ToJsonString()); - var gitRepository = new FakeGitRepository(null); var vsCodeCliRunner = new FakeVsCodeCliRunner(null); - var scanner = new VsCodeAgentEnvironmentScanner(gitRepository, vsCodeCliRunner, NullLogger.Instance); - var context = new AgentEnvironmentScanContext { WorkingDirectory = workspace.WorkspaceRoot }; + var scanner = new VsCodeAgentEnvironmentScanner(vsCodeCliRunner, NullLogger.Instance); + var context = new AgentEnvironmentScanContext + { + WorkingDirectory = workspace.WorkspaceRoot, + RepositoryRoot = workspace.WorkspaceRoot + }; await scanner.ScanAsync(context, CancellationToken.None); @@ -244,14 +270,6 @@ public async Task ApplyAsync_UpdatesExistingAspireServerConfig() Assert.NotNull(otherServer); } - /// - /// A fake implementation of for testing. - /// - private sealed class FakeGitRepository(DirectoryInfo? gitRoot) : IGitRepository - { - public Task GetRootAsync(CancellationToken cancellationToken) => Task.FromResult(gitRoot); - } - /// /// A fake implementation of for testing. /// diff --git a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs index 554d9b59a58..fa382741199 100644 --- a/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs +++ b/tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs @@ -7,6 +7,7 @@ using Aspire.Cli.Certificates; using Aspire.Cli.Commands; using Aspire.Cli.DotNet; +using Aspire.Cli.Git; using Aspire.Cli.Interaction; using Aspire.Cli.NuGet; using Aspire.Cli.Projects; @@ -99,6 +100,7 @@ public static IServiceCollection CreateServiceCollection(TemporaryWorkspace work services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(options.AuxiliaryBackchannelMonitorFactory); services.AddSingleton(options.AgentEnvironmentDetectorFactory); + services.AddSingleton(options.GitRepositoryFactory); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -358,6 +360,13 @@ public ISolutionLocator CreateDefaultSolutionLocatorFactory(IServiceProvider ser { return new AgentEnvironmentDetector([]); }; + + public Func GitRepositoryFactory { get; set; } = (IServiceProvider serviceProvider) => + { + var executionContext = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetRequiredService>(); + return new GitRepository(executionContext, logger); + }; } internal sealed class TestOutputTextWriter : TextWriter