Metrics Model
Priority is round(100 × refactor_utility × action_probability), where
action_probability = detection_reliability × interpretation_reliability.
Coverage is reported independently and never discounts an already observed
issue. Utility uses non-negative weights summing to one; correlated metrics use
the strongest evidence within a factor rather than being added together.
Reforge separates measurement from interpretation. The scanner first collects raw directory, file, function, type, and churn metrics, then derives summaries, hotspots, and findings from that model. The model reports maintainability and refactoring signals; it is not a quality score, health score, bug detector, or defect probability model.
Raw Metrics
File metrics:
loc: total line count.imports: top-level import/use declarations for supported Tree-sitter languages.public_items: public or exported top-level items.is_test: whether the path looks like a test file.churn: git churn metrics when enabled.
Directory metrics:
source_files: number of direct source files. A directory is sampled once, so large directories do not receive extra percentile weight from containing more files.
Function metrics:
loc: function line span.complexity: estimated cyclomatic complexity.nesting_depth: maximum nested control-flow depth.parameter_count: parameter count.is_test: whether the function belongs to a test file.
Type metrics:
loc: type line span.member_count: fields, variants, methods, signatures, or equivalent member constructs.is_test: whether the type belongs to a test file.
Churn metrics:
commits_touchedlines_addedlines_deletedauthors_countrecent_weighted_churn
Percentiles
metrics_summary records p50, p75, p90, p95, and max for each metric
category. Percentiles help rank hotspots relative to the scanned project, not
against a universal standard.
Finding metrics use canonical dotted IDs such as file.loc and
function.complexity. A detector can emit only metrics declared by its
manifest entry. Finding metrics may include a percentile value when at least
five values are available for that metric. When both threshold excess and a
project percentile describe the same observation, intensity takes the
stronger lens rather than adding duplicate evidence.
Finding Priority
priority is a refactoring priority score from 0 through 100. It is not a
defect probability, quality grade, or health score.
Priority factors:
impact: how important the detector’s signal usually is.intensity: how far the strongest metric exceeds its threshold or normalized baseline.spread: how broadly related locations cross files.change_pressure: churn pressure from matching hotspots.actionability: how directly the signal suggests a refactoring action.detection_reliability: estimated probability that the evidence is correct.interpretation_reliability: conditional probability that the proposed action is suitable.
The weighted priority formula is:
((impact * 0.30)
+ (intensity * 0.30)
+ (spread * 0.15)
+ (change_pressure * 0.15)
+ (actionability * 0.10))
* detection reliability × interpretation reliability
Severity bands:
info: priority 0 through 34.warning: priority 35 through 69.critical: priority 70 through 100.
The bands are workflow labels for triage and CI policy. They do not claim that a file is defective or that a change is safe.
Constructs, Mechanisms, and Issue Clusters
Each finding declares one ISO/IEC 25010-aligned maintainability construct
and one source-observable mechanism. These classifications replace the old
metric-dimension label, which mixed measurements, symptoms, and quality
outcomes at different abstraction levels.
Correlated atomic findings remain available for filtering, baselines, and CI,
but issues combine evidence in the same family for the same normalized subject,
mechanism, and likely action. Human and HTML output present the cluster’s
highest-priority finding as the issue and retain member IDs for auditability.
See Metric Ontology for definitions and invariants.
Confidence
Threshold-based structural findings generally use confidence 1.0. Combined
readability risk uses confidence 0.90 because the measured evidence is
objective, but the readability interpretation is still a review prompt.
Heuristic detectors use lower values when false positives are more likely. For
example, repeated literals can be weaker in tests or report text, and
happy-path-only test risk is intentionally conservative.
Hotspots
Hotspots rank files, functions, and types independently from findings. They are
retained when priority >= 35.
static_risk and churn_risk are floating-point scores from 0 through 100.
Hotspot priority applies the selected model, rounds the result to an integer,
and clamps it to the same 0-100 range.
Static risk is the strongest applicable structural signal for the location, not a blend of every detector mechanism:
- File risk considers the file-LOC threshold, import threshold at 80% weight, public-item threshold at 80%, and file-LOC percentile at 35%.
- Function risk considers line and complexity thresholds, nesting at 85%, parameter count at 75%, and function-LOC percentile at 35%.
- Type risk considers line and member-count thresholds plus type-LOC percentile at 35%.
Threshold-based inputs use the same effective scan thresholds as findings after configuration and CLI overrides. Reforge takes the maximum weighted input and clamps it to 0-100.
Churn risk likewise takes the strongest of these project-percentile inputs:
commits_touchedrecent_weighted_churnauthors_countat 70% weight
Function and type churn is inherited from file churn only when the scoped item
has static_risk >= 35; otherwise its churn_risk is zero. File-level churn
pressure is capped for line-level findings unless there is an exact
function/type hotspot match.
Hotspot models:
static:priority = static_riskchurn:priority = churn_riskhybrid:priority = static_risk * 0.65 + churn_risk * 0.35
Hotspots are a review watchlist. They help identify places where static maintenance pressure and churn overlap, but they are not findings and should not be used as a hard CI gate by themselves.
Interpreting Empty Findings
findings=0 means no unsuppressed findings remain after scoring, filters, and
suppressions. It does not mean the project has no maintainability risk, no
hotspots, no raw metric outliers, or no bugs. Review raw_metrics,
metrics_summary, hotspots, and suppression summary context before treating
an empty finding list as a clean refactoring backlog.
Suppression summaries are audit context. They should explain how many findings were intentionally removed and why, so an empty finding list is not confused with an absence of measured signals.
Calibration
Calibrate thresholds and priority expectations with multiple real projects, not a single repository or synthetic fixture set.
- Pick a representative sample, such as a small library, a service, a frontend-heavy project, and a test-heavy project.
- Run stable reports with the same settings across the sample:
cargo run -- scan D:\path\to\project --churn off --hotspot-model static --output json --progress never
- Compare
metrics_summarypercentiles, top findings, and hotspots across projects. Look for detectors that are consistently noisy, consistently silent, or only useful for one project shape. - Review high-priority findings with maintainers who know the codebase. A calibrated model should surface plausible refactoring work, not force every mature project toward zero findings.
- Tune thresholds or detector filters only when the same pattern repeats
across the sample. Keep
priorityas an ordering signal, not an absolute quality score. - Validate the tuned settings on a holdout project before enabling a blocking
CI gate. Prefer a baseline gate such as
new-or-worseso unchanged legacy findings remain visible without blocking every change.
Churn Collection
When enabled, Reforge runs git with --no-merges, --numstat, and the
configured time window. Binary numstat rows, paths outside the scan root, and
commits above --churn-max-commit-lines are ignored.
--churn auto falls back gracefully when git history is unavailable.
--churn on fails the scan if churn cannot be collected. --churn off skips
git entirely.