# How to Prevent Score Changes Across Different React-Doctor Version Upgrades

> Prevent React-Doctor score changes during upgrades. Pin versions, use offline mode, and freeze configs for deterministic results. Ensure stable project scores.

- Repository: [Million Software, Inc./react-doctor](https://github.com/millionco/react-doctor)
- Tags: best-practices
- Published: 2026-05-12

---

**Pin your React-Doctor and OXlint versions, run scans in offline mode, and freeze configuration files to ensure deterministic project scores across version upgrades.**

React-Doctor is a diagnostic tool from millionco/react-doctor that calculates project health scores based on linting rules and configurable penalties. While the scoring algorithm itself is a pure function of diagnostics and constants, version upgrades can introduce score variations due to evolving rule sets, updated penalty values, or changes to the remote scoring API. Understanding how to lock these variables ensures that your scores remain stable and comparable over time.

## Why React-Doctor Scores Change Between Versions

React-Doctor computes scores through a four-stage pipeline that introduces multiple variability points. First, the **discovery** phase loads linters and configuration files. Second, **OXlint** (or ESLint) generates diagnostics based on the current rule set. Third, the **local scoring** algorithm in [`packages/react-doctor/src/utils/calculate-score-locally.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/calculate-score-locally.ts) calculates penalties using constants defined in [`packages/react-doctor/src/constants.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/constants.ts). Finally, an optional **API fallback** in [`packages/react-doctor/src/utils/try-score-from-api.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/try-score-from-api.ts) may override local calculations with server-side values.

Any upgrade can modify the OXlint rules, adjust penalty constants like `ERROR_RULE_PENALTY` (1.5) or `WARNING_RULE_PENALTY` (0.75), or change the remote API algorithm, resulting in different scores for identical codebases.

## Strategies to Prevent Score Variations

### Pin the Exact React-Doctor Version

Lock your dependency to a specific version in [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) to guarantee identical rule implementations and bundled OXlint versions across all environments.

```json
{
  "dependencies": {
    "react-doctor": "0.12.3"
  }
}

```

Avoid using caret (`^`) or tilde (`~`) ranges, as these allow automatic installation of newer versions that may contain updated rules or scoring logic.

### Run in Offline Mode

Pass the `--offline` flag to bypass the remote scoring API entirely. In offline mode, React-Doctor calls `calculateScoreLocally` directly, eliminating variability from network latency, API versioning, or server-side algorithm changes.

```typescript
import { scan } from "react-doctor";

const diagnostics = await scan({ cwd: process.cwd(), offline: true });

```

This ensures the score depends solely on the diagnostics generated by the bundled linter and the local penalty constants.

### Lock the OXlint Engine Version

OXlint is the underlying linter that produces diagnostics. Pin its version using `pnpm.overrides` in your root [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) to ensure the same rule set and penalty values are used even if upstream releases new rules.

```json
{
  "pnpm": {
    "overrides": {
      "oxlint": "1.63.0"
    }
  }
}

```

According to the millionco/react-doctor source code, this override ensures that [`packages/react-doctor/src/utils/calculate-score-locally.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/calculate-score-locally.ts) processes identical diagnostic types regardless of React-Doctor upgrades.

### Freeze Rule-Penalty Constants

The score formula multiplies distinct error and warning rule counts by constants defined in [`packages/react-doctor/src/constants.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/constants.ts). Keep these values unchanged to guarantee identical numeric outputs:

- `ERROR_RULE_PENALTY`: 1.5
- `WARNING_RULE_PENALTY`: 0.75

These constants directly influence the final score calculation at lines 41-44 of the constants file.

### Use a Stable Configuration File

The lint rules applied to your project are driven by configuration files such as [`oxlintrc.json`](https://github.com/millionco/react-doctor/blob/main/oxlintrc.json) and [`eslintrc.json`](https://github.com/millionco/react-doctor/blob/main/eslintrc.json). Store these files in version control and avoid automatic config migrations to prevent inadvertent rule additions or removals that would alter diagnostic counts.

### Cache Diagnostics for Reproducibility

Because the score only depends on the diagnostic list, you can cache the JSON report generated by [`packages/react-doctor/src/utils/build-json-report.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/build-json-report.ts). Re-using this cached report with the same React-Doctor version yields identical scores even if the codebase changes later.

```typescript
import { readFile } from "fs/promises";
import { calculateScoreLocally } from "react-doctor/src/utils/calculate-score-locally.js";

const report = JSON.parse(await readFile("./react-doctor-report.json", "utf8"));
const { score, label } = calculateScoreLocally(report.diagnostics);

```

### Avoid API-Driven Scoring

The remote API (`SCORE_API_URL` in [`packages/react-doctor/src/constants.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/constants.ts)) returns scores that may differ if the backend algorithm changes. Force local scoring by disabling the API call through the offline flag or by ensuring the `tryScoreFromApi` function in [`packages/react-doctor/src/utils/try-score-from-api.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/try-score-from-api.ts) falls back to local calculation.

## Summary

- **Lock versions**: Pin `react-doctor` and `oxlint` in [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) and `pnpm.overrides`.
- **Run offline**: Use `--offline` to bypass the remote API and use local scoring.
- **Freeze constants**: Maintain stable values for `ERROR_RULE_PENALTY` and `WARNING_RULE_PENALTY`.
- **Control configuration**: Version-control your [`oxlintrc.json`](https://github.com/millionco/react-doctor/blob/main/oxlintrc.json) files and disable automatic migrations.
- **Cache results**: Store diagnostic JSON reports to recompute identical scores later.

## Frequently Asked Questions

### Why does my React-Doctor score change after upgrading?

Scores change because version upgrades may include updated OXlint rules, modified penalty constants in [`packages/react-doctor/src/constants.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/constants.ts), or algorithm changes in the remote scoring API. Each of these affects the diagnostic count or the weighting applied to errors and warnings.

### How do I guarantee the same score across CI and local environments?

Pin the exact React-Doctor version in [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json), lock the OXlint version via `pnpm.overrides`, and run all scans with the `--offline` flag. This ensures both environments use identical rule sets and the local scoring algorithm from [`packages/react-doctor/src/utils/calculate-score-locally.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/calculate-score-locally.ts).

### Can I use the remote API and still get stable scores?

No. The remote API endpoint defined in `SCORE_API_URL` can return different scores if the server-side algorithm evolves. To prevent score changes across different react-doctor version upgrades, disable API calls and rely on the deterministic local scoring function.

### What constants determine the final score label?

The thresholds in [`packages/react-doctor/src/constants.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/constants.ts) map numeric scores to labels: `SCORE_GOOD_THRESHOLD` (75) qualifies as "Great", and `SCORE_OK_THRESHOLD` (50) qualifies as "Needs work". Scores below 50 are labeled "Critical". Freeze these thresholds in your documentation to ensure consistent interpretations across versions.