Skip to content

Fix version inconsistency handling for PackageReference Update items - #128

Merged
georgepwall1991 merged 89 commits into
mainfrom
fix/version-inconsistency-update-attributes
Aug 10, 2026
Merged

Fix version inconsistency handling for PackageReference Update items#128
georgepwall1991 merged 89 commits into
mainfrom
fix/version-inconsistency-update-attributes

Conversation

@georgepwall1991

@georgepwall1991 georgepwall1991 commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Treat Update-only PackageReference declarations as real declarations for version inconsistency analysis and fixing.
  • Preserve conditional boundaries, ignore metadata-only Updates as version evidence, and protect floating/range/property conditional pins.
  • Apply an unconditional version-bearing Update to every earlier matching item while preserving conditional Include behavior, target-specific VersionOverride metadata, and an unconditional base record for inherited items.
  • Retain Update items that may target SDK or Directory.Build.props references; local Include order cannot prove an Update is inert.
  • Treat Update-only histories as potentially carrying inherited VersionOverride state so a conditional clear cannot expose a target-specific version as unconditional.
  • Expand semicolon-separated Update item specifications in scanner matching, while the fixer refuses a shared multi-package declaration before any unrelated package can be rewritten.
  • Ignore metadata-only shared PackageReference declarations and independent conditional metadata when deciding whether an unconditional version fix is safe; retain the refusal when no unconditional version-bearing declaration remains.
  • Preserve and rewrite unconditional Version or VersionOverride metadata independently when the same PackageReference also contains conditioned metadata.
  • Ignore conditioned metadata when proving property-valued supersession, and fail closed for globbed or expandable version-bearing Update specifications instead of inventing literal package IDs.
  • Propagate unevaluable Update targets as declaration-scan failures and exclude the affected project from analysis/fixing rather than rewriting only its visible declarations.
  • Decline fixes when an unconditional empty Version clear remains active, including Update-only histories that may clear imported versions, and remove only the duplicate package from list-valued Include declarations.
  • Retire inherited Update-only clear sentinels when a local Include begins a new visible item history, so later unconditional Updates are still considered.
  • Scope conditional-clear protection to cases without an unconditional declaration for the requested version, and recognize exact inclusive NuGet ranges such as [1.0.0] as literal pins.
  • Match exact inclusive ranges to their resolved NuGet versions so conditional branches such as [2.0.0] are excluded from resolved inconsistency analysis.
  • Keep metadata-only Update declarations available for casing analysis without counting them as redundant package references.
  • Serialize combined conditional metadata guards as a parseable Or disjunction so wider exact Updates supersede floating projections across all covered branches.
  • Treat active literal VersionOverride clears as unsafe, scope conditioned override state to overlapping branches, and discover properties nested inside MSBuild condition functions before folding identical guards.
  • Fail closed for expandable version-bearing Includes as well as Updates, retain metadata-only Update declarations after matching Includes for casing analysis, and rewrite individual package tokens inside list-valued Include/Update attributes.
  • Treat child Version and VersionOverride metadata with their own Condition as conditional, preserve separate unconditional projections for unaffected sibling metadata, emit separate projections for independently conditioned metadata under item scopes, normalize metadata guards before splitting, and fail closed before rewriting condition-bearing metadata.
  • Fold sequential conditional Updates and same-scope conditional Include/Update pairs only when their effective condition scope matches, while preserving distinct conditional branches and independent Choose/Otherwise branches.
  • Apply wider conditional Updates to already-created narrower projections so superseded nested metadata cannot produce false floating-version findings, including wider condition subsets that are not an ordered suffix, equivalent disjunctions with different operand order, per-disjunct conjunction coverage, and equivalent external conditions for Choose/When/Otherwise branches, while preserving sibling branch identity for nested pathless conditions and matching equivalent branches across independent Choose elements only when preceding When guard signatures also agree, using an order-independent, length-delimited guard set; equivalent Otherwise branches use the same guard identity.
  • Preserve inherited ordinary Version and VersionOverride metadata across unconditional, same-scope, nested, and wider conditional scopes, including conditional override clears.
  • Track explicit Version and VersionOverride presence separately from normalized values so empty clears survive later Updates, and only protect empty Version clears when a prior inline version exists in an overlapping scope, with exclusivity inferred only from known literal equality operands, rejecting property, item, and metadata expansions.
  • Do not infer mutually exclusive scopes from mutable property equality text; reassigned TargetFramework, TargetFrameworkIdentifier, TargetFrameworkVersion, Configuration, Platform, and arbitrary MSBuild properties remain potentially overlapping, while Choose branch identity remains explicit.
  • Split independently conditioned Version and VersionOverride metadata only when their effective branches require it; normalize MSBuild property-name casing, preserve override precedence for overlapping guards, and retain wider Version branches outside narrower overrides.
  • Avoid synthesizing an unconditional projection for an inherited conditional clear, including when another conditional branch carries a nonempty override.
  • Retain a possible unconditional/inherited projection when an unconditional Update follows a conditional Include, including explicit nonempty VersionOverride-only Updates, while continuing to fold ordinary conditional Updates.
  • Emit both an unconditional VersionOverride projection and a conditional ordinary-Version projection when an unconditional override follows a conditional Version update.
  • Preserve a versionless unconditional base projection when an explicit unconditional VersionOverride clear follows a conditional ordinary-Version update, including when the conditional update also carries an explicit VersionOverride clear.
  • Keep empty conditional Version and VersionOverride clears protected, normalize whitespace around complete comparison operators and parentheses before comparing conditional scopes, including serialized preceding Choose guards, and strip balanced outer grouping parentheses for simple equality overlap checks; canonicalize safe simple equality literals on either side without folding path-like values, and recognize literal-first equality in overlap checks.
  • Protect resolved central versions produced by conditional Version="" and VersionOverride="" clears from unconditional drift/fix selection, while requiring the cleared metadata itself to be empty.
  • Ignore superseded property-valued Version or VersionOverride metadata when later literal or empty Update metadata is effective, while retaining safe failure for unresolved metadata that remains effective.
  • Carry effective VersionOverride state from preceding Include and Update declarations into fixer supersession checks.
  • Preserve ordinary Version metadata when a conditional VersionOverride clear can expose it, including when the effective override is on a separate later declaration or the clear precedes a later Version-only Update.
  • Add regression coverage for scanner, analyzer, fixer, fallback, conditional, property, inherited-reference, same-condition, different-condition, Choose/Otherwise, nested-scope, conditional Include/Update, metadata ordering, superseded property, explicit-clear, wider-condition-subset, inherited-projection, provenance, and later-Include fixer shapes, including per-Include state scoping and conditional-Include projection suppression.
  • Preserve a conditional ordinary Version when later VersionOverride assignments, including temporary non-empty overrides and final empty clears, do not replace that ordinary metadata; track whether Version originated on the conditional Update so an unconditional Version is not misclassified as conditional; reset that provenance when an unconditional Version replaces it.
  • Keep identical conditional guard text separate when the referenced MSBuild property is reassigned, skip target-time property mutations, ignore mutually exclusive assignment guards, and treat intervening imports as mutation barriers, while allowing unrelated property assignments to preserve valid Update folding.
  • Evaluate conditional clears and non-literal pins before accepting an unconditional matching version, so unresolved target-specific values remain protected from unsafe inconsistency fixes.
  • Bump the package and discoverability surfaces to 3.61.0.

Verification

  • dbt CPMigrate.sln --configuration Release — 0 warnings, 0 errors
  • DeclaredUpdateItemTests — 108/108
  • VersionInconsistencyAnalyzerTests — 17/17
  • VersionInconsistencyFixerTests — 46/46
  • FloatingVersionAnalyzerTests — 46/46
  • Combined impacted scanner/analyzer/fixer gate — 217/217
  • RedundantReferenceAnalyzerTests — 4/4
  • RedundantReferenceFixerTests — 14/14
  • Duplicate package analyzer/fixer plus handler gate — 33/33
  • Combined owned gate including duplicate package analyzer/fixer and AnalysisHandler scan-failure suite — 268/268
  • dotnet pack CPMigrate/CPMigrate.csproj --configuration Release --no-build --output artifacts/packages — created CPMigrate 3.61.0
  • bash scripts/verify-packages.sh artifacts/packages — passed for CPMigrate 3.61.0
  • Final regression tests for the fourteen hosted-review findings — 14/14
  • Changed-line hygiene scan and git diff --check — clean
  • Full local suite — not used as the publication gate; the complete owned analyzer-health gate passed 268/268.
  • Final head: 7eb42d2
  • Final-head hosted CI, Distribution Smoke, and fresh hosted Codex review — pending for this exact head
  • Claude review wrapper — unavailable because weekly usage limit is reached (resets Aug 13); hosted Codex is the publication-bound reviewer for this iteration
  • Local Codex review — unavailable after a bounded 7+ minute diagnostic run with no verdict
  • Hosted Codex reviews found actionable P1/P2 issues on earlier heads; each was converted to a red regression, fixed, and covered by the verification above.

…ta was reported as fixable but could not be safely resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dd73a098c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CPMigrate/Services/ProjectFileScanner.cs Outdated
…— unconditional Update ordering was losing target-specific metadata

Copy link
Copy Markdown
Owner Author

@codex review Please review the current head 4d08e11, including the fix for the prior conditional-update finding.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

IsConditional =
existing.IsConditional
&& (!existing.IsConditionalUpdate || conditionalMetadataSurvives),

P1 Badge Retain the unconditional version beside a surviving override

When an inherited reference is amended first by a conditional VersionOverride="9.0.0" and then by an unconditional Version="3.0.0", both effective states remain: 9.0.0 inside the condition and 3.0.0 elsewhere. This assignment keeps the only declaration record conditional because the override survives, so no unconditional record for 3.0.0 is emitted; IsConditionallyDeclared then sees every declaration as conditional and filters both resolved versions, hiding a real cross-project inconsistency. Fresh evidence after the prior fix is this new conditionalMetadataSurvives branch collapsing the two evaluation states into one record; retain a separate unconditional base-version record alongside the conditional override.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Owner Author

@codex review Please review the final head 57074fd. The required review policy is now Codex review because Claude usage is unavailable.

Copy link
Copy Markdown
Owner Author

Addressed the prior P1 in 4d08e11 and finalized in 57074fd: unconditional version-bearing Updates now amend every earlier matching item while preserving conditional Include state and target-specific VersionOverride metadata. Added scanner/analyzer regressions. The prior thread is resolved; final-head CI and hosted Codex review are pending.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57074fd72f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +480 to +482
IsConditional =
existing.IsConditional
&& (!existing.IsConditionalUpdate || conditionalMetadataSurvives),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep an unconditional record when a conditional override survives

When an inherited PackageReference is amended by a conditional Update VersionOverride="9.0.0" and then by an unconditional Update Version="3.0.0", these lines retain the only synthetic declaration as conditional because the override survives. With no local Include, there is consequently no unconditional declaration for 3.0.0; IsConditionallyDeclared treats both the ordinary resolved version and the conditional override as conditional and filters them from version-inconsistency analysis, hiding real cross-project drift. Preserve the conditional override as a separate record while also representing the later unconditional Update.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review Please review final head ffc6d8f, including the P2 fix that preserves a conditional VersionOverride alongside an unconditional inherited base record.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffc6d8fad5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +234 to +238
if (
isUpdate
&& !hasPriorReference
&& HasLaterInclude(items, itemIndex, packageName)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain updates that may target inherited references

When the package was already included by an SDK or Directory.Build.props, an Update here amends that inherited item even if this project contains a later unconditional Include; the later declaration does not make the earlier update inert. This check therefore drops a real inherited version (and IsInertUpdateBeforeInclude makes the fixer skip the same XML), so a project with an inherited Serilog, then Update="Serilog" Version="2.0.0", then a local Include="Serilog" Version="1.0.0" can have its conflict hidden or only the local declaration rewritten.

Useful? React with 👍 / 👎.

A later local Include cannot prove that an earlier Update is inert because SDK and Directory.Build.props items may already exist. Keep and amend those Update items so inherited version conflicts remain visible and fixable.

Copy link
Copy Markdown
Owner Author

@codex review Please review the final head 3603e67bab3a5352f54c708149cb0f15bdbf35c2. The latest change removes the unsafe Update-before-Include inertness heuristic in both scanner and fixer and adds red regressions for inherited-reference targets.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3603e67bab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +369 to +371
if (!isUpdate || isConditional)
{
return [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fold later updates under the same condition

When two Update items execute under the same condition, the later one amends the metadata set by the earlier one, but this unconditional return keeps both declarations. For example, conditional Update="Pkg" Version="4.*" followed under the identical condition by Update="Pkg" Version="4.0.0" is exact after evaluation, while FloatingVersionAnalyzer still sees the superseded 4.* record and reports a false floating-version finding. Preserve separate records for distinct conditional branches, but fold sequential updates with the same effective condition.

Useful? React with 👍 / 👎.

A later Update in the same effective condition amends the earlier item, so retaining the superseded floating specification creates a false analyzer finding. Track conditional scope and fold only updates in the same branch while preserving distinct conditions.

Copy link
Copy Markdown
Owner Author

@codex review Please review the new final head 19f5c5c2439ccfdc94f2a24c6b27c33b3dc412d6. This addresses the prior P2 by tracking conditional scope and folding sequential conditional Updates only within the same effective branch; distinct branches remain separate.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19f5c5c243

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (current is ProjectOtherwiseElement)
{
return true;
conditions.Add("<Otherwise>");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Choose branch identity in conditional scopes

Using the constant "<Otherwise>" makes every Otherwise block share a scope, even across different Choose elements whose sibling When predicates differ. If the first Otherwise contains Update="Pkg" Version="4.*" and a later, independently controlled Otherwise contains version 4.0.0, only the first may execute in a given evaluation, but FindAmendmentIndices folds them and hides the active floating version. Include the enclosing Choose branch identity or its effective predicate so independently evaluated branches remain separate.

Useful? React with 👍 / 👎.

Otherwise has no condition text of its own, so a constant scope merged branches from separate Choose elements and hid active version evidence. Include the structural branch path so only the same conditional branch can fold sequential Updates.

Copy link
Copy Markdown
Owner Author

@codex review Please review final head 796d6f90c55871b620bb8e0ea3c907101c479aef. This adds the red Choose/Otherwise regression and scopes branches by structural path so independent Otherwise blocks cannot fold together.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 796d6f90c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +425 to +429
item.existing.IsConditionalUpdate
&& string.Equals(
item.existing.ConditionalScope,
conditionalScope,
StringComparison.Ordinal

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fold updates into same-scope conditional includes

When an Include and a later Update are inside the same conditioned ItemGroup or When branch, MSBuild applies the update to that included item. This predicate only matches earlier records marked IsConditionalUpdate—and conditional includes do not retain their scope—so a conditional Include Version="4.*" followed by Update Version="4.0.0" remains as two declarations and FloatingVersionAnalyzer falsely reports the superseded 4.*. Match conditional item declarations in the same structural scope while continuing to keep independent branches separate.

Useful? React with 👍 / 👎.

A conditional Update in the same item scope amends a conditional Include just like a prior conditional Update. Retain the branch identity on Includes so stale floating versions are not reported while independent branches remain visible.

Copy link
Copy Markdown
Owner Author

@codex review Please review final head 35ef744bb87be76430ce43bea2291c997e85fa0e. This addresses the prior P2 by retaining conditional scope on Include declarations and folding same-scope conditional Include/Update pairs, with red scanner/analyzer regressions.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35ef744bb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +425 to +434
&& (
!isConditional
|| (
item.existing.IsConditional
&& string.Equals(
item.existing.ConditionalScope,
conditionalScope,
StringComparison.Ordinal
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve inherited metadata on conditional updates

When an unconditional reference already has VersionOverride and a later conditional Update sets only Version (for example, Version="4.*"), MSBuild retains the existing override, so the update does not make 4.* effective. This predicate refuses to amend the unconditional record and instead emits a standalone conditional record without the inherited override; FloatingVersionAnalyzer then reads its Version and reports a false floating-version finding. Retain the unconditional base record, but synthesize the conditional effective record with metadata not replaced by the update.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review Please review exact final head 858d091942a80fa985e96be7a463a67e382ed49b. This fixes the P2 by preserving an inherited exact VersionOverride on a conditional Version-only Update; red scanner and FloatingVersion regressions now pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 858d091942

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

&& string.IsNullOrWhiteSpace(versionOverride)
? references
.LastOrDefault(existing =>
!existing.IsConditional

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prefer the latest same-scope override

When a base Update VersionOverride="1.0.0" is followed under condition C by Update VersionOverride="2.*" and then a same-scope Update Version="3.0.0", MSBuild leaves 2.* in force because the last update does not replace VersionOverride. This lookup considers only unconditional records, so the final update passes 1.0.0 to ApplyAmendments and overwrites the nearer conditional 2.*, causing FloatingVersion to miss the active float. The fresh final-head case is the additional same-scope conditional override between the base and Version-only update; prefer that metadata over the unconditional base.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review Please review exact final head dcf86027d57fa969dd93e6ba0d93d043b8ee8206. This preserves the latest same-scope conditional VersionOverride by delaying inherited-override synthesis until after amendment matching; red scanner and FloatingVersion regressions pass.

Copy link
Copy Markdown
Owner Author

Correction to the previous review request: the exact pushed head is dcf8602317cfb727f4e1326340067eedb7433875. Please review this SHA.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4de9613232

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +626 to +629
if (string.IsNullOrWhiteSpace(version))
{
hasActiveClear = hasInlineVersion;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat inherited versions as potentially cleared

When the package is introduced by an earlier import and the project contains only an unconditional <PackageReference Update="Foo" Version="" />, this initializes hasInlineVersion to false and therefore does not recognize the Update as an active clear. The safety preflight then passes, but UpdateProjectVersions skips the empty metadata and can report that no fix is needed while the imported version remains cleared and the effective central version still conflicts. Fresh evidence at this head is the new guard requiring a locally observed nonempty Version before treating a clear as unsafe; Update-only histories need to start conservatively because imported item metadata is not visible in this XML.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head 975785c084868b6ef031fd5646cedd9b14052a1b for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 975785c084

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +444 to +447
// An Update-only history may target an item imported from Directory.Build.props or an SDK.
// Its inherited VersionOverride is not visible in this local XML, so begin conservatively as
// potentially overridden until a local declaration proves otherwise.
(null, 0, true, false),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retire the inherited sentinel after a local Include

When a local Include="Foo" Version="1.0.0" is followed by a conditional Update that clears VersionOverride and then an unconditional Update that restates Version="1.0.0", this sentinel remains active even though the local Include has no override. The conditional clear marks the sentinel, causing HasConditionalOverrideClearAfter to skip rewriting the final, effective Version Update; the fixer rewrites only the superseded Include and reports success while the project still resolves the old version. Remove or otherwise neutralize the speculative inherited state once a local item history is established, or fail the fix instead of reporting a partial rewrite as successful.

Useful? React with 👍 / 👎.

Comment thread CPMigrate/Models/ProjectPackageInfo.cs Outdated
Comment on lines +184 to +186
if (hasConditionalVersionClear)
{
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit conditional-clear protection to the cleared version

When a project has an unconditional Version="1.0.0" plus a conditional Version="" Update that exposes central version 2.0.0, this package-wide return classifies both resolved versions as conditional. Protecting 2.0.0 is correct, but the unconditional 1.0.0 is also removed from version-inconsistency analysis, so drift between that branch and another project's pin is missed. At this head the new hasConditionalVersionClear result is not tied to the version argument; preserve protection only for the resolved central branch rather than every reference from the project.

Useful? React with 👍 / 👎.

Comment thread CPMigrate/Models/ProjectPackageInfo.cs Outdated
Comment on lines +233 to +239
private static bool IsNonLiteralVersion(string version)
{
return !string.IsNullOrWhiteSpace(version)
&& (
version.Contains("$(", StringComparison.Ordinal)
|| !NuGetVersion.TryParse(version, out _)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize exact NuGet ranges as literal pins

When a conditional declaration uses an exact NuGet range such as [1.0.0] (or [1.0.0,1.0.0]) alongside an unconditional 1.0.0 pin, NuGetVersion.TryParse rejects the range syntax and this method labels it non-literal. The early conditional protection then excludes the project's unconditional resolved version from inconsistency analysis, potentially hiding drift against another project, even though these ranges deterministically pin one release. Parse ranges with VersionRange and treat equal inclusive bounds as literal, consistent with FloatingVersionAnalyzer.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head 5ec5c58a286da1c7b250951bb7e04974f02ae7e1 for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ec5c58a28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +245 to +246
return !NuGetVersion.TryParse(version, out _)
&& !IsExactVersionRange(version);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match exact ranges to their resolved versions

When a project has an unconditional 1.0.0 declaration and a conditional exact range [2.0.0], the resolved 2.0.0 branch is no longer classified as non-literal here, but matching still compares the declaration text [2.0.0] directly with the resolved text 2.0.0. Because the project also has an unconditional declaration, the final fallback returns false, so the conditional branch enters version-inconsistency analysis and remains after the fixer rewrites only unconditional metadata, leaving the finding unresolved. Fresh evidence beyond the earlier exact-range comment is that this head exempts exact ranges without adding semantic range-to-version matching.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head bd0ff1346232ea05b146ced24b9b63bccef55d85 for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd0ff13462

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +433 to +435
if (isUpdate && !hasVersionMetadata && hasPriorReference)
{
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude metadata-only Updates from duplicate counts

When an unconditional metadata-only Update precedes a local Include, such as <PackageReference Update="Foo" PrivateAssets="all" /> followed by <PackageReference Include="Foo" />, hasPriorReference is false, so this retains the Update and later adds the Include. RedundantReferenceAnalyzer does not filter IsMetadataOnlyUpdate, so it reports two declarations even though the Update creates no item; the finding is also marked fixable, but RedundantReferenceFixer sees only the single Include and makes no change. Keep this record available for casing analysis without allowing it to count as an independent reference.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head c0d4564d0768c2b4a47b054f434b0c5be9432a9f for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0d4564d07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.GroupBy(NormalizeConditionSyntax, StringComparer.Ordinal)
.Select(group => group.First())
.ToArray();
return conditions.Length == 0 ? null : string.Join(" || ", conditions);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a parseable disjunction for combined metadata guards

When Version is guarded by Configuration == Debug And TargetFramework == net8.0 and a floating VersionOverride is guarded by TargetFramework == net8.0, ShouldSplitConditionedMetadata keeps one projection and this serializes its scope using ||. A later exact override Update under TargetFramework == net8.0 supersedes the float everywhere it can apply, but ConditionCovers only recognizes the word Or; it treats the || expression as a conjunct and can leave the old floating projection alive, producing a false FloatingVersion finding. Fresh evidence beyond the previously fixed identical-guard case is that distinct covered guards still take this || path; serialize a recognized Or expression or preserve the guards structurally.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head 6df193017d727a47928fff38a70bc69a98e42d6b for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6df193017d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


foreach (var reference in matchingReferences)
{
foreach (var version in GetUnconditionalMetadataValues(reference, "Version"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat active VersionOverride clears as unsafe

When an unconditional VersionOverride="" Update clears an earlier override, this preflight examines only ordinary Version metadata and therefore permits the fix. For example, an Include with VersionOverride="1.0.0" followed by an unconditional override clear can resolve to central version 2.0.0; fixing drift to 3.0.0 rewrites the superseded 1.0.0 while leaving the clear active, then reports a successful change although the effective version remains 2.0.0. Unlike the handled Version-clear case, the exact head never checks VersionOverride clears here.

Useful? React with 👍 / 👎.

Comment on lines +467 to +469
!string.IsNullOrWhiteSpace(
GetMetadataValues(element, "VersionOverride").LastOrDefault()
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope conditioned overrides before protecting ordinary versions

When an Include has an unconditional Version="1.0.0" and a conditioned VersionOverride="2.0.0", this initializes the override as globally active. A later conditional override clear in a mutually exclusive branch then causes HasConditionalOverrideClearAfter to protect the ordinary version, even though that clear cannot clear the conditioned override. If the unconditional branch is reported drifting to 3.0.0, the fixer skips its Version, modifies nothing in this project, and can return NoFixNeeded while the conflict remains; track the override and clear scopes rather than reading all metadata as unconditional state.

Useful? React with 👍 / 👎.

Comment on lines +259 to +260
var closingIndex = conditionalScope.IndexOf(')', index + 2);
if (closingIndex <= index + 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track properties nested inside condition functions

When identical package guards use a property function such as $([System.String]::Equals('$(Mode)', 'A')), this lookup takes the first closing parenthesis, treats the whole prefix through $(Mode) as an invalid property name, and then advances past the nested property without recording Mode. If Mode is reassigned between a floating Include and an exact Update, PropertyMutationStatesMatch consequently folds them even though the Update no longer executes, hiding the effective floating version. The condition-property extraction needs to recognize nested property references rather than skipping them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head b7449e5af707fb99737f81ebe2ff673aede5eb97 for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7449e5af7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +374 to +376
isUpdate
&& hasVersionMetadata
&& IsUnevaluablePackageSpecification(packageName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject expandable Include specifications

When an Include uses an evaluated specification such as <PackageReference Include="$(PackageId)" Version="1.0.0" />, this guard applies only to Updates, so the declaration scan records a fictitious package named $(PackageId) while the resolved scan reports the actual package. A version-inconsistency finding for the resolved package is then marked fixable, but VersionInconsistencyFixer cannot match the expression-valued Include and reports a no-op. Fail the declared scan for unevaluable version-bearing Includes as well, or expand them before analysis.

Useful? React with 👍 / 👎.

Comment on lines +455 to +457
if (isUpdate && !hasVersionMetadata && hasPriorReference)
{
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain metadata-only Updates after matching Includes

When <PackageReference Include="Foo" /> is followed by <PackageReference Update="foo" PrivateAssets="all" />, this branch discards the Update because a prior reference exists. DuplicatePackageAnalyzer therefore cannot detect the inconsistent casing even though its fixer supports Update attributes; the newly added IsMetadataOnlyUpdate filter in RedundantReferenceAnalyzer already prevents this record from creating a false duplicate. Retain the marked metadata-only record regardless of declaration order.

Useful? React with 👍 / 👎.


private static IEnumerable<string> ExpandPackageNames(string packageName)
{
return packageName.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rewrite casing within list-valued Includes

Expanding list-valued declarations exposes casing findings that DuplicatePackageFixer cannot repair token-by-token. For example, Include="foo;Bar" followed by two Include="Foo" declarations makes Foo the selected standard casing, but the fixer compares the entire foo;Bar attribute with foo, finds nothing to rewrite, and returns NoFixNeeded while the finding remains. Either teach the casing fixer to replace individual list entries while preserving siblings or exclude these declarations from fixable casing findings.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current head c69d0b51dcf95eefd8bb4d0467cc05479b6191ad for actionable bugs, regressions, and missing edge cases before merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c69d0b51dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CPMigrate/Models/ProjectPackageInfo.cs Outdated
Comment on lines +179 to +181
&& item.reference.HasVersionMetadata
&& string.IsNullOrWhiteSpace(item.reference.Version)
&& item.reference.VersionOverride is null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Protect branches exposed by conditional override clears

When a centrally managed reference has an unconditional VersionOverride="2.0.0" and a conditional Update clears it with VersionOverride="", the cleared branch resolves to the central version even though no declaration text names that version. This protection only recognizes Version clears, so IsConditionallyDeclared classifies the central branch as unconditional; version-inconsistency fixing can then rewrite the unconditional override while leaving the conditional branch on the old central pin, and the reported conflict survives. Detect conditional HasVersionOverrideMetadata clears analogously.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current PR head c54368d30e985d2b547c1234e96ac7d3d803184d only. This is the final iteration; report actionable bugs, regressions, or missing edge cases. The latest fix protects resolved central versions after conditional VersionOverride="" clears.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c54368d30e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +240 to +242
item.reference.IsConditionalUpdate
&& hasMetadata(item.reference)
&& isClear(item.reference)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require cleared metadata to actually be empty

The generic helper never checks that getValue(item.reference) is empty, and both new isClear delegates inspect only the sibling metadata. Consequently, nonempty conditional assignments are treated as clears. For example, a versionless central Include followed by conditional Version="1.0.0" and Version="2.0.0" Updates makes the central version used outside those conditions appear conditionally declared, so VersionInconsistencyAnalyzer suppresses legitimate drift for that central branch. Restore the emptiness check for the metadata being classified as cleared.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review the exact current PR head 7eb42d2925bb1b73e952e42f749b05f11398a9e6 only. This is the final iteration; report actionable bugs, regressions, or missing edge cases. The latest repair requires the metadata being protected as a conditional clear to actually be empty.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 7eb42d2925

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@georgepwall1991
georgepwall1991 merged commit 4b3779f into main Aug 10, 2026
8 checks passed
@georgepwall1991
georgepwall1991 deleted the fix/version-inconsistency-update-attributes branch August 10, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant