# Scorecard > Scorecard (`sc`) is a local-first, deterministic code quality gate. One run checks that a project builds, tests pass, complex code is covered (CRAP), no secrets are committed, the linter is clean, and every import is a declared dependency. It prints a verdict for people and a JSON scorecard for agents and CI. Ten language packs are built in. `sc-mcp` is a stdio MCP server for agents. This file is for agents. Full docs: https://scorecardcli.com/llms-full.txt. Tool list: https://scorecardcli.com/agents.json. Source: https://github.com/moonbase2090/Scorecard (MIT). ## Prerequisites - macOS (Apple silicon or Intel) or Linux (x86_64 or arm64) for release binaries. No Windows release archive is published. - `git` (diff scoring and the secrets scan use it). - The toolchain of the project you analyze (each pack runs that language's own build, test and lint tools). Rust coverage needs `rustup component add llvm-tools` and `cargo install cargo-llvm-cov`; without them coverage is reported as not measured. - Rust 1.88 or newer to build from source. ## Install Latest release: v0.1.10 — https://github.com/moonbase2090/Scorecard/releases/tag/v0.1.10. Each archive contains `sc` and `sc-mcp`. ### macOS - `sc-v0.1.10-macos.pkg` or `sc-v0.1.10-universal-apple-darwin.dmg` installs `sc` and `sc-mcp` to `/usr/local/bin`. - Or a tarball: `sc-v0.1.10-aarch64-apple-darwin.tar.gz` (Apple silicon), `sc-v0.1.10-x86_64-apple-darwin.tar.gz` (Intel). ### Linux ```sh TARGET=x86_64-unknown-linux-gnu # or aarch64-unknown-linux-gnu curl -fsSLO https://github.com/moonbase2090/Scorecard/releases/download/v0.1.10/sc-v0.1.10-$TARGET.tar.gz curl -fsSLO https://github.com/moonbase2090/Scorecard/releases/download/v0.1.10/SHA256SUMS sha256sum -c --ignore-missing SHA256SUMS # macOS: shasum -a 256 -c --ignore-missing SHA256SUMS tar -xzf sc-v0.1.10-$TARGET.tar.gz install -m 755 sc-v0.1.10-$TARGET/sc sc-v0.1.10-$TARGET/sc-mcp ~/.local/bin/ ``` ### Windows No Windows binary is published. Build from source (below) where a Rust toolchain is available. ### Build from source ```sh git clone https://github.com/moonbase2090/Scorecard.git && cd Scorecard cargo install --locked --path crates/sc-cli cargo install --locked --path crates/sc-mcp ``` ## Configuration - Config file: `--config PATH`, else `./analyzer.toml`, else `~/.config/sc/analyzer.toml`. `sc config init` writes a starter user config; `sc config path` prints its path. - Add `.sc/` to `.gitignore`; `sc` keeps its last report and caches there. - `sc setup` installs the agent skill and registers the `sc-mcp` server for this user. `sc skills install --agent detected` installs only the skill instructions. - MCP: run `sc-mcp` (stdio) in the project directory, e.g. `{"mcpServers": {"scorecard": {"command": "sc-mcp"}}}`. ## Tools ### MCP tools (`sc-mcp`) - `analyze_paths` — analyze explicit source paths and return a scorecard. Params: `paths` (array of strings, required), `spec_ref`. Example: `{"paths": ["src/lib.rs"]}` - `analyze_diff` — analyze a git diff and return a scorecard. Params: `base`, `head`, `spec_ref`. Example: `{"base": "main"}` - `explain` — explain one finding from the last scorecard. Params: `finding_id` (required). - `list_findings` — list findings from the last scorecard. Params: `scorecard_id`, `severity`. ### CLI (`sc`) - `sc analyze [PATH]` — analyze a project tree (default `.`). Flags: - `--format json|pretty|md|sarif|html|all` (default: `pretty` on a terminal, else `json`) - `--out PATH` — also write the report there; stdout prints a summary - `--fail-on LIST` — gates that set exit 1 (default `types,tests,crap,secrets,lint`; `none` suppresses) - `--pack rust|node|python|bash|go|java|csharp|php|cpp|web|command` - `--diff [BASE]` (bare = auto: `HEAD~1`, else `main`, else `master`), `--diff-head COMMIT`, `--paths LIST` (newline-separated; exclusive with `--diff`) - `--spec FILE`, `--mutation off|diff|full`, `--llm off|on` (optional spec review against a configured endpoint), `--intent TEXT` - `--budget-seconds N` (default 120), `--config PATH`, `--quiet` - Examples: `sc analyze .`, `sc analyze . --diff main --format json`, `sc analyze . --format sarif --out sc.sarif` - `sc config path` | `sc config init`. - `sc setup` — install the agent skill and register `sc-mcp`. - `sc skills install [--agent AGENT] [--check] [--force]` — `--agent` defaults to `all`; `detected` targets only agents already configured or on PATH. Run `sc skills install --help` for the identifiers. - `sc --version`. Exit codes: `0` no `--fail-on` gate failed; `1` a selected gate failed; `2` `sc` could not run. ## Usage workflows 1. Before committing: `sc analyze . --diff --format json` and read `verdict`, gates and findings. 2. Agents via MCP: `analyze_diff` (or `analyze_paths`), then `list_findings` and `explain` for each finding to fix. 3. CI / code scanning: `sc analyze . --format sarif --out sc.sarif`. Only `secrets.*` findings are level `error`. 4. Full reports: `sc analyze . --format all --out report` writes every format. 5. Cargo workspace: analyze the root for every member, or a member directory for one package. ## Troubleshooting - `--diff` exits 2 in CI: shallow clone or missing base; fetch full history (`fetch-depth: 0`) or pass an explicit base. - Coverage "not measured" on Rust: install `llvm-tools` and `cargo-llvm-cov`. - `secrets.empty_scope`: the scope selected no files; check the path and exclusions. - Exit 0 but an enforced gate failed: `--fail-on` excluded it; `sc` prints a stderr warning naming it. - Several manifests match: choose with `--pack`. - More: https://github.com/moonbase2090/Scorecard/blob/main/docs/troubleshooting.md ## Links - [Full agent docs](https://scorecardcli.com/llms-full.txt) - [agents.json](https://scorecardcli.com/agents.json) - [Wiki](https://scorecardcli.com/wiki/) - [Source](https://github.com/moonbase2090/Scorecard) - [Releases](https://github.com/moonbase2090/Scorecard/releases) - [CLI reference](https://github.com/moonbase2090/Scorecard/blob/main/docs/reference/cli.md) - [Gates](https://github.com/moonbase2090/Scorecard/blob/main/docs/reference/gates.md) # Appendix: docs/reference/cli.md (from the Scorecard repo) ## CLI reference ```text sc analyze [PATH] [OPTIONS] sc config path sc config init [--force] sc setup sc skills install [--agent AGENT] [--check] [--force] sc help [COMMAND] ``` ### sc analyze `PATH` is the project directory. Default `.`. | Flag | Default | Meaning | |---|---|---| | `--format FORMAT` | `pretty` on a terminal, `json` otherwise | `json`, `pretty`, `md`, `sarif`, `html`, or `all`. See [Output](#output). | | `--out PATH` | none | Also write the report to `PATH`. Stdout prints the short summary, including when it is not a terminal. Without this flag, stdout is the chosen `--format`. | | `--fail-on LIST` | `types,tests,crap,secrets,lint` | Comma-separated [gates](gates.md) whose failures set exit 1. Use `none` by itself to suppress gate failures from the exit code. Empty or blank lists are invalid. Overrides `gates.fail_on`; already enforced failures remain in the report verdict. | | `--pack PACK` | detected | `rust`, `node`, `python`, `bash`, `go`, `java`, `csharp`, `php`, `cpp`, `web`, or `command`. Needed when the tree has two markers. See [packs](../packs.md). | | `--diff [BASE]` | off | Score only what changed against git `BASE`. Paths are relative to the project directory, including when that directory sits inside a larger git repository. Python compiles, lints, and checks imports only in changed `.py` files, runs pytest only on changed `test_*.py` and `*_test.py` files, and drops secret findings on unchanged paths. Without `BASE` (AUTO): `HEAD~1`, else `main`, else `master`, skipping a candidate that is the same commit as `HEAD`. When none remain — a shallow clone, a single commit, or a depth-1 clone of `main` or `master` — the run exits 2 with verdict `fail` and one stderr line; fetch full history (`fetch-depth: 0`) or pass `--diff BASE`. A missing explicit base exits 2 the same way and names the ref. An explicit `--diff HEAD` still works. Uncommitted changes count. | | `--diff-head REV` | worktree | With `--diff`, compare `BASE` to commit `REV` instead of the worktree. | | `--paths FILE` | off | Score only the source files listed in `FILE`, one per line. Cannot be combined with `--diff`. | | `--spec FILE` | off | Check that files and public items named in `FILE` exist (`spec` gate). | | `--mutation MODE` | `off` | `off`, `diff`, or `full`. Runs `cargo mutants` (`mutation` gate). | | `--llm MODE` | `off` | `off` or `on`. Runs an LLM review of `--spec`, or of `--intent` when there is no spec. See [LLM providers](../how-to/llm.md). | | `--intent TEXT` | none | What the change is meant to do. Stored on the scorecard. With `--llm on` and no `--spec`, this is enough to run the review. | | `--budget-seconds N` | `120` | Wall-clock budget for pack commands, in seconds. One clock for the whole run. A command still running at the deadline is killed, including its child processes, and the report records the timeout. | | `--config PATH` | see [config](config.md#where-sc-looks) | Config file to use. | | `--quiet` | off | Suppress the progress spinner on stderr. | While analysis runs, a spinner with the current step and elapsed time animates on stderr when it is a terminal (silent when piped, in CI, with `TERM=dumb`, or from `sc-mcp`); stdout is unchanged, so JSON/SARIF pipes are unaffected. `NO_COLOR` preserves this uncolored progress display, including step and elapsed-time updates during tests and coverage. Use `--quiet` to hide progress. Command-line flags override the matching config keys. ### sc config Print or create `~/.config/sc/analyzer.toml`. See [Configure](../how-to/config.md). | Command | Meaning | |---|---| | `sc config path` | Print the user config path. | | `sc config init` | Create `~/.config/sc` if needed and write a starter from `analyzer.toml.example`. Does not replace an existing file. | | `sc config init --force` | Replace an existing file. | ### sc setup Installs the Scorecard agent skill for the legacy agent locations and registers the `sc-mcp` server for the current user. It leaves existing skill files unchanged. No flags. See [Agents and MCP](../how-to/agents.md). ### sc skills install | Flag | Default | Meaning | |---|---|---| | `--agent AGENT` | `all` | Agent identifier, `all`, or `detected`. Run `sc skills install --help` for supported values. | | `--check` | off | Check selected targets without writing. Exits 1 if a skill is missing or differs from the embedded copy. Cannot be combined with `--force`. | | `--force` | off | Replace an existing skill, including a user-edited copy. | Existing files that differ from the embedded skill are preserved unless `--force` is set. See [Agents and MCP](../how-to/agents.md) for destinations and automatic installation. ### sc help Print general help with `sc help`, or command help with `sc help analyze`. ### Output | `--format` | stdout | With `--out PATH` | |---|---|---| | `pretty` | Terminal layout | Same text to `PATH` | | `json` | JSON scorecard | Same JSON to `PATH` | | `md` | Markdown | Same Markdown to `PATH` | | `sarif` | SARIF 2.1.0. Only `secrets.*` findings are level `error`; every other rule is `warning` or `note`. | Same SARIF to `PATH` | | `html` | Self-contained HTML page | Same HTML to `PATH` | | `all` | JSON, then Markdown | `.json`, `.md`, `.sarif`, and `.html` files next to `PATH` | With `--out`, stdout is the short summary and the third column is what is written to the file. Without `--out`, stdout is the second column. Every run also writes `.sc/last-scorecard.json`. SARIF stores the report verdict in `runs[0].properties.scorecardVerdict` and each gate's `pass` and `enforced` flags in `runs[0].properties.scorecardGates`. JSON includes the selected gates in `fail_on`. SARIF stores the same list in `runs[0].properties.fail_on`. If an enforced gate fails but is not selected, `sc` writes one stderr warning with the failed gate names. Scorecard excludes its saved report, cache and coverage files under `.sc/`, and report files selected by `--out` from the dirty-tree check. Other changes set `git.dirty` and appear in `git.dirty_paths`. ### Exit status | Status | Meaning | |---|---| | 0 | No selected gate failed. `--fail-on none` deliberately selects no gates. If an enforced gate failed, `sc` names it in one stderr warning and the report verdict remains `fail`. | | 1 | A selected gate failed | | 2 | `sc` could not run: missing path, invalid config (including an empty `--fail-on` or `gates.fail_on`), a required toolchain missing, or compile or tests timed out | A skipped coverage, mutation, or LLM engine never causes exit 2. ### Environment | Variable | Effect | |---|---| | `NO_COLOR` | Turns off color | | `CLICOLOR_FORCE=1` | Forces color | # Appendix: docs/reference/gates.md (from the Scorecard repo) ## Gates A gate is a pass or fail check. Any failed enforced gate makes the report verdict `FAIL`; advisory gates are reported without affecting the verdict. `gates.fail_on` (or `--fail-on`) selects which enforced gate failures set process exit 1. Naming an advisory gate can enforce it; leaving an already enforced gate out of the exit list does not hide its failure from the report. | Gate | Enforced by default | Fails when | Rules | |---|---|---|---| | `types` | yes | The project does not build or type-check | `compile.error`, `compile.failed` | | `tests` | yes | A test fails | `test.failed` | | `crap` | yes | A function's CRAP score is over `gates.crap_threshold`, or a complex function has 0% coverage | `crap.over_threshold`, `complexity.untested` | | `secrets` | yes | A token or private key is in the tree, including PEM split across source string lines or YAML list items | `secrets.*` | | `lint` | yes | The linter reports problems | `lint.failed` | | `sca` | no | An import is undeclared, hallucinated, or not classified | `sca.undeclared_dependency`, `sca.hallucinated_import`, `sca.import_unresolved` | | `spec` | only with `--spec` | A file or public item named in the spec is missing | `spec.missing_file`, `spec.missing_item`, `spec.llm_gap` | | `mutation` | only with `--mutation` | A mutant survives the tests | `mutation.survivor` | | `html` | yes, web pack | The HTML does not parse cleanly | `html.*` | | `links` | no | A link points at a missing file in the tree | `links.missing` | | `a11y` | no | A static accessibility check fails | see [accessibility](../a11y.md) | When a function has no coverage record and no measured function fails, the `crap` gate is advisory for that run: the gate shows `advisory` and the reason says some functions have no coverage record and were not scored. A measured function still fails the gate when it is over the CRAP threshold, or when its CC is at or above `gates.new_fn_untested_cc` with 0% coverage (`complexity.untested`), even if another function has no coverage record. A function with no coverage record is not scored, so it cannot fail the gate itself. Node and Python record a file the tests never load at 0%. With other packs' coverage tools, such a file can have no record. To enforce an advisory gate, add it to the list: ```bash doctest sc analyze . --fail-on types,tests,crap,secrets,lint,sca >/dev/null ``` or in `analyzer.toml`: ```toml [gates] fail_on = ["types", "tests", "crap", "secrets", "lint", "sca"] ``` # Appendix: docs/reference/config.md (from the Scorecard repo) ## Config reference `sc` works without a config file. To change a default, create `analyzer.toml` at the root of the project you analyze. [analyzer.toml.example](../../analyzer.toml.example) lists every key with its default. ### Where sc looks 1. `--config PATH`, if given. 2. `analyzer.toml` in the analyzed directory (the `PATH` given to `sc analyze`). Parent and sub-directories are not searched. 3. `~/.config/sc/analyzer.toml`. Only the first file found is read. Command-line flags override the matching keys. Unknown keys are ignored without a warning, so check spelling against the tables below. ### Keys | Key | Default | Meaning | |---|---|---| | `install_agent_skills` | `true` | Allow installer hooks to install the Scorecard skill for detected agents. Hooks read this from the user config `~/.config/sc/analyzer.toml`. Set it to `false` to opt out. `SCORECARD_NO_AGENT_SKILLS=1` also skips automatic installation. | | `pack` | detected | Language pack when the tree has more than one marker. Same values as `--pack`. | | `toolchain` | empty | Rustup channel for check, test, coverage, and lint (`1.85.0`, `stable`, …). When set, Cargo uses that channel. When empty, a `rust-toolchain.toml` / `rust-toolchain` at or above the project clears an inherited `RUSTUP_TOOLCHAIN` so rustup reads the file; with no file, the inherited variable is left alone. | #### `[gates]` | Key | Default | Meaning | |---|---|---| | `gates.fail_on` | `["types", "tests", "crap", "secrets", "lint"]` | [Gates](gates.md) whose failures set exit 1. Set it to `["none"]` to suppress gate failures from the exit code. An empty list, blank value, mixed `none` list, or unknown gate is an error (exit 2). Enforced failures remain in the report verdict. | | `gates.crap_threshold` | `30` | A function with a [CRAP](../crap.md) score above this fails the `crap` gate. | | `gates.new_fn_untested_cc` | `15` | A function at or above this complexity with 0% measured coverage fails the `crap` gate (`complexity.untested`). With `--diff`, only new functions count. | #### `[scope]` | Key | Default | Meaning | |---|---|---| | `scope.exclude` | `[]` | Additional paths to leave out of scans. A matching exclusion wins over `scope.include_generated`. | | `scope.include_generated` | `[]` | Paths that may reopen built-in generated or vendored directory skips, or generated file markers. `.gitignore` rules still apply. | To exclude committed test certificates in a public repository, add a narrow path pattern: ```toml [scope] exclude = ["tests/certs/**"] ``` This skips files under `tests/certs/` from scans. Keep the pattern limited to test fixtures so real credentials elsewhere remain detectable. All recursive scans respect `.gitignore`, including nested files. They also skip `.git`, `.hg`, `.svn`, `.sc`, and other dot-directories. The built-in generated and vendored directories are `target`, `dist`, `build`, `out`, `cdk.out`, `coverage`, `generated`, `vendor`, `node_modules`, `__pycache__`, `.next`, `.nuxt`, `.output`, `.turbo`, `.parcel-cache`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.tox`, `.venv`, `venv`, `.gradle`, `.yarn`, `bin`, `obj`, `Pods`, `Carthage`, `bower_components`, and `storybook-static`. The secrets scan is different: in a git work tree it uses `git ls-files` (tracked and non-ignored untracked files), so committed secrets under those directories are still checked. Outside git it follows the directory and ignore rules above. Secret scanning includes generated files within the configured scope. Source analysis skips files with a generator header in the leading comments within the first 16 KiB. A comment line must start with `@generated` as a separate word, or follow `Code generated ... DO NOT EDIT.` on one line. Mentions in prose, strings, or comments after code do not count. Hash comments are recognized for file types that use them, including Python and shell scripts. C/C++ preprocessor directives and Rust attributes end the header. Add an explicit path pattern to `scope.include_generated` to scan one of these files or reopen one of the listed directories. If `.gitignore` also excludes the path, remove that ignore rule or add a negation there. `scope.exclude` always takes precedence. Every report lists generated files skipped by source analysis and explains how to include them. Generated or vendored files analyzed through an explicit include are listed separately with advice to add them to `scope.exclude`. Secrets scanning includes both groups. If exclusions leave no files for secrets scanning, `secrets.empty_scope` fails the secrets gate. Restore the intended scan scope before treating the report as a completed check. #### `[engines]` | Key | Default | Meaning | |---|---|---| | `engines.coverage` | `true` | Run tests with coverage. `false` skips coverage; no CRAP scores are reported and nothing is treated as 0% coverage. | | `engines.sca` | `true` | Check that imports are declared dependencies. | | `engines.perf` | `false` | When true, Rust nested loops and `.clone()` inside a loop become `perf.*` findings (disposition `ignore`). Paths under `tests/` or `benches/`, `src/test.rs` / `src/tests.rs`, inline `#[cfg(test)]` / `#[…::test]` items, files from an out-of-line `#[cfg(test)] mod name;` (whole-tree lookup, including `--diff`), and children under that module's directory, are not scanned. Off by default so ordinary Rust does not change the report. | #### `[commands]` | Key | Default | Meaning | |---|---|---| | `commands.lint` | `cargo clippy --workspace` | Lint command for the Rust and `command` packs. At a workspace root, Cargo Clippy checks every member; analyzing a member keeps lint scoped to that package. Empty skips lint. Other packs use their own linter; see [packs](../packs.md). Add `-- -D warnings` to fail on warnings at the workspace root: `cargo clippy --workspace -- -D warnings`. | #### `[mutation]` | Key | Default | Meaning | |---|---|---| | `mutation.mode` | `"off"` | `off`, `diff`, or `full`. `--mutation` overrides. | | `mutation.max_mutants` | `50` | With more mutants than this in a diff, mutation testing is skipped. | | `mutation.budget_seconds` | `180` | Time limit for the mutation run. | #### `[llm]` See [LLM providers](../how-to/llm.md) for setups. | Key | Default | Meaning | |---|---|---| | `llm.enabled` | `false` | Run the LLM review without `--llm on`. With no `--spec`, `--intent` is enough. | | `llm.model` | `"qwen2.5-coder"` | Model name for the chosen backend. | #### `[html]`, `[links]`, `[a11y]` For the web pack and, for `a11y`, JSX and TSX files. | Key | Default | Meaning | |---|---|---| | `html.enforce` | `"auto"` | `auto` enforces the `html` gate when `fail_on` is the default list or names `html`. `on` or `off` force it. | | `links.enforce` | `false` | `true` makes a missing internal link fail the run. | | `a11y.enforce` | `false` | `true` makes accessibility findings fail the run. | | `a11y.disable` | `[]` | Accessibility rule ids to skip, such as `"contrast"`. See [accessibility](../a11y.md). | # Appendix: docs/report.md (from the Scorecard repo) ## Reading the report `sc analyze` prints the terminal layout when stdout is a terminal and JSON otherwise. `--format` picks one explicitly; see [CLI](reference/cli.md#output). Every run also saves the JSON to `.sc/last-scorecard.json`. ### Terminal layout ```text sc 0.1.6 . python 99b879c dirty scope tree changed paths: src/vectorvault/config.py ``` The header: `sc` version, analyzed path, [pack](packs.md), git commit, and Git state. A dirty report names up to three changed paths on the next line; JSON includes every path in `git.dirty_paths`. Dirty paths are relative to the repository root, matching `git status`, including when the analyzed project is in a subdirectory. Scorecard's saved report, parse cache, coverage output, and earlier outputs from `--out` do not make a clean checkout dirty. Scope is `tree` (everything), `diff` (`--diff`), or `paths` (`--paths`). A `diff` report lists the changed paths, relative to the project directory, and a one-line count of the other source paths still in the tree. A crate in a subdirectory of the repository is scored the same way as a crate at the repository root. ```text PASS 2 advisory gates failing ``` The verdict. `PASS` means every enforced gate passed. Advisory gates can still fail under a `PASS`; the note counts them. `FAIL` means at least one enforced gate failed. `--fail-on` controls the process exit code, so it can be 0 while the report says `FAIL` if an enforced failure is not selected. In that case, `sc` writes one stderr warning with the failed gate names. Naming an advisory gate can enforce it; omitting an already enforced gate from the exit list does not change the report verdict. `--fail-on none` selects no exit gates and leaves enforced failures visible: terminal, Markdown, and HTML still show `FAIL`, and stderr names the failed gates, even though the process exits 0. `REPORT ONLY` is reserved for reports where no provided gate is enforced and at least one advisory gate fails. An empty or blank selection is invalid. Omit `--fail-on` and `gates.fail_on` to use the defaults. ```text gates ✓ types enforced ✗ tests enforced pytest is not installed ✗ sca advisory 16 undeclared dependencies (advisory; does not fail the process) ``` One row per gate: result, `enforced` or `advisory`, and the reason when it did not pass. Enforced gates determine the report verdict. `--fail-on` selects which enforced failures set exit 1. [Gates](reference/gates.md) lists the gates and how to enforce an advisory one. ```text scores correctness 1.00 [##########] maintainability 0.00 [..........] security 0.20 [##........] ``` Each score starts at 1.00. Every error finding in that area subtracts 0.25 and every warning 0.05, down to 0. Scores are a summary; the gates decide the verdict. | Score | Lowered by findings from | |---|---| | correctness | compile, tests, lint, html, config | | efficiency | perf | | security | secrets, sca (undeclared dependencies) | | a11y | a11y | | maintainability | everything else: complexity, coverage, CRAP, mutation, spec, links | ```text worst crap threshold 30 CRAP CC COV SYMBOL LOCATION 75.7 9 6% Config.from_ssm src/vectorvault/config.py:56 ``` The functions with the highest [CRAP](crap.md) score: cyclomatic complexity (CC) weighted by missing test coverage (COV). A score above the threshold fails the `crap` gate. When coverage was not measured at all, the table is empty (`(none)`) and a note says no CRAP scores are reported and nothing is treated as 0% coverage. ```text findings error crap.over_threshold src/vectorvault/config.py:56 Config.from_ssm Add tests covering branches or split the function ``` Each finding: severity, [rule id](reference/rules.md), location, and a suggested fix. The terminal shows the first findings and a count of the rest; the full list is in `.sc/last-scorecard.json` and in any `--out` report. ```text engines run: compile, tests, crap, sca, secrets, lint engines skipped: complexity, mutation, llm exit 0: enforced gates passed ``` Which checks ran, which did not, and the exit status. An engine is skipped when its tool is missing or when it is off by default (`mutation`, `llm`, `spec`). A skip caused by a missing tool also appears as an `engine.unavailable` finding with the reason. ### Dispositions Each JSON finding has a `disposition` that tells an agent what to do: | Disposition | Meaning | |---|---| | `fix` | A real problem. Change the code. | | `ask` | Needs a human decision, such as installing a tool or declaring a dependency. | | `ignore` | Informational, such as a `perf.*` hint. It stays in this report and is left out of the SARIF upload, so a pull-request check does not annotate it. | ### JSON Top-level fields of `.sc/last-scorecard.json` and `--format json`: | Field | Contents | |---|---| | `verdict` | `pass` or `fail` | | `fail_on` | Gate ids whose failures set exit 1; an empty list means `none` was selected | | `pack`, `repo`, `scope` | What was analyzed | | `git` | `head`, `dirty`, and `dirty_paths` (all changed paths relative to the repository root; omitted when clean) | | `gates` | `id`, `pass`, `enforced`, and `reason` per gate | | `scores` | The score rows above | | `metrics` | Changed lines and files, `coverage_changed`, `crap_max`, `crap_over_threshold`, `hallucinated_imports` | | `crap` | Threshold and worst functions with `cc`, `coverage`, and `crap` | | `mutation` | `status`, `score`, `killed`, `survived`, `timeout`, `unviable` when `--mutation` is not `off` | | `test_value` | Present when `--mutation` is not `off` or when `--diff` runs the test-weakening engine; mirrors `mutation` and reports `weakening` (see `docs/design/test-value.md`) | | `findings` | `rule`, `engine`, `severity`, `file`, `span`, `symbol`, `message`, `suggested_action`, `disposition`, `evidence` | | `engines_run`, `engines_skipped` | As in the terminal footer | | `runs` | Every command `sc` ran, with exit code and duration | `verdict` is `fail` when any enforced gate fails. The `fail_on` list selects the process exit code; it does not hide failures from enforced gates. SARIF stores the selected list in `runs[0].properties.fail_on`. List the findings an agent should fix: ```bash doctest sc analyze . >/dev/null jq -r '.findings[] | select(.disposition == "fix") | "\(.rule) \(.file) \(.message)"' .sc/last-scorecard.json ``` ### Other formats ```bash doctest sc analyze . --format all --out sc-report >/dev/null ls sc-report.json sc-report.md sc-report.sarif sc-report.html ``` `--format all --out NAME` writes JSON, Markdown, SARIF, and a self-contained HTML page next to `NAME`. The HTML page is described in [HTML report](html-report.md). SARIF is for GitHub code scanning; see [CI](how-to/ci.md). GitHub counts every SARIF `error` as a high severity security alert, so only `secrets.*` findings use level `error`. A failing test or missing coverage report is level `warning` in SARIF, stays severity `error` in the JSON report, and still fails its gate. ### The `.sc/` directory | Path | Contents | |---|---| | `.sc/last-scorecard.json` | The last report. `sc-mcp` reads it. | | `.sc/cache/` | Parse cache | | `.sc/coverage/` | Coverage reports written by the test runs | It is safe to delete. Add `.sc/` to `.gitignore`. Generated source files appear in `generated_files_warning.skipped_files` when their leading generator header excludes them from source analysis. The Markdown, HTML, and terminal reports list these paths under "Generated files skipped by source analysis" with the `scope.include_generated` setting needed to analyze them. Secrets scanning still checks these files. The existing `files` list identifies generated paths that source analysis included. SARIF carries this section in `runs[0].properties.generated_files_warning`. ![Report naming a generated file skipped by source analysis](images/generated-skipped-report.png) # Appendix: docs/troubleshooting.md (from the Scorecard repo) ## Troubleshooting Each section starts with what the report says. ### `tests: pytest is not installed` When the project has `uv.lock` or a `.venv` directory, `sc` runs `uv run --extra dev --with pytest-cov pytest`. If `uv` itself is not installed, the finding says `uv is not installed`. Install `uv`, then create the environment once: ```bash uv venv && uv pip install -e ".[dev]" ``` or activate your own virtualenv, with pytest installed, before running `sc`. ### Coverage not measured The `crap` gate shows `advisory` with `some functions have no coverage record and were not scored`, and the worst-CRAP table is empty (`(none)`). A `coverage.missing` warning says coverage was not measured and how to collect it. Tests did not run, or the coverage tool for the pack is missing: | Pack | Install | |---|---| | Rust | `rustup component add llvm-tools && cargo install cargo-llvm-cov` | | Python | `pytest-cov` (added automatically when `sc` runs tests through `uv`) | | Node | `c8` | | Go | nothing; uses `go test -coverprofile` | | Bash | `kcov` | | C++ | `lcov` | | PHP | PHPUnit with `pcov` | Until coverage runs, no CRAP scores are reported and nothing is treated as 0% coverage; the `crap` gate stays advisory. ### `types: compiler errors` with a rustup message When `analyzer.toml` sets `toolchain`, Cargo check, test, coverage, and lint use that channel. When a `rust-toolchain.toml` or `rust-toolchain` is at or above the project, `sc` clears an inherited `RUSTUP_TOOLCHAIN` so rustup reads the file (including components). With no pin, an inherited `RUSTUP_TOOLCHAIN` is left alone. Install the channel with `rustup toolchain install`. The `scorecard-tools` Docker image only has stable; a non-stable `analyzer.toml` pin fails there until that channel is in the image. `rustup could not choose a version of cargo to run` means no default Rust toolchain: ```bash rustup default stable ``` ### `several language packs match (rust, node)` Exit 2 when more than one language marker matches and more than one of those languages has source files, or none of them do. A larger file count does not choose a pack. Pass `--pack rust`, or add `pack = "rust"` to `analyzer.toml`. Headers do not count as source and do not turn a Python project into an ambiguous tree. The terminal may cut this message short; `.sc/last-scorecard.json` has the full text. An empty tree says to set `pack` in `analyzer.toml` or pass `--pack`. ### The header says `dirty` The report names up to three uncommitted or untracked paths below the Git status; the JSON lists them in `git.dirty_paths`. Scorecard's saved report, cache and coverage files under `.sc/`, and files selected by `--out`, are excluded. Add `.sc/` to `.gitignore` to keep Scorecard's generated files out of Git status too. ### `N more, see --out report` The terminal lists the first findings only. All of them are in `.sc/last-scorecard.json`, or in the file written by `--out`. `--format html --out report.html` gives a browsable page. ### LLM review skipped The `llm` engine is listed as skipped with an `engine.unavailable` finding that gives the reason: | Reason | Fix | |---|---| | `llm is on, and no readable --spec file was given` | Pass `--spec FILE` | ### `sca.hallucinated_import` for a package I use The import is not declared in `pyproject.toml`, `requirements.txt`, `setup.cfg`, or `setup.py` `install_requires`. This includes packages installed only as a dependency of another package, such as `botocore` through `boto3`. Declare it, or leave the warning: `sca` does not fail the run unless `fail_on` names it. A sentence in a docstring, `_typeshed`, and an import inside `try` / `except ImportError` are not this warning. ### Exit 2 `sc` could not run. The report says why: | Cause | Fix | |---|---| | The path does not exist | Check the `PATH` argument | | `config.invalid` | Fix `analyzer.toml` at the path in the message, for example an unknown gate in `fail_on` | | Several language packs match | See [above](#several-language-packs-match-rust-node) | | A required toolchain is missing | Install the compiler for the pack, or use the `scorecard-tools` image ([packs](packs.md)) | | Compile or tests timed out | Raise `--budget-seconds` (default 120) | ### A config change has no effect `sc` reads `analyzer.toml` only from the root of the analyzed directory, and ignores unknown keys. Check the file location and the key names in the [config reference](reference/config.md).