Skip to content

Fix unit-inconsistent adoption metric: count drifted components, not findings#5

Merged
jongjesse merged 2 commits into
mainfrom
fix/adoption-metric-component-level
Jun 22, 2026
Merged

Fix unit-inconsistent adoption metric: count drifted components, not findings#5
jongjesse merged 2 commits into
mainfrom
fix/adoption-metric-component-level

Conversation

@jongjesse

@jongjesse jongjesse commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Problem (review finding #10)

The Design system adoption: NN% headline in the PR comment was unit-inconsistent. In src/comment/adoption.ts + src/comment/analyze.ts it was computed as:

canonicalUsages / (canonicalUsages + driftSignals)

canonicalUsages counts import specifiers (one per DS component used correctly, from countCanonicalUsages), but driftSignals counted findings. One drifting local component emits 3-4 findings at once (token-fingerprint + prop-match + subcomponent), so a single drifted component depressed adoption 3-4× harder than one correct import raised it. The headline wasn't a meaningful ratio.

Fix

Redefine the metric at component granularity so numerator and denominator compare like with like:

canonicalUsages / (canonicalUsages + driftedComponents)

New countDriftedComponents(findings) (in findings.ts) dedups findings by (file, key):

  • Every inline rule (local-shadow, token-fingerprint, prop-match, subcomponent) keys on the local component name, so its multiple signals fold back into one component.
  • import-drift keys on the wrongly-sourced specifier (Button from './ui'), which is already one drifted DS component per entry — the natural counterpart to one canonical import specifier.

analyze.ts uses it for both the head adoption headline and the base-branch delta. The definition is documented in adoption.ts and findings.ts.

Example

A ProductCard that trips 3 inline signals against 1 canonical (Tile) import now reads 50% (1 drifted component vs 1 canonical), not 25% (1 / (1+3)) as before.

Tests

  • tests/comment-core.test.ts — new countDriftedComponents block (folds inline signals to one component, counts each import-drift specifier, file-scoped, empty case).
  • tests/comment-analyze.test.ts — end-to-end: one multi-signal local component → 3 findings but 50% adoption.

npm run typecheck && npm test && npm run build:all all pass (122 tests); dist/ rebuilt and committed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved adoption percentage metrics in generated comments to count unique affected components (deduplicated) rather than raw drift finding signals, resulting in more accurate “adoption” and adoption delta calculations.
  • Tests
    • Added/extended coverage to ensure multiple inline drift signals for the same component are treated as one.
    • Added unit tests for component deduplication behavior (including per-file scoping and empty-input handling).

…findings

The "Design system adoption: NN%" headline divided canonical DS usages
(import specifiers) by the raw drift FINDING count. But one drifting local
component emits 3-4 findings at once (token-fingerprint + prop-match +
subcomponent), so a single drifted component depressed adoption 3-4x harder
than one correct import raised it — the ratio wasn't comparing like with like.

Redefine the metric at component granularity:

    canonicalUsages / (canonicalUsages + driftedComponents)

`countDriftedComponents` dedups findings by (file, key): every inline rule
keys on the local component name, so its multiple signals fold back into one
component; import-drift keys on the wrongly-sourced specifier, already one
drifted DS component per entry (the counterpart to one canonical import).

analyze.ts now uses it for both head adoption and the base-branch delta.
Documents the definition in adoption.ts / findings.ts and adds tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3682c022-f4de-4c55-ac6b-e0b3549e7b24

📥 Commits

Reviewing files that changed from the base of the PR and between 97bdf91 and 4c175d0.

⛔ Files ignored due to path filters (2)
  • dist/cli/index.js is excluded by !**/dist/**, !dist/**
  • dist/index.js is excluded by !**/dist/**, !dist/**
📒 Files selected for processing (2)
  • src/comment/analyze.ts
  • tests/comment-analyze.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/comment-analyze.test.ts
  • src/comment/analyze.ts

📝 Walkthrough

Walkthrough

Introduces countDriftedComponents in findings.ts to deduplicate drift findings by (file, key). Updates adoptionPct's parameter from driftSignals to driftedComponents and revises its JSDoc. Rewires analyzePr in analyze.ts to compute adoption using the new function. Adds unit and integration tests.

Changes

Component-level adoption metric

Layer / File(s) Summary
countDriftedComponents and adoptionPct contract
src/comment/findings.ts, src/comment/adoption.ts
Adds countDriftedComponents that deduplicates Finding[] by (file, key) using a Set. Renames adoptionPct's second parameter to driftedComponents and updates the JSDoc to reflect the new denominator semantics.
analyzePr wiring
src/comment/analyze.ts
Imports countDriftedComponents, computes distinct drifted-component counts for both head and base findings, and passes them to adoptionPct replacing the old raw finding-list lengths.
Tests
tests/comment-core.test.ts, tests/comment-analyze.test.ts
Unit tests cover inline-signal deduplication, per-specifier import drift, cross-file same-name separation, and empty input. Integration test asserts 3 raw findings from one component still yield 50% adoption.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the primary change: fixing a unit-inconsistent adoption metric by changing from counting drift findings to counting drifted components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/adoption-metric-component-level

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/comment-analyze.test.ts (1)

32-52: ⚡ Quick win

Add a regression case for readBase + suppressions affecting adoption delta.

This suite now validates component-level head adoption well; please add one case asserting base/head parity when suppress rules are active, so adoptionDeltaPct can’t regress silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/comment-analyze.test.ts` around lines 32 - 52, Add a new test case
after the existing test that validates adoption delta calculation when
suppressions or rules are active. Create a scenario using both readBase and
readCurrent options in the base() configuration where one version has
suppressions applied and the other doesn't, then assert that adoptionDeltaPct is
correctly calculated and doesn't regress. Reference the analyzePr function call
pattern and adoption percentage expectations from the existing test to structure
the new regression case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/comment/analyze.ts`:
- Around line 66-77: The baseAdopt calculation uses unsuppressed findings from
the baseFindings array, while headAdopt uses suppressed findings, causing
inconsistent adoption percentage calculations. Apply the same suppression logic
to baseFindings (similar to how suppressions are applied to the head findings)
before passing the suppressed findings to countDriftedComponents when computing
baseAdopt. This ensures that adoptionDeltaPct is calculated consistently with
the same suppression rules applied to both base and head findings.

---

Nitpick comments:
In `@tests/comment-analyze.test.ts`:
- Around line 32-52: Add a new test case after the existing test that validates
adoption delta calculation when suppressions or rules are active. Create a
scenario using both readBase and readCurrent options in the base() configuration
where one version has suppressions applied and the other doesn't, then assert
that adoptionDeltaPct is correctly calculated and doesn't regress. Reference the
analyzePr function call pattern and adoption percentage expectations from the
existing test to structure the new regression case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c59e383c-7735-4ea7-8866-94d8832d8b97

📥 Commits

Reviewing files that changed from the base of the PR and between 42663d9 and 97bdf91.

⛔ Files ignored due to path filters (2)
  • dist/cli/index.js is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (5)
  • src/comment/adoption.ts
  • src/comment/analyze.ts
  • src/comment/findings.ts
  • tests/comment-analyze.test.ts
  • tests/comment-core.test.ts

Comment thread src/comment/analyze.ts
Comment on lines +66 to +77
const baseFindings: Finding[] = [];
for (const file of p.files) {
const base = p.readBase(file);
if (base == null) continue;
const res = checkDriftFull(base, p.dsExports, p.canonicalPkgs, p.allowlist, file);
const baseFindings = flattenFindings(file, res);
for (const f of baseFindings) preexistingIds.add(f.id);
baseDrift += baseFindings.length;
const ff = flattenFindings(file, res);
for (const f of ff) preexistingIds.add(f.id);
baseFindings.push(...ff);
baseCanonical += countCanonicalUsages(base, p.dsExports, p.canonicalPkgs);
}
const baseAdopt = adoptionPct(baseCanonical, baseDrift);
const headAdopt = adoptionPct(canonicalUsages, findings.length);
const baseAdopt = adoptionPct(baseCanonical, countDriftedComponents(baseFindings));
const headAdopt = adoptionPct(canonicalUsages, driftedComponents);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply suppressions to base findings before computing base adoption.

headAdopt uses suppressed findings, but baseAdopt currently uses unsuppressed findings. With readBase + active suppress rules, adoptionDeltaPct can be materially incorrect even when no real drift changed.

Suggested patch
     const baseAdopt = adoptionPct(baseCanonical, countDriftedComponents(baseFindings));
+    const baseFindingsForAdoption = applySuppressions(baseFindings, p.suppress);
+    const baseAdopt = adoptionPct(baseCanonical, countDriftedComponents(baseFindingsForAdoption));
     const headAdopt = adoptionPct(canonicalUsages, driftedComponents);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/comment/analyze.ts` around lines 66 - 77, The baseAdopt calculation uses
unsuppressed findings from the baseFindings array, while headAdopt uses
suppressed findings, causing inconsistent adoption percentage calculations.
Apply the same suppression logic to baseFindings (similar to how suppressions
are applied to the head findings) before passing the suppressed findings to
countDriftedComponents when computing baseAdopt. This ensures that
adoptionDeltaPct is calculated consistently with the same suppression rules
applied to both base and head findings.

…omponent-level

# Conflicts:
#	dist/cli/index.js
#	dist/index.js
#	src/comment/analyze.ts
@jongjesse jongjesse merged commit 2da0a3a into main Jun 22, 2026
2 checks passed
@jongjesse jongjesse deleted the fix/adoption-metric-component-level branch June 22, 2026 10:04
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