convert-to-cpm
Convert .NET projects and solutions (.sln, .slnx) to NuGet Central Package Management (CPM) using Directory.Packages.props. USE FOR: converting to CPM, centralizing or aligning NuGet package versions across multiple projects, inlining MSBuild version properties from Directory.Bui
Install
npx skills add https://github.com/dotnet/skills/tree/main/plugins/dotnet-nuget/skills/convert-to-cpm
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dotnet-skills@llmmart
git clone https://github.com/dotnet/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole dotnet/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Convert to Central Package Management
Centralize package versions in Directory.Packages.props while preserving project behavior and producing reviewable before/after evidence.
Choose a mode first
Do this before running builds or changing files.
- Guard mode -- If any in-scope project uses
packages.config, stop. Explain that CPM requiresPackageReferenceand recommend migrating first. Do not create or modify files. - Package-maintenance mode -- A request to update, align, bump, or sync packages authorizes those package edits, not CPM conversion. Audit the named scope, resolve the requested versions, update existing project/shared version declarations, and restore/build every affected CLI target from the directory that establishes its applicable
global.json. Ask only when the version or alignment policy is ambiguous. Do not create or modifyDirectory.Packages.props, remove versions for CPM, or capture conversion artifacts. Complete the package work, then recommend CPM as the durable follow-up. - Conversion mode -- Use only when the user explicitly asks to adopt, enable, or convert to CPM. Follow the workflow below.
If the scope is unclear, ask once before proceeding.
Default execution plan
- Guard: use a minimal scoped detection pass, then answer and stop.
- Package maintenance: use a compact audit, edit only the requested package versions in their existing locations, validate affected targets, then recommend CPM. Do not read conversion references or enter the conversion workflow.
- Conversion: batch the preflight, baseline, audit/mutation, final validation, and report work to avoid redundant turns. Revisit a stage only when new CPM-specific evidence requires a targeted follow-up.
This plan is an efficiency default, not a hard cap. Never omit an in-scope project, imported .props/.targets file, detected complexity, required validation, or deliverable to save a turn. Batch complete work where practical.
Inputs
| Input | Required | Rule |
|---|---|---|
| Scope | Yes | Project, solution, or directory containing the projects to inspect or convert |
| Conflict strategy | For package maintenance or conversion with conflicts | If the user already supplied a strategy such as "use the highest version," apply it without asking again and record its impact. Otherwise stop after the audit and ask before editing. |
Read references only when needed
Never preload all references.
| Condition | Read |
|---|---|
| Entering conversion baseline or producing the package diff | baseline-comparison.md |
A conflict, conditional reference, shared import, security concern, or VersionOverride is detected |
audit-complexities.md |
Placement is unclear or conditional PackageVersion/VersionOverride is required |
directory-packages-props.md |
| A package version uses an MSBuild property | msbuild-property-handling.md |
| Restore or build fails after conversion | validation-and-errors.md |
| Writing the final report | report-template.md |
Conversion workflow
1. Scope and preflight
- Resolve the project/solution scope. For a solution, list its projects. For a directory, search only beneath that directory and create an explicit target set that covers the full scope: use each applicable
.sln/.slnx, then add each project not covered by a solution. Verify that every in-scope project is covered and avoid duplicate work for projects that occur in more than one target. Ask only when overlapping targets or repository boundaries make the intended coverage ambiguous; never ask the user to select one target when that would omit in-scope projects. - Determine CPM management scopes separately from CLI targets. Group projects that will share one central version policy and place one
Directory.Packages.propsat each group's first common ancestor, while respecting existing nearest-file boundaries. Multiple CLI targets can share one CPM file; independent project groups can require separate files. - Check for
packages.config; if found, switch to Guard mode and stop. - Check the scope and ancestors for
Directory.Packages.props. If CPM is already fully enabled, report that and stop. If a partial file exists, preserve it and ask only when its intended scope is ambiguous. - Choose one common artifact directory within the resolved scope, normally the targets' first common ancestor. Use explicit paths into it for every binlog, package snapshot, and the report.
- Run each target's .NET commands from its solution/project directory or another directory that establishes its applicable
global.json, not from an unrelated parent workspace. - Do not inspect unrelated projects or host-tool configuration when the user supplied a scope.
2. Capture the baseline
Read baseline-comparison.md. For each target, determine the active SDK once from that target's command directory and select the documented command syntax for that version. If SDK resolution fails or the SDK cannot process the requested solution format, stop and report the prerequisite; do not alter the host SDK or repository SDK policy unless the user asks.
Then use one command batch to:
- Clean, restore, and build every explicit target. Use
baseline.binlogfor one target or a uniquebaseline-<target-key>.binlogfor each of multiple targets. - Write resolved packages for every target without restoring again. Use
baseline-packages.jsonfor one target or a matchingbaseline-packages-<target-key>.jsonfor each of multiple targets. - Keep normal command output concise. Save full output to artifacts when useful; inspect only errors on failure and never read the binlog as text.
Finish every baseline before editing. If any baseline build fails, stop without modifying files and preserve all artifacts already produced.
3. Audit with a targeted checklist
Use all baseline snapshots plus one targeted scan of in-scope project, .props, and .targets files. Identify:
- Package IDs, resolved versions, and consuming projects
- Version conflicts
- MSBuild property-based versions and their definitions
- Conditional
PackageReferenceitems - Imported files containing package references
- Existing
VersionOverrideusage
For a complex scope, complete every applicable item above across all projects and imported files; do not stop after finding the first conflict.
Do not run broad --outdated or --deprecated scans by default. Before editing, attempt a scoped --vulnerable --include-transitive query when the user requested security information, a known advisory must be verified, or conflict resolution will move a project across a major package version. Record the compact findings, "no advisories found," or why the check could not run. If a high-risk check is unavailable because of authentication, package-source, or offline constraints, surface the uncertainty and confirm the user's strategy rather than silently treating it as safe. Do not upgrade beyond the highest version already in scope as part of a CPM conversion.
Present conflicts and their impact. Explicitly classify major-version alignment as high risk and minor/patch alignment as moderate risk without performing an extra online scan. If the user supplied a conflict strategy, proceed. Otherwise ask for the unresolved decisions and stop before editing.
4. Create CPM files and update references
- Create or update each required
Directory.Packages.propsat its computed management scope withManagePackageVersionsCentrallyset totrue. - Add one alphabetically sorted
PackageVersionper package, preserving required target-framework conditions. - Remove only
Versionfrom managedPackageReferenceitems in projects and imported files. - Preserve conditions, whitespace, and all other metadata such as
PrivateAssets,IncludeAssets,ExcludeAssets,GeneratePathProperty, andAliases. - Use
VersionOverrideonly when the chosen strategy requires it.
For MSBuild version properties, follow msbuild-property-handling.md. When the user directs inlining, include both the literal PackageVersion and removal of the obsolete property definition in the same mutation batch. Before final validation, verify separately that:
- No
$(PropertyName)references remain in scoped project,.props, or.targetsfiles. - No
<PropertyName>...</PropertyName>definition remains for each property chosen for removal.
Do not rely on a $() reference scan to prove that the XML property definition was removed.
5. Validate and compare
Using baseline-comparison.md, validate the final on-disk state after all project, shared-file, and property edits. Use one command batch to:
- Clean, restore, and build every explicit target after all CPM edits. Use
after-cpm.binlogfor one target or a matchingafter-cpm-<target-key>.binlogfor each of multiple targets. - Write resolved packages for every target without restoring again. Use
after-cpm-packages.jsonfor one target or a matchingafter-cpm-packages-<target-key>.jsonfor each of multiple targets. - Produce a compact per-project changes/unchanged comparison without printing or rereading the full JSON files.
- If resolved versions changed and the repository exposes a routine, scoped test command for affected projects, run it with
--no-build --no-restoreand record the result. If tests require substantial setup, broad infrastructure, or user approval, recommend the exact scoped command instead. A version-neutral conversion does not require an automatic test run.
If restore or build fails with a CPM-related error, read validation-and-errors.md, inspect only the relevant error lines, make a targeted correction, and rerun the affected validation. For SDK, authentication, package-source, file-lock, test-host, or other environmental failures, report the blocker instead of changing the machine or expanding the investigation.
If a test run fails after a successful build, inspect only enough output to determine whether CPM package resolution caused it. Apply a targeted correction only when the evidence clearly identifies a CPM defect; otherwise record the failure and recommended user action without expanding into test-host, SDK, output-directory, or dependency-copy debugging.
6. Write the report
Read report-template.md now, not earlier. Create convert-to-cpm.md beside the other artifacts. It must include the six required sections, every explicit target and CPM management scope, concrete conflict impacts, the aggregate package comparison, risk level, follow-ups, artifact usage, and the name of every shared .props/.targets file inspected or changed. In the final response, mention those shared files, the risk level, and how any conditional references and target frameworks were preserved. Avoid rewriting the report after validation unless verification finds an omission or incorrect evidence.
Required conversion artifacts
Preserve the report and every target's four evidence files; they are not temporary files. For one target, the five deliverables are:
baseline.binlogafter-cpm.binlogbaseline-packages.jsonafter-cpm-packages.jsonconvert-to-cpm.md
For multiple targets, replace the four fixed evidence names with unique target-keyed pairs such as baseline-api.binlog, after-cpm-api.binlog, baseline-packages-api.json, and after-cpm-packages-api.json. Keep one aggregate convert-to-cpm.md.
Efficiency rules
- Batch independent reads and edits when supported.
- Keep full build logs and package JSON out of the conversation; return compact summaries and artifact paths.
- Do not repeat successful commands or reread successful output.
- In conversion mode, do not perform package upgrades, broad outdated/deprecated scans, repeated tests, or unrelated repository exploration. The single conditional vulnerability query and test run defined above are part of complete high-risk conversion validation. Package maintenance can perform the requested upgrades and one scoped version-discovery query needed to resolve them.
- Do not install or remove an SDK, create a temporary SDK selector, change roll-forward policy, invoke SDK-internal assemblies, kill unrelated processes, or clean host tooling/temp infrastructure. Report an environment prerequisite and stop.
Validation
- Baseline and converted builds succeeded for every explicit target and all target binlogs exist
- Every managed
PackageReferencehas noVersion, or intentionally usesVersionOverride - Every managed package has the correct central
PackageVersion - Conditions and non-version metadata were preserved
- Before/after package comparison contains no unexplained changes
- Inlined version properties have neither remaining
$()references nor obsolete XML definitions - The report and all per-target baseline and converted artifacts exist
Files (skills)
-
references
-
audit-complexities.md 5.3 KB
# Audit Complexities Audit only the resolved scope. Use the baseline package snapshot plus one targeted search across in-scope project, `.props`, and `.targets` files; do not explore unrelated repository files. Read the sections below only for complexities that were actually detected. ## 1. Version set via MSBuild property If a `PackageReference` uses a property for its version (e.g., `Version="$(SomePackageVersion)"`), trace the property definition. If the property is defined in a `Directory.Build.props`, `.props` import, or the project file itself, note it for the user. These require manual decisions about whether to replace the property with a literal version in `Directory.Packages.props` or to keep the property and use it within `Directory.Packages.props`. See [msbuild-property-handling.md](msbuild-property-handling.md) for decision workflow. ## 2. Conditional PackageReference items If a `PackageReference` is inside a conditional `<ItemGroup>` (e.g., `Condition="'$(TargetFramework)' == 'net8.0'"`), the version must still be centralized. The `PackageVersion` entry in `Directory.Packages.props` can use the same condition, or the project can use `VersionOverride` if the condition is project-specific. ## 3. Same package with different versions If the same package ID appears with different versions across projects, this is a **version conflict** that requires a user decision before proceeding. CPM requires a single `<PackageVersion>` per package (unless `VersionOverride` is used), so conflicts must be resolved. For each conflict, present a focused summary showing: - The package name and all distinct versions found - Which projects use each version, so the user can see the scope of the disagreement - Whether the difference is major, minor, or patch — this signals the level of risk - Any known security advisories on the lower versions (cross-reference with `dotnet package list --vulnerable` if available) Then present the resolution options with their trade-offs: 1. **Align to the highest version** — simplest path; all projects get the latest. Risk: a major version bump may introduce breaking API changes in projects that were on an older version. 2. **Align to the lowest version** — conservative; avoids pulling in untested changes. Risk: projects already on higher versions would be downgraded, which may break them or regress security fixes. 3. **Use `VersionOverride`** — projects that need a different version keep it via `VersionOverride` in their project file. The central `<PackageVersion>` holds the default. This preserves the status quo but partially undermines centralization for that package. Do not upgrade any package beyond the highest version already in use across the scope — this avoids introducing version incompatibilities or breaking changes that are unrelated to the CPM conversion itself. Instead, note any known advisories or upgrade opportunities as follow-up items in the post-conversion report for the user to address after the conversion is complete. If the user already supplied a conflict strategy, treat that as the decision and do not ask again. Otherwise ask the user to choose for each conflict and stop before editing. After each decision, record which projects will see a version change and which will stay the same. - **Major version difference**: Emphasize the risk of breaking API changes. Recommend `VersionOverride` unless the user is prepared to validate all affected projects. - **Minor or patch difference**: Prefer the highest version but highlight any security advisories. Note that patch-level alignment is usually safe. - **One version is vulnerable**: Note the advisory in the post-conversion report as a follow-up item. Do not upgrade the version as part of the CPM conversion. ## 4. Known security advisories If the user requested security information, a known advisory must be verified, or conflict resolution crosses a major package version, prefer one scoped vulnerability query after the baseline restore: - SDK 10+: `dotnet package list --project <scope> --vulnerable --include-transitive --format json --no-restore` - SDK 7.0.200–9.x: `dotnet list <scope> package --vulnerable --include-transitive --format json --no-restore` Extract only package ID, resolved version, advisory severity/URL, and affected projects into the audit summary. Preserve the full output as an optional artifact if useful, but do not read it repeatedly. Do not also run deprecated or outdated scans by default. Do not upgrade beyond the highest version already in scope; record advisory remediation as a follow-up item instead. ## 5. Packages without a Version attribute These may already be managed by CPM from a parent directory or may be using a default version. Verify whether a `Directory.Packages.props` in an ancestor directory already provides the version. ## 6. PackageReference in imported .props/.targets files Scan for `<Import>` elements in project files and `Directory.Build.props` to discover shared `.props` or `.targets` files that may contain `PackageReference` items. Search those imported files for package references — they need the same treatment but modifying shared build files has broader impact. Flag these to the user. ## 7. VersionOverride already in use If any project already uses `VersionOverride`, note it — this suggests partial CPM adoption may already be in progress. -
baseline-comparison.md 7.2 KB
# Baseline Comparison Verify the CPM conversion is version-neutral by comparing resolved package versions before and after conversion using `dotnet package list`. Binlogs are also captured as artifacts for manual inspection or troubleshooting. ## Capturing package lists Use the same explicit project or solution targets before and after conversion. A directory scope can require multiple targets to cover all projects. Choose one common artifact directory within the resolved scope and use explicit paths into it from every target's command directory. Always build each target from a clean state first. Create a stable, unique `<target-key>` from each target's path relative to the common artifact directory when more than one target exists. Use it in that target's artifact names so one target cannot overwrite another: - One target: `baseline.binlog`, `after-cpm.binlog`, `baseline-packages.json`, and `after-cpm-packages.json`. - Multiple targets: `baseline-<target-key>.binlog`, `after-cpm-<target-key>.binlog`, `baseline-packages-<target-key>.json`, and `after-cpm-packages-<target-key>.json`. Complete the full baseline sequence for every target before editing any file. Complete the full post-conversion sequence for every target after all edits. Run `dotnet --version` once from each target's command directory and select that target's package-list syntax by SDK version instead of probing with commands that may fail: - SDK 10 or later: use `dotnet package list --project <scope> --format json --include-transitive --no-restore`. - SDK 7.0.200 through 9.x: use `dotnet list <scope> package --format json --include-transitive --no-restore`. - SDK older than 7.0.200 cannot produce the required JSON snapshots; stop and report that SDK 7.0.200 or later is required for this workflow. - For a single project when the working directory contains exactly that project, the target may be omitted. - A `.slnx` scope requires SDK 9.0.201 or later so build, restore, and package-list operations all support the format. If it is unsupported, stop and report the prerequisite. If `dotnet --version` fails, do not try roll-forward overrides, install an SDK, create a temporary `global.json`, or invoke SDK assemblies directly. Report the SDK required by the existing `global.json` or project and stop. Set `<baseline-binlog>`, `<after-binlog>`, `<baseline-packages>`, and `<after-packages>` below to explicit paths in the common artifact directory, using the target-keyed names when applicable. ### Baseline for each target (before conversion) ```bash dotnet clean <scope> dotnet restore <scope> dotnet build <scope> --no-restore -bl:<baseline-binlog> ``` Then run exactly one package-list command for the active SDK: ```bash # SDK 10 or later dotnet package list --project <scope> --format json --include-transitive --no-restore > <baseline-packages> # SDK 7.0.200 through 9.x dotnet list <scope> package --format json --include-transitive --no-restore > <baseline-packages> ``` ### Post-conversion for each target (after all changes) ```bash dotnet clean <scope> dotnet restore <scope> dotnet build <scope> --no-restore -bl:<after-binlog> ``` Then run exactly one package-list command for the active SDK: ```bash # SDK 10 or later dotnet package list --project <scope> --format json --include-transitive --no-restore > <after-packages> # SDK 7.0.200 through 9.x dotnet list <scope> package --format json --include-transitive --no-restore > <after-packages> ``` Do not try both package-list forms after the SDK version has been determined. Keep normal output small: - Redirect routine build output to a log or suppress it. On success, report only status and artifact paths. - On failure, inspect the relevant error lines or a short tail rather than loading the full build output. - Never read a binlog as text. - Preserve package JSON, but use a JSON parser to extract only project path, framework, package ID, requested version, and resolved version. Do not print or read the raw JSON when a compact extraction is available. ## Producing the comparison Compare each target's baseline and post-conversion package files, then aggregate results by project. Deduplicate projects that appeared in overlapping targets. For each project, identify: 1. **Version changes**: Packages whose resolved version differs. 2. **Added packages**: Packages present after conversion but not in the baseline. 3. **Removed packages**: Packages present in the baseline but not after conversion. 4. **VersionOverride entries**: Packages that use `VersionOverride` (their version matches baseline but the mechanism changed). 5. **Transitive changes**: If `CentralPackageTransitivePinningEnabled` was set, note any transitive packages that are now pinned. ### Example comparison tables Present changes and unchanged packages in separate tables. The **Changes** table highlights anything that differs from baseline — version alignment from conflict resolution, `VersionOverride` entries, and added/removed packages. The **Unchanged** table lists everything else for reference and confidence. **Changes:** ``` | Project | Package | Before | After | Status | |---------|---------|--------|-------|--------| | ProjectA.csproj | PackageA | 1.0.0 | 2.0.0 | Aligned to highest version | | ProjectB.csproj | PackageA | 1.0.0 | 1.0.0 | VersionOverride | | ProjectC.csproj | PackageB | — | 3.1.0 | Added | ``` **Unchanged:** ``` | Project | Package | Version | |---------|---------|---------| | ProjectA.csproj | PackageB | 3.1.0 | | ProjectB.csproj | PackageC | 4.2.0 | ``` If there are no changes at all, state that the conversion is fully version-neutral and present only the unchanged table. ## Binlog artifacts MSBuild binary logs (binlogs) are captured alongside the package list snapshots as supplementary artifacts. Inform the user they are available for manual validation and troubleshooting if needed: - `baseline.binlog` and `after-cpm.binlog` — Build state before and after a single-target conversion - Target-keyed binlog pairs — Build state before and after each target in a multi-target conversion The user can learn more about MSBuild binary logs from: - [Troubleshoot and create logs for MSBuild problems](https://learn.microsoft.com/visualstudio/ide/msbuild-logs?view=visualstudio#provide-msbuild-binary-logs-for-investigation) - [Obtaining Build Logs with MSBuild](https://learn.microsoft.com/visualstudio/msbuild/obtaining-build-logs-with-msbuild?view=visualstudio#save-a-binary-log) - https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Binary-Log.md ## When comparison reveals unexpected differences If the post-conversion package list resolves different versions than expected (beyond intentional changes like version conflict alignment or `VersionOverride`), investigate: - Missing `<PackageVersion>` entries causing fallback behavior - Conditional `<PackageVersion>` entries not matching the project's target framework - Import order issues where a property referenced in `Directory.Packages.props` is not yet defined - Transitive dependency resolution differences from version alignment - Packages unexpectedly added or removed due to conditional ItemGroup changes The binlogs can help diagnose these issues by showing the full MSBuild evaluation and package resolution. Flag any unexpected differences to the user before considering the conversion complete. -
directory-packages-props.md 3.6 KB
# Directory.Packages.props Creation ## Placement - **Repository scope**: First group projects by the central version policy they must share. If all in-scope projects share one policy, place one file at their first common ancestor. If independent solutions or existing nearest-file boundaries require separate policies, place one file at each group's first common ancestor. This may produce one or more files, and none must be at the repository root. - **Solution scope**: Place at the first common ancestor of all governed projects, while respecting existing nearest-file boundaries. This is the solution directory only when it is an ancestor of every governed project. - **Single project scope**: Default to the project directory. If the project is inside a repository with other projects that may be converted later, ask the user where to place it. Only the nearest `Directory.Packages.props` is evaluated per project. CPM also supports `Directory.Packages.props` in sub-folders — for example, test projects may have different dependencies than source code and can use a separate `Directory.Packages.props` in their sub-folder. A `Directory.Packages.props` in a sub-folder does not implicitly override or extend a parent file; it is independent and replaces the parent for projects in that folder. To share settings, explicitly chain files using MSBuild `<Import>` elements. See [Central Package Management rules](https://github.com/NuGet/docs.microsoft.com-nuget/blob/main/docs/consume-packages/Central-Package-Management.md#central-package-management-rules) for how NuGet resolves which file applies. When in doubt about placement, ask the user. CLI targets and CPM management scopes are different concepts. Multiple solution or project targets can use one common `Directory.Packages.props`, while one repository conversion can require separate files for independent project groups. Compute placement from the projects that share policy, not from the number or location of solution files. ## Creating the file Create the file directly so the workflow does not depend on whether the installed SDK includes the `packagesprops` template: ```xml <Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup> <!-- PackageVersion items will be added here --> </ItemGroup> </Project> ``` ## Adding PackageVersion entries Add a `<PackageVersion>` entry for each unique package, using the resolved version from the audit. Sort entries alphabetically by package ID: ```xml <PackageVersion Include="PackageA" Version="1.2.3" /> <PackageVersion Include="PackageB" Version="4.5.6" /> ``` ## Conditional versions If the same package needs different versions for different target frameworks, use MSBuild conditions: ```xml <PackageVersion Include="PackageA" Version="1.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" /> <PackageVersion Include="PackageA" Version="2.0.0" Condition="'$(TargetFramework)' == 'net8.0'" /> ``` Preserve an existing target-framework-specific version split when a single version is incompatible. Ask only when multiple valid policies remain and the user has not already supplied a strategy. Record the preserved condition in the report. ## VersionOverride If a project intentionally needs a different version than the centrally defined one, use `VersionOverride` in the project file instead of removing the `Version` attribute: ```xml <PackageReference Include="System.Text.Json" VersionOverride="9.0.0" /> ``` Apply `VersionOverride` only when the user's chosen strategy requires it. If no strategy was supplied, ask before applying it; in most cases, version alignment is preferred. -
msbuild-property-handling.md 3.5 KB
# MSBuild Property Handling This covers how to handle MSBuild properties that define package versions (for example, `Version="$(PackageVersionProperty)"`) during CPM conversion. ## Import order guidance If keeping a property reference in `Directory.Packages.props` (e.g., `Version="$(PackageAVersion)"`), the property must be defined in a file that MSBuild evaluates before `Directory.Packages.props`. Properties in `Directory.Build.props` satisfy this requirement because MSBuild imports `Directory.Build.props` before `Directory.Packages.props`. ## Part 1: Make property decisions For each `PackageReference` that used an MSBuild property for its version: ### 1.1. Check if the property is used elsewhere Search all project files, `.props`, and `.targets` files in scope for references to the property name: ```bash # Unix/macOS grep -r '$(PropertyName)' --include='*.csproj' --include='*.props' --include='*.targets' . # Windows (PowerShell) Get-ChildItem -Recurse -Include *.csproj,*.props,*.targets | Select-String '$(PropertyName)' ``` If it appears only in `PackageReference` version attributes, it is safe to remove after inlining. ### 1.2. Property only used for versioning (in scope) If the property is defined in a file within scope (e.g., `Directory.Build.props`), ask the user whether to: - **Inline**: Replace the property usage with a literal version in `Directory.Packages.props` and remove the property definition before final validation, after verifying no references remain - **Keep**: Reference the property from `Directory.Packages.props` (e.g., `<PackageVersion Include="PackageA" Version="$(PackageAVersion)" />`) ### 1.3. Property used for other purposes If the property is used beyond package versioning, do not remove it. Use the property's resolved value in `Directory.Packages.props` and inform the user. ### 1.4. Property defined outside scope If the property is defined outside the conversion scope (e.g., in parent repository build infrastructure), stop before editing that package. Ask the user to choose one safe option: 1. Expand the conversion scope to include the defining file. 2. Use the resolved literal value in `Directory.Packages.props` and leave the external property unchanged. 3. Keep the property reference in `Directory.Packages.props` only after confirming its definition is evaluated before that file. Do not skip the central `PackageVersion` and continue: after CPM is enabled that would leave the project with either `NU1008` or `NU1010`. ## Part 2: Clean up obsolete properties After updating all package references and before the final restore/build, remove property definitions that the user chose to inline. Match the XML element structurally rather than depending on a particular newline style. Before removing any property, verify it has zero remaining references outside its own definition: ```bash # Unix/macOS grep -r '$(PropertyName)' --include='*.csproj' --include='*.props' --include='*.targets' . # Windows (PowerShell) Get-ChildItem -Recurse -Include *.csproj,*.props,*.targets | Select-String '$(PropertyName)' ``` Only remove a property if it has zero remaining references outside its own definition. Preserve all non-versioning properties in the same file (e.g., `OutputPath`, `LangVersion`). Then run two distinct checks: - Search for `$(PropertyName)` to confirm no uses remain. - Search for the XML element name (for example, `<PropertyName>`) to confirm the obsolete definition itself is gone. Both checks must pass before final validation. -
report-template.md 3.6 KB
# CPM Conversion Report Create `convert-to-cpm.md` with the baseline and converted artifacts in their common artifact directory. The report must be self-contained and suitable for a pull request or team review. Use compact evidence extracted from the package snapshots; do not load raw JSON again solely to write prose. ## 1. Conversion overview Include: - Scope and projects converted - Every explicit project or solution CLI target used for baseline and validation - Each `Directory.Packages.props` path and the projects governed by that management scope - Number of unique packages centralized - Projects or packages skipped, with reasons - MSBuild version properties inlined, retained, or removed - Every shared `.props`/`.targets` file inspected or changed, named explicitly (for example, `SharedPackages.props`) - Conditional references preserved ## 2. Version conflict resolutions For every conflict, provide: | Package | Versions and projects | Decision | Impact | |---------|-----------------------|----------|--------| State which projects resolve a different version after conversion. If no conflicts existed, say that versions were already consistent. ## 3. Package comparison: baseline vs. result Use every target's baseline and post-conversion package snapshots to produce two aggregate tables. Deduplicate projects that occur in overlapping targets. **Changes** | Project | Framework | Package | Before | After | Reason | |---------|-----------|---------|--------|-------|--------| Include changed versions, added/removed packages, and `VersionOverride` decisions. If no entries changed, state that the conversion is version-neutral. **Unchanged** | Project | Framework | Package | Version | |---------|-----------|---------|---------| List unchanged top-level packages compactly without repeating explanatory prose for each row. ## 4. Risk assessment Choose one level and explain the evidence: - **Low risk** -- Version-neutral conversion; restore/build succeeded. - **Moderate risk** -- Intentional patch/minor alignment or limited overrides; name affected projects. - **High risk** -- Major version changes, unexpected additions/removals, or unresolved validation concerns. Call out `VersionOverride`, removed MSBuild properties, conditional-version changes, and unexplained package differences. Recommend `dotnet test` when it was not run; claim it ran only when the user requested it or the workflow's resolved-version-change rule actually ran it. Treat intentional major-version alignment as high risk and minor/patch alignment as moderate risk unless stronger project-specific evidence supports another classification. This warning does not require an additional package scan. If resolved versions changed and tests were run, state the exact test result. If tests failed for a reason not clearly caused by CPM, preserve that distinction and list the failure as follow-up work rather than claiming conversion failure. ## 5. Follow-up items Use a numbered checklist for applicable items only: - Security advisories and minimum patched versions - Deprecated package replacements - Future alignment where `VersionOverride` preserved differences - Test validation and release-note review These are follow-ups, not additional work to perform during the CPM conversion. ## 6. Artifacts and usage List: - The single-target or target-keyed baseline and post-conversion binlog pairs for manual MSBuild comparison and troubleshooting - The single-target or target-keyed baseline and post-conversion package JSON pairs for machine-readable resolved-package comparison - `convert-to-cpm.md` as the shareable conversion record End with any user action required before merge. -
validation-and-errors.md 3.2 KB
# Validation and Common Errors ## Diagnose a failed validation batch The main workflow already ran clean, restore, and build. Do not repeat them merely to diagnose the same failure. First inspect the relevant error lines and determine whether the failure is caused by CPM edits. For multi-target framework projects (those with `<TargetFrameworks>` containing multiple TFMs), verify restore works for each framework. If restoration errors are framework-specific, the solution may require conditional `<PackageVersion>` entries or `VersionOverride` for specific projects. ## NuGet error codes | Error | Meaning | Fix | |-------|---------|-----| | **NU1008** | A `PackageReference` still has a `Version` attribute when CPM is enabled | Remove the `Version` attribute or convert to `VersionOverride` | | **NU1010** | A `PackageReference` has no corresponding `PackageVersion` entry | Add the missing `<PackageVersion>` entry to `Directory.Packages.props` | | **NU1507** | Multiple package sources without package source mapping | Configure [package source mapping](https://learn.microsoft.com/nuget/consume-packages/package-source-mapping) | Keep full build output out of the conversation. On success, report a concise status. On failure, inspect only the relevant error lines or a short tail before making a targeted correction. Only after one CPM-specific correction should you rerun the failed final validation batch from the main workflow. Do not start a separate open-ended validation sequence. Do not run tests for a version-neutral conversion unless the user explicitly requested them. When the main workflow runs one scoped test pass because resolved versions changed, treat a failure separately unless the evidence clearly ties it to CPM package resolution; avoid unrelated dependency or test-host debugging. Only CPM-related restore/build errors justify an automatic correction and retry. Do not install SDKs, change `global.json` or roll-forward policy, invoke SDK-internal DLLs, kill processes, or debug file locks/package sources as part of this skill. Report those environmental blockers with the failed command and required user action. ## Common pitfalls | Pitfall | Solution | |---------|----------| | `Directory.Packages.props` not picked up | Ensure it is in the project directory or an ancestor directory. Only the closest one is evaluated | | Multiple `Directory.Packages.props` files conflict | Use `Import` to chain files, or consolidate into one. Only the nearest file is evaluated per project | | Version properties in `.props` files cause build errors | Decide whether to inline the version or keep the property. See [msbuild-property-handling.md](msbuild-property-handling.md) | | Conditional PackageReference loses its condition | Move the condition to the `PackageVersion` entry in `Directory.Packages.props`, or use `VersionOverride` in the project | | `packages.config` projects are in scope | These must first be [migrated to PackageReference](https://learn.microsoft.com/nuget/consume-packages/migrate-packages-config-to-package-reference) before CPM conversion | | Global tools or CLI tool references affected | `DotNetCliToolReference` items are deprecated and not managed by CPM. They can be ignored |
-
-
SKILL.md 13.9 KB
--- name: convert-to-cpm description: > Convert .NET projects and solutions (.sln, .slnx) to NuGet Central Package Management (CPM) using Directory.Packages.props. USE FOR: converting to CPM, centralizing or aligning NuGet package versions across multiple projects, inlining MSBuild version properties from Directory.Build.props into Directory.Packages.props, resolving version conflicts or mismatches across a solution or repository, updating or bumping or syncing package versions across projects. Also activate when packages are out of sync, drifting, or inconsistent -- even without the user mentioning CPM. Provides baseline build capture, version conflict resolution, build validation with binlog comparison, and a structured post-conversion report. DO NOT USE FOR: packages.config projects (must migrate to PackageReference first) or repositories that already have CPM fully enabled. license: MIT --- # Convert to Central Package Management Centralize package versions in `Directory.Packages.props` while preserving project behavior and producing reviewable before/after evidence. ## Choose a mode first Do this before running builds or changing files. 1. **Guard mode** -- If any in-scope project uses `packages.config`, stop. Explain that CPM requires `PackageReference` and recommend migrating first. Do not create or modify files. 2. **Package-maintenance mode** -- A request to update, align, bump, or sync packages authorizes those package edits, not CPM conversion. Audit the named scope, resolve the requested versions, update existing project/shared version declarations, and restore/build every affected CLI target from the directory that establishes its applicable `global.json`. Ask only when the version or alignment policy is ambiguous. Do not create or modify `Directory.Packages.props`, remove versions for CPM, or capture conversion artifacts. Complete the package work, then recommend CPM as the durable follow-up. 3. **Conversion mode** -- Use only when the user explicitly asks to adopt, enable, or convert to CPM. Follow the workflow below. If the scope is unclear, ask once before proceeding. ### Default execution plan - **Guard**: use a minimal scoped detection pass, then answer and stop. - **Package maintenance**: use a compact audit, edit only the requested package versions in their existing locations, validate affected targets, then recommend CPM. Do not read conversion references or enter the conversion workflow. - **Conversion**: batch the preflight, baseline, audit/mutation, final validation, and report work to avoid redundant turns. Revisit a stage only when new CPM-specific evidence requires a targeted follow-up. This plan is an efficiency default, not a hard cap. Never omit an in-scope project, imported `.props`/`.targets` file, detected complexity, required validation, or deliverable to save a turn. Batch complete work where practical. ## Inputs | Input | Required | Rule | |-------|----------|------| | Scope | Yes | Project, solution, or directory containing the projects to inspect or convert | | Conflict strategy | For package maintenance or conversion with conflicts | If the user already supplied a strategy such as "use the highest version," apply it without asking again and record its impact. Otherwise stop after the audit and ask before editing. | ## Read references only when needed Never preload all references. | Condition | Read | |-----------|------| | Entering conversion baseline or producing the package diff | [baseline-comparison.md](references/baseline-comparison.md) | | A conflict, conditional reference, shared import, security concern, or `VersionOverride` is detected | [audit-complexities.md](references/audit-complexities.md) | | Placement is unclear or conditional `PackageVersion`/`VersionOverride` is required | [directory-packages-props.md](references/directory-packages-props.md) | | A package version uses an MSBuild property | [msbuild-property-handling.md](references/msbuild-property-handling.md) | | Restore or build fails after conversion | [validation-and-errors.md](references/validation-and-errors.md) | | Writing the final report | [report-template.md](references/report-template.md) | ## Conversion workflow ### 1. Scope and preflight - Resolve the project/solution scope. For a solution, list its projects. For a directory, search only beneath that directory and create an explicit target set that covers the full scope: use each applicable `.sln`/`.slnx`, then add each project not covered by a solution. Verify that every in-scope project is covered and avoid duplicate work for projects that occur in more than one target. Ask only when overlapping targets or repository boundaries make the intended coverage ambiguous; never ask the user to select one target when that would omit in-scope projects. - Determine CPM management scopes separately from CLI targets. Group projects that will share one central version policy and place one `Directory.Packages.props` at each group's first common ancestor, while respecting existing nearest-file boundaries. Multiple CLI targets can share one CPM file; independent project groups can require separate files. - Check for `packages.config`; if found, switch to Guard mode and stop. - Check the scope and ancestors for `Directory.Packages.props`. If CPM is already fully enabled, report that and stop. If a partial file exists, preserve it and ask only when its intended scope is ambiguous. - Choose one common artifact directory within the resolved scope, normally the targets' first common ancestor. Use explicit paths into it for every binlog, package snapshot, and the report. - Run each target's .NET commands from its solution/project directory or another directory that establishes its applicable `global.json`, not from an unrelated parent workspace. - Do not inspect unrelated projects or host-tool configuration when the user supplied a scope. ### 2. Capture the baseline Read [baseline-comparison.md](references/baseline-comparison.md). For each target, determine the active SDK once from that target's command directory and select the documented command syntax for that version. If SDK resolution fails or the SDK cannot process the requested solution format, stop and report the prerequisite; do not alter the host SDK or repository SDK policy unless the user asks. Then use one command batch to: 1. Clean, restore, and build every explicit target. Use `baseline.binlog` for one target or a unique `baseline-<target-key>.binlog` for each of multiple targets. 2. Write resolved packages for every target without restoring again. Use `baseline-packages.json` for one target or a matching `baseline-packages-<target-key>.json` for each of multiple targets. 3. Keep normal command output concise. Save full output to artifacts when useful; inspect only errors on failure and never read the binlog as text. Finish every baseline before editing. If any baseline build fails, stop without modifying files and preserve all artifacts already produced. ### 3. Audit with a targeted checklist Use all baseline snapshots plus one targeted scan of in-scope project, `.props`, and `.targets` files. Identify: - Package IDs, resolved versions, and consuming projects - Version conflicts - MSBuild property-based versions and their definitions - Conditional `PackageReference` items - Imported files containing package references - Existing `VersionOverride` usage For a complex scope, complete every applicable item above across all projects and imported files; do not stop after finding the first conflict. Do not run broad `--outdated` or `--deprecated` scans by default. Before editing, attempt a scoped `--vulnerable --include-transitive` query when the user requested security information, a known advisory must be verified, or conflict resolution will move a project across a major package version. Record the compact findings, "no advisories found," or why the check could not run. If a high-risk check is unavailable because of authentication, package-source, or offline constraints, surface the uncertainty and confirm the user's strategy rather than silently treating it as safe. Do not upgrade beyond the highest version already in scope as part of a CPM conversion. Present conflicts and their impact. Explicitly classify major-version alignment as high risk and minor/patch alignment as moderate risk without performing an extra online scan. If the user supplied a conflict strategy, proceed. Otherwise ask for the unresolved decisions and stop before editing. ### 4. Create CPM files and update references - Create or update each required `Directory.Packages.props` at its computed management scope with `ManagePackageVersionsCentrally` set to `true`. - Add one alphabetically sorted `PackageVersion` per package, preserving required target-framework conditions. - Remove only `Version` from managed `PackageReference` items in projects and imported files. - Preserve conditions, whitespace, and all other metadata such as `PrivateAssets`, `IncludeAssets`, `ExcludeAssets`, `GeneratePathProperty`, and `Aliases`. - Use `VersionOverride` only when the chosen strategy requires it. For MSBuild version properties, follow [msbuild-property-handling.md](references/msbuild-property-handling.md). When the user directs inlining, include both the literal `PackageVersion` and removal of the obsolete property definition in the same mutation batch. Before final validation, verify separately that: 1. No `$(PropertyName)` references remain in scoped project, `.props`, or `.targets` files. 2. No `<PropertyName>...</PropertyName>` definition remains for each property chosen for removal. Do not rely on a `$()` reference scan to prove that the XML property definition was removed. ### 5. Validate and compare Using [baseline-comparison.md](references/baseline-comparison.md), validate the final on-disk state after all project, shared-file, and property edits. Use one command batch to: 1. Clean, restore, and build every explicit target after all CPM edits. Use `after-cpm.binlog` for one target or a matching `after-cpm-<target-key>.binlog` for each of multiple targets. 2. Write resolved packages for every target without restoring again. Use `after-cpm-packages.json` for one target or a matching `after-cpm-packages-<target-key>.json` for each of multiple targets. 3. Produce a compact per-project changes/unchanged comparison without printing or rereading the full JSON files. 4. If resolved versions changed and the repository exposes a routine, scoped test command for affected projects, run it with `--no-build --no-restore` and record the result. If tests require substantial setup, broad infrastructure, or user approval, recommend the exact scoped command instead. A version-neutral conversion does not require an automatic test run. If restore or build fails with a CPM-related error, read [validation-and-errors.md](references/validation-and-errors.md), inspect only the relevant error lines, make a targeted correction, and rerun the affected validation. For SDK, authentication, package-source, file-lock, test-host, or other environmental failures, report the blocker instead of changing the machine or expanding the investigation. If a test run fails after a successful build, inspect only enough output to determine whether CPM package resolution caused it. Apply a targeted correction only when the evidence clearly identifies a CPM defect; otherwise record the failure and recommended user action without expanding into test-host, SDK, output-directory, or dependency-copy debugging. ### 6. Write the report Read [report-template.md](references/report-template.md) now, not earlier. Create `convert-to-cpm.md` beside the other artifacts. It must include the six required sections, every explicit target and CPM management scope, concrete conflict impacts, the aggregate package comparison, risk level, follow-ups, artifact usage, and the name of every shared `.props`/`.targets` file inspected or changed. In the final response, mention those shared files, the risk level, and how any conditional references and target frameworks were preserved. Avoid rewriting the report after validation unless verification finds an omission or incorrect evidence. ## Required conversion artifacts Preserve the report and every target's four evidence files; they are not temporary files. For one target, the five deliverables are: - `baseline.binlog` - `after-cpm.binlog` - `baseline-packages.json` - `after-cpm-packages.json` - `convert-to-cpm.md` For multiple targets, replace the four fixed evidence names with unique target-keyed pairs such as `baseline-api.binlog`, `after-cpm-api.binlog`, `baseline-packages-api.json`, and `after-cpm-packages-api.json`. Keep one aggregate `convert-to-cpm.md`. ## Efficiency rules - Batch independent reads and edits when supported. - Keep full build logs and package JSON out of the conversation; return compact summaries and artifact paths. - Do not repeat successful commands or reread successful output. - In conversion mode, do not perform package upgrades, broad outdated/deprecated scans, repeated tests, or unrelated repository exploration. The single conditional vulnerability query and test run defined above are part of complete high-risk conversion validation. Package maintenance can perform the requested upgrades and one scoped version-discovery query needed to resolve them. - Do not install or remove an SDK, create a temporary SDK selector, change roll-forward policy, invoke SDK-internal assemblies, kill unrelated processes, or clean host tooling/temp infrastructure. Report an environment prerequisite and stop. ## Validation - [ ] Baseline and converted builds succeeded for every explicit target and all target binlogs exist - [ ] Every managed `PackageReference` has no `Version`, or intentionally uses `VersionOverride` - [ ] Every managed package has the correct central `PackageVersion` - [ ] Conditions and non-version metadata were preserved - [ ] Before/after package comparison contains no unexplained changes - [ ] Inlined version properties have neither remaining `$()` references nor obsolete XML definitions - [ ] The report and all per-target baseline and converted artifacts exist
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.