Overview
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.
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, andtsx. - 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, combiningstatic_risk * 0.65andchurn_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.
| Option | Default | Purpose |
|---|---|---|
--preset | balanced | Use strict, balanced, or relaxed threshold defaults before per-threshold overrides. |
--max-file-lines | 800 | Report files above this total line count. |
--max-dir-files | 40 | Report directories above this direct source-file count. |
--include-hidden | false | Include hidden files and directories. |
--include-generated | false | Include dependency and generated output directories. |
--no-gitignore | false | Do not apply git ignore rules during scanning. |
--exclude-tests | false | Exclude test files and test directories from scanning. |
--ignore-path | none | Additional path to skip; can be repeated. |
--only | none | Report only these finding kinds, as kind[,kind...]. |
--exclude-detector | none | Exclude these finding kinds, as kind[,kind...]. |
--min-priority | none | Report findings whose final priority is at least this 0-100 value. |
--severity | none | Report findings at or above info, warning, or critical. |
--min-similar-functions | 3 | Report similar-function groups at or above this size. |
--min-function-tokens | 80 | Ignore smaller normalized function bodies. |
--function-similarity | 0.85 | Minimum normalized token similarity for grouping. |
--include-test-similarity | false | Include tests in similar-function analysis. |
--max-function-lines | 80 | Report functions above this line span. |
--max-function-complexity | 15 | Report functions above this estimated complexity. |
--max-nesting-depth | 4 | Report functions above this nested control-flow depth. |
--max-function-parameters | 5 | Report functions with more parameters than this threshold. |
--max-type-lines | 250 | Report types above this line span. |
--max-type-members | 30 | Report types above this member count. |
--max-imports | 35 | Report files with more imports than this threshold. |
--max-public-items | 30 | Report files with more public/exported items than this threshold. |
--max-functions-per-file | 40 | Report over-splitting risk only when this function count and density signals are exceeded. |
--max-functions-per-100-lines | 12 | Report over-splitting risk only when function density also exceeds this threshold. |
--max-small-function-ratio | 70 | Report over-splitting risk only when this percentage of functions are small and simple. |
--min-repeated-literal-occurrences | 12 | Report repeated literals seen at least this many times. |
--min-data-clump-occurrences | 4 | Report repeated parameter groups seen at least this many times. |
--include-test-structure | false | Include tests in general structural checks. |
--config | discovered | Read a specific configuration file. |
--baseline | none | Read a prior schema 18 JSON/YAML report for gate comparison. |
--baseline-mode | new-or-worse | Gate on new, new-or-worse, or all findings when a baseline is present. |
--show | all | Display new, new-or-worse, or all current findings in human baseline reports. |
--fail-on | none | Exit nonzero when selected findings meet info, warning, or critical. |
--churn | auto | Use auto, on, or off for git churn metrics. |
--hotspot-model | hybrid | Use static, churn, or hybrid hotspot ranking. |
--churn-window-days | 180 | Days of git history to include. |
--churn-max-commit-lines | 2000 | Skip commits above this added+deleted line count. |
--output | inferred | Use human, html, json, yaml, or sarif. |
--output-file | stdout | Write the report to a file. |
--progress | auto | Use auto, always, or never for progress output. |
--color | auto | Use auto, always, or never for human-output color. |
--help | none | Print 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
| Key | Default | Equivalent CLI option |
|---|---|---|
preset | balanced | --preset |
max-file-lines | 800 | --max-file-lines |
max-dir-files | 40 | --max-dir-files |
min-similar-functions | 3 | --min-similar-functions |
min-function-tokens | 80 | --min-function-tokens |
function-similarity | 0.85 | --function-similarity |
max-function-lines | 80 | --max-function-lines |
max-function-complexity | 15 | --max-function-complexity |
max-nesting-depth | 4 | --max-nesting-depth |
max-function-parameters | 5 | --max-function-parameters |
max-type-lines | 250 | --max-type-lines |
max-type-members | 30 | --max-type-members |
max-imports | 35 | --max-imports |
max-public-items | 30 | --max-public-items |
max-functions-per-file | 40 | --max-functions-per-file |
max-functions-per-100-lines | 12 | --max-functions-per-100-lines |
max-small-function-ratio | 70 | --max-small-function-ratio |
min-repeated-literal-occurrences | 12 | --min-repeated-literal-occurrences |
min-data-clump-occurrences | 4 | --min-data-clump-occurrences |
churn | auto | --churn |
hotspot-model | hybrid | --hotspot-model |
churn-window-days | 180 | --churn-window-days |
churn-max-commit-lines | 2000 | --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...]] reasonsuppresses same-line findings.reforge:ignore-next-line [kind[,kind...]] reasonsuppresses next-line findings.reforge:ignore-file [kind[,kind...]] reasonsuppresses 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 is18.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, orhybrid.churn: churn collection details.
summary.churn fields:
mode: requested churn mode, one ofauto,on, oroff.enabled: whether churn metrics were collected.status:enabled,disabled, orunavailable.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:
p50p75p90p95max
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:
pathlocimportspublic_itemsis_testchurn
raw_metrics.directories entries:
pathsource_files
raw_metrics.files[].churn entries:
commits_touchedlines_addedlines_deletedauthors_countrecent_weighted_churn
raw_metrics.functions entries:
pathnamelineloccomplexitynesting_depthparameter_countis_test
raw_metrics.types entries:
pathnamelinelocmember_countis_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, ortype.path: location path.line: source line for function/type hotspots, otherwisenull.name: function/type name, otherwisenull.priority: 0 through 100 ranking score.severity:info,warning, orcritical.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 formrf3-<hex>.severity:info,warning, orcritical.path: primary path.line: primary line ornull.metrics: finding-specific measurements.construct: primary ISO/IEC 25010-aligned maintainability construct.mechanism: primary source-observable maintenance mechanism.issue_id: owning issue ID ornull.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 fromkind.related_locations: additional locations for grouped findings.
metrics entries contain:
namevaluethresholdunitexcess_rationormalizedpercentile
construct is one of modularity, reusability, analysability,
modifiability, or testability. mechanism is defined in the
metric ontology.
priority_factors contains:
impactintensityspreadchange_pressureactionabilitydetection_reliabilityinterpretation_reliability
related_locations entries contain:
pathlinename
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, ornullwhen 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:errorforcritical,warningforwarning, andnoteforinfo.message.text: finding message.locations[].physicalLocation: primary path and line.relatedLocations: related finding locations when present.partialFingerprints.reforgeFindingId: stable findingid.properties.id: stable findingid.properties.recommendation: concise refactoring hint.
Finding Kinds
Current kind values:
large_filelarge_directorydebt_markersimilar_functionslong_functioncomplex_functiondeep_nestingmany_parametersreadability_risklarge_typelarge_public_surfaceimport_heavy_filefunction_proliferationunused_functionrepeated_literalrepeated_error_patterntest_duplicationhappy_path_only_testsfile_naming_driftdirectory_driftdata_clumpparallel_implementationshadowed_abstractionduplicate_type_shapeconfig_key_driftfixture_factory_driftgeneric_bucket_driftadapter_boundary_bypassstale_compatibility_pathmissing_documentation_setmissing_user_guidemissing_report_schema_docsmissing_metrics_model_docsmissing_architecture_docsstale_cli_documentationstale_schema_documentationdependency_cycledependency_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:
- Rust scanner collects source metrics and findings.
- Scanner assembles a schema 18
ScanReport. - HTML output serializes the
ScanReportas JSON. - Reforge writes an HTML shell containing that JSON payload.
- The shell inlines the compiled React bundle and stylesheet.
- 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.jsassets/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_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.
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:
| Construct | Question answered |
|---|---|
modularity | Will a change to one component propagate into others? |
reusability | Is a concept represented once in a reusable form? |
analysability | Can a maintainer locate, understand, and diagnose the change point? |
modifiability | Can the code be changed locally without unnecessary work or regression risk? |
testability | Can 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:
| Mechanism | Representative evidence | Common confounders |
|---|---|---|
cognitive_load | function length, branching, nesting, parameters | generated dispatch, parsers |
dependency_propagation | fan-in/out, cycles, imports, boundary bypasses | incomplete module resolution |
responsibility_dispersion | oversized files/types/directories, mixed concepts | generated registries, declarative tables |
duplication_divergence | similar implementations, repeated shapes or setup | intentional protocol symmetry |
change_pressure | debt markers, compatibility paths, churn | migrations with explicit exit plans |
verification_difficulty | missing negative or boundary evidence | tests stored outside the scan root |
knowledge_drift | naming inconsistency, missing or stale documentation | project-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:
- Raw metrics record observations such as LOC, complexity, fan-out, and churn.
- Findings interpret observations through one detector.
- Constructs and mechanisms explain the maintenance capability and causal pressure represented by the evidence.
- Issue clusters join related findings that describe the same entity and
typed refactoring
action. - 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
constructandmechanism; - its typed refactoring
actionandentity_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, andconstituent_kindsfor 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 surface | Coverage state | Explicit non-coverage |
|---|---|---|
| Source paths and physical LOC | Language-neutral for discovered source files | Excluded, hidden, generated, and dependency paths follow scan configuration. |
| Parsed functions, types, structure, and similarity | Rust, JavaScript, TypeScript/TSX, Python, Go, Java, C#, Kotlin, PHP, and Ruby as declared per detector | Parse failures and unsupported grammars are not observations. |
| Unused-function analysis | Rust, JavaScript, TypeScript/TSX, Python, and Go | Dynamic and unresolved references can reduce recall. |
| Dependency graph | Rust, JavaScript, TypeScript/TSX, Python, Ruby, C, and C++ | Unresolved external or framework-specific edges are omitted. |
| Repository documentation contract | Reforge repository scope | This is not a universal documentation policy for arbitrary projects. |
| Change history | Git history when churn is enabled and available | Disabled, unavailable, binary, out-of-root, and oversized commits are omitted. |
Theoretical Basis
- ISO/IEC 25010:2023 supplies the product-quality reference model and maintainability constructs; it does not prescribe Reforge’s detectors or weights: https://www.iso.org/standard/78176.html.
- ISO/IEC 25023:2016 defines product-quality measurement guidance and leaves rated ranges contextual to the product and user needs: https://www.iso.org/standard/35747.html.
- Goal Question Metric motivates deriving measurements from explicit goals and questions: https://csis.pace.edu/~ogotel/teaching/CS777gqm.pdf.
- Briand, Morasca, and Basili motivate explicit entities, attributes, and mathematical properties for software measures: https://www.cs.umd.edu/~basili/publications/journals/J58.pdf.
- ISO/IEC 25020:2019 supplies the quality-measurement reference model and guidance for selecting, constructing, validating, and documenting measures: https://www.iso.org/standard/72117.html.
- ISO/IEC/IEEE 15939:2017 supplies the measurement process for connecting information needs, measures, analysis, application, and validity checks: https://www.iso.org/standard/71197.html.
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 containsTODOorFIXME.
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-linesor--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 ofdependency_cycleinstead 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-occurrencestimes 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-occurrencestimes.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 assnake_case,kebab-case,PascalCase,camelCase,lowercase,dot.separated, ormixed.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 ascommon,helpers,shared, orutilsaccumulate 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 underdocs/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
| Sample | Command | Status |
|---|---|---|
| large-cli-a | reforge scan <large-cli-a> --output json --output-file target/calibration/large-cli-a.json --progress never --churn off --hotspot-model static | Completed. Reported scan duration: 780,923 ms. |
| large-cli-b | reforge scan <large-cli-b> --output json --output-file target/calibration/large-cli-b.json --progress never --churn off --hotspot-model static | Completed. Reported scan duration: 122,485 ms. |
Sample Summary
| Sample | Source files | Directories | Function candidates | Raw functions | Raw types | Test files | Findings | Hotspots | Similar function groups |
|---|---|---|---|---|---|---|---|---|---|
| large-cli-a | 3,205 | 715 | 7,171 | 38,534 | 7,586 | 737 | 4,844 | 3,126 | 69 |
| large-cli-b | 2,178 | 563 | 11,298 | 31,382 | 2,168 | 954 | 3,725 | 1,885 | 4 |
Finding severity:
| Sample | Critical | Warning | Info |
|---|---|---|---|
| large-cli-a | 0 | 4,177 | 667 |
| large-cli-b | 0 | 3,148 | 577 |
Top Finding Kinds
| Kind | large-cli-a | large-cli-b |
|---|---|---|
| complex_function | 869 | 793 |
| long_function | 706 | 611 |
| readability_risk | 555 | 589 |
| many_parameters | 554 | 86 |
| large_file | 364 | 157 |
| repeated_literal | 292 | 54 |
| deep_nesting | 291 | 390 |
| debt_marker | 261 | 94 |
| import_heavy_file | 184 | 40 |
| test_duplication | 122 | 626 |
| large_type | 65 | 90 |
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.
| Sample | Top hotspot priority | Top hotspot severity | File hotspots | Function hotspots | Type hotspots |
|---|---|---|---|---|---|
| large-cli-a | 100 | critical | 2 | 2 | 1 |
| large-cli-b | 100 | critical | 0 | 4 | 1 |
Metric Percentiles
| Metric | large-cli-a p50 | large-cli-a p75 | large-cli-a p90 | large-cli-a p95 | large-cli-a max | large-cli-b p50 | large-cli-b p75 | large-cli-b p90 | large-cli-b p95 | large-cli-b max |
|---|---|---|---|---|---|---|---|---|---|---|
| file loc | 128 | 392 | 881 | 1,447 | 11,396 | 155 | 326 | 653 | 992 | 5,430 |
| file imports | 6 | 18 | 33 | 47 | 524 | 4 | 7 | 10 | 14 | 109 |
| file public items | 1 | 3 | 9 | 16 | 758 | 1 | 2 | 5 | 8 | 209 |
| function loc | 14 | 29 | 54 | 77 | 2,228 | 8 | 22 | 49 | 90 | 3,911 |
| function complexity | 1 | 3 | 7 | 11 | 444 | 1 | 1 | 5 | 10 | 476 |
| function nesting depth | 0 | 2 | 2 | 3 | 10 | 0 | 0 | 2 | 3 | 15 |
| function parameter count | 1 | 2 | 3 | 4 | 34 | 0 | 1 | 1 | 2 | 19 |
| type loc | 5 | 8 | 14 | 22 | 929 | 7 | 17 | 80 | 194 | 3,427 |
| type member count | 2 | 4 | 7 | 10 | 270 | 3 | 6 | 11 | 16 | 492 |
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_filedefault800sits 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=80is 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=15is above p95 for both samples, yetcomplex_functionis 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=5behaves differently across samples: large-cli-a has 554many_parametersfindings 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_duplicationfindings, while large-cli-a has 737 test files and 122test_duplicationfindings. 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, andrelaxedpresets against smaller libraries and service applications before changing their thresholds. - For large repositories, consider a calibration mode or docs recipe that uses
--exclude-testsand higher--min-function-tokenswhen 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.
| Sample | Shape | Source files | Function candidates | Findings | Issues | Findings merged into issues | Hotspots | Similar groups | Duration (ms) |
|---|---|---|---|---|---|---|---|---|---|
| core-rust-cli | multi-crate CLI | 101 | 378 | 183 | 136 | 25.7% | 99 | 10 | 6,479 |
| core-python-framework | small framework | 83 | 68 | 83 | 62 | 25.3% | 40 | 0 | 486 |
| core-go-library | small HTTP library | 78 | 82 | 38 | 25 | 34.2% | 41 | 0 | 381 |
| core-js-framework | web framework | 141 | 7 | 47 | 45 | 4.3% | 2 | 0 | 1,076 |
| core-java-service | service application | 48 | 10 | 0 | 0 | 0.0% | 0 | 0 | 96 |
| core-ts-monorepo | plugin monorepo | 1,451 | 556 | 933 | 556 | 40.4% | 310 | 3 | 4,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.javafiles 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 finalScanReportassembly.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-ongate 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
- Parse
reforge scan [OPTIONS] [PATH]with Clap. - Resolve the scan root and load effective arguments from CLI plus optional
reforge.toml. - Walk source files with default exclusions, explicit ignored paths, and hidden/generated controls.
- Read each source file, collect line counts, file metrics, TODO/FIXME debt markers, and parsed Tree-sitter sources where supported.
- Run structural, unused-function, dependency-graph, agent-drift, similar-function, and documentation detectors.
- Merge structural raw metrics and the resolved dependency graph snapshot into the report model.
- Collect git churn when enabled.
- Summarize raw metrics into percentiles.
- Rank hotspots with the chosen model.
- Finalize finding metrics, priority, confidence, severity, and ranking explanations.
- Apply finding filters and suppressions, recording
suppression_summaryfor findings removed by suppressions. - Render human, HTML, JSON, YAML, or SARIF output to stdout or
--output-file. - Apply
--fail-onto 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:
- The Rust scanner builds a schema 18
ScanReport. - The HTML output path serializes that report as JSON.
- Reforge writes an HTML shell containing the serialized report data.
- The shell inlines the compiled React bundle and CSS.
- 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.jsassets/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:
- Add a
FindingKindand display metadata. - Implement the detector in
src/detectors/or extend an existing detector family. - Add metrics with meaningful names, units, thresholds, and related locations.
- Wire the detector into
scan_report. - Add manifest classification, coverage, precision risk, parent, and overlap metadata; update confidence, impact, and actionability when needed.
- Update report schema and detector docs.
- Add focused unit tests next to the module being changed.
To add a language:
- Add the Tree-sitter crate dependency.
- Extend
LanguageFamilyandadapter_for_path. - Add function, type, import, public item, complexity, and test-case handling where applicable.
- Add tests for parsing, metrics, and detector behavior.
To change report shape:
- Update
SCAN_REPORT_SCHEMA_VERSION. - Update serializable model types.
- Update
docs/report-schema.md. - Update the React report app when the visual report depends on the changed fields.
- 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:
versionrust-versiondescriptionlicensereadmerepositoryhomepagedocumentationkeywordscategories
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_VERSIONinsrc/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.mdquick start still works.docs/user-guide.mdincludes all current CLI flags.docs/configuration.mdmatches config keys inscan/mod.rs.docs/report-schema.mdmatches the serializedScanReportmodel.docs/detectors.mdlists currentFindingKindvalues 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.