Skip to main content
FeatureFactory
Docs Scoring methodology

Docs

Scoring methodology

Every merged PR gets three numbers. Here is exactly how they are computed.

Architecture overview

Every merged PR produces three numbers:

MetricRangeProduced by
quality_score0-100LLM (banded rubric)
effort_multiplier0.0-1.0LLM (fraction of real work vs. churn)
velocity_scorecalculatedDeterministic: base_velocity x effort_multiplier

The core design decision: the LLM never produces an effort number directly. LLMs are unreliable at absolute magnitude but good at classification. The size/effort base is computed deterministically from lines changed, and the LLM only answers two questions: "how much of this diff is real work vs. churn?" and "is it good?"

Step 1 - Exclusions

Before any counting, these files are stripped and do not contribute to any score:

  • -Test files: paths containing /tests/, /test/, /__tests__/, /__mocks__/; files ending in Test.php, .spec.ts, .test.ts, etc.
  • -Vendored/generated: paths containing /vendor/, /node_modules/, /site-packages/, /boto3/, etc.
  • -Dependency lockfiles: package-lock.json, composer.lock, yarn.lock, poetry.lock, symfony.lock.

These same exclusion rules apply to both velocity scoring and AI attribution share percentages, so "non-excluded lines" means the same thing in both contexts.

Step 2 - Non-code weighting (1/4)

After exclusions, remaining files are classified as non-code if they match any of these rules. Non-code lines count at 1/4 weight when computing effective line counts.

Rule typeMatches
Extensions.md, .txt, .env, .gitignore, .editorconfig, .yml, .yaml, .sh, .bash, .zsh
FilenamesMakefile, Dockerfile, docker-compose.yml, .dockerignore
Path segments.github/workflows/, .github/actions/, .claude/

Formula: effective_lines = code_lines + round(non_code_lines / 4)

Step 3 - Per-module grouping

Files are grouped by their top-level directory. Files at repository root go into a _root group. Each group is scored independently, and the group scores are summed.

Rationale: two developers sharing one branch score the same total as two separate PRs. The logarithmic compression applies per module, not globally, preventing one massive directory from drowning everything else.

Step 4 - Base velocity step table

Each group's effective line count maps to a base score through this step table. Above 3000 lines, scoring grows logarithmically - it does not hit a flat cap.

Effective lines (per module)Base score
0 lines0
1-10 lines3
11-30 lines5
31-100 lines10
101-300 lines20
301-700 lines30
701-1200 lines40
1201-2000 lines50
2001-3000 lines65
3000+ lines95 + round(75 * log2(lines / 3000))
Discrepancy note: The methodology document (pr-scoring-methodology.md Part 1) states a flat base of 95 for 3000+ lines. The production code scales logarithmically above 3000 using 95 + round(75 * log2(lines / 3000)), so 6000 lines yields 170. FeatureFactory ports the code behavior, not the doc.
No cap: Scores are not capped per module or globally. Each group score is floored at 0, and the final sum is also floored at 0. Two features that are documented modifiers - architecture/design +8 and churn-heavy -15 - appear in the methodology doc and commit-scoring LLM prompt but are not implemented in the deterministic code path and are therefore not applied here.

Step 5 - Per-group modifiers

ConditionModifier
3-5 files in a group+3
6-10 files in a group+5
>10 files in a group+8
Files span 2+ top-level directories+5 (cross-module, applied once)
Group touches a critical area+5

Critical area patterns

A group is flagged as touching a critical area if any filename (case-insensitive) contains one of these substrings:

/auth//permissions//billing//payment//invoice//pricing//migrations/schema/guards//middleware/

All patterns except "schema" are directory segments (i.e. /invoice/). A file named app/Models/Invoice.php does NOT match /invoice/, but app/Billing/Invoice.php matches /billing/. API-contract changes are not currently included in critical-area detection.

Effort multiplier (LLM)

An LLM classifies each file in the diff into a churn bucket, then estimates what fraction of total lines represent genuine engineering effort. The multiplier is a number from 0.0 to 1.0.

ValueMeaning
1.0All changes are meaningful code - new features, logic changes, bug fixes, real refactoring.
0.75Mostly real changes with some boilerplate, config, or trivial adjustments.
0.5Mixed - significant portions are generated code, copy-paste, or mechanical refactoring.
0.25Mostly formatting, whitespace, renaming, or auto-generated code with some real changes.
0.0Entirely formatting, whitespace, or no-op changes.

Large feature PRs that introduce substantial new functionality score high effort even when they include some boilerplate or config files. The multiplier only drops significantly when changes are predominantly auto-generated, copy-pasted, or mechanical.

For very large PRs (diff exceeding ~250K chars), the diff is chunked and each chunk analyzed independently. A second LLM pass reads all chunk summaries to emit a single unified effort_multiplier for the PR.

Final velocity: velocity_score = base_velocity x effort_multiplier

Quality score (LLM, 0-100)

Produced by the same LLM call as the effort multiplier. Uses banded anchors to keep scores comparable across PRs and reviewers.

90-100

Excellent

Correct, clear, maintainable. Tests present where appropriate. Safe error handling, minimal unintended coupling.

70-89

Solid

Good work with minor nits - small missing tests or docs, low-risk edge cases.

50-69

Mixed

Unclear intent, weak tests, likely edge cases, or maintainability concerns.

0-49

Risky

Incorrect or confusing changes, missing critical tests, or high regression risk.

Auditability

Every scored PR stores:

  • -Typed score columns (quality_score, velocity_score) for fast queries and dashboards.
  • -A full JSON blob of the LLM response - summaries, strengths, risks, review focus, confidence, and the raw file classifications.
  • -A breakdown of the base velocity computation: which files were excluded and why, which group each file landed in, the effective line count, the step-table base, and each modifier applied.

This means you can always answer "why did this PR score X?" with a specific, auditable trail - not a black box number.

Every score stays connected to the code evidence and attribution behind it.

Was this article helpful?

Your response enters the same customer insight queue used by product feedback.