diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/AutoDiscoveryService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/AutoDiscoveryService.cs
new file mode 100644
index 0000000..5e2f0c8
--- /dev/null
+++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/AutoDiscoveryService.cs
@@ -0,0 +1,86 @@
+using Microsoft.Extensions.Logging;
+
+namespace JdeScoping.ConfigManager.Services;
+
+///
+/// Service for auto-discovering configuration file locations.
+/// Searches in prioritized order:
+/// 1. JDESCOPING_CONFIG_PATH environment variable
+/// 2. Same directory as executable
+/// 3. ../JdeScoping.Host/ relative to executable
+/// 4. User config directory (~/.jdescoping on Unix, %LOCALAPPDATA%\JdeScoping on Windows)
+///
+public class AutoDiscoveryService : IAutoDiscoveryService
+{
+ private readonly IFileSystem _fileSystem;
+ private readonly ILogger? _logger;
+ private const string EnvVarName = "JDESCOPING_CONFIG_PATH";
+ private const string AppSettingsFileName = "appsettings.json";
+
+ public AutoDiscoveryService(IFileSystem fileSystem, ILogger? logger = null)
+ {
+ _fileSystem = fileSystem;
+ _logger = logger;
+ }
+
+ public Task FindConfigFolderAsync(CancellationToken ct = default)
+ {
+ // 1. Check environment variable
+ var envPath = Environment.GetEnvironmentVariable(EnvVarName);
+ if (!string.IsNullOrEmpty(envPath) && IsValidConfigFolder(envPath))
+ {
+ _logger?.LogInformation("Found config folder from environment variable: {Path}", envPath);
+ return Task.FromResult(envPath);
+ }
+
+ // 2. Check same directory as executable
+ var exeDir = AppContext.BaseDirectory;
+ if (IsValidConfigFolder(exeDir))
+ {
+ _logger?.LogInformation("Found config folder in executable directory: {Path}", exeDir);
+ return Task.FromResult(exeDir);
+ }
+
+ // 3. Check ../JdeScoping.Host/ relative to executable
+ var hostDir = _fileSystem.Combine(exeDir, "..", "JdeScoping.Host");
+ if (IsValidConfigFolder(hostDir))
+ {
+ _logger?.LogInformation("Found config folder in host directory: {Path}", hostDir);
+ return Task.FromResult(hostDir);
+ }
+
+ // 4. Check user config directory
+ var userConfigDir = GetUserConfigDirectory();
+ if (userConfigDir != null && IsValidConfigFolder(userConfigDir))
+ {
+ _logger?.LogInformation("Found config folder in user directory: {Path}", userConfigDir);
+ return Task.FromResult(userConfigDir);
+ }
+
+ _logger?.LogWarning("Could not find config folder in any standard location");
+ return Task.FromResult(null);
+ }
+
+ private bool IsValidConfigFolder(string path)
+ {
+ if (!_fileSystem.DirectoryExists(path))
+ return false;
+
+ var appSettingsPath = _fileSystem.Combine(path, AppSettingsFileName);
+ return _fileSystem.FileExists(appSettingsPath);
+ }
+
+ private string? GetUserConfigDirectory()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ return _fileSystem.Combine(localAppData, "JdeScoping");
+ }
+ else
+ {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ return _fileSystem.Combine(home, ".jdescoping");
+ }
+ }
+}
diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/IAutoDiscoveryService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IAutoDiscoveryService.cs
new file mode 100644
index 0000000..4372214
--- /dev/null
+++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IAutoDiscoveryService.cs
@@ -0,0 +1,19 @@
+namespace JdeScoping.ConfigManager.Services;
+
+///
+/// Service for auto-discovering configuration file locations.
+///
+public interface IAutoDiscoveryService
+{
+ ///
+ /// Searches for a configuration folder containing appsettings.json.
+ /// Search order:
+ /// 1. JDESCOPING_CONFIG_PATH environment variable
+ /// 2. Same directory as executable
+ /// 3. ../JdeScoping.Host/ relative to executable
+ /// 4. User config directory (~/.jdescoping on Unix, %LOCALAPPDATA%\JdeScoping on Windows)
+ ///
+ /// Cancellation token.
+ /// The path to the config folder, or null if not found.
+ Task FindConfigFolderAsync(CancellationToken ct = default);
+}
diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Services/AutoDiscoveryServiceTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/AutoDiscoveryServiceTests.cs
new file mode 100644
index 0000000..6d8c8b3
--- /dev/null
+++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/AutoDiscoveryServiceTests.cs
@@ -0,0 +1,210 @@
+using JdeScoping.ConfigManager.Services;
+
+namespace JdeScoping.ConfigManager.Tests.Services;
+
+public class AutoDiscoveryServiceTests : IDisposable
+{
+ private readonly IFileSystem _fileSystem;
+ private readonly AutoDiscoveryService _sut;
+ private readonly string? _originalEnvVar;
+
+ public AutoDiscoveryServiceTests()
+ {
+ _fileSystem = Substitute.For();
+ _sut = new AutoDiscoveryService(_fileSystem);
+ // Save original env var to restore later
+ _originalEnvVar = Environment.GetEnvironmentVariable("JDESCOPING_CONFIG_PATH");
+
+ // Set up default Combine behavior to work like Path.Combine
+ _fileSystem.Combine(Arg.Any()).Returns(callInfo =>
+ {
+ var paths = callInfo.Arg();
+ return Path.Combine(paths);
+ });
+ }
+
+ public void Dispose()
+ {
+ // Restore original env var
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", _originalEnvVar);
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenEnvVarSet_ReturnsEnvPath()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config");
+ _fileSystem.DirectoryExists("/custom/config").Returns(true);
+ _fileSystem.FileExists(Arg.Is(s => s.Contains("appsettings.json") && s.Contains("/custom/config"))).Returns(true);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBe("/custom/config");
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenEnvVarSetButDirectoryInvalid_SearchesOtherLocations()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/invalid/config");
+ _fileSystem.DirectoryExists(Arg.Any()).Returns(false);
+ _fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenEnvVarSetButNoAppSettings_SearchesOtherLocations()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config");
+ // Directory exists but no appsettings.json
+ _fileSystem.DirectoryExists("/custom/config").Returns(true);
+ _fileSystem.FileExists(Arg.Any()).Returns(false);
+ // All other locations also don't have config
+ _fileSystem.DirectoryExists(Arg.Is(s => s != "/custom/config")).Returns(false);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenNotFound_ReturnsNull()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null);
+ _fileSystem.DirectoryExists(Arg.Any()).Returns(false);
+ _fileSystem.FileExists(Arg.Any()).Returns(false);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenExeDirectoryHasConfig_ReturnsExeDirectory()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null);
+ var exeDir = AppContext.BaseDirectory;
+
+ _fileSystem.DirectoryExists(exeDir).Returns(true);
+ _fileSystem.FileExists(Arg.Is(s => s.Contains(exeDir) && s.Contains("appsettings.json"))).Returns(true);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBe(exeDir);
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenHostDirectoryHasConfig_ReturnsHostDirectory()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null);
+ var exeDir = AppContext.BaseDirectory;
+ var hostDir = Path.Combine(exeDir, "..", "JdeScoping.Host");
+
+ // Exe directory doesn't have config
+ _fileSystem.DirectoryExists(exeDir).Returns(false);
+
+ // Host directory has config
+ _fileSystem.DirectoryExists(hostDir).Returns(true);
+ _fileSystem.FileExists(Arg.Is(s => s.Contains("JdeScoping.Host") && s.Contains("appsettings.json"))).Returns(true);
+
+ // Other directories don't have config
+ _fileSystem.DirectoryExists(Arg.Is(s => s != exeDir && s != hostDir)).Returns(false);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBe(hostDir);
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WhenUserConfigDirectoryHasConfig_ReturnsUserConfigDirectory()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null);
+ var exeDir = AppContext.BaseDirectory;
+ var hostDir = Path.Combine(exeDir, "..", "JdeScoping.Host");
+
+ // Determine user config path based on OS
+ string userConfigDir;
+ if (OperatingSystem.IsWindows())
+ {
+ var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ userConfigDir = Path.Combine(localAppData, "JdeScoping");
+ }
+ else
+ {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ userConfigDir = Path.Combine(home, ".jdescoping");
+ }
+
+ // Exe directory doesn't have config
+ _fileSystem.DirectoryExists(exeDir).Returns(false);
+
+ // Host directory doesn't have config
+ _fileSystem.DirectoryExists(hostDir).Returns(false);
+
+ // User config directory has config
+ _fileSystem.DirectoryExists(userConfigDir).Returns(true);
+ _fileSystem.FileExists(Arg.Is(s => s.Contains(userConfigDir) && s.Contains("appsettings.json"))).Returns(true);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert
+ result.ShouldBe(userConfigDir);
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_SearchesPrioritizedOrder()
+ {
+ // Arrange - all locations have config, but env var should be returned first
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/env/config");
+ var exeDir = AppContext.BaseDirectory;
+
+ // Both env var path and exe dir have valid config
+ _fileSystem.DirectoryExists("/env/config").Returns(true);
+ _fileSystem.DirectoryExists(exeDir).Returns(true);
+ _fileSystem.FileExists(Arg.Any()).Returns(true);
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync();
+
+ // Assert - should return env var path, not exe dir
+ result.ShouldBe("/env/config");
+ }
+
+ [Fact]
+ public async Task FindConfigFolderAsync_WithCancellationToken_DoesNotThrow()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config");
+ _fileSystem.DirectoryExists("/custom/config").Returns(true);
+ _fileSystem.FileExists(Arg.Any()).Returns(true);
+
+ using var cts = new CancellationTokenSource();
+
+ // Act
+ var result = await _sut.FindConfigFolderAsync(cts.Token);
+
+ // Assert
+ result.ShouldBe("/custom/config");
+ }
+}