diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ed63bb2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,42 @@
+root = true
+
+[*]
+indent_style = space
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.cs]
+indent_size = 4
+
+[*.{csproj,props,targets,nuspec,resx}]
+indent_size = 2
+
+[*.{json,yml,yaml}]
+indent_size = 2
+
+[*.{md,sql}]
+trim_trailing_whitespace = false
+
+[*.cs]
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_using_directive_placement = outside_namespace:suggestion
+
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+dotnet_diagnostic.CA1001.severity = warning
+dotnet_diagnostic.CA1051.severity = warning
+dotnet_diagnostic.CA1816.severity = warning
+dotnet_diagnostic.CA2227.severity = warning
diff --git a/.gitignore b/.gitignore
index af2a9f3..1e10283 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@ Content/
tiles/
ready/
.DS_Store
+TestResults/
+coverage.cobertura.xml
+coverage.opencover.xml
+*.coverage
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..c428384
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+ true
+ latest
+ None
+ false
+
+
+
diff --git a/SatelliteProvider.Tests/ToolingConfigurationTests.cs b/SatelliteProvider.Tests/ToolingConfigurationTests.cs
new file mode 100644
index 0000000..a371aba
--- /dev/null
+++ b/SatelliteProvider.Tests/ToolingConfigurationTests.cs
@@ -0,0 +1,100 @@
+using FluentAssertions;
+
+namespace SatelliteProvider.Tests;
+
+public class ToolingConfigurationTests
+{
+ [Fact]
+ public void EditorConfig_ExistsAtRoot_AZ372_AC1()
+ {
+ var path = LocateRepoFile(".editorconfig");
+
+ path.Should().NotBeNull(".editorconfig must exist at the workspace root for `dotnet format` to honor formatting rules");
+
+ var content = File.ReadAllText(path!);
+ content.Should().Contain("root = true");
+ content.Should().Contain("indent_style = space");
+ content.Should().Contain("end_of_line = lf");
+ content.Should().Contain("insert_final_newline = true");
+ content.Should().Contain("trim_trailing_whitespace = true");
+ }
+
+ [Fact]
+ public void EditorConfig_DefinesInitialAnalyzerRuleset_AZ372_AC3()
+ {
+ var path = LocateRepoFile(".editorconfig");
+ path.Should().NotBeNull();
+
+ var content = File.ReadAllText(path!);
+
+ content.Should().Contain("dotnet_diagnostic.CA1001.severity = warning");
+ content.Should().Contain("dotnet_diagnostic.CA1051.severity = warning");
+ content.Should().Contain("dotnet_diagnostic.CA1816.severity = warning");
+ content.Should().Contain("dotnet_diagnostic.CA2227.severity = warning");
+ }
+
+ [Fact]
+ public void DirectoryBuildProps_ExistsAtRoot_AZ372_AC3()
+ {
+ var path = LocateRepoFile("Directory.Build.props");
+ path.Should().NotBeNull("Directory.Build.props centralizes analyzer settings for every csproj");
+
+ var content = File.ReadAllText(path!);
+
+ content.Should().Contain("true");
+ content.Should().Contain("latest");
+ content.Should().Contain("None",
+ "AnalysisMode=None ensures only rules explicitly enabled in .editorconfig fire; protects against analyzer flood");
+ }
+
+ [Fact]
+ public void RunTestsScript_WiresFormatVerify_AZ372_AC1()
+ {
+ var path = LocateRepoFile(Path.Combine("scripts", "run-tests.sh"));
+ path.Should().NotBeNull();
+
+ var content = File.ReadAllText(path!);
+
+ content.Should().Contain("dotnet format whitespace");
+ content.Should().Contain("--verify-no-changes",
+ "AC-1 requires the script to gate on whitespace conformance via dotnet format");
+ }
+
+ [Fact]
+ public void RunTestsScript_CollectsCoverage_AZ372_AC2()
+ {
+ var path = LocateRepoFile(Path.Combine("scripts", "run-tests.sh"));
+ path.Should().NotBeNull();
+
+ var content = File.ReadAllText(path!);
+
+ content.Should().Contain("XPlat Code Coverage");
+ }
+
+ [Fact]
+ public void TestProject_ReferencesCoverletCollector_AZ372_AC2()
+ {
+ var path = LocateRepoFile(Path.Combine("SatelliteProvider.Tests", "SatelliteProvider.Tests.csproj"));
+ path.Should().NotBeNull();
+
+ var content = File.ReadAllText(path!);
+
+ content.Should().Contain("coverlet.collector",
+ "Coverlet collector is the data collector that produces XPlat Code Coverage reports");
+ }
+
+ private static string? LocateRepoFile(string relativePath)
+ {
+ var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
+ while (dir is not null)
+ {
+ var candidate = Path.Combine(dir.FullName, relativePath);
+ if (File.Exists(candidate))
+ {
+ return candidate;
+ }
+ dir = dir.Parent;
+ }
+ return null;
+ }
+}
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 4a9cade..61628b8 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -11,40 +11,62 @@ trap cleanup EXIT
usage() {
cat <