From 03821876db2bab919b77b96ff061abdefcb4d903 Mon Sep 17 00:00:00 2001 From: Hash Date: Thu, 10 Sep 2026 14:13:39 +0300 Subject: [PATCH] chore(repo): organize solution, documentation and repository checks --- .editorconfig | 6 + .gitattributes | 2 +- .github/workflows/ci.yml | 9 +- .gitignore | 2 +- AGENTS.md | 11 + CONTRIBUTING.md | 132 +++++++++++ ChessRealms.ChessEngine.slnx | 19 ++ README.md | 26 ++- docs/dotnet-10-migration.md | 5 + docs/game-rules-validation.md | 5 + scripts/Verify-SolutionStructure.ps1 | 215 ++++++++++++++++++ .../packages.lock.json | 2 +- .../packages.lock.json | 2 +- .../packages.lock.json | 2 +- .../packages.lock.json | 2 +- src/ChessRealms.ChessEngine.sln | 63 ----- .../Core/Constants/DefaultChessConstants.cs | 2 +- .../Core/Constants/SquareMapping.cs | 2 +- src/ChessRealms.ChessEngine/PieceColor.cs | 2 +- .../packages.lock.json | 2 +- src/ChessRealms.MagicBruteforce/Magic.cs | 2 +- src/ChessRealms.MagicBruteforce/Program.cs | 2 +- .../Properties/launchSettings.json | 2 +- .../packages.lock.json | 2 +- 24 files changed, 428 insertions(+), 91 deletions(-) create mode 100644 .editorconfig create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 ChessRealms.ChessEngine.slnx create mode 100644 scripts/Verify-SolutionStructure.ps1 delete mode 100644 src/ChessRealms.ChessEngine.sln diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b83dcd1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes index 1ff0c42..6f3fcbd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### -* text=auto +* text=auto eol=lf ############################################################################### # Set default behavior for command prompt diff. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70149cb..90d71cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,12 @@ jobs: - uses: actions/setup-dotnet@v4 with: global-json-file: global.json + - name: Verify solution structure + shell: pwsh + run: ./scripts/Verify-SolutionStructure.ps1 - name: Restore - run: dotnet restore src/ChessRealms.ChessEngine.sln --locked-mode + run: dotnet restore ChessRealms.ChessEngine.slnx --locked-mode - name: Release build - run: dotnet build src/ChessRealms.ChessEngine.sln --configuration Release --no-restore + run: dotnet build ChessRealms.ChessEngine.slnx --configuration Release --no-restore - name: Fast tests - run: dotnet test src/ChessRealms.ChessEngine.sln --configuration Release --no-build --filter "TestCategory!=Deep" + run: dotnet test ChessRealms.ChessEngine.slnx --configuration Release --no-build --filter "TestCategory!=Deep" diff --git a/.gitignore b/.gitignore index 9491a2f..eaa9902 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,4 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9460ae6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +# Repository instructions + +- Follow [CONTRIBUTING.md](CONTRIBUTING.md). +- Keep the physical repository layout and solution structure in sync. +- Run `pwsh -NoProfile -File scripts/Verify-SolutionStructure.ps1` when changing + docs, scripts, the solution, or the set or location of projects. +- Use LF line endings and respect `.editorconfig` and `.gitattributes`. +- Run checks appropriate to the changes, following CONTRIBUTING.md. +- In the final response, include proposed commit and PR titles, check results, + and validation limits. Providing titles does not authorize an automatic + commit, push, or PR creation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6d6032e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,132 @@ +# Contributing + +## Prerequisites and checks + +Install Git, PowerShell 7 (`pwsh`), and .NET SDK **10.0.401**, the exact stable +version pinned in `global.json`. Restore needs access to NuGet.org or a cache +containing the locked packages. No additional PowerShell modules are required. + +Run these commands from the repository root: + +```sh +pwsh -NoProfile -File scripts/Verify-SolutionStructure.ps1 +dotnet sln ChessRealms.ChessEngine.slnx list +dotnet restore ChessRealms.ChessEngine.slnx --locked-mode +dotnet build ChessRealms.ChessEngine.slnx -c Release --no-restore +dotnet test ChessRealms.ChessEngine.slnx -c Release --no-build --filter "TestCategory!=Deep" +git diff --check +``` + +For the ordinary developer loop, run `dotnet test` from the root. Run the structure +check whenever docs, scripts, the solution, or project membership/locations change. +For code, project, solution, SDK, dependency, or build/CI changes, also run locked +restore, the Release build, and the fast tests above. Documentation-only changes +need the structure check, link/command review, and `git diff --check`. + +Deep tests and performance runs are opt-in when relevant to the change; see +[README.md](README.md) for commands. CI checks structure before locked restore, +Release build, and fast tests on both Windows and Linux. Report local results as +local results; do not infer a CI, Linux, or Visual Studio pass from them. + +## Repository and solution structure + +- `ChessRealms.ChessEngine.slnx`: the root solution in XML `.slnx` format. +- `src/`: six projects (engine, tests, console, perft runner, benchmarks, and + magic-number search). Each project stays in its own physical directory. +- `docs/`: API documentation, support boundaries, and historical validation records. +- `scripts/`: repository tooling, initially only `Verify-SolutionStructure.ps1`. +- `.github/workflows/ci.yml`: the Windows/Linux verification workflow. +- Root files such as `README.md`, `CONTRIBUTING.md`, `AGENTS.md`, `global.json`, + and `Directory.Build.props`: repository instructions and shared configuration. + +Keep solution folders synchronized whenever files or projects are added, removed, +renamed, or moved: + +- `docs` and `scripts` mirror their physical subdirectories recursively. Add each + tracked or new nonignored file once as a Solution Item in the matching folder, + using its full path relative to the root solution in a `` entry. + For example, `` belongs in + ``. Do not add empty folders + or ignored artifacts. Tracked files remain checked even if an ignore rule matches. +- Put all `.csproj` files under the matching `src` solution structure. A project + node represents its own directory, so `src/Example/Example.csproj` belongs directly + in `src`, and `src/tools/Example/Example.csproj` belongs in `src/tools`. A project + directly in `src` also belongs in `src`. Do not duplicate project source files as + Solution Items. Preserve existing project/folder `Id` values, project types, + and build configurations. The standard Debug/Release and Any CPU mappings use + SLNX defaults, verified against the previous solution during migration. +- Match spelling and case exactly, including on Windows. Both slash styles in + solution paths are supported. SLNX folder elements are direct children of + ``; their absolute names, such as `/docs/guides/`, define nesting. + Ancestor folders may be implicit. Record case-only renames in Git as well. + +Keep a single root solution so `dotnet test` can discover it without ambiguity. +The SDK's `dotnet sln migrate` command converts a classic solution to +SLNX, but leaves the old file in place. After validating the migration, remove +the old solution and update current commands. See the +[official CLI documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln). + +Run the structure checker after editing the solution. It uses its own location +to find the repository, works from any current directory, reports discrepancies, +and exits nonzero on failure. It never fixes files automatically. Keep historical +reports accurate: preserve commands and results as originally run and add a note +pointing to current instructions when paths change. Root service files stay at +the root and do not need entries in `docs`. + +## Namespaces and project names + +`ChessRealms` is the organization prefix, matching the configured GitHub origin +`ChessRealms/ChessEngine`. `ChessRealms.ChessEngine` identifies the library; +suffixes such as `.Tests`, `.Console`, `.Perft`, and `.Benchmark` identify related +projects by purpose. `ChessRealms.MagicBruteforce` is a supporting tool under the +same organization. This follows the +[.NET namespace naming guidance](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-namespaces). + +Keep these names consistent across namespaces, project/assembly names, project +references, and `InternalsVisibleTo` declarations. Removing the organization prefix +would change public type names and, if projects are renamed, assembly identities. +Treat such a rename as an intentional compatibility change with a migration plan. +There is no technical requirement to rename them when changing solution format. + +## Branches, commits, and pull requests + +Use `/` for branches. Use +`(): ` for commit and PR titles; choose +a concise scope such as `repo`, `engine`, `tests`, or `ci`. + +| Branch type | Purpose | Commit / PR prefix | +| --- | --- | --- | +| `feature` | Add user-facing functionality | `feat` | +| `fix` | Correct a defect | `fix` | +| `docs` | Update documentation | `docs` | +| `test` | Add or improve tests | `test` | +| `refactor` | Restructure code without changing behavior | `refactor` | +| `perf` | Improve performance | `perf` | +| `ci` | Change continuous integration | `ci` | +| `chore` | Maintain tooling or repository organization | `chore` | + +The branch type is **`feature`**, while the corresponding commit and PR type is +**`feat`**. All other branch types use the same prefix in commit and PR titles. +For example: + +- `feature/add-uci-support` → `feat(engine): add UCI support` +- `fix/validate-promotion` → `fix(engine): validate promotion choices` +- `docs/update-api-guide` → `docs(api): clarify draw claims` +- `chore/repository-organization` → + `chore(repo): organize solution, documentation and repository checks` + +Describe the problem, resulting behavior, and scope in the PR. Include relevant +design choices or compatibility implications, the exact checks run and their +results (including test counts where available), and any checks not run or other +validation limits. Link a related issue when one exists. Keep the title and +description aligned with the final changes reviewers will see. + +## Text files + +Use UTF-8, LF line endings, and a final newline, following `.editorconfig` and +`.gitattributes`. Preserve existing indentation unless a change requires otherwise. +Git automatically classifies text files; do not convert binary files. Add CRLF +exceptions only when a tool demonstrably requires them. `git add --renormalize` +normalizes the index but does not rewrite every working-copy file. If normalizing +existing files, rewrite text files safely and verify that their content is unchanged +apart from line endings; avoid destructive checkout/reset operations. diff --git a/ChessRealms.ChessEngine.slnx b/ChessRealms.ChessEngine.slnx new file mode 100644 index 0000000..bdeeae2 --- /dev/null +++ b/ChessRealms.ChessEngine.slnx @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index f7bd507..935675e 100644 --- a/README.md +++ b/README.md @@ -8,25 +8,28 @@ dependencies. All six projects target .NET 10. Install the stable [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) version **10.0.401**, as pinned in `global.json`. `rollForward: disable` requires this exact SDK; `allowPrerelease: false` excludes previews. CI reads the same file. +Install PowerShell 7 (`pwsh`) and Git for the solution structure check. See +[CONTRIBUTING.md](CONTRIBUTING.md) for repository layout and contribution rules. Run from the repository root: ```sh dotnet --version -dotnet restore src/ChessRealms.ChessEngine.sln --locked-mode -dotnet build src/ChessRealms.ChessEngine.sln --configuration Release --no-restore -dotnet test src/ChessRealms.ChessEngine.sln --configuration Release --no-build --filter "TestCategory!=Deep" +pwsh -NoProfile -File scripts/Verify-SolutionStructure.ps1 +dotnet restore ChessRealms.ChessEngine.slnx --locked-mode +dotnet build ChessRealms.ChessEngine.slnx --configuration Release --no-restore +dotnet test ChessRealms.ChessEngine.slnx --configuration Release --no-build --filter "TestCategory!=Deep" ``` -For the ordinary developer loop, `cd src` and run `dotnet test`. This builds and -runs all ordinary tests, including fast perft; no settings file is required. -The solution lives in `src`, so commands from the repository root need its path. +For the ordinary developer loop, run `dotnet test` from the repository root. +This builds and runs all ordinary tests, including fast perft; no settings file is required. +The solution lives at the repository root; all six projects remain in `src`. Tests continue to use NUnit 3 through VSTest, explicitly selected in `global.json`. The existing filters and NUnit `Explicit` behavior are unchanged. Committed `packages.lock.json` files pin direct and transitive package versions and content hashes. CI uses `--locked-mode` to reject dependency drift. When intentionally updating packages, run -`dotnet restore src/ChessRealms.ChessEngine.sln --force-evaluate`, review the +`dotnet restore ChessRealms.ChessEngine.slnx --force-evaluate`, review the lock-file changes, and repeat the checks above. When updating the SDK, update `global.json` and this README together, then regenerate/review the lock files with that SDK. Restore requires access to NuGet.org or a cache containing the locked packages. @@ -43,21 +46,22 @@ that SDK. Restore requires access to NuGet.org or a cache containing the locked opt-in for cost, not failing tests being suppressed. Select them explicitly: ```sh -dotnet test src/ChessRealms.ChessEngine.sln --configuration Release --no-build --filter "TestCategory=Deep" +dotnet test ChessRealms.ChessEngine.slnx --configuration Release --no-build --filter "TestCategory=Deep" ``` Run both test commands after the Release build, which compiles all tests. Tests, the console perft runner and benchmarks use the same `PerftDriver` implementation. -GitHub Actions (`.github/workflows/ci.yml`) runs restore, Release build and the -same fast-test command on Windows and Linux with SDK 10.0.401, on pushes and pull requests. +GitHub Actions (`.github/workflows/ci.yml`) checks solution structure, then runs +locked restore, Release build and the same fast-test command on Windows and Linux +with SDK 10.0.401, on pushes and pull requests. Deep tests are not part of the default CI job. A local pass does not establish that either GitHub Actions job has passed. Optional coverage check using the existing VSTest collector: ```sh -dotnet test src/ChessRealms.ChessEngine.sln -c Release --no-build --filter "TestCategory!=Deep" --collect:"XPlat Code Coverage" +dotnet test ChessRealms.ChessEngine.slnx -c Release --no-build --filter "TestCategory!=Deep" --collect:"XPlat Code Coverage" ``` ### Tool smoke checks diff --git a/docs/dotnet-10-migration.md b/docs/dotnet-10-migration.md index 5446fd0..b9c3e23 100644 --- a/docs/dotnet-10-migration.md +++ b/docs/dotnet-10-migration.md @@ -1,5 +1,10 @@ # .NET 10 migration +> Repository organization note: the solution now lives at the repository root +> as `ChessRealms.ChessEngine.slnx`. +> Commands and results below preserve the paths used at the time of validation. +> See [CONTRIBUTING.md](../CONTRIBUTING.md) for current commands. + Validated on 2026-09-10, Windows 11 x64. The starting commit was `d8eb1e3` (`test: add regression test foundation and .NET 8 CI`), confirmed as `origin/main` after fetching. The working tree was clean; no applicable `AGENTS.md` was found. diff --git a/docs/game-rules-validation.md b/docs/game-rules-validation.md index ead99a4..32ce7d5 100644 --- a/docs/game-rules-validation.md +++ b/docs/game-rules-validation.md @@ -1,5 +1,10 @@ # Game-rules validation — 2026-09-10 +> Repository organization note: the solution now lives at the repository root +> as `ChessRealms.ChessEngine.slnx`. +> Commands and results below preserve the paths used at the time of validation. +> See [CONTRIBUTING.md](../CONTRIBUTING.md) for current commands. + Base: `origin/main` at `49fc3a5` (`.NET 10` migration), containing `d8eb1e3` (restored tests and CI). Work branch: `feature/complete-game-rules`. No applicable AGENTS.md was present in the repository or ancestor directories. diff --git a/scripts/Verify-SolutionStructure.ps1 b/scripts/Verify-SolutionStructure.ps1 new file mode 100644 index 0000000..7b12047 --- /dev/null +++ b/scripts/Verify-SolutionStructure.ps1 @@ -0,0 +1,215 @@ +#requires -Version 7.0 +<# +Checks the SLNX solution against tracked and untracked, nonignored repository files. +Run with: pwsh -NoProfile -File scripts/Verify-SolutionStructure.ps1 +No files are changed. Git must be available on PATH. +#> + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) +$solutionPath = Join-Path $repositoryRoot 'ChessRealms.ChessEngine.slnx' +$issues = [Collections.Generic.List[string]]::new() + +function Normalize-RelativePath([string] $Path) { + # Normalize separators only: spelling and hierarchy must remain exact. + return $Path.Replace('\', '/') +} + +function Get-ParentPath([string] $Path) { + $separator = $Path.LastIndexOf('/') + if ($separator -lt 0) { return '' } + return $Path.Substring(0, $separator) +} + +function Get-PhysicalPath([string] $Path) { + if ($Path -match '^[/\\]|^[A-Za-z]:' -or + @($Path.Split('/') | Where-Object { $_ -in @('', '.', '..') }).Count -gt 0) { + $issues.Add("Invalid repository-relative path: '$Path'.") + return $null + } + + # Test-Path alone cannot detect casing errors on a case-insensitive volume. + $current = $repositoryRoot + $parts = [Collections.Generic.List[string]]::new() + foreach ($part in $Path.Split('/')) { + if (-not [IO.Directory]::Exists($current)) { return $null } + $entries = @([IO.Directory]::EnumerateFileSystemEntries($current)) + $entry = @($entries | Where-Object { [IO.Path]::GetFileName($_) -ceq $part }) + if ($entry.Count -eq 0) { + $entry = @($entries | Where-Object { [IO.Path]::GetFileName($_) -ieq $part }) + } + if ($entry.Count -ne 1) { return $null } + $current = $entry[0] + $parts.Add([IO.Path]::GetFileName($current)) + } + if (-not [IO.File]::Exists($current)) { return $null } + return $parts -join '/' +} + +function Add-FolderAncestors([string] $Path) { + while ($Path) { + [void] $expectedFolders.Add($Path) + $Path = Get-ParentPath $Path + } +} + +function Compare-References($Expected, $Actual, [string] $Kind) { + $seen = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + foreach ($reference in $Actual) { + $path = $reference.Path + if (-not $seen.Add($path)) { $issues.Add("Duplicate ${Kind}: '$path'.") } + if (-not $Expected.ContainsKey($path)) { + $caseMatch = @($Expected.Keys | Where-Object { $_ -ieq $path }) + if ($caseMatch.Count -gt 0) { + $issues.Add("Case mismatch in ${Kind}: '$path'; expected '$($caseMatch -join "', '")'.") + } else { + $issues.Add("Extra ${Kind}: '$path'.") + } + } elseif ($reference.Folder -cne $Expected[$path]) { + $issues.Add("Misplaced ${Kind}: '$path' is in '$($reference.Folder)'; expected '$($Expected[$path])'.") + } + $physicalPath = Get-PhysicalPath $path + if ($null -eq $physicalPath) { + $issues.Add("Nonexistent file for ${Kind}: '$path'.") + } elseif ($physicalPath -cne $path) { + $issues.Add("Physical path case mismatch: '$path'; on disk '$physicalPath'.") + } + } + foreach ($path in $Expected.Keys) { + if (@($Actual | Where-Object { $_.Path -ceq $path }).Count -eq 0) { + $issues.Add("Missing ${Kind}: '$path' in '$($Expected[$path])'.") + } + } +} + +try { + if ([IO.File]::Exists((Join-Path $repositoryRoot 'ChessRealms.ChessEngine.sln'))) { + $issues.Add('Legacy .sln remains beside .slnx; keep a single root solution for CLI discovery.') + } + # NUL-delimited output preserves spaces, Unicode and Git's otherwise quoted names. + $git = [Diagnostics.ProcessStartInfo]::new('git') + $git.WorkingDirectory = $repositoryRoot + $git.UseShellExecute = $false + $git.RedirectStandardOutput = $true + $git.StandardOutputEncoding = [Text.UTF8Encoding]::new($false) + foreach ($argument in @('ls-files', '--cached', '--others', '--exclude-standard', '-z')) { + $git.ArgumentList.Add($argument) + } + $process = [Diagnostics.Process]::Start($git) + $output = $process.StandardOutput.ReadToEnd() + $process.WaitForExit() + $gitExitCode = $process.ExitCode + $process.Dispose() + if ($gitExitCode -ne 0) { throw "git ls-files failed (exit $gitExitCode). Run inside a Git checkout." } + + $expectedItems = [Collections.Generic.Dictionary[string, string]]::new([StringComparer]::Ordinal) + $expectedProjects = [Collections.Generic.Dictionary[string, string]]::new([StringComparer]::Ordinal) + $expectedFolders = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) + $gitPaths = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) + foreach ($gitPath in $output.Split([char]0, [StringSplitOptions]::RemoveEmptyEntries)) { + if (-not $gitPaths.Add($gitPath)) { continue } + if ($gitPath -notmatch '^(docs|scripts)/' -and $gitPath -notmatch '^src/.*\.csproj$') { continue } + $path = Get-PhysicalPath $gitPath + if ($null -eq $path) { + $issues.Add("Repository file is missing on disk: '$gitPath' (still listed by Git).") + $path = $gitPath + } elseif ($path -cne $gitPath) { + $issues.Add("Git path case mismatch: '$gitPath'; on disk '$path'. Record case-only renames in Git.") + } + if ($path -cmatch '^(docs|scripts)/') { + $parent = Get-ParentPath $path + $expectedItems[$path] = $parent + } elseif ($path -cmatch '^src/' -and $path -imatch '\.csproj$') { + # A project represents its own directory. Mirror only grouping directories + # above it: src/group/Project/Project.csproj belongs to src/group. + $directory = Get-ParentPath $path + $parent = if ($directory -ceq 'src') { 'src' } else { Get-ParentPath $directory } + $expectedProjects[$path] = $parent + } else { + $issues.Add("Repository root folder case mismatch: '$path'; use docs, scripts or src.") + continue + } + Add-FolderAncestors $parent + } + + # SLNX represents nested folders by absolute names such as /docs/guides/. + # XML Folder elements are siblings, and their Project/File children are direct. + $settings = [Xml.XmlReaderSettings]::new() + $settings.DtdProcessing = [Xml.DtdProcessing]::Prohibit + $settings.XmlResolver = $null + $reader = [Xml.XmlReader]::Create($solutionPath, $settings) + $document = [Xml.XmlDocument]::new() + $document.XmlResolver = $null + try { $document.Load($reader) } finally { $reader.Dispose() } + if ($document.DocumentElement.LocalName -cne 'Solution' -or $document.DocumentElement.NamespaceURI) { + throw 'Expected an SLNX root without an XML namespace.' + } + + $items = [Collections.Generic.List[object]]::new() + $projectReferences = [Collections.Generic.List[object]]::new() + $actualFolders = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal) + $declaredFolders = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + $ids = [Collections.Generic.HashSet[guid]]::new() + foreach ($node in $document.SelectNodes('//*')) { + if ($node.HasAttribute('Id')) { + $id = [guid]::Empty + if (-not [guid]::TryParse($node.GetAttribute('Id'), [ref] $id) -or $id -eq [guid]::Empty) { + $issues.Add("Invalid solution item GUID: '$($node.GetAttribute('Id'))'.") + } elseif (-not $ids.Add($id)) { + $issues.Add("Duplicate project/folder GUID: $id.") + } + } + if ($node.LocalName -in @('Folder', 'Project', 'File') -and + ($node.LocalName -cnotin @('Folder', 'Project', 'File') -or $node.NamespaceURI)) { + $issues.Add("Invalid SLNX element name: '$($node.LocalName)'.") + } + } + foreach ($folder in $document.SelectNodes('//Folder')) { + if ($folder.ParentNode -ne $document.DocumentElement) { + $issues.Add('Folder elements must be direct children of Solution; use full names such as /docs/guides/.') + } + $name = Normalize-RelativePath $folder.GetAttribute('Name') + if ($name -notmatch '^/[^/]+(?:/[^/]+)*/$' -or + @($name.Trim('/').Split('/') | Where-Object { $_ -in @('.', '..') }).Count -gt 0) { + $issues.Add("Invalid solution folder path: '$name'. Expected /folder/ or /folder/subfolder/.") + } + $path = $name.Trim('/') + if (-not $declaredFolders.Add($path)) { $issues.Add("Duplicate solution folder: '$path'.") } + # SLNX permits implicit ancestors: /docs/guides/ also creates /docs/. + while ($path) { + [void] $actualFolders.Add($path) + $path = Get-ParentPath $path + } + } + foreach ($node in $document.SelectNodes('//File | //Project')) { + $parent = $node.ParentNode + $folder = '' + if ($parent.LocalName -ceq 'Folder') { + $folder = (Normalize-RelativePath $parent.GetAttribute('Name')).Trim('/') + } elseif ($node.LocalName -ceq 'File' -or $parent -ne $document.DocumentElement) { + $issues.Add("Invalid placement of SLNX $($node.LocalName) element: '$($node.GetAttribute('Path'))'.") + } + $path = Normalize-RelativePath $node.GetAttribute('Path') + $reference = [pscustomobject]@{ Path = $path; Folder = $folder } + if ($node.LocalName -ceq 'File') { $items.Add($reference) } else { $projectReferences.Add($reference) } + } + foreach ($path in $actualFolders) { + if (-not $expectedFolders.Contains($path)) { $issues.Add("Extra or incorrectly cased solution folder: '$path'.") } + } + foreach ($path in $expectedFolders) { + if (-not $actualFolders.Contains($path)) { $issues.Add("Missing solution folder: '$path'.") } + } + Compare-References $expectedItems $items 'solution item' + Compare-References $expectedProjects $projectReferences 'project reference' +} catch { + $issues.Add("Verification failed: $($_.Exception.Message)") +} + +if ($issues.Count -gt 0) { + Write-Output "Solution structure check failed ($($issues.Count) discrepancies):" + foreach ($issue in $issues | Sort-Object -Unique) { Write-Output " - $issue" } + exit 1 +} +Write-Output "Solution structure verified: $($expectedProjects.Count) projects, $($expectedItems.Count) solution items, $($expectedFolders.Count) folders." +exit 0 diff --git a/src/ChessRealms.ChessEngine.Benchmark/packages.lock.json b/src/ChessRealms.ChessEngine.Benchmark/packages.lock.json index c2e0953..db7d648 100644 --- a/src/ChessRealms.ChessEngine.Benchmark/packages.lock.json +++ b/src/ChessRealms.ChessEngine.Benchmark/packages.lock.json @@ -177,4 +177,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine.Console/packages.lock.json b/src/ChessRealms.ChessEngine.Console/packages.lock.json index 9b6a75e..3e128ec 100644 --- a/src/ChessRealms.ChessEngine.Console/packages.lock.json +++ b/src/ChessRealms.ChessEngine.Console/packages.lock.json @@ -7,4 +7,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine.Perft/packages.lock.json b/src/ChessRealms.ChessEngine.Perft/packages.lock.json index 9b6a75e..3e128ec 100644 --- a/src/ChessRealms.ChessEngine.Perft/packages.lock.json +++ b/src/ChessRealms.ChessEngine.Perft/packages.lock.json @@ -7,4 +7,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine.Tests/packages.lock.json b/src/ChessRealms.ChessEngine.Tests/packages.lock.json index 904b746..56a9dbb 100644 --- a/src/ChessRealms.ChessEngine.Tests/packages.lock.json +++ b/src/ChessRealms.ChessEngine.Tests/packages.lock.json @@ -87,4 +87,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine.sln b/src/ChessRealms.ChessEngine.sln deleted file mode 100644 index badddee..0000000 --- a/src/ChessRealms.ChessEngine.sln +++ /dev/null @@ -1,63 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34728.123 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChessRealms.MagicBruteforce", "ChessRealms.MagicBruteforce\ChessRealms.MagicBruteforce.csproj", "{5C55E3CA-2541-4EA4-B4B6-3C5AAE87FB9D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ChessEngine", "ChessEngine", "{0785FE11-03DA-45AC-8704-A67DE5C4777C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChessRealms.ChessEngine", "ChessRealms.ChessEngine\ChessRealms.ChessEngine.csproj", "{E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChessRealms.ChessEngine.Tests", "ChessRealms.ChessEngine.Tests\ChessRealms.ChessEngine.Tests.csproj", "{530985CE-2BD3-437A-8C66-C50A98CEA318}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChessRealms.ChessEngine.Benchmark", "ChessRealms.ChessEngine.Benchmark\ChessRealms.ChessEngine.Benchmark.csproj", "{029E70A1-269B-4F1F-A132-60011E54392D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChessRealms.ChessEngine.Perft", "ChessRealms.ChessEngine.Perft\ChessRealms.ChessEngine.Perft.csproj", "{2C20D484-BE51-452B-A393-F8F0C969F72E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChessRealms.ChessEngine.Console", "ChessRealms.ChessEngine.Console\ChessRealms.ChessEngine.Console.csproj", "{CED75849-6A1D-4150-879D-103DFCD5C547}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5C55E3CA-2541-4EA4-B4B6-3C5AAE87FB9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C55E3CA-2541-4EA4-B4B6-3C5AAE87FB9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C55E3CA-2541-4EA4-B4B6-3C5AAE87FB9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C55E3CA-2541-4EA4-B4B6-3C5AAE87FB9D}.Release|Any CPU.Build.0 = Release|Any CPU - {E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A}.Release|Any CPU.Build.0 = Release|Any CPU - {530985CE-2BD3-437A-8C66-C50A98CEA318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {530985CE-2BD3-437A-8C66-C50A98CEA318}.Debug|Any CPU.Build.0 = Debug|Any CPU - {530985CE-2BD3-437A-8C66-C50A98CEA318}.Release|Any CPU.ActiveCfg = Release|Any CPU - {530985CE-2BD3-437A-8C66-C50A98CEA318}.Release|Any CPU.Build.0 = Release|Any CPU - {029E70A1-269B-4F1F-A132-60011E54392D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {029E70A1-269B-4F1F-A132-60011E54392D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {029E70A1-269B-4F1F-A132-60011E54392D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {029E70A1-269B-4F1F-A132-60011E54392D}.Release|Any CPU.Build.0 = Release|Any CPU - {2C20D484-BE51-452B-A393-F8F0C969F72E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C20D484-BE51-452B-A393-F8F0C969F72E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C20D484-BE51-452B-A393-F8F0C969F72E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C20D484-BE51-452B-A393-F8F0C969F72E}.Release|Any CPU.Build.0 = Release|Any CPU - {CED75849-6A1D-4150-879D-103DFCD5C547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CED75849-6A1D-4150-879D-103DFCD5C547}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CED75849-6A1D-4150-879D-103DFCD5C547}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CED75849-6A1D-4150-879D-103DFCD5C547}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {E8906E4A-0DDD-45E8-8C0B-8FBB4B5D832A} = {0785FE11-03DA-45AC-8704-A67DE5C4777C} - {530985CE-2BD3-437A-8C66-C50A98CEA318} = {0785FE11-03DA-45AC-8704-A67DE5C4777C} - {029E70A1-269B-4F1F-A132-60011E54392D} = {0785FE11-03DA-45AC-8704-A67DE5C4777C} - {2C20D484-BE51-452B-A393-F8F0C969F72E} = {0785FE11-03DA-45AC-8704-A67DE5C4777C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E65FD084-D2F8-4F38-93AC-24C1EB66CE8A} - EndGlobalSection -EndGlobal diff --git a/src/ChessRealms.ChessEngine/Core/Constants/DefaultChessConstants.cs b/src/ChessRealms.ChessEngine/Core/Constants/DefaultChessConstants.cs index be3bb06..b7251d5 100644 --- a/src/ChessRealms.ChessEngine/Core/Constants/DefaultChessConstants.cs +++ b/src/ChessRealms.ChessEngine/Core/Constants/DefaultChessConstants.cs @@ -200,4 +200,4 @@ public static bool IsValid(int square) { return square >= a1 && square <= h8; } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine/Core/Constants/SquareMapping.cs b/src/ChessRealms.ChessEngine/Core/Constants/SquareMapping.cs index 2656557..b1bf086 100644 --- a/src/ChessRealms.ChessEngine/Core/Constants/SquareMapping.cs +++ b/src/ChessRealms.ChessEngine/Core/Constants/SquareMapping.cs @@ -35,4 +35,4 @@ internal static class SquareMapping public const ulong DARK_SQUARES = 0xAA55AA55AA55AA55; public const ulong ALL_SQUARES = LIGHT_SQUARES | DARK_SQUARES; -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine/PieceColor.cs b/src/ChessRealms.ChessEngine/PieceColor.cs index 2a3cb85..4038ffb 100644 --- a/src/ChessRealms.ChessEngine/PieceColor.cs +++ b/src/ChessRealms.ChessEngine/PieceColor.cs @@ -17,4 +17,4 @@ public static bool IsBlackOrWhite(this PieceColor color) { return color == PieceColor.Black || color == PieceColor.White; } -} \ No newline at end of file +} diff --git a/src/ChessRealms.ChessEngine/packages.lock.json b/src/ChessRealms.ChessEngine/packages.lock.json index 4a91a8c..b9dcfe1 100644 --- a/src/ChessRealms.ChessEngine/packages.lock.json +++ b/src/ChessRealms.ChessEngine/packages.lock.json @@ -3,4 +3,4 @@ "dependencies": { "net10.0": {} } -} \ No newline at end of file +} diff --git a/src/ChessRealms.MagicBruteforce/Magic.cs b/src/ChessRealms.MagicBruteforce/Magic.cs index 92bcce8..8a4aeb5 100644 --- a/src/ChessRealms.MagicBruteforce/Magic.cs +++ b/src/ChessRealms.MagicBruteforce/Magic.cs @@ -82,4 +82,4 @@ public static ulong FindMagic(int square, int iterations, bool isBishop, Random return 0; } -} \ No newline at end of file +} diff --git a/src/ChessRealms.MagicBruteforce/Program.cs b/src/ChessRealms.MagicBruteforce/Program.cs index 6569aad..e6cecc4 100644 --- a/src/ChessRealms.MagicBruteforce/Program.cs +++ b/src/ChessRealms.MagicBruteforce/Program.cs @@ -59,4 +59,4 @@ static void Exit(int code, string msg) { Console.Error.WriteLine(msg); Environment.Exit(code); -} \ No newline at end of file +} diff --git a/src/ChessRealms.MagicBruteforce/Properties/launchSettings.json b/src/ChessRealms.MagicBruteforce/Properties/launchSettings.json index fcd6c78..189de57 100644 --- a/src/ChessRealms.MagicBruteforce/Properties/launchSettings.json +++ b/src/ChessRealms.MagicBruteforce/Properties/launchSettings.json @@ -5,4 +5,4 @@ "commandLineArgs": "\"-777\" 750000" } } -} \ No newline at end of file +} diff --git a/src/ChessRealms.MagicBruteforce/packages.lock.json b/src/ChessRealms.MagicBruteforce/packages.lock.json index 9b6a75e..3e128ec 100644 --- a/src/ChessRealms.MagicBruteforce/packages.lock.json +++ b/src/ChessRealms.MagicBruteforce/packages.lock.json @@ -7,4 +7,4 @@ } } } -} \ No newline at end of file +}