Architecture Validation and Code Quality - Garume/Manifold GitHub Wiki

Architecture Validation and Code Quality

Manifold enforces structural integrity and code quality through a multi-layered system of build-time checks, static analysis, formatting enforcement, and custom architecture validation scripts. These mechanisms operate in concert to ensure that every commit merged into the repository meets a consistent bar for correctness, style, and architectural compliance. This page documents each layer in detail, from the architecture.ps1 structural invariant checker through EditorConfig rules, MSBuild analyzer configuration, the warnings-as-errors policy, and deterministic build settings.

For an overview of the build scripts that orchestrate these checks, see Build System and Scripts. For the CI pipeline that runs them automatically, see CI/CD and Release Pipeline. For testing-specific quality enforcement (coverage thresholds), see Testing Strategy.

Quality Enforcement Pipeline

The quality.ps1 script orchestrates the full quality pipeline, executing each stage sequentially and halting on the first failure.

flowchart TD
    A[quality.ps1] --> B[Restore Dependencies]
    B --> C[Build Projects]
    C --> D[Format Verification]
    D --> E[Run Tests]
    E --> F[Architecture Validation]
    F --> G[All Checks Passed]

    B -->|Failure| X[Exit with Error]
    C -->|Failure| X
    D -->|Failure| X
    E -->|Failure| X
    F -->|Failure| X
Loading

The pipeline follows a strict sequential order: restore, build, format check, test, and architecture validation. Each stage depends on the prior stage succeeding. The same pipeline runs in CI on every push and pull request.

Sources: build/quality.ps1:1-34

# quality.ps1 invocation chain
& (Join-Path $PSScriptRoot 'restore.ps1') -Solution $Solution
& (Join-Path $PSScriptRoot 'build.ps1') -Solution $Solution -NoRestore
& (Join-Path $PSScriptRoot 'format.ps1') -Solution $Solution -NoRestore
& (Join-Path $PSScriptRoot 'test.ps1') -Solution $Solution
& (Join-Path $PSScriptRoot 'architecture.ps1')

Sources: build/quality.ps1:16-31

Architecture Validation Script

The architecture.ps1 script enforces two categories of structural invariants across the repository: required file presence and forbidden namespace references.

Required Files

The script validates that a set of critical repository files exist at the expected paths:

Required File Purpose
Manifold.slnx Solution file for all projects
README.md Repository documentation
LICENSE License file
build/pack.ps1 NuGet packaging script
.github/workflows/ci.yml CI workflow definition

If any required file is missing, the script emits an error and fails the build.

Sources: build/architecture.ps1:7-20

Forbidden Namespace References

The script scans all .cs, .csproj, .props, and .targets files under src/, tests/, and samples/ for references to legacy namespace identifiers that must not appear in the codebase:

Forbidden Reference Description
DalamudMCP.Plugin Legacy plugin package
DalamudMCP.Protocol Legacy protocol package
DalamudMCP.Cli Legacy CLI package
DalamudMCP.Framework Legacy framework package

This prevents accidental reintroduction of dependencies on the predecessor project from which Manifold was extracted.

flowchart TD
    A[architecture.ps1] --> B[Check Required Files]
    B --> C{All Files Present?}
    C -->|No| D[Collect Errors]
    C -->|Yes| E[Scan Source Files]
    E --> F[Check .cs .csproj .props .targets]
    F --> G{Forbidden References?}
    G -->|Yes| D
    G -->|No| H[Architecture Checks Passed]
    D --> I[Exit with Error Code 1]
Loading
$forbiddenReferences = @(
    'DalamudMCP.Plugin',
    'DalamudMCP.Protocol',
    'DalamudMCP.Cli',
    'DalamudMCP.Framework'
)

$sourceFiles = Get-ChildItem (Join-Path $root 'src'), (Join-Path $root 'tests'), (Join-Path $root 'samples') -Recurse -File |
    Where-Object { $_.Extension -in '.cs', '.csproj', '.props', '.targets' }

Sources: build/architecture.ps1:22-40

Code Style Enforcement via EditorConfig

The .editorconfig file at the repository root defines formatting rules and diagnostic severities that apply to all C# files. These rules are enforced both by the IDE and at build time via the EnforceCodeStyleInBuild MSBuild property.

Formatting Rules

Rule Setting
Character set UTF-8
Line endings CRLF
Indentation 4 spaces
Final newline Required
Trailing whitespace Trimmed
Brace placement New line before all braces
Namespace style File-scoped (error)
Using placement Outside namespace (error)
Accessibility modifiers Always required (error)

Sources: .editorconfig:1-24

Diagnostic Rules

The EditorConfig configures specific Roslyn analyzer diagnostics to error or none severity:

Diagnostic ID Description Severity
IDE0005 Remove unnecessary imports Error
IDE0161 Use file-scoped namespace Error
CA2211 Non-constant fields should not be visible Error
CA2227 Collection properties should be read only Error
CA2252 Opt-in to preview features Error
CA1707 Identifiers should not contain underscores None (disabled)
CA1716 Identifiers should not match keywords None (disabled)
CA1724 Type names should not match namespaces None (disabled)
CA1812 Avoid uninstantiated internal classes None (disabled)
CA2007 Do not directly await a Task None (disabled)

The disabled rules reflect deliberate design decisions. For example, CA1812 is suppressed because the source generator instantiates classes indirectly, and CA1707 is suppressed to allow underscored test method names.

Sources: .editorconfig:25-34

Format Verification with dotnet format

The format.ps1 script wraps dotnet format to verify or fix code style compliance:

$arguments = @('format', $Solution)
if (-not $Fix) {
    $arguments += '--verify-no-changes'
}

In verification mode (the default, used in CI), dotnet format checks all files against the EditorConfig rules and fails if any file differs from the expected format. The -Fix flag switches to auto-correction mode for local development.

Sources: build/format.ps1:1-32

MSBuild Configuration — Directory.Build.props

The Directory.Build.props file at the repository root applies global build properties to every project in the solution. This centralizes quality enforcement so that individual project files do not need to repeat configuration.

flowchart TD
    A[Directory.Build.props] --> B[Language Settings]
    A --> C[Quality Enforcement]
    A --> D[Build Determinism]
    A --> E[Test Configuration]

    B --> B1[".NET 10.0 / Latest C#"]
    B --> B2["Nullable Enabled"]
    B --> B3["Implicit Usings"]

    C --> C1["Warnings as Errors"]
    C --> C2["Code Style in Build"]
    C --> C3[".NET Analyzers"]
    C --> C4["Analysis Level: latest-recommended"]

    D --> D1["Deterministic: true"]
    D --> D2["Lock File Restore"]
    D --> D3["Documentation File"]

    E --> E1["Microsoft Testing Platform"]
    E --> E2["coverlet.MTP"]
    E --> E3["xunit.analyzers"]
Loading

Core Properties

Property Value Purpose
TargetFramework net10.0 Target .NET 10.0 runtime
LangVersion latest Use latest C# language features
ImplicitUsings enable Auto-import common namespaces
Nullable enable Enable nullable reference types
TreatWarningsAsErrors true Fail build on any warning
WarningsAsErrors $(WarningsAsErrors);nullable Explicitly elevate nullable warnings
EnforceCodeStyleInBuild true Apply EditorConfig during build
AnalysisLevel latest-recommended Use latest recommended analyzer rules
EnableNETAnalyzers true Enable .NET code analysis
Deterministic true Ensure reproducible builds
GenerateDocumentationFile true Require XML documentation
RestorePackagesWithLockFile true Use NuGet lock files

Sources: Directory.Build.props:1-16

Warnings-as-Errors Policy

The TreatWarningsAsErrors property set to true means that every compiler warning, analyzer warning, and code style warning is promoted to an error. This prevents warnings from accumulating in the codebase and ensures that developers address issues immediately. The additional WarningsAsErrors property with ;nullable explicitly includes nullable reference type warnings, ensuring that null safety violations are always treated as build failures.

The NoWarn property suppresses only CS1591 (missing XML documentation comments), allowing projects to build without requiring documentation on every public API member while still generating the documentation file.

Sources: Directory.Build.props:7-14

Test Project Configuration

Projects that set IsManifoldTestProject to true receive additional configuration automatically:

  • Output type set to Exe for Microsoft Testing Platform compatibility
  • UseMicrosoftTestingPlatformRunner and TestingPlatformDotnetTestSupport enabled
  • Automatic package references: coverlet.MTP (8.0.0) for code coverage and xunit.analyzers (1.27.0) for test quality
  • The shared xunit.runner.json configuration is linked into the output directory

Sources: Directory.Build.props:18-33

MSBuild Configuration — Directory.Build.targets

The Directory.Build.targets file provides two post-build enforcement mechanisms:

NuGet Package Content

Packable projects automatically include README.md and LICENSE in their NuGet packages, ensuring consistent package metadata across all published packages.

Coverage Threshold Enforcement

A custom MSBuild target FailIfCoverageThresholdMissing runs before the Test target and errors if any test project does not define a CoverageThreshold property. This guarantees that every test project has a coverage gate configured.

<Target Name="FailIfCoverageThresholdMissing"
        BeforeTargets="Test"
        Condition="'$(IsManifoldTestProject)' == 'true' and '$(CoverageThreshold)' == ''">
  <Error Text="CoverageThreshold must be set for all test projects." />
</Target>

Sources: Directory.Build.targets:1-18

Source Generator Analyzer Configuration

The Manifold.Generators project has a specialized configuration because it targets netstandard2.0 (required for Roslyn analyzer compatibility) and operates as a Roslyn component rather than a standard library.

Property Value Rationale
IsRoslynComponent true Marks the project as a Roslyn analyzer/generator
EnforceCodeStyleInBuild false Relaxed because netstandard2.0 has limited style support
EnforceExtendedAnalyzerRules true Enables extended rules for analyzer authors
NoWarn suppression RS2008 Suppresses analyzer release tracking diagnostic

The project references Microsoft.CodeAnalysis.Analyzers (3.11.0) for analyzer best practices and Microsoft.CodeAnalysis.CSharp (4.14.0) for the Roslyn API.

Sources: src/Manifold.Generators/Manifold.Generators.csproj:1-29

Deterministic Build Configuration

Deterministic builds ensure that compiling the same source code with the same inputs always produces byte-identical output binaries. This is critical for:

  • Reproducibility: Any build from the same commit produces the same artifacts
  • Security: Build outputs can be verified against expected hashes
  • Caching: Build systems can reliably cache compilation results

The Deterministic property in Directory.Build.props enables this for all projects. Combined with RestorePackagesWithLockFile, which pins exact NuGet package versions via packages.lock.json files, the entire build pipeline produces reproducible results.

flowchart TD
    A[Deterministic Build] --> B["Deterministic: true"]
    A --> C["RestorePackagesWithLockFile: true"]
    A --> D["global.json SDK Pin"]

    B --> E["Byte-identical Binaries"]
    C --> F["Pinned NuGet Versions"]
    D --> G["Fixed SDK Version 10.0.101"]

    E --> H[Reproducible Artifacts]
    F --> H
    G --> H
Loading

Sources: Directory.Build.props:12-15

CI Integration

The CI workflow runs the full quality pipeline on every push and pull request:

jobs:
  quality-and-test:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-dotnet@v5
        with:
          global-json-file: global.json
          cache: true
          cache-dependency-path: |
            **/packages.lock.json
      - name: Quality
        shell: pwsh
        run: ./build/quality.ps1
      - name: Pack
        shell: pwsh
        run: ./build/pack.ps1 -NoRestore

The workflow uses packages.lock.json for NuGet cache keying and pins the SDK version via global.json. The quality.ps1 script executes all checks (build, format, test, architecture) as a single gate.

Sources: .github/workflows/ci.yml:1-24

Complete Quality Enforcement Summary

flowchart TD
    A[Code Quality Layers] --> B[Compile-Time]
    A --> C[Build-Time]
    A --> D[Script-Time]
    A --> E[CI-Time]

    B --> B1["Warnings as Errors"]
    B --> B2["Nullable Analysis"]
    B --> B3[".NET Analyzers"]
    B --> B4["Source Generator Diagnostics"]

    C --> C1["EditorConfig Enforcement"]
    C --> C2["dotnet format Verification"]
    C --> C3["Coverage Threshold Check"]

    D --> D1["Required Files Check"]
    D --> D2["Forbidden References Scan"]

    E --> E1["quality.ps1 Pipeline"]
    E --> E2["Pack Verification"]
Loading
Layer Mechanism Failure Mode
Compile-time TreatWarningsAsErrors, nullable analysis, .NET analyzers Build error
Code style EditorConfig rules with error severity Build error
Format check dotnet format --verify-no-changes Non-zero exit code
Test quality xunit.analyzers, coverage threshold enforcement Build/test error
Architecture Required files check, forbidden reference scan Script error
CI gate quality.ps1 orchestration on every push/PR Workflow failure

Related Pages

⚠️ **GitHub.com Fallback** ⚠️