Skip to content

feat(diff): detect version downgrades instead of mislabeling them as upgrades#24

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-v54gwk
Open

feat(diff): detect version downgrades instead of mislabeling them as upgrades#24
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-v54gwk

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

diff() lumps every version change into the upgraded array, so a dependency that moves backwards (e.g. 1.3.0 → 1.1.0) is reported as a non-major "upgrade." For a tool keyworded supply-chain-security / vulnerability-management, a rollback is itself a high-signal event:

  • a return to a yanked or CVE-affected release,
  • a version-pinning regression that quietly undoes a security bump,
  • a dependency-confusion / substitution that swaps in a lower version.

Today every one of those renders identically to a routine patch bump. This PR makes downgrades a distinct, visible signal.

Evidence (current behavior)

  • src/diff.ts pushes any aComp.version !== bComp.version change into upgraded; direction is never considered.
  • isMajorVersionBump() returns false for 2.0.0 → 1.0.0 (since toMajor > fromMajor is false), so a rollback across a major boundary shows up as a plain "upgrade" with no flag at all.

What changed

  • types (src/types.ts) — additive, backward compatible:
    • VersionChange.isDowngrade: boolean
    • summary.totalDowngraded: number
  • diff (src/diff.ts) — direction is computed with a small dependency-free segment comparator (compareVersions). A downgrade is never also reported as a major bump.
  • reporter (src/reporter.ts) — text and Markdown gain a dedicated "Downgraded Components" section; the summary splits upgrades vs. downgrades so the counts are honest.
  • tests — rollback detection, forward-upgrade non-regression, the major-version-rollback edge case, and both report renderers (5 new tests; suite goes 29 → 34, all green).

Example

Summary:
  Upgraded:    1
  Downgraded:  1
...
↑ Upgraded Components:
  ~ lodash: 4.17.20 → 4.17.21

↓ Downgraded Components:
  ~ left-pad: 1.3.0 → 1.1.0 [DOWNGRADE]

Why this is a clean addition

Verification

npm run lint, npm run typecheck, npm test (34 passing), and npm run build all pass locally, plus an end-to-end CLI run against CycloneDX fixtures in both text and Markdown.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KPWdF3VnuxevNSR6iUwbQF


Generated by Claude Code

…upgrades

diff() lumped every version change into the `upgraded` array, so a dependency
moving backwards (e.g. 1.3.0 -> 1.1.0) was reported as a non-major "upgrade".
For a supply-chain tool a rollback is itself a high-signal event: a return to a
yanked or CVE-affected release, a pinning regression, or a dependency-confusion
substitution.

- types: add `VersionChange.isDowngrade` and `summary.totalDowngraded` (additive)
- diff: compute direction with a lightweight segment comparator; a downgrade is
  never also reported as a major bump
- reporter: render downgrades in a dedicated section (text + markdown) and split
  the summary count so upgrades and downgrades are counted separately
- tests: cover rollback detection, forward-upgrade non-regression, the
  major-version rollback edge case, and both report renderers

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPWdF3VnuxevNSR6iUwbQF
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.

2 participants