Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

Reforge logo

Reforge

A source-tree scanner for maintainability signals, refactoring priorities, and codebase drift.

Reforge collects directory, file, function, type, dependency, and optional git churn metrics before deriving hotspots and findings. It is designed for local audits, CI gates, and evidence-led refactoring work. It is not a quality score, bug detector, or defect probability model.

Findings Threshold and detector signals that merit review.
Hotspots A ranked watchlist combining static pressure and churn.
Metrics Raw measurements and project percentile context.

Quick Start

Reforge requires Rust 1.85 or newer.

cargo build --release
cargo run -- scan .

Generate the same self-contained visual report published as this site’s sample:

cargo run -- scan . --output-file reforge-report.html --progress never

Documentation Map

Use Reforge

  • User Guide: install the CLI, run scans, choose output, and troubleshoot common failures.
  • Configuration: configure thresholds, exclusions, suppressions, churn, and precedence.
  • Report Schema: consume JSON/YAML schema 18 and SARIF 2.1.0.
  • HTML Report: build and use the self-contained visual report.

Understand Results

  • Metrics Model: interpret priority, confidence, severity, percentiles, and hotspot ranking.
  • Detector Reference: review every detector family and its thresholds or heuristics.
  • Calibration Samples: inspect the sample set used to sanity-check report volume.

Maintain Reforge

  • Architecture: follow the scan pipeline, module boundaries, and extension points.
  • Contributing: set up local development and validate changes.
  • Release: prepare and package a release.

Supported Languages

Tree-sitter structural and similar-function analysis covers Rust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby. Basic source-tree metrics also include supported C and C++ source extensions.

User Guide

This guide covers installing Reforge, running scans, choosing output formats, tuning thresholds, and troubleshooting common problems.

Reforge reports maintainability and refactoring signals. It does not produce a quality score, health score, bug probability, or proof that code is safe to change.

Installation

Reforge requires Rust 1.85 or newer.

Tagged releases publish platform archives on the GitHub Releases page. When a release is available, extract its archive, then move reforge or reforge.exe to a directory on PATH. The Windows archive is a ZIP; Linux and macOS archives are compressed tar files. Each archive also contains the README and license.

Build a debug binary from this checkout:

cargo build

Build an optimized binary:

cargo build --release

Install the CLI from this checkout:

cargo install --path .
reforge scan D:\path\to\project

During local development, the examples below use cargo run -- scan .... After installation, replace cargo run -- scan with reforge scan.

Agent Skill Installation

Reforge ships an optional agent skill in skills/reforge-scan. Install it when you want an agent to run Reforge, choose report formats, interpret findings, or turn scan output into scoped refactoring recommendations.

For Codex on Windows:

.\scripts\install-agent-skill.ps1

For Codex on macOS or Linux:

sh scripts/install-agent-skill.sh

To update an existing install, pass -Force or --force:

.\scripts\install-agent-skill.ps1 -Force
sh scripts/install-agent-skill.sh --force

For another agent that consumes the same skill folder shape, pass the directory that contains skill folders:

.\scripts\install-agent-skill.ps1 -Agent generic -SkillsDir D:\path\to\agent\skills -Force
sh scripts/install-agent-skill.sh --agent generic --skills-dir ~/.agent/skills --force

The install scripts copy only the skill folder. Add -InstallCli or --install-cli to also run cargo install --path ..

Quick Start

Scan the current repository:

cargo run -- scan .

Produce stable machine-readable output:

cargo run -- scan . --output json --progress never

Disable git churn when you want deterministic static-only output:

cargo run -- scan . --churn off --hotspot-model static --output json --progress never

Write a report to disk:

cargo run -- scan . --output-file reforge-report.json --progress never

When --output is omitted, --output-file extensions .html, .htm, .json, .yaml, .yml, and .sarif select HTML, JSON, YAML, or SARIF automatically. Other extensions default to human output. Missing parent directories in the output path are created automatically, so a path such as reports/current/reforge-report.html does not need to exist before the scan.

What Gets Scanned

The scanner accepts either a directory or a single file as [PATH]. It scans source files with these extensions:

  • Broad source-file discovery: c, cc, cpp, cs, go, java, js, jsx, kt, php, py, rb, rs, ts, and tsx.
  • Tree-sitter structural analysis: Rust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby.

By default, hidden files are skipped and common generated or dependency directories are skipped, including target, node_modules, dist, build, out, coverage, .next, .nuxt, .svelte-kit, and .vite. Git ignore rules are also applied by default, including .gitignore, .git/info/exclude, and global git ignore files.

Use --include-hidden to include hidden paths and --include-generated to include generated or dependency directories. Use --ignore-path <PATH> to add Reforge-specific ignored paths, and use --no-gitignore to scan paths ignored by git.

Test files and test directories such as tests, __tests__, spec, and *.test.ts are scanned by default. Use --exclude-tests when you want a production-source-only scan.

Use finding filters when you want the report and CI gate to consider only part of the final scored finding set:

cargo run -- scan . --only large_file,complex_function --min-priority 35
cargo run -- scan . --exclude-detector debt_marker --severity warning

--severity warning keeps warning and critical findings. --severity critical keeps only critical findings.

Output

Reforge supports human, html, json, yaml, and sarif output.

Human output is intended for terminal review:

cargo run -- scan . --output human --progress never

JSON and YAML are intended for CI, automation, and agent-to-agent handoff:

cargo run -- scan . --output json --progress never
cargo run -- scan . --output yaml --output-file reforge-report.yaml --progress never

SARIF output targets SARIF 2.1.0 for CI code-scanning integrations:

cargo run -- scan . --output sarif --output-file reforge-report.sarif --progress never

HTML output is a React-powered visual report for local review. It is still written as a single offline HTML artifact, so it can be opened directly in a browser without a server:

cargo run -- scan . --output html --output-file reforge-report.html --progress never

Use --progress never for stable stdout. Progress is written to stderr when enabled. Use --color always, --color never, or the default --color auto to control ANSI color in human output.

Reading Results

Human output is organized for quick terminal triage:

  • Result: total threshold signals, severity counts, hotspot watchlist size, suppression summary when present, and similar-function group count.
  • Scan details: source files, directories, and function candidates scanned.
  • Signal mix: finding counts by detector kind, shown when findings exist.
  • Findings: actionable threshold signals sorted by priority.
  • Watchlist: hotspot locations ranked by static risk, churn risk, or both.

HTML output renders the same report through the React + TypeScript report app as summary cards, a severity distribution bar, construct/mechanism metadata, the File Overview, hotspot watchlist, similar-function groups, and prioritized findings. When --output is omitted, .html and .htm output-file extensions select the same HTML report format automatically.

Reports contain four main data layers plus suppression audit context:

  • raw_metrics: directory, file, function, type, and churn measurements.
  • metrics_summary: percentile summaries for the scanned project.
  • hotspots: file, function, and type locations ranked by static risk, churn risk, or both.
  • suppression_summary: counts of findings removed by suppressions.
  • issues: compatible atomic evidence grouped into refactoring issues.
  • detector_manifest: detector coverage, classification, and overlap metadata.
  • findings: actionable refactoring signals derived from thresholds and detectors.

Severity comes from priority: info is below 35, warning is 35 through 69, and critical is 70 or above. Priority is a refactoring priority signal, not a claim that the code is defective.

findings=0 means no findings remain after scoring, filters, and suppressions. It does not prove code quality, rule out bugs, or mean the hotspot watchlist and raw metrics are empty. When suppressions are used, keep the suppression summary visible so reviewers can distinguish zero unsuppressed findings from zero observed signals.

Every finding in JSON and YAML has a stable evidence id with an rf3- prefix. The ID is derived from the finding kind, primary location, related locations, and metric names so it can be used for baseline comparison.

Filtering and suppression happen after scoring, so priority, severity, and stable IDs are calculated the same way whether or not a finding appears in the final report.

unused_function findings are conservative dead-code prompts. Reforge reports private named free functions only when no same-name reference appears outside the function body. Public/exported functions, methods, and common entry-point names are skipped.

Churn and Hotspots

The default --churn auto mode collects git churn when the scan root is inside a git repository. Outside git history, auto records the reason and continues without churn. Use --churn on when git churn is required and the scan should fail if it is unavailable. Use --churn off to skip git entirely.

Hotspot models:

  • --hotspot-model static: rank by the strongest 0-100 structural risk for each location. File risk considers lines, imports, public items, and file-LOC percentile; function risk considers lines, complexity, nesting, parameters, and function-LOC percentile; type risk considers lines, members, and type-LOC percentile.
  • --hotspot-model churn: rank by the strongest 0-100 project-percentile signal from commits touched and recent weighted churn, with author-count percentile weighted at 70%. Function and type locations inherit file churn only when their static risk is at least 35.
  • --hotspot-model hybrid: default ranking, combining static_risk * 0.65 and churn_risk * 0.35, then rounding priority to an integer.

Tune churn collection with --churn-window-days and --churn-max-commit-lines. Commits above the max added+deleted line count are ignored so large mechanical changes do not dominate results.

Hotspots are a watchlist, not findings. Use them to choose where to inspect or plan refactoring work; do not treat hotspot presence alone as a hard CI gate.

CI Gates and Baselines

Use --fail-on info|warning|critical to make a scan exit nonzero when selected findings meet or exceed that severity. Reforge writes the requested report before returning the failing exit status.

CI gates evaluate selected findings only. They do not fail on raw metrics, metric summaries, or hotspot watchlist entries by themselves. Suppressed findings are excluded from gate selection, so suppression summary context matters when a blocking gate reports zero findings.

Without --baseline, all current findings are selected:

cargo run -- scan . --output json --progress never --fail-on warning

With --baseline <PATH>, Reforge reads a prior schema 18 JSON or YAML report and matches findings by stable id. Older reports without IDs are rejected; regenerate the baseline with the current Reforge.

--baseline-mode controls the selected findings:

  • new: IDs absent from the baseline.
  • new-or-worse: new findings plus findings whose priority or severity increased. This is the default.
  • all: all current findings.
cargo run -- scan . --baseline baseline.json --baseline-mode new-or-worse --fail-on warning --output json --progress never

Human reports include baseline diff counts when --baseline is supplied: new, worse, same, and resolved. Use --show new|new-or-worse|all to choose which current findings appear in the human Findings section. The default is all, so existing report output is unchanged unless the display filter is selected.

cargo run -- scan . --baseline baseline.json --show new-or-worse --output human --progress never

Before making a gate blocking, calibrate it on several real projects. Run the same JSON settings across representative repositories, compare high-priority findings with maintainers’ refactoring backlog, tune thresholds only for repeatable noise or blind spots, and validate the settings on a holdout project. Prefer --baseline-mode new-or-worse for pull request gates so unchanged legacy findings stay visible without blocking every change.

CLI Reference

Usage:

reforge init [PATH] [--force]
reforge config validate [PATH] [--config CONFIG]
reforge config show [PATH] [--config CONFIG] [--output human|json|yaml]
reforge scan [OPTIONS] [PATH]

init writes a default reforge.toml. config validate and config show parse discovered or explicit config without scanning source files or reading git churn.

OptionDefaultPurpose
--presetbalancedUse strict, balanced, or relaxed threshold defaults before per-threshold overrides.
--max-file-lines800Report files above this total line count.
--max-dir-files40Report directories above this direct source-file count.
--include-hiddenfalseInclude hidden files and directories.
--include-generatedfalseInclude dependency and generated output directories.
--no-gitignorefalseDo not apply git ignore rules during scanning.
--exclude-testsfalseExclude test files and test directories from scanning.
--ignore-pathnoneAdditional path to skip; can be repeated.
--onlynoneReport only these finding kinds, as kind[,kind...].
--exclude-detectornoneExclude these finding kinds, as kind[,kind...].
--min-prioritynoneReport findings whose final priority is at least this 0-100 value.
--severitynoneReport findings at or above info, warning, or critical.
--min-similar-functions3Report similar-function groups at or above this size.
--min-function-tokens80Ignore smaller normalized function bodies.
--function-similarity0.85Minimum normalized token similarity for grouping.
--include-test-similarityfalseInclude tests in similar-function analysis.
--max-function-lines80Report functions above this line span.
--max-function-complexity15Report functions above this estimated complexity.
--max-nesting-depth4Report functions above this nested control-flow depth.
--max-function-parameters5Report functions with more parameters than this threshold.
--max-type-lines250Report types above this line span.
--max-type-members30Report types above this member count.
--max-imports35Report files with more imports than this threshold.
--max-public-items30Report files with more public/exported items than this threshold.
--max-functions-per-file40Report over-splitting risk only when this function count and density signals are exceeded.
--max-functions-per-100-lines12Report over-splitting risk only when function density also exceeds this threshold.
--max-small-function-ratio70Report over-splitting risk only when this percentage of functions are small and simple.
--min-repeated-literal-occurrences12Report repeated literals seen at least this many times.
--min-data-clump-occurrences4Report repeated parameter groups seen at least this many times.
--include-test-structurefalseInclude tests in general structural checks.
--configdiscoveredRead a specific configuration file.
--baselinenoneRead a prior schema 18 JSON/YAML report for gate comparison.
--baseline-modenew-or-worseGate on new, new-or-worse, or all findings when a baseline is present.
--showallDisplay new, new-or-worse, or all current findings in human baseline reports.
--fail-onnoneExit nonzero when selected findings meet info, warning, or critical.
--churnautoUse auto, on, or off for git churn metrics.
--hotspot-modelhybridUse static, churn, or hybrid hotspot ranking.
--churn-window-days180Days of git history to include.
--churn-max-commit-lines2000Skip commits above this added+deleted line count.
--outputinferredUse human, html, json, yaml, or sarif.
--output-filestdoutWrite the report to a file.
--progressautoUse auto, always, or never for progress output.
--colorautoUse auto, always, or never for human-output color.
--helpnonePrint generated help.

Examples

Scan another project with stricter size thresholds:

cargo run -- scan D:\path\to\project --max-file-lines 600 --max-function-lines 60

Use a built-in threshold preset:

cargo run -- scan . --preset strict

Tune similar-function detection:

cargo run -- scan . --min-similar-functions 4 --min-function-tokens 60 --function-similarity 0.90

Include tests in duplication or structural analysis:

cargo run -- scan . --include-test-similarity
cargo run -- scan . --include-test-structure

Exclude tests entirely:

cargo run -- scan . --exclude-tests

Use a specific configuration file:

cargo run -- scan . --config reforge.toml --output json --progress never

Suppress a known intentional finding inline:

#![allow(unused)]
fn main() {
// TODO: generated migration marker reforge:ignore debt_marker tracked in issue 123
// reforge:ignore-next-line large_file generated fixture snapshot
}

Troubleshooting

failed to resolve path: confirm [PATH] exists. Put options before or after the path normally, but do not pass -- --help after scan; use cargo run -- scan --help.

scan root is not inside a git repository: use --churn off, or keep --churn auto if churn is optional. Use --churn on only when git history is required.

Unexpected generated files in results: check whether --include-generated was used and whether ignore-paths in reforge.toml should include local output directories. If a path is ignored by git and you still want to scan it, add --no-gitignore.

No similar functions found: lower --min-function-tokens, lower --function-similarity, or add --include-test-similarity if test code is in scope.

JSON output is mixed with progress text: add --progress never. Progress is intended for terminals, not machine parsing.

Configuration

Reforge can read scan defaults from reforge.toml. Command-line values take precedence over configuration values.

Discovery

When --config is not provided, Reforge looks for reforge.toml starting at the scan root and walking upward through parent directories. If the scan root is a file, discovery starts from that file’s parent directory.

Use --config <CONFIG> to read a specific file:

cargo run -- scan . --config D:\path\to\reforge.toml

Commands

Create a default config in the current directory:

cargo run -- init

Pass a directory to write <PATH>\reforge.toml, or pass a path ending in .toml to write that exact file. Existing files are preserved unless --force is supplied.

cargo run -- init D:\path\to\project
cargo run -- init D:\path\to\project\reforge.toml --force

Validate a discovered or explicit config without scanning:

cargo run -- config validate D:\path\to\project
cargo run -- config validate . --config D:\path\to\reforge.toml

Show effective scan defaults after applying discovered or explicit config:

cargo run -- config show . --output human
cargo run -- config show . --output json
cargo run -- config show . --output yaml

config validate and config show parse reforge.toml but do not collect source files, run detectors, or read git churn. The effective thresholds shown by config show are used by both threshold findings and static hotspot risk.

Precedence

Reforge applies presets and configuration as defaults. Threshold precedence is: CLI per-threshold values, CLI --preset, reforge.toml per-threshold values, reforge.toml preset, then the built-in balanced preset. A per-threshold override is detected when its value differs from the built-in balanced default, so a generated config can switch presets without deleting every balanced threshold entry.

Boolean flags such as --include-hidden, --include-generated, --no-gitignore, --exclude-tests, --include-test-similarity, and --include-test-structure are CLI-only today. They are not read from reforge.toml.

CI workflow flags such as --baseline, --baseline-mode, --show, --fail-on, --output, --output-file, --progress, and --color are also CLI-only.

Finding filters such as --only, --exclude-detector, --min-priority, and --severity are CLI-only. Long-lived suppressions can be recorded in reforge.toml.

Example

This example shows a tuned project configuration, not the built-in defaults.

preset = "strict"
max-file-lines = 600
max-dir-files = 35
max-function-lines = 60
max-function-complexity = 12
max-nesting-depth = 3
max-function-parameters = 4
max-type-lines = 200
max-type-members = 25
max-imports = 25
max-public-items = 20
max-functions-per-file = 40
max-functions-per-100-lines = 12
max-small-function-ratio = 70

min-similar-functions = 3
min-function-tokens = 70
function-similarity = 0.9
min-repeated-literal-occurrences = 5
min-data-clump-occurrences = 4

churn = "auto"
hotspot-model = "hybrid"
churn-window-days = 180
churn-max-commit-lines = 2000

ignore-paths = [
  "vendor",
  "generated/snapshots",
]

[[suppressions]]
kind = "large_file"
path = "src/generated.rs"
line = 1
reason = "generated fixture checked by snapshot tests"

[[suppressions]]
path = "src/legacy/generated.rs"
reason = "legacy migration tracked separately"

Supported Keys

KeyDefaultEquivalent CLI option
presetbalanced--preset
max-file-lines800--max-file-lines
max-dir-files40--max-dir-files
min-similar-functions3--min-similar-functions
min-function-tokens80--min-function-tokens
function-similarity0.85--function-similarity
max-function-lines80--max-function-lines
max-function-complexity15--max-function-complexity
max-nesting-depth4--max-nesting-depth
max-function-parameters5--max-function-parameters
max-type-lines250--max-type-lines
max-type-members30--max-type-members
max-imports35--max-imports
max-public-items30--max-public-items
max-functions-per-file40--max-functions-per-file
max-functions-per-100-lines12--max-functions-per-100-lines
max-small-function-ratio70--max-small-function-ratio
min-repeated-literal-occurrences12--min-repeated-literal-occurrences
min-data-clump-occurrences4--min-data-clump-occurrences
churnauto--churn
hotspot-modelhybrid--hotspot-model
churn-window-days180--churn-window-days
churn-max-commit-lines2000--churn-max-commit-lines
ignore-paths[]--ignore-path
suppressions[]none

preset accepts strict, balanced, or relaxed. churn accepts auto, on, or off. hotspot-model accepts static, churn, or hybrid.

Ignored Paths

ignore-paths entries are relative to the scan root. Both \ and / are normalized to /, and leading or trailing slashes are ignored. An ignored entry matches the path itself and any descendant path.

Example:

ignore-paths = ["vendor", "src/generated"]

This skips vendor, vendor/foo.rs, src/generated, and src/generated/schema.ts.

The built-in generated/dependency exclusions still apply unless --include-generated is passed.

Reforge also applies .gitignore, .git/info/exclude, and global git ignore rules by default. Use --no-gitignore when you intentionally want to scan paths ignored by git. --include-generated only disables Reforge’s built-in generated/dependency directory list; it does not override git ignore rules.

Suppressions

Use suppressions for intentional findings that should be absent from reports and CI gates. Suppressions remove matching entries from findings; they do not remove hotspot watchlist entries, because hotspots are ranked from raw metrics. Suppression summary context should stay visible in reviews so a report with zero findings is read as zero unsuppressed findings, not as proof that no maintainability signals were measured. Config suppressions use TOML tables:

[[suppressions]]
kind = "large_file"
path = "src/generated.rs"
line = 1
reason = "generated fixture"

path is required and is matched relative to the scan root. Both \ and / separators are accepted. kind is optional; when omitted, every finding kind on that path can match. line is optional; when omitted, the whole path can match. reason is required and must be non-empty.

Inline comments can suppress findings near the source:

  • reforge:ignore [kind[,kind...]] reason suppresses same-line findings.
  • reforge:ignore-next-line [kind[,kind...]] reason suppresses next-line findings.
  • reforge:ignore-file [kind[,kind...]] reason suppresses matching findings anywhere in that file.

When no kind list is provided, an inline suppression matches every finding kind in its scope. Unknown kinds in CLI filters, config suppressions, or kind-like inline suppression tokens fail the scan with a clear error.

Report Schema

Schema 18 separates atomic evidence (findings) from decision units (issues). Each finding exposes detection_reliability and interpretation_reliability; the manifest declares issue_family, evidence_role, and constituent_kinds. coverage_manifest declares the supported mechanism and entity-scope matrix, while coverage_summary records observed languages, analyzed entities, parse failures, and unobservable reasons for this run.

JSON and YAML reports use schema version 18. Version 17 reports and baselines are rejected and must be regenerated. The same Rust data model is serialized for both formats. SARIF output is a separate SARIF 2.1.0 document that carries the same finding IDs in result fingerprints.

Top-Level Shape

{
  "schema_version": 18,
  "summary": {},
  "stats": {},
  "metrics_summary": {},
  "raw_metrics": {},
  "raw_metric_manifest": [],
  "dependency_graph": {},
  "hotspots": [],
  "suppression_summary": {},
  "coverage_manifest": [],
  "coverage_summary": {},
  "issues": [],
  "detector_manifest": [],
  "findings": []
}

Top-level fields:

  • schema_version: report schema version. Current value is 18.
  • summary: scan totals, duration, hotspot model, and churn status.
  • stats: source files, directories, and function candidates counted.
  • metrics_summary: percentile distributions for raw metrics.
  • raw_metrics: directory, file, function, type, and churn measurements.
  • raw_metric_manifest: scale, unit, scope, direction, and meaning of every raw metric family.
  • dependency_graph: resolved source-file dependency graph snapshot.
  • hotspots: ranked file, function, and type locations.
  • suppression_summary: aggregate counts for findings removed by suppressions.
  • issues: compatible atomic evidence grouped into stable human-facing refactoring issues.
  • detector_manifest: coverage and classification metadata for every finding kind.
  • findings: detector findings with priority, confidence, metrics, and related locations.

Reports contain maintainability and refactoring signals. They are not a quality score, health score, bug detector, or defect probability model. findings is the post-scoring, post-filter, post-suppression list; an empty list does not mean raw metrics, hotspots, or suppressed signals were absent.

summary

Fields:

  • scanned_files: number of source files scanned.
  • finding_count: number of findings emitted after filters and suppressions.
  • issue_count: findings after clustered secondary facets are counted once.
  • hotspot_count: number of hotspots emitted for the watchlist.
  • similar_function_group_count: number of similar-function findings.
  • duration_ms: scan duration in milliseconds.
  • hotspot_model: static, churn, or hybrid.
  • churn: churn collection details.

summary.churn fields:

  • mode: requested churn mode, one of auto, on, or off.
  • enabled: whether churn metrics were collected.
  • status: enabled, disabled, or unavailable.
  • reason: optional human-readable reason when churn is disabled or unavailable.
  • window_days: configured git history window.
  • max_commit_lines: configured max added+deleted lines per commit.

stats

Fields:

  • source_files_scanned: source files scanned.
  • directories_scanned: directories visited.
  • function_candidates: function bodies considered for similarity analysis.

metrics_summary

metrics_summary contains maps for directories, files, functions, types, and churn. Each metric has:

  • p50
  • p75
  • p90
  • p95
  • max

Directory metrics include source_files. Each directory contributes exactly one observation, independent of the number of files it contains.

File metrics include loc, imports, and public_items.

Function metrics include loc, complexity, nesting_depth, and parameter_count.

Type metrics include loc and member_count.

Churn metrics include commits_touched, lines_added, lines_deleted, authors_count, and recent_weighted_churn.

raw_metrics

raw_metrics.files entries:

  • path
  • loc
  • imports
  • public_items
  • is_test
  • churn

raw_metrics.directories entries:

  • path
  • source_files

raw_metrics.files[].churn entries:

  • commits_touched
  • lines_added
  • lines_deleted
  • authors_count
  • recent_weighted_churn

raw_metrics.functions entries:

  • path
  • name
  • line
  • loc
  • complexity
  • nesting_depth
  • parameter_count
  • is_test

raw_metrics.types entries:

  • path
  • name
  • line
  • loc
  • member_count
  • is_test

dependency_graph

dependency_graph records the resolved source-file import graph used by the dependency-cycle and dependency-hub detectors. External packages and unresolved imports are not included.

dependency_graph.nodes entries:

  • path: source file path.
  • fan_in: number of resolved files that import or include this file.
  • fan_out: number of resolved files imported or included by this file.

dependency_graph.edges entries:

  • from: importing or including source file.
  • to: resolved imported or included source file.

hotspots

Hotspot fields:

  • level: file, function, or type.
  • path: location path.
  • line: source line for function/type hotspots, otherwise null.
  • name: function/type name, otherwise null.
  • priority: 0 through 100 ranking score.
  • severity: info, warning, or critical.
  • static_risk: floating-point structural risk score from 0 through 100.
  • churn_risk: floating-point git-churn risk score from 0 through 100.
  • reason: short explanation of the ranking model and dominant risk.

The selected hotspot model converts those components into integer priority from 0 through 100. Hotspots are retained when priority >= 35 and sorted by priority descending. They are watchlist entries, not detector findings, and should not be treated as hard CI gate failures by themselves.

findings

Finding fields:

  • kind: detector-specific finding kind.
  • id: stable evidence identifier in the form rf3-<hex>.
  • severity: info, warning, or critical.
  • path: primary path.
  • line: primary line or null.
  • metrics: finding-specific measurements.
  • construct: primary ISO/IEC 25010-aligned maintainability construct.
  • mechanism: primary source-observable maintenance mechanism.
  • issue_id: owning issue ID or null.
  • priority: 0 through 100 refactoring priority.
  • detection_reliability: detector reliability from 0.0 through 1.0.
  • interpretation_reliability: interpretation reliability from 0.0 through 1.0.
  • priority_factors: scoring inputs.
  • rank_explanation: short ranking explanation.
  • message: human-readable summary.
  • recommendation: concise refactoring hint computed from kind.
  • related_locations: additional locations for grouped findings.

metrics entries contain:

  • name
  • value
  • threshold
  • unit
  • excess_ratio
  • normalized
  • percentile

construct is one of modularity, reusability, analysability, modifiability, or testability. mechanism is defined in the metric ontology.

priority_factors contains:

  • impact
  • intensity
  • spread
  • change_pressure
  • actionability
  • detection_reliability
  • interpretation_reliability

related_locations entries contain:

  • path
  • line
  • name

Very large similar_functions groups serialize at most 50 related locations to keep reports bounded.

Finding IDs are deterministic for the same evidence identity. The rf3- ID uses the finding kind, metric names, and the normalized, sorted, deduplicated set of primary and related path/line locations. It intentionally does not include the representative location choice, related-location order, names, message text, or metric values. Baseline comparison therefore recognizes the same evidence group when detector traversal order or ranking changes.

issues

Issues contain id, construct, mechanism, action, path, line, primary_finding_id, finding_ids, kinds, priority, and severity. Finding id values are stable EvidenceId values (rf3-...). Issue id values are stable IssueKey values (ri3-...) derived only from the issue family and canonical subject, not from evidence membership or input order. Every compatible atomic evidence group emits an issue. The primary member is the highest-priority finding; member findings remain in findings for baselines and detector-specific filtering.

detector_manifest

Each entry contains kind, construct, mechanism, action, entity_scope, approach, supported_languages, precision_risk, typed input_metrics, issue_family, evidence_role, constituent_kinds, default_detection_reliability, default_interpretation_reliability, impact, and actionability. Consumers can distinguish unsupported analysis from an observed absence of findings.

raw_metric_manifest

Each entry contains a stable dotted name, entity_scope, unit, scale, direction, and description. higher_is_more_pressure means larger values may contribute to hotspot or finding intensity; context_only metrics remain observable but do not independently vote for maintenance pressure. A metric definition describes an observation, not a universal threshold or quality grade.

findings=0 means no unsuppressed findings were emitted. Consumers should avoid presenting that as proof that the scanned code is healthy or bug-free.

suppression_summary

Fields:

  • suppressed_count: number of findings removed by suppressions.
  • suppressed_by_kind: map of finding kind to suppressed count.
  • suppressed_by_severity: map of severity to suppressed count.
  • highest_suppressed_priority: highest suppressed finding priority, or null when no findings were suppressed.

Suppressions remove matching entries from findings before report emission and CI gate selection. The suppression summary is report context, not a finding: its purpose is to show that findings were intentionally removed and whether an empty finding list means zero unsuppressed findings rather than zero observed signals.

Schema version 13 does not serialize suppressed finding bodies in findings. Consumers should render suppression_summary near summary.finding_count and avoid counting suppressed findings as gate failures.

SARIF Output

--output sarif and .sarif output files emit SARIF version 2.1.0. The SARIF log contains one run with Reforge as the tool driver. Rules are keyed by finding kind, and each result contains:

  • ruleId: finding kind.
  • ruleIndex: index into the run’s rule table.
  • level: error for critical, warning for warning, and note for info.
  • message.text: finding message.
  • locations[].physicalLocation: primary path and line.
  • relatedLocations: related finding locations when present.
  • partialFingerprints.reforgeFindingId: stable finding id.
  • properties.id: stable finding id.
  • properties.recommendation: concise refactoring hint.

Finding Kinds

Current kind values:

  • large_file
  • large_directory
  • debt_marker
  • similar_functions
  • long_function
  • complex_function
  • deep_nesting
  • many_parameters
  • readability_risk
  • large_type
  • large_public_surface
  • import_heavy_file
  • function_proliferation
  • unused_function
  • repeated_literal
  • repeated_error_pattern
  • test_duplication
  • happy_path_only_tests
  • file_naming_drift
  • directory_drift
  • data_clump
  • parallel_implementation
  • shadowed_abstraction
  • duplicate_type_shape
  • config_key_drift
  • fixture_factory_drift
  • generic_bucket_drift
  • adapter_boundary_bypass
  • stale_compatibility_path
  • missing_documentation_set
  • missing_user_guide
  • missing_report_schema_docs
  • missing_metrics_model_docs
  • missing_architecture_docs
  • stale_cli_documentation
  • stale_schema_documentation
  • dependency_cycle
  • dependency_hub

Compatibility Notes

Consumers should check schema_version before assuming field shape. Schema version 18 formalizes rf3- EvidenceIds and ri3- IssueKeys over canonical subjects. Issue identity is independent of alternative evidence membership and input ordering. Schema version 16 gives every finding metric a canonical dotted ID, adds directory raw metrics and percentile summaries, removes repeated parent-directory counts from file raw metrics and file hotspots, and exposes detector metric and ranking-policy inputs in detector_manifest. Schema version 14 adds finding constructs and mechanisms, issue clusters, detector manifests, and summary.issue_count; it removes metric dimension. Schema version 13 does not emit the legacy v4 fields score, score_breakdown, or rank_reason; use priority, priority_factors, and rank_explanation instead. Schema version 13 includes stable finding id, per-finding recommendation, the dependency_graph snapshot, and suppression_summary. Schema version 12 included stable finding IDs, recommendations, and dependency_graph, but did not include suppression summary context. Schema version 11 included stable finding IDs and recommendations, but did not include dependency_graph. Reports without IDs should be regenerated before being used as baselines.

New finding kinds may be added in future schema versions. Consumers should handle unknown kind values gracefully when possible.

HTML Report App

Reforge’s HTML report is implemented by the React + TypeScript report app. The CLI still emits one self-contained offline .html file, with the interactive UI provided by the frontend bundle.

User Flow

Generate an HTML report explicitly:

cargo run -- scan . --output html --output-file reforge-report.html --progress never

Or let the output file extension select HTML:

cargo run -- scan . --output-file reforge-report.html --progress never

The resulting file contains the scan data, HTML shell, CSS, and JavaScript app bundle. It can be opened directly in a browser without a local server and without network access. Reforge creates missing parent directories in the output path before writing the report.

Architecture

The report path is:

  1. Rust scanner collects source metrics and findings.
  2. Scanner assembles a schema 18 ScanReport.
  3. HTML output serializes the ScanReport as JSON.
  4. Reforge writes an HTML shell containing that JSON payload.
  5. The shell inlines the compiled React bundle and stylesheet.
  6. React renders the visualization from the embedded report data.

The frontend must treat ScanReport as its data contract. When fields are added, removed, or renamed, update docs/report-schema.md and the report app together.

Source and Build Flow

Frontend source lives in web/report-app. Use that package for React components, TypeScript types, styling, and visualization behavior.

Frontend development requires Node.js ^20.19.0 or >=22.12.0 and npm; CI uses Node.js 22. The project uses its pinned Vite 8 dependency from package-lock.json; install it with npm ci and run it through the package scripts rather than relying on a global Vite installation.

Build the app after changing report UI code:

cd web\report-app
npm ci
npm run build

The build is expected to refresh these checked-in assets:

  • assets/report-app.js
  • assets/report-app.css

Rust embeds those generated assets into the single-file HTML report. Commit the frontend source changes and the regenerated assets together so --output html uses the current React app. These two bundles are the repository’s intentional exception to the rule against committing generated build output.

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_touched
  • lines_added
  • lines_deleted
  • authors_count
  • recent_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_touched
  • recent_weighted_churn
  • authors_count at 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_risk
  • churn: priority = churn_risk
  • hybrid: 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.

  1. Pick a representative sample, such as a small library, a service, a frontend-heavy project, and a test-heavy project.
  2. 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
  1. Compare metrics_summary percentiles, top findings, and hotspots across projects. Look for detectors that are consistently noisy, consistently silent, or only useful for one project shape.
  2. 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.
  3. Tune thresholds or detector filters only when the same pattern repeats across the sample. Keep priority as an ordering signal, not an absolute quality score.
  4. Validate the tuned settings on a holdout project before enabling a blocking CI gate. Prefer a baseline gate such as new-or-worse so 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.

Metric Ontology

Reforge separates the quality property being discussed from the mechanism observed in source code. This prevents convenient measurements such as line count from being treated as maintainability itself and prevents correlated signals from receiving multiple votes merely because several detectors can describe them.

The ontology is scoped to maintainability signals that Reforge can observe statically. It is not a claim that source analysis completely measures maintainability.

Quality Constructs

Findings use one primary maintainability construct, aligned with ISO/IEC 25010:

ConstructQuestion answered
modularityWill a change to one component propagate into others?
reusabilityIs a concept represented once in a reusable form?
analysabilityCan a maintainer locate, understand, and diagnose the change point?
modifiabilityCan the code be changed locally without unnecessary work or regression risk?
testabilityCan maintainers establish and execute effective verification criteria?

Every finding has exactly one primary construct. A detector may provide evidence relevant to other constructs, but those secondary interpretations do not increase its score.

Signal Mechanisms

Mechanisms describe how the observed evidence may create maintenance pressure:

MechanismRepresentative evidenceCommon confounders
cognitive_loadfunction length, branching, nesting, parametersgenerated dispatch, parsers
dependency_propagationfan-in/out, cycles, imports, boundary bypassesincomplete module resolution
responsibility_dispersionoversized files/types/directories, mixed conceptsgenerated registries, declarative tables
duplication_divergencesimilar implementations, repeated shapes or setupintentional protocol symmetry
change_pressuredebt markers, compatibility paths, churnmigrations with explicit exit plans
verification_difficultymissing negative or boundary evidencetests stored outside the scan root
knowledge_driftnaming inconsistency, missing or stale documentationproject-specific conventions

Mechanisms are mutually exclusive as primary classifications. They are not assumed to be statistically independent. Correlated raw measurements remain separate evidence facets and are not added together automatically.

Goal and Measurement Layers

Reforge’s measurement goal is to prioritize reviewable, behavior-preserving refactoring opportunities from source trees and optional repository history. The model follows a goal-question-metric direction: measurements are admitted only when they answer a declared maintenance question and support a review or refactoring decision.

Reforge uses five layers:

  1. Raw metrics record observations such as LOC, complexity, fan-out, and churn.
  2. Findings interpret observations through one detector.
  3. Constructs and mechanisms explain the maintenance capability and causal pressure represented by the evidence.
  4. Issue clusters join related findings that describe the same entity and typed refactoring action.
  5. Priority ranks the resulting evidence for review; it is not a quality score or defect probability.

For example, a function that exceeds complexity and nesting thresholds retains both atomic findings for filtering, baselines, and auditability. The report also emits one cognitive_load issue cluster and human-facing output displays the highest-priority member as the primary issue.

Detector Manifest Contract

Every finding kind has an entry in the report-level detector_manifest with:

  • its primary construct and mechanism;
  • its typed refactoring action and entity_scope;
  • detection approach;
  • supported languages or repository scope;
  • qualitative precision_risk;
  • canonical input_metrics, dual reliability, impact, and actionability policy values;
  • issue_family, evidence_role, and constituent_kinds for composition.

Adding a detector requires adding its manifest entry. Tests enforce one entry per finding kind. Unsupported languages mean “not observed,” not “no issue.”

Orthogonality Rules

  • One finding has one primary construct and one primary mechanism.
  • Human-facing uniqueness is defined by refactoring action and evidence identity, not by requiring raw metrics to be statistically independent.
  • One raw observation may appear as evidence in multiple atomic findings, but issue clustering prevents it from becoming multiple human-facing issues.
  • Parent and child findings remain traceable; they are not summed.
  • Detector confidence represents interpretation uncertainty. It does not compensate for missing language coverage.
  • Threshold and percentile evidence are normalized within a finding by taking the strongest facet rather than summing correlated facets.
  • A detector cannot emit a metric outside its manifest-declared input set.
  • Metric IDs are canonical and entity-qualified, so aliases cannot make one observation appear to be independent evidence.
  • Clusters use complete-link compatibility: every member must be related to every other member. A chain of shared files cannot merge unrelated endpoint findings.
  • Findings carry stable EvidenceIds. Clustering sorts by EvidenceId before grouping, and each IssueKey is derived from the sorted member EvidenceIds, so detector emission order cannot change cluster membership or identity.

Raw Metric Contract

The report-level raw_metric_manifest defines the canonical metric ID, entity scope, unit, scale, direction, and meaning of every raw metric family. Directory observations are stored once per directory instead of being repeated on every contained file. Boolean context fields are not treated as numeric pressure. Counts remain ratio-scale observations, but their thresholds are contextual policy rather than universal quality grades.

Coverage Boundary

Completeness is relative to evidence Reforge declares observable: discovered source paths, supported parsed syntax, the resolved dependency graph, repository documentation, and optional Git history. Unsupported languages, disabled history, excluded paths, and unresolvable dependencies mean “not observed,” never “no maintenance pressure.” Detector manifest language scope and raw metric definitions make this boundary machine-readable.

Evidence surfaceCoverage stateExplicit non-coverage
Source paths and physical LOCLanguage-neutral for discovered source filesExcluded, hidden, generated, and dependency paths follow scan configuration.
Parsed functions, types, structure, and similarityRust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby as declared per detectorParse failures and unsupported grammars are not observations.
Unused-function analysisRust, JavaScript, TypeScript/TSX, Python, and GoDynamic and unresolved references can reduce recall.
Dependency graphRust, JavaScript, TypeScript/TSX, Python, Ruby, C, and C++Unresolved external or framework-specific edges are omitted.
Repository documentation contractReforge repository scopeThis is not a universal documentation policy for arbitrary projects.
Change historyGit history when churn is enabled and availableDisabled, unavailable, binary, out-of-root, and oversized commits are omitted.

Theoretical Basis

Validation Expectations

The ontology establishes completeness relative to declared constructs, not to all possible maintenance work. Calibration should separately test detector precision, recall on seeded and reviewed examples, ranking agreement with maintainers, language coverage, and correlations among raw metrics.

Detectors

Reforge emits refactoring signals from threshold checks, Tree-sitter analysis, git churn, and heuristic drift detectors. Findings are signals for review; they are not automatic proof that code must be changed, that code is low quality, or that a bug exists.

Every finding kind is also described by the report-level detector_manifest. The manifest declares its primary maintainability construct, signal mechanism, refactoring action, entity scope, detection approach, supported languages, precision risk, composite parent, and typed detector relations. Clustering consumes the relation contract so shared paths alone cannot double-count or merge unrelated detectors. See Metric Ontology.

File and Directory Signals

  • large_file: source file line count exceeds --max-file-lines.
  • large_directory: direct source-file count exceeds --max-dir-files.
  • debt_marker: a comment line contains TODO or FIXME.

Hidden paths are skipped unless --include-hidden is set. Generated and dependency directories are skipped unless --include-generated is set. Test files and directories are scanned by default; --exclude-tests removes them before detector-specific analysis runs.

Similar Functions

similar_functions uses Tree-sitter to extract named functions and methods in Rust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby. Function bodies are normalized so identifiers become ID, strings become STR, and numbers become NUM.

Candidates are grouped only within the same language family and same category of function or method. Similarity uses length ratio, multiset overlap, and a longest-common-subsequence check.

Controls:

  • --min-similar-functions
  • --min-function-tokens
  • --function-similarity
  • --include-test-similarity

Test files are excluded from this detector by default.

Structural Signals

Structural detectors use Tree-sitter for supported languages.

  • long_function: function line span exceeds --max-function-lines.
  • complex_function: estimated complexity exceeds --max-function-complexity.
  • deep_nesting: nested control-flow depth exceeds --max-nesting-depth.
  • many_parameters: parameter count exceeds --max-function-parameters.
  • readability_risk: one function combines at least two function-level readability signals, such as length, complexity, nesting, or parameter pressure.
  • large_type: type line span or member count exceeds --max-type-lines or --max-type-members.
  • large_public_surface: public/exported item count exceeds --max-public-items.
  • import_heavy_file: import count exceeds --max-imports.
  • function_proliferation: a production file has many functions, high functions-per-100-lines density, and a high percentage of small simple functions. This is an over-splitting signal, not proof that any function is unused.

Tests are excluded from general structural findings unless --include-test-structure is passed.

For Rust, large_public_surface counts items with visibility modifiers such as pub, pub(crate), and pub(super), including public re-exports, because each one expands the module surface visible to another scope.

Unused Functions

unused_function builds a conservative project-wide identifier index for Rust, JavaScript, TypeScript/TSX, Python, and Go. It reports private named free functions that have no same-name references outside their own function body. Java, C#, Kotlin, PHP, and Ruby are parsed for structural and similarity signals, but skipped for unused-function candidates until their reference and visibility rules can be modeled more precisely.

The detector skips public or exported functions, methods, common entry-point names such as main and init, and test helper definitions by default. References from scanned test files still count, so production helpers called only by tests are not reported unless tests are excluded from the scan.

Dependency Graph Signals

dependency_cycle and dependency_hub use a conservative source-file import graph. The detector resolves only imports that point to another scanned source file under the scan root, such as relative JavaScript/TypeScript imports, Rust mod declarations, Python relative imports, Ruby require_relative calls, and quoted C/C++ includes that resolve to scanned source files.

  • dependency_cycle: a resolved strongly connected component spans multiple source files. The finding reports cycle size, internal dependency edge count, and internal edge density.
  • dependency_hub: a project with enough resolved graph data has a file with unusually high fan-in or fan-out. The finding reports direct fan-in/fan-out, transitive reach, dependency depth, and instability percentage so broad, deep, and mixed-responsibility hubs rank higher. Dependency depth is the longest path through the strongly connected component condensation graph. Files in the same cycle therefore share one component depth; cycle size and density remain evidence of dependency_cycle instead of being counted again as depth.

External packages, unresolved aliases, generated paths skipped by scan filters, and ambiguous language-specific module systems are ignored rather than guessed.

Duplication and Test-Risk Signals

  • repeated_literal: string or numeric literals occur at least --min-repeated-literal-occurrences times after normalization and filtering.
  • repeated_error_pattern: repeated catch/except/error handling patterns are found across supported languages.
  • data_clump: repeated parameter groups occur at least --min-data-clump-occurrences times.
  • test_duplication: repeated setup, fixture, mock, fake, or before-hook patterns occur in tests.
  • happy_path_only_tests: a test file has at least three test cases with assertion evidence but no negative, error, or boundary evidence.

Repeated-literal confidence is lower when the literal appears only in tests or looks like report/fixture text.

Drift Signals

  • file_naming_drift: a directory mixes naming styles such as snake_case, kebab-case, PascalCase, camelCase, lowercase, dot.separated, or mixed.
  • directory_drift: a directory mixes more concepts than the directory-size threshold allows.
  • parallel_implementation: multiple functions/classes appear to implement the same capability.
  • shadowed_abstraction: helper/common/shared/util abstractions are shadowed by similar local helpers.
  • duplicate_type_shape: multiple type-like shapes share enough fields to suggest duplicated data modeling.
  • config_key_drift: config, route, env, endpoint, token, or similar keys are repeated across locations.
  • fixture_factory_drift: test factory, fixture, mock, fake, or sample concepts are repeated across locations.
  • generic_bucket_drift: generic directories such as common, helpers, shared, or utils accumulate unrelated concepts.
  • adapter_boundary_bypass: a boundary module exists, but other files make direct HTTP, config, filesystem, or logging calls.
  • stale_compatibility_path: legacy, deprecated, fallback, shim, polyfill, or versioned compatibility paths appear without a clear sunset, owner, or migration boundary.

Drift detectors use path and identifier heuristics, so grouped cross-file findings deserve more weight than isolated info-level findings.

Documentation Signals

When the scan root looks like a project, Reforge checks for a stable documentation set.

  • missing_documentation_set: expected docs are missing under docs/ or at the repository root.
  • missing_user_guide: user-guide topics such as installation, quick start, CLI, configuration, output, and troubleshooting are missing.
  • missing_report_schema_docs: JSON/YAML fields and compatibility expectations are undocumented.
  • missing_metrics_model_docs: raw metrics, findings, hotspots, priority, or confidence are undocumented.
  • missing_architecture_docs: scan pipeline, detector boundaries, data flow, or extension points are undocumented.
  • stale_cli_documentation: docs mention CLI flags but omit current flags.
  • stale_schema_documentation: report-schema docs omit current fields.

Expected docs include a docs index, user guide, configuration reference, report schema, metrics model, detector reference, architecture guide, and contributing guide.

Interpreting Detector Output

Prefer findings with high priority, high confidence, cross-file spread, and clear related locations. Treat low-confidence heuristic findings as prompts for inspection, not automatic refactor instructions.

findings=0 means no findings remain after scoring, filters, and suppressions. It does not prove that the scanned code is healthy, bug-free, or free of maintainability pressure. Check hotspot watchlists, raw metrics, and suppression summary context when explaining an empty finding list.

Filtering and Suppression

Finding-kind controls use the snake-case detector names above. --only keeps only selected kinds, --exclude-detector removes selected kinds, --min-priority keeps findings at or above the final scored priority, and --severity warning keeps warning and critical findings.

Intentional findings can be suppressed in source comments with reforge:ignore, reforge:ignore-next-line, or reforge:ignore-file. Each directive accepts an optional comma-separated kind list followed by a reason. Long-lived suppressions can also be recorded in reforge.toml with [[suppressions]] entries.

Suppressions remove matching findings from reports and CI gate selection, but they do not remove raw metrics or hotspot watchlist entries. Suppression summary information exists to preserve that audit context and prevent zero unsuppressed findings from being read as zero observed signals.

Maintainer Calibration Samples

Sample collection date: July 9, 2026.

This is a dated maintainer calibration record, not a public benchmark or normative user reference. These anonymized samples are useful for checking report volume, detector balance, and runtime on large repositories, but they should not be treated as a default-threshold mandate.

Source identities and local collection paths are intentionally omitted. Raw reports were generated outside the committed documentation set.

This pass used reproducible static settings for threshold and scoring sanity-checks: --churn off --hotspot-model static --output json --progress never.

Commands

SampleCommandStatus
large-cli-areforge scan <large-cli-a> --output json --output-file target/calibration/large-cli-a.json --progress never --churn off --hotspot-model staticCompleted. Reported scan duration: 780,923 ms.
large-cli-breforge scan <large-cli-b> --output json --output-file target/calibration/large-cli-b.json --progress never --churn off --hotspot-model staticCompleted. Reported scan duration: 122,485 ms.

Sample Summary

SampleSource filesDirectoriesFunction candidatesRaw functionsRaw typesTest filesFindingsHotspotsSimilar function groups
large-cli-a3,2057157,17138,5347,5867374,8443,12669
large-cli-b2,17856311,29831,3822,1689543,7251,8854

Finding severity:

SampleCriticalWarningInfo
large-cli-a04,177667
large-cli-b03,148577

Top Finding Kinds

Kindlarge-cli-alarge-cli-b
complex_function869793
long_function706611
readability_risk555589
many_parameters55486
large_file364157
repeated_literal29254
deep_nesting291390
debt_marker26194
import_heavy_file18440
test_duplication122626
large_type6590

Static Hotspot Summary

Raw hotspot locations are omitted from this committed note. The top static hotspots were retained only as aggregate shape data so the note documents watchlist behavior without exposing sample-specific paths.

SampleTop hotspot priorityTop hotspot severityFile hotspotsFunction hotspotsType hotspots
large-cli-a100critical221
large-cli-b100critical041

Metric Percentiles

Metriclarge-cli-a p50large-cli-a p75large-cli-a p90large-cli-a p95large-cli-a maxlarge-cli-b p50large-cli-b p75large-cli-b p90large-cli-b p95large-cli-b max
file loc1283928811,44711,3961553266539925,430
file imports6183347524471014109
file public items139167581258209
function loc142954772,22882249903,911
function complexity1371144411510476
function nesting depth022310002315
function parameter count123434011219
type loc581422929717801943,427
type member count24710270361116492

Observations

  • Both samples were scanned with churn disabled, so hotspot ranking is entirely static. This makes the sample reproducible but does not calibrate hybrid ranking behavior.
  • The samples are large: 1,149,253 and 652,390 total scanned file LOC respectively. Debug scans are expensive on this scale.
  • Neither sample produced critical findings, but both produced many critical static hotspots. This supports keeping hotspots as a watchlist rather than a hard CI gate.
  • large_file default 800 sits near the upper decile for both samples: large-cli-a file LOC p90 is 881 and large-cli-b p90 is 653. That threshold is broadly plausible for large mixed repositories, but stricter teams may want 600 or lower.
  • max_function_lines=80 is close to large-cli-a p95 at 77 and below large-cli-b p95 at 90. It is a reasonable default, but expect many warnings in CLI/TUI orchestration code.
  • max_function_complexity=15 is above p95 for both samples, yet complex_function is the largest finding kind in both reports. The extreme max values suggest the detector is dominated by a long tail rather than by normal functions.
  • max_function_parameters=5 behaves differently across samples: large-cli-a has 554 many_parameters findings while large-cli-b has 86. This should stay configurable and should not be weighted as a universal style rule.
  • Test maintenance signals vary by project shape. large-cli-b has 954 test files and 626 test_duplication findings, while large-cli-a has 737 test files and 122 test_duplication findings. Teams may need separate presets for production-source gates and test-maintenance audits.
  • Similar-function detection produced 69 groups in large-cli-a but only 4 in large-cli-b under default thresholds. The current default appears conservative for TypeScript-heavy samples but can still be costly on very large trees.

Calibration Follow-Ups

  • Review a stratified sample of high-priority findings with maintainers to estimate false-positive rates by detector kind.
  • Calibrate the documented strict, balanced, and relaxed presets against smaller libraries and service applications before changing their thresholds.
  • For large repositories, consider a calibration mode or docs recipe that uses --exclude-tests and higher --min-function-tokens when the goal is structural threshold calibration rather than duplication analysis.

Cross-Project Core Pass

A second pass on July 10, 2026 added smaller libraries, frameworks, a service application, and a TypeScript monorepo. Sample identities, clone locations, and raw reports remain outside the committed documentation. Each source snapshot was frozen before scanning, and all samples used the same reproducible static settings as the large CLI pass.

SampleShapeSource filesFunction candidatesFindingsIssuesFindings merged into issuesHotspotsSimilar groupsDuration (ms)
core-rust-climulti-crate CLI10137818313625.7%99106,479
core-python-frameworksmall framework8368836225.3%400486
core-go-librarysmall HTTP library7882382534.2%410381
core-js-frameworkweb framework141747454.3%201,076
core-java-serviceservice application4810000.0%0096
core-ts-monorepoplugin monorepo1,45155693355640.4%31034,195

The pass exposed three instrumentation and model issues before the final figures above were recorded:

  • Python annotations and default expressions were initially traversed as if every identifier were a parameter. A representative eight-parameter function was measured as 30 parameters. Parameter extraction now counts the declared bindings only and excludes method receivers.
  • Java package-info.java files were initially treated as kebab-case business source, producing four naming-drift findings in the service sample. Java package and module metadata are now naming-neutral.
  • Dependency depth recursively enumerated simple paths in cyclic graphs. The TypeScript monorepo did not complete within 15 minutes. Depth is now computed once on the strongly connected component condensation DAG; the same complete static scan finishes in about four seconds on the calibration machine.

These corrections changed instrumentation and graph semantics, not default thresholds. The remaining large differences in report volume are therefore treated as project-shape evidence. In particular, the Java service is a useful zero-finding control for the balanced preset, while the TypeScript monorepo is a detector-balance and issue-clustering stress sample. The JavaScript framework’s happy-path test findings remain low-confidence review prompts and need maintainer labeling before any threshold or confidence change.

Architecture

Reforge is a single Rust CLI crate. The code is organized around a scan pipeline that collects raw metrics first, then runs detectors, ranks hotspots, scores findings, and renders reports.

Module Boundaries

  • src/main.rs: CLI entrypoint, progress/color/output routing, file writing, baseline gate evaluation, and broken-pipe handling.
  • src/cli.rs: Clap command definitions, scan arguments, output inference, progress modes, color modes, churn modes, and hotspot models.
  • src/scan/mod.rs: scan orchestration, config discovery, source walking, file-level findings, git churn collection, progress reporting, and final ScanReport assembly.
  • src/lang/mod.rs: Tree-sitter language adapters and shared node-kind constants.
  • src/model/mod.rs: serializable report model, finding kinds, raw metrics, summaries, hotspots, severities, and schema version.
  • src/detectors/: similarity, structure, drift, and documentation detector implementations.
  • src/scoring/mod.rs: metric summaries, priority scoring, severity mapping, and hotspot ranking.
  • src/baseline.rs: schema 18 baseline loading, finding ID comparison, diff classification, and --fail-on gate selection.
  • src/output/mod.rs: human, HTML, JSON, YAML, and SARIF output entry points.

src/main.rs re-exports internal modules under compatibility names such as scanner, report, similar_functions, and structural so existing inline tests and module references can stay stable while the implementation is split into clearer directories.

Scan Flow

  1. Parse reforge scan [OPTIONS] [PATH] with Clap.
  2. Resolve the scan root and load effective arguments from CLI plus optional reforge.toml.
  3. Walk source files with default exclusions, explicit ignored paths, and hidden/generated controls.
  4. Read each source file, collect line counts, file metrics, TODO/FIXME debt markers, and parsed Tree-sitter sources where supported.
  5. Run structural, unused-function, dependency-graph, agent-drift, similar-function, and documentation detectors.
  6. Merge structural raw metrics and the resolved dependency graph snapshot into the report model.
  7. Collect git churn when enabled.
  8. Summarize raw metrics into percentiles.
  9. Rank hotspots with the chosen model.
  10. Finalize finding metrics, priority, confidence, severity, and ranking explanations.
  11. Apply finding filters and suppressions, recording suppression_summary for findings removed by suppressions.
  12. Render human, HTML, JSON, YAML, or SARIF output to stdout or --output-file.
  13. Apply --fail-on to all current unsuppressed findings or to the baseline-selected finding set after the report is written. Human output can also render baseline diff counts and --show-selected current findings.

Data Flow

ScanArgs is the input configuration. scan_report produces a ScanReport with schema version 18. Detectors emit Finding values with metrics and related locations. The dependency-graph detector also emits a resolved source-file graph snapshot. Scoring later enriches findings with constructs and mechanisms, normalized values, percentiles, priority_factors, priority, severity, rank_explanation, and stable rf3- IDs. After filtering and suppression, overlapping findings are grouped into issue clusters.

Raw metrics remain available in reports so consumers can build their own ranking or dashboards without relying only on findings.

Parser Integration

Tree-sitter support is routed through LanguageAdapter. Structural and similarity analysis currently supports Rust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby.

Files with parse errors are skipped for Tree-sitter detectors but can still contribute basic file metrics and debt-marker findings. Broad source discovery includes more extensions than Tree-sitter supports so simple file and directory signals still work on mixed repositories.

Progress and Output

Progress is abstracted behind ProgressSink. NoopProgress is used when progress is disabled. StderrProgress writes either dynamic terminal progress or coarser line-oriented progress, depending on whether stderr is a TTY.

Human, HTML, and SARIF output are produced from the same ScanReport as JSON and YAML. The terminal-oriented renderer lives in src/output/human.rs, SARIF 2.1.0 output lives in src/output/sarif.rs, and src/output/mod.rs keeps the format entry points and JSON/YAML writers. Color is applied only to human output.

HTML Report App

--output html and output-file extensions .html or .htm produce a single offline HTML artifact. The active HTML implementation is the React + TypeScript report app.

The data and packaging flow is:

  1. The Rust scanner builds a schema 18 ScanReport.
  2. The HTML output path serializes that report as JSON.
  3. Reforge writes an HTML shell containing the serialized report data.
  4. The shell inlines the compiled React bundle and CSS.
  5. The browser runs the embedded app locally with no network or server dependency.

Frontend source lives under web/report-app. Build the app there when the visual report changes:

cd web\report-app
npm ci
npm run build

The build is expected to refresh the checked-in report assets:

  • assets/report-app.js
  • assets/report-app.css

Keep those generated assets in sync with frontend source changes so Rust can embed the current app into the offline report. Update docs/report-schema.md when the ScanReport shape changes; the report app should read the documented schema rather than private scanner internals.

Extension Points

To add a detector:

  1. Add a FindingKind and display metadata.
  2. Implement the detector in src/detectors/ or extend an existing detector family.
  3. Add metrics with meaningful names, units, thresholds, and related locations.
  4. Wire the detector into scan_report.
  5. Add manifest classification, coverage, precision risk, parent, and overlap metadata; update confidence, impact, and actionability when needed.
  6. Update report schema and detector docs.
  7. Add focused unit tests next to the module being changed.

To add a language:

  1. Add the Tree-sitter crate dependency.
  2. Extend LanguageFamily and adapter_for_path.
  3. Add function, type, import, public item, complexity, and test-case handling where applicable.
  4. Add tests for parsing, metrics, and detector behavior.

To change report shape:

  1. Update SCAN_REPORT_SCHEMA_VERSION.
  2. Update serializable model types.
  3. Update docs/report-schema.md.
  4. Update the React report app when the visual report depends on the changed fields.
  5. Add or update report tests that pin important fields.

Contributing

This project follows the repository guidelines in AGENTS.md. Keep changes small, behavior-focused, and covered by targeted tests.

Setup

Install Rust 1.85 or newer, then run:

cargo build
cargo test

For a quick end-to-end smoke test:

cargo run -- scan . --progress never

For stable machine-readable output:

cargo run -- scan . --output json --progress never

Development Workflow

Use cargo fmt before review:

cargo fmt

Run tests:

cargo test

Run Clippy before larger changes:

cargo clippy --all-targets --all-features

When report formatting or schema behavior changes, include sample human, HTML, JSON, YAML, or SARIF output in the pull request description.

Report App Development

The React report app requires Node.js ^20.19.0 or >=22.12.0 and npm; CI uses Node.js 22. Vite 8 is installed from the locked frontend dependencies, so use the package scripts instead of a global Vite installation:

cd web\report-app
npm ci
npm run test
npm run build

The build refreshes assets/report-app.js and assets/report-app.css. Rust embeds those files in offline HTML reports, so commit both generated assets with the frontend source change.

Documentation Site

The documentation site uses mdBook 0.5.4. Install that exact version before building or serving the site locally:

cargo install mdbook --version 0.5.4 --locked

On Windows, generate the current self-scan sample and serve the site with:

.\scripts\serve-docs.ps1

Build static files into target/docs-site without starting a server:

.\scripts\build-docs.ps1

On macOS or Linux, use the matching shell scripts:

sh scripts/serve-docs.sh
sh scripts/build-docs.sh

The published documentation root is https://lylemi.github.io/Reforge/; the generated self-scan is published at https://lylemi.github.io/Reforge/sample/. Repository administrators must set Settings > Pages > Build and deployment > Source to GitHub Actions before the Pages workflow can deploy for the first time. Keep the github-pages environment restricted to the main branch; the workflow also enforces that branch boundary for manual runs.

Tests

Unit tests live next to the modules they exercise under #[cfg(test)] or in module-specific test files included from the module. There is currently no separate tests/ directory.

Add tests for:

  • CLI parsing and default values when flags change.
  • Config precedence and discovery when configuration changes.
  • Scanner exclusions, thresholds, ordering, and report fields.
  • Detector behavior, including false-positive guards.
  • Output stability for human, HTML, JSON, YAML, and SARIF report changes.

Name tests by behavior, such as parses_output_format or groups_similar_functions.

Style

Use idiomatic Rust formatted by cargo fmt. Prefer the existing module split: cli, scan, model, detectors, scoring, and output.

Use snake_case for functions, variables, modules, and test names. Use PascalCase for structs, enums, and traits. Keep CLI flags long, descriptive, and kebab-case.

Avoid unrelated refactors in behavior changes. If a refactor is needed to make a feature safe, keep it scoped and covered by tests.

Report Compatibility

JSON, YAML, and SARIF reports are external interfaces. When fields are added, removed, or renamed:

  • Update SCAN_REPORT_SCHEMA_VERSION.
  • Update docs/report-schema.md.
  • Update output tests.
  • Mention the compatibility impact in the pull request.

Consumers should rely on stable finding id, priority, confidence, priority_factors, and rank_explanation; legacy v4 fields are not emitted.

Commits and Pull Requests

Use Conventional Commits:

feat(scanner): detect directories with many source files
fix(report): keep JSON output stable
docs: add report schema reference

Keep descriptions imperative, lowercase, and without a trailing period. Keep commits scoped to one behavior change.

Pull requests should describe:

  • User-visible effect.
  • Validation commands run.
  • Related issues.
  • Sample human, HTML, JSON, YAML, or SARIF output when report formatting changes.

Do not commit generated outputs, dependency directories, build artifacts, or local scan artifacts. The checked-in assets/report-app.js and assets/report-app.css bundles are the sole generated-output exception because the Rust HTML renderer embeds them.

Release

This checklist is for maintainers preparing a Reforge release.

Pre-Release Checks

Confirm the crate metadata in Cargo.toml:

  • version
  • rust-version
  • description
  • license
  • readme
  • repository
  • homepage
  • documentation
  • keywords
  • categories

Run validation:

cargo fmt --check
cargo test
cargo clippy --all-targets --all-features
cargo run -- scan . --output json --progress never --churn off

Review the generated JSON for unexpected schema or detector changes.

Schema Review

If serialized report shape changed:

  • Increment SCAN_REPORT_SCHEMA_VERSION in src/model/mod.rs.
  • Update docs/report-schema.md.
  • Update README references to the schema version.
  • Add or update output tests.

If only detector behavior or scoring changed without report shape changes, do not increment the schema version solely for ranking changes unless consumers need a compatibility boundary.

Documentation Review

Before tagging, check:

  • README.md quick start still works.
  • docs/user-guide.md includes all current CLI flags.
  • docs/configuration.md matches config keys in scan/mod.rs.
  • docs/report-schema.md matches the serialized ScanReport model.
  • docs/detectors.md lists current FindingKind values and detector families.

Packaging

Build a release binary:

cargo build --release

Install locally from the release candidate:

cargo install --path .
reforge scan . --progress never

Release Notes

Release notes should include:

  • New detector or CLI capabilities.
  • Changed thresholds, scoring, output, or schema.
  • Bug fixes that affect scan results.
  • Any compatibility notes for JSON/YAML consumers.