scan¶
The scan command is the main entry point. It analyzes Go modules, computes CRAP scores, and outputs the results.
Usage¶
Arguments:
| Argument | Description | Default |
|---|---|---|
pattern |
Go package pattern(s) to scan. Accepts any number of patterns. | ./... (entire module) |
Patterns use the same syntax as go list / go build:
| Pattern | Meaning |
|---|---|
. |
Current package |
./... |
Current package and all sub-packages |
./internal/score |
Single package |
./internal/... |
All packages under internal |
./internal/foo ./internal/bar |
Multiple specific packages |
Examples:
# Scan entire current module (default)
go-crap scan
# Scan a specific package
go-crap scan ./internal/score
# Scan multiple packages
go-crap scan ./internal/score ./internal/coverage
# Scan by import path
go-crap scan github.com/padiazg/go-crap/internal/...
Two-layer selection¶
go-crap uses two independent mechanisms to narrow what gets analyzed. They operate at different granularities:
- Patterns — select which Go packages are scanned. Operates at the package/directory level via
go listsyntax. Choosing whole packages is efficient because coverage tests are only run on matching packages. --exclude— filters files and functions within matched packages. Operates at the file-path and function-name level via regex. Cannot exclude whole packages from being tested, only from the final report.
They are designed to be used together:
# Scan all packages, but skip generated protobuf files inside them
go-crap scan --exclude '\.pb\.go$'
# Scan only internal/foo, and within that package also exclude testdata files
go-crap scan ./internal/foo --exclude 'testdata/.*\.go'
# Narrow to specific packages first, then exclude a file pattern within them
go-crap scan ./internal/scan ./internal/coverage --exclude 'mock_'
Using patterns alone is the right choice when you know which packages to scan. Using --exclude is necessary when you need to skip individual files that live inside matched packages — something go list cannot do, since Go has no negative package patterns.
Flags¶
| Flag | Short | Description | Default |
|---|---|---|---|
--threshold |
-t |
Score above which a function is marked as problematic | 30.0 |
--fail-above |
Exit with code 1 if any function exceeds the threshold | false |
|
--format |
-f |
Output format: table, json, github, sarif, or pr-comment |
table |
--top |
Show only the N worst offenders (0 = all). CoverageUntrusted entries always survive, even when N is small |
0 |
|
--min |
Hide entries below this score. CoverageUntrusted entries are never hidden, regardless of their score |
0 |
|
--missing |
Policy for functions without coverage: pessimistic, optimistic, or skip |
pessimistic |
|
--coverage-profile |
Use an existing coverage profile (as produced by go test -coverprofile) instead of running go test |
"" (disabled) |
|
--exclude |
Exclude files and functions matching this regex pattern (repeatable). _test.go files are excluded by default. e.g. pb/.*\.go to exclude protobuf files |
none | |
--include-tests |
Include _test.go files in analysis (overrides default exclude) |
false |
|
--verbose |
Enable verbose (debug-level) logging | false |
|
--progress |
Show progress indicators (default: auto-detect terminal) | false |
|
--no-progress |
Disable progress indicators | false |
|
--output |
-o |
Output file path (default: stdout) | stdout |
--mutation-report |
Path to gremlins JSON mutation report to validate coverage reliability | "" (disabled) |
|
--detailed |
Include mutation failure details (original/replacement code, line, type) in report output | false |
|
--timeout |
Timeout for the full scan (e.g. 30s, 5m, 1h30m) |
10m0s |
|
--baseline |
Path to a previous JSON report for baseline comparison | "" |
|
--fail-regression |
Exit code 1 when functions regressed vs baseline | false |
|
--fail-regression-threshold |
Minimum delta to consider regression | 0.01 |
|
--fail-regression-ignore-covered |
Exclude fully covered functions from regression failures (still shows them with ~) |
false |
|
--show-unchanged |
In baseline mode, also show functions whose CRAP score did not change (requires --baseline) |
false |
CoverageUntrustedhas meaning only if--mutation-reportwas used.
Using an existing coverage profile¶
By default scan runs go test -coverprofile=<tmp> ./... in each discovered module to produce coverage data. The --coverage-profile flag supplies a profile that already exists (as produced by go test -coverprofile) and skips running go test entirely:
This is useful when:
- Coverage was already generated by a prior step or CI job — no need to re-run the suite.
- The environment cannot build or run the tests, but a profile is available.
Notes:
- If the profile path does not exist,
scanfails immediately rather than producing an empty report. - The same profile is applied to every discovered module; entries whose paths do not belong to a module are skipped.
- Duplicate block entries from merged coverage profiles (common with
./...test runs across multiple packages) are automatically deduplicated by(startLine, endLine)position using OR semantics — a block is covered if any test binary covered it. - When the scan
pathis a package subdirectory that has nogo.modof its own, the profile is resolved against the enclosing module (the nearest parent containing ago.mod). This lets you pointscanat a single package:
Coverage Unavailable Warning¶
When a Go module fails to build or run tests, go-crap still parses the coverage profile from any passing tests. The error is reported in all output formats; functions exercised by passing tests appear with their real coverage, while functions with no coverage data get a warning.
- table — coverage column shows
N/A ‼, footer lists unavailable modules with error messages - json —
coverageisnull,coverage_warningcontains the error - github —
::warningannotation with module error - sarif — result with
RuleID: "go-crap/coverage-unavailable" - pr-comment — "Coverage Unavailable" section
This is distinct from the --missing policy, which handles functions that have no coverage data because they were not exercised by tests. Coverage unavailable means the entire module's test run failed.
If the test run was killed because it exceeded --timeout, the error message says so explicitly (go test: timed out (increase --timeout to allow more time)) instead of the generic signal: killed.
Progress Indicators¶
During long scans, go-crap can show real-time progress bars on stderr:
- Default — auto-detects terminal: enabled when stderr is a TTY, disabled when piped
--progress— force-enable progress indicators--no-progress— force-disable (useful in CI or when redirecting stderr)
Phases tracked: Discovering modules, Running coverage tests, Analyzing complexity, Processing results.
# Force progress in CI
go-crap scan --progress
# Suppress progress (e.g. in scripts that capture stderr)
go-crap scan --no-progress --format json
Examples¶
Scan all packages¶
Scan a specific package¶
Scan multiple packages¶
Scan a project outside the current module¶
Show only the top 20 worst offenders¶
CI integration - fail on high CRAP scores¶
Filter by minimum score¶
Exclude generated or test files¶
Exclude protobuf and mock files at any depth¶
Machine-readable JSON output¶
SARIF output¶
Outputs SARIF 2.1.0 compliant JSON for integration with code scanning tools, IDEs, and CI platforms that support SARIF.
Pull request comment output¶
Generates a markdown table with status symbols, CRAP scores, complexity, coverage, and file locations — formatted for pasting into pull request comments.
Write to file¶
Uses the --output / -o flag to write results to a file instead of stdout. Works with any format.
Verbose / debug logging¶
Enables debug-level logging to help diagnose issues with module discovery, coverage parsing, or path matching.
Mutation report validation¶
Validates coverage reliability by comparing mutation testing results against go-crap's coverage data. When a function has lived mutants (mutations that survived because tests didn't catch them), go-crap marks the coverage as unreliable and recalculates the CRAP score assuming 0% coverage.
Unreliable coverage is indicated by:
- A ⚠ warning next to the coverage percentage in
tableandpr-commentoutput - An additional
coverage-untrustedSARIF result insarifformat - A mutation score in
jsonoutput (mutation_scorefield) - A
::warningannotation ingithubformat, emitted even when the CRAP score is below threshold - An "Unreliable Coverage" section in
pr-commentoutput listing all affected functions (always rendered when mutation report is provided)
This is useful when you use gremlins or similar mutation testing tools to catch functions that appear well-tested but have blind spots.
Detailed mutation output¶
The --detailed flag includes full mutation failure details in the report output:
- JSON:
mutation_detailsarray per entry withtype,mutator_name,file,line,status,original_text,replacement_text - SARIF: survived mutations appended to warning messages with type, line, and code diff (e.g.
"a < b" → "a >= b") - PR Comment:
Survived Mutantscolumn in the Unreliable Coverage section, with code snippets inline - Table: no change — still shows ⚠ for untrusted coverage
This is useful for debugging which specific mutants survived and what code transformations they represented.
Baseline comparison¶
Compare the current scan against a previous JSON report. Every formatter shows deltas:
- pr-comment: header shows Combined/Average CRAP with deltas vs baseline, plus "New Functions" and "Regressions" sections
- table:
Δcolumn with per-function delta (e.g.+15 ↑), delta footer lines - json: per-entry
baseline_crapanddeltafields,summaryobject with baseline/compare stats - github: summary
::notice::annotation when baseline provided
Regression enforcement¶
Exits with code 1 when any function's CRAP score has increased (regressed) compared to the baseline. Use --fail-regression-threshold to adjust the minimum delta that counts as a regression.
Ignoring fully covered regressions¶
When --fail-regression-ignore-covered is set, fully covered functions (coverage ≥ 99.95%) that have regressed are excluded from triggering the exit code 1 failure. They are still reported in the PR comment output with a ~ symbol to indicate they were ignored.
This is useful in CI pipelines where you want to enforce regression detection on partially covered functions while acknowledging that fully tested code shouldn't count as a failure.
Combined threshold + regression¶
Fails if any function exceeds the threshold OR has regressed compared to baseline.
Creating a baseline¶
Generate a JSON report to use as a baseline. Subsequent scans can compare against it to track quality over time.
Docker¶
Mount the project directory at /code — the container analyses whatever directory you mount. Pass any flags directly:
To scan a specific package inside the mounted directory:
Images are available at docker.io/padiazg/go-crap and ghcr.io/padiazg/go-crap. Tags map to releases. Multi-arch images (linux/amd64, linux/arm64).