Two instructions from the base prompt never got their user manual: “solution in .slnx format” and “NuGet Central Package Management”. Fixed today, express format: two five-minute migrations that make your repository more readable for humans — and for agents. Timer started: it’s not rocket science.

Migration 1: from .sln to .slnx

Open a .sln file: a 25-year-old proprietary format, GUIDs everywhere, cryptic sections — and the all-category champion of the unreadable merge conflict. Its successor .slnx is the same content in minimal XML:

<Solution>
  <Folder Name="/src/">
    <Project Path="src/Orders.Api/Orders.Api.csproj" />
    <Project Path="src/Web.Front/Web.Front.csproj" />
  </Folder>
  <Folder Name="/tests/">
    <Project Path="tests/Orders.Tests/Orders.Tests.csproj" />
  </Folder>
</Solution>

The migration fits in one command:

dotnet sln MySolution.sln migrate

You get the .slnx next to it; check everything builds, delete the old .sln, commit. Support has been around for a while now — Visual Studio (17.14+), the dotnet CLI, Rider, VS Code — so no tooling excuse left.

Why it matters beyond aesthetics: a PR diff on a .slnx can be read (“one project added”, visible in two lines); a merge conflict can be resolved by hand without cold sweat; and an agent that must add a project to the solution edits obvious XML instead of juggling GUIDs. Readability is a feature.

Migration 2: Central Package Management

You know the symptom: Newtonsoft.Json at 13.0.1 in one project, 13.0.3 in another, a lingering NU1603 warning, and nobody knows which version is the truth. One solution, N projects, N truths.

CPM flips the logic: all versions in a single file at the root, Directory.Packages.props:

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Carter" Version="9.0.0" />
    <PackageVersion Include="FluentValidation" Version="12.1.0" />
    <PackageVersion Include="xunit" Version="2.9.3" />
  </ItemGroup>
</Project>

…and the .csproj files keep only the reference, without the version:

<ItemGroup>
  <PackageReference Include="Carter" />
  <PackageReference Include="FluentValidation" />
</ItemGroup>

Manual migration in three moves (ten minutes on an average solution): create the file, move every Version= into it, remove the Version attributes from the csproj files. Two options worth knowing:

  • VersionOverride: the owned exception — this project needs a different version, and it shows explicitly.
  • GlobalPackageReference: the package injected everywhere (analyzers, source generators) — perfect for tooling the whole solution at once, base-prompt analyzers included.

The vibe engineering dividend

These two migrations are cousins: they replace scattered and unreadable with one readable source of truth — and you know the tune:

  • “Update FluentValidation everywhere” becomes, for an agent, editing one line in one file — instead of a csproj hunt with the risk of missing one.
  • Dependabot and Renovate open one-line PRs, reviewable in three seconds.
  • And the security audit (remember slopsquatting) happens on one file: what enters the solution is visible in a single place.

The two honest traps: the CPM migration is done in one go (a half-migrated project = cascading NU1008 errors — block ten minutes, not two sprints); and transitive versions stay outside the file by default (the CentralPackageTransitivePinningEnabled option exists to pin those too — enable it knowingly).

In summary

  • .slnx: dotnet sln migrate, five minutes — readable diffs and resolvable conflicts.
  • CPM: Directory.Packages.props, ten minutes — one version per package, for the whole solution.
  • VersionOverride for the explicit exception, GlobalPackageReference for analyzers everywhere.
  • The recurring theme: one readable source of truth serves humans in PRs… and agents on assignment.

Two files modernized, a quarter of an hour, and your repository gains clarity for the next ten years. And that, honestly… is not rocket science.