diff --git a/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/ComponentScaffold.cs b/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/ComponentScaffold.cs
index c97ba2d..6cf54e0 100644
--- a/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/ComponentScaffold.cs
+++ b/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/ComponentScaffold.cs
@@ -43,6 +43,9 @@ public static ScaffoldResult Apply(ComponentScaffoldRequest request)
throw new NotSupportedException(
$"Component type '{request.ComponentType}' has no scaffold applier. Supported types: {string.Join(", ", Appliers.Keys)}.");
}
+ // Uses the supplied path only when it identifies a solution root; otherwise
+ // auto-detects the root from the current directory.
+ request.SolutionRootPath = SolutionRootLocator.Resolve(request.SolutionRootPath, Directory.GetCurrentDirectory());
return applier(request);
}
diff --git a/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/Helpers/SolutionRootLocator.cs b/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/Helpers/SolutionRootLocator.cs
new file mode 100644
index 0000000..cb7ca83
--- /dev/null
+++ b/src/TALXIS.Platform.Metadata.Serialization.Xml/Scaffolding/Helpers/SolutionRootLocator.cs
@@ -0,0 +1,78 @@
+using System.Xml;
+
+namespace TALXIS.Platform.Metadata.Serialization.Xml.Scaffolding;
+
+///
+/// Resolves the unpacked solution root for a scaffold request. If the supplied
+/// path does not identify an existing directory, detects the root from the base
+/// directory: Other/Solution.xml in the base itself, the SolutionRootPath property
+/// of the single .csproj, or a unique Other/Solution.xml below the base.
+///
+public static class SolutionRootLocator
+{
+ public static string Resolve(string? requested, string baseDirectory)
+ {
+ if (!string.IsNullOrWhiteSpace(requested))
+ {
+ var requestedPath = Path.IsPathRooted(requested) ? requested : Path.Combine(baseDirectory, requested);
+ if (Directory.Exists(requestedPath)) return requested;
+ }
+
+ if (IsSolutionRoot(baseDirectory)) return baseDirectory;
+
+ var fromProject = FromProjectProperty(baseDirectory);
+ if (fromProject != null) return Path.Combine(baseDirectory, fromProject);
+
+ return FromDirectorySearch(baseDirectory);
+ }
+
+ private static bool IsSolutionRoot(string directory) =>
+ File.Exists(Path.Combine(directory, "Other", "Solution.xml"));
+
+ private static string? FromProjectProperty(string baseDirectory)
+ {
+ var projects = Directory.Exists(baseDirectory)
+ ? Directory.GetFiles(baseDirectory, "*.csproj", SearchOption.TopDirectoryOnly)
+ : Array.Empty();
+ if (projects.Length > 1)
+ {
+ throw new InvalidOperationException(
+ $"Multiple .csproj files found in '{baseDirectory}': {string.Join(", ", projects.Select(Path.GetFileName))}");
+ }
+ if (projects.Length == 0) return null;
+
+ var doc = new XmlDocument();
+ try
+ {
+ doc.Load(projects[0]);
+ }
+ catch (XmlException)
+ {
+ return null;
+ }
+
+ var node = doc.SelectSingleNode(
+ "/*[local-name()='Project']/*[local-name()='PropertyGroup']/*[local-name()='SolutionRootPath']");
+ var value = node?.InnerText.Trim();
+ return string.IsNullOrWhiteSpace(value) ? null : value;
+ }
+
+ private static string FromDirectorySearch(string baseDirectory)
+ {
+ var candidates = Directory.Exists(baseDirectory)
+ ? Directory.GetFiles(baseDirectory, "Solution.xml", SearchOption.AllDirectories)
+ .Where(path => string.Equals(Path.GetFileName(Path.GetDirectoryName(path)), "Other", StringComparison.OrdinalIgnoreCase))
+ .ToArray()
+ : Array.Empty();
+
+ if (candidates.Length > 1)
+ {
+ throw new InvalidOperationException(
+ $"Multiple Other/Solution.xml files found under '{baseDirectory}':{Environment.NewLine}{string.Join(Environment.NewLine, candidates)}");
+ }
+ if (candidates.Length == 0)
+ throw new InvalidOperationException($"Failed to determine SolutionRootPath from '{baseDirectory}'.");
+
+ return Path.GetDirectoryName(Path.GetDirectoryName(candidates[0]))!;
+ }
+}
diff --git a/tests/TALXIS.Platform.Metadata.Tests/SolutionRootLocatorTests.cs b/tests/TALXIS.Platform.Metadata.Tests/SolutionRootLocatorTests.cs
new file mode 100644
index 0000000..e1eeea1
--- /dev/null
+++ b/tests/TALXIS.Platform.Metadata.Tests/SolutionRootLocatorTests.cs
@@ -0,0 +1,76 @@
+using TALXIS.Platform.Metadata.Serialization.Xml.Scaffolding;
+
+namespace TALXIS.Platform.Metadata.Tests;
+
+public class SolutionRootLocatorTests : IDisposable
+{
+ private readonly string _base = Directory.CreateTempSubdirectory("metadata-solution-root-locator").FullName;
+
+ public void Dispose() => Directory.Delete(_base, recursive: true);
+
+ private void CreateSolutionRoot(params string[] segments)
+ {
+ var other = Path.Combine(Path.Combine(new[] { _base }.Concat(segments).ToArray()), "Other");
+ Directory.CreateDirectory(other);
+ File.WriteAllText(Path.Combine(other, "Solution.xml"), "");
+ }
+
+ [Fact]
+ public void Resolve_ValidRequestedRoot_IsHonored()
+ {
+ CreateSolutionRoot("Declarations");
+
+ var resolved = SolutionRootLocator.Resolve("Declarations", _base);
+
+ Assert.Equal("Declarations", resolved);
+ }
+
+ [Fact]
+ public void Resolve_PlaceholderRequested_DetectsBaseDirectoryRoot()
+ {
+ CreateSolutionRoot();
+
+ var resolved = SolutionRootLocator.Resolve("__solution-root-path__", _base);
+
+ Assert.Equal(_base, resolved);
+ }
+
+ [Fact]
+ public void Resolve_ReadsCsprojProperty()
+ {
+ CreateSolutionRoot("Declarations", "Source");
+ Directory.CreateDirectory(Path.Combine(_base, "Declarations", "Decoy"));
+ File.WriteAllText(Path.Combine(_base, "Sandbox.csproj"), """
+ Declarations/Source
+ """);
+
+ var resolved = SolutionRootLocator.Resolve(null, _base);
+
+ Assert.Equal(Path.Combine(_base, "Declarations/Source"), resolved);
+ }
+
+ [Fact]
+ public void Resolve_FallsBackToUniqueNestedRoot()
+ {
+ CreateSolutionRoot("Declarations");
+
+ var resolved = SolutionRootLocator.Resolve(null, _base);
+
+ Assert.Equal(Path.Combine(_base, "Declarations"), resolved);
+ }
+
+ [Fact]
+ public void Resolve_MultipleNestedRoots_Throws()
+ {
+ CreateSolutionRoot("A");
+ CreateSolutionRoot("B");
+
+ Assert.Throws(() => SolutionRootLocator.Resolve(null, _base));
+ }
+
+ [Fact]
+ public void Resolve_NothingFound_Throws()
+ {
+ Assert.Throws(() => SolutionRootLocator.Resolve(null, _base));
+ }
+}