CRAP Analyzer
CRAP (Change Risk Anti-Patterns) flags functions that are both complex and poorly tested — the worst-risk code to ship.
CRAP(m) = comp(m)² × (1 − cov(m))³ + comp(m)
This skill scopes analysis to a diff, ranks findings worst-first, and turns each finding into a concrete refactor + test-stub proposal.
When DAE runtime quality gates are active, CRAP evidence is mandatory by default after implementation-affecting edits. Write machine-readable gate evidence to features/<feature>/evidence/quality/crap.json and validate it with python3 plugins/engineer/scripts/dae_guard.py quality-verify before completion or release.
Workflow
Determine the diff. First that works:
gh pr diff / gh pr diff <number> if a GitHub PR is referenced.
git diff --merge-base <main-branch> — detect the main branch with git remote show origin | grep 'HEAD branch'.
git diff --cached for staged changes.
git diff HEAD~N..HEAD for a named commit range.
- Explicit file list if the user names files.
Pipe the diff to scripts/compute_crap.py --diff -.
Locate or generate coverage. Let the script auto-discover coverage files first (lcov, Cobertura, JaCoCo, Clover, Go coverage.out, coverage.py JSON). If nothing is found, follow references/coverage-discovery.md to detect the toolchain, then ask before running it. On decline, proceed with coverage=0% and flag it in the report header.
Run the analyzer.
python3 <skill-dir>/scripts/compute_crap.py --diff - --repo-root <repo> --threshold <N> --format both
Default threshold is 20. Read .crap-analyzer.json at repo root if present and pass its threshold through. Full flag list and output JSON shape: references/script-reference.md.
For DAE quality gates, use the strict policy thresholds unless project config explicitly relaxes them with audit fields:
- fail at
max_crap_score >= 30;
- warn at
max_crap_score >= 20;
- require explicit coverage evidence in strict mode;
- fail if high-risk findings exceed 0.
Evidence shape:
{
"schema_version": 1,
"gate": "crap",
"tool": "crap-analyzer",
"status": "PASS",
"generated_at": "2026-05-22T00:00:00Z",
"feature": "features/001-demo",
"changed_files": ["src/app.py"],
"coverage_source": "coverage.xml",
"thresholds": {
"max_crap_score": 30,
"warn_crap_score": 20,
"missing_coverage_policy": "assume_zero_and_fail_if_threshold_exceeded",
"max_high_risk_findings": 0
},
"summary": {
"changed_functions": 1,
"max_crap_score": 8.0,
"high_risk_findings": 0
},
"findings": []
}
Present the report. Show the markdown table. For each finding, link file:start_line. If more than ~8 findings, surface the top 5 and mention the rest.
Propose fixes per finding, worst-first. For each function above threshold:
- Read the function. Score alone is not enough — look at the code.
- Refactor proposal. Extract-method diff or guard-clause rewrite. Patterns: references/refactor-patterns.md.
- Test stubs. Cover each uncovered branch in the repo's test framework. Templates: references/test-stub-templates.md.
- Respect codebase conventions. Read nearby code and match naming, error handling, DI, async patterns.
When len(findings) >= 3, dispatch one subagent per finding in a single message — per-finding work is independent so parallelizing drops wall-clock from O(n) to O(1). Prompt template + aggregation rules: references/subagent-prompt.md. After subagents return, sort by CRAP descending and sanity-check every "safe to auto-apply" claim against step 7.
Present the wrap-up menu. Dispatch refactor / test-stub work via prompt the user. Menu structure and apply loop: references/wrap-up-menu.md. "Safe refactor" = pure extract-method with no behavior change (same inputs → same outputs, same side effects, same order). One Edit per action; confirm each before applying.
Never auto-apply:
- Changes that reorder side effects or async operations (
await, .then, subscribe, promise chains, RxJS / coroutine / goroutine ordering).
- Changes to constructors, initializers, or lifecycle hooks.
- Changes to reactive / stream operator ordering.
- Template / markup / view changes — the script doesn't analyze those.
Report format
### 1. `file.py:42` — `do_thing` (CRAP 240)
**Why it's flagged:** complexity 15, coverage 0% (no tests touching body).
**Refactor proposal:**
<unified diff or code block>
**Test stubs to add:**
<framework-appropriate test block>
Keep each section tight — one paragraph of "why", diff, stubs. No preamble.
Config file
Optional .crap-analyzer.json at repo root:
{ "threshold": 20 }
Read if present, pass --threshold to the script. No other keys for now.
When not to use this skill
- Analyzing a full codebase unprompted — scope is diff-only by design.
- Running as a standalone CI replacement. In DAE runtime, the analyzer produces evidence and
engineer owns the cross-plugin blocking gate.
- Files the script doesn't recognize. Supported extensions listed in references/script-reference.md; binaries, generated code, and config files are skipped automatically.
1---2name: crap-analyzer3description: Analyze changed code for complex, under-tested functions using CRAP scoring; produce ranked refactor and test recommendations.4---56# CRAP Analyzer78CRAP (Change Risk Anti-Patterns) flags functions that are both **complex** and **poorly tested** — the worst-risk code to ship.910```11CRAP(m) = comp(m)² × (1 − cov(m))³ + comp(m)12```1314This skill scopes analysis to a diff, ranks findings worst-first, and turns each finding into a concrete refactor + test-stub proposal.1516When DAE runtime quality gates are active, CRAP evidence is mandatory by default after implementation-affecting edits. Write machine-readable gate evidence to `features/<feature>/evidence/quality/crap.json` and validate it with `python3 plugins/engineer/scripts/dae_guard.py quality-verify` before completion or release.1718## Workflow19201. **Determine the diff.** First that works:21 - `gh pr diff` / `gh pr diff <number>` if a GitHub PR is referenced.22 - `git diff --merge-base <main-branch>` — detect the main branch with `git remote show origin | grep 'HEAD branch'`.23 - `git diff --cached` for staged changes.24 - `git diff HEAD~N..HEAD` for a named commit range.25 - Explicit file list if the user names files.2627 Pipe the diff to `scripts/compute_crap.py --diff -`.28292. **Locate or generate coverage.** Let the script auto-discover coverage files first (lcov, Cobertura, JaCoCo, Clover, Go `coverage.out`, coverage.py JSON). If nothing is found, follow [references/coverage-discovery.md](references/coverage-discovery.md) to detect the toolchain, then ask before running it. On decline, proceed with coverage=0% and flag it in the report header.30313. **Run the analyzer.**32 ```bash33 python3 <skill-dir>/scripts/compute_crap.py --diff - --repo-root <repo> --threshold <N> --format both34 ```35 Default threshold is 20. Read `.crap-analyzer.json` at repo root if present and pass its `threshold` through. Full flag list and output JSON shape: [references/script-reference.md](references/script-reference.md).3637 For DAE quality gates, use the strict policy thresholds unless project config explicitly relaxes them with audit fields:38 - fail at `max_crap_score >= 30`;39 - warn at `max_crap_score >= 20`;40 - require explicit coverage evidence in strict mode;41 - fail if high-risk findings exceed 0.4243 Evidence shape:4445 ```json46 {47 "schema_version": 1,48 "gate": "crap",49 "tool": "crap-analyzer",50 "status": "PASS",51 "generated_at": "2026-05-22T00:00:00Z",52 "feature": "features/001-demo",53 "changed_files": ["src/app.py"],54 "coverage_source": "coverage.xml",55 "thresholds": {56 "max_crap_score": 30,57 "warn_crap_score": 20,58 "missing_coverage_policy": "assume_zero_and_fail_if_threshold_exceeded",59 "max_high_risk_findings": 060 },61 "summary": {62 "changed_functions": 1,63 "max_crap_score": 8.0,64 "high_risk_findings": 065 },66 "findings": []67 }68 ```69704. **Present the report.** Show the markdown table. For each finding, link `file:start_line`. If more than ~8 findings, surface the top 5 and mention the rest.71725. **Propose fixes per finding, worst-first.** For each function above threshold:73 - **Read the function.** Score alone is not enough — look at the code.74 - **Refactor proposal.** Extract-method diff or guard-clause rewrite. Patterns: [references/refactor-patterns.md](references/refactor-patterns.md).75 - **Test stubs.** Cover each uncovered branch in the repo's test framework. Templates: [references/test-stub-templates.md](references/test-stub-templates.md).76 - **Respect codebase conventions.** Read nearby code and match naming, error handling, DI, async patterns.7778 When `len(findings) >= 3`, dispatch one subagent per finding in a **single message** — per-finding work is independent so parallelizing drops wall-clock from O(n) to O(1). Prompt template + aggregation rules: [references/subagent-prompt.md](references/subagent-prompt.md). After subagents return, sort by CRAP descending and sanity-check every "safe to auto-apply" claim against step 7.79806. **Present the wrap-up menu.** Dispatch refactor / test-stub work via `prompt the user`. Menu structure and apply loop: [references/wrap-up-menu.md](references/wrap-up-menu.md). "Safe refactor" = pure extract-method with no behavior change (same inputs → same outputs, same side effects, same order). One Edit per action; confirm each before applying.81827. **Never auto-apply:**83 - Changes that reorder side effects or async operations (`await`, `.then`, `subscribe`, promise chains, RxJS / coroutine / goroutine ordering).84 - Changes to constructors, initializers, or lifecycle hooks.85 - Changes to reactive / stream operator ordering.86 - Template / markup / view changes — the script doesn't analyze those.8788## Report format8990```markdown91### 1. `file.py:42` — `do_thing` (CRAP 240)9293**Why it's flagged:** complexity 15, coverage 0% (no tests touching body).9495**Refactor proposal:**96<unified diff or code block>9798**Test stubs to add:**99<framework-appropriate test block>100```101102Keep each section tight — one paragraph of "why", diff, stubs. No preamble.103104## Config file105106Optional `.crap-analyzer.json` at repo root:107108```json109{ "threshold": 20 }110```111112Read if present, pass `--threshold` to the script. No other keys for now.113114## When not to use this skill115116- Analyzing a full codebase unprompted — scope is diff-only by design.117- Running as a standalone CI replacement. In DAE runtime, the analyzer produces evidence and `engineer` owns the cross-plugin blocking gate.118- Files the script doesn't recognize. Supported extensions listed in [references/script-reference.md](references/script-reference.md); binaries, generated code, and config files are skipped automatically.