Customization guide
How to tune CleanLens for your team: recipes for strictness, noise, scoring, identities, history, CI
This guide explains how to tune CleanLens to your team's needs: what to change, where, and what effect it has. For the exact list of keys and their types, see 10 — Configuration reference.
Contents
- Choose a starting point
- Rules: strictness and noise
- What to analyze: exclusions
- Scoring
- Developer identities
- History window
- Editor experience
- CI quality gates
- Personal overrides
- Ready-made team profiles
- Calibrating on your own repository
- Customizing in source code
- Keep VS Code and CLI in sync
1. Choose a starting point
Run CleanLens: Initialize Project and pick the preset that matches the repository:
| Your project | Preset | Why |
|---|---|---|
| Node, TypeScript, plain JS | javascript | Base rules and excludes |
| React / Next.js front end | react | Also skips stories, __tests__, *.test.*, *.spec.* |
| Python library or service | python | Also skips caches, tests/, test_*.py, conftest.py |
| Django | django | Python excludes + migrations and manage.py; maxFileLines 500 |
| Monorepo (e.g. Django + React) | django or python, then add the React excludes by hand | One config covers the whole repository |
After that, edit the generated .clean-code-tracker.json. The preset is only
used to create the file; later changes are yours.
2. Rules: strictness and noise
The three levers per rule
| Lever | Effect on findings | Effect on the score |
|---|---|---|
enabled: false | None reported | None |
limit / options | Fewer (looser) or more (stricter) | Indirect, through the count |
severity | Same findings | Direct: weight 1 / 3 / 5 / 8; also the editor diagnostic level |
Rule of thumb: change limit when a rule fires on code you consider fine;
change severity when it fires correctly but matters less (or more) to you;
disable it when it is mostly wrong for your codebase.
Strictness profiles
| Rule | Lenient | Default | Strict |
|---|---|---|---|
maxFunctionLines.limit | 80 | 40 | 25 |
maxFileLines.limit | 800 | 400 | 250 |
maxParameters.limit | 7 | 5 | 3 |
maxComplexity.limit | 15 | 10 | 7 |
maxNestingDepth.limit | 6 | 4 | 3 |
detectDuplicateCode.options.minLines | 10 | 6 | 4 |
requireSingleResponsibility.options.maxMethods | 20 | 10 | 7 |
requireDocumentationForComplexCode.options.complexityThreshold | 25 | 15 | 10 |
Remember that requireSingleResponsibility also fires for functions over
1.5 × maxFunctionLines.limit and 1.5 × maxComplexity.limit, so
changing those two limits moves it too.
Reducing noise
Most noise comes from a few heuristic rules. Try these in order:
requireErrorHandling: if errors are handled centrally (Express error middleware, Django views, a React Query layer), lower it tolowor disable it.json "requireErrorHandling": { "enabled": true, "severity": "low" }forbidDebugStatements: for CLI tools and scripts, whereprint/console.logis the real output, exclude those folders (section 3) instead of disabling it for the whole project.requireDocumentationForComplexCode: raisecomplexityThresholdto 20+, or disable it if your team documents with regular comments.requireSingleResponsibility: raisemaxMethodsfor frameworks with large, cohesive classes (Django admin, class components, service objects).- Python
maxNestingDepth: Python depth counts theclassanddeflevels, so a method body already starts at depth 2. For Python-heavy repositories uselimit: 5or6.
Emphasizing what matters to you
- Security first: keep
forbidHardcodedSecretsatcriticaland raiseforbidEmptyCatchBlockstocritical. - Maintainability first: raise
maxComplexityandmaxNestingDepthtocritical, lower naming and debug rules tolow. - Onboarding a legacy codebase: keep limits loose and severities low for structure rules at first; tighten every few sprints.
3. What to analyze: exclusions
Exclude anything that is not code your team writes and maintains. It makes the analysis faster and fairer.
Where to add patterns
| Where | Use for |
|---|---|
exclude in .clean-code-tracker.json | Team-wide exclusions (commit it) |
.gitignore | Already read automatically |
.gitattributes with linguist-generated | Generated files (also helps GitHub's language stats) |
cleanlens.exclude (VS Code) | Workspace/personal additions, extension only |
exclude in .clean-code-tracker.local.json | Personal additions, both front ends |
Common patterns
"exclude": [
"**/__tests__/**", "*.test.*", "*.spec.*", // tests
"**/fixtures/**", "**/__mocks__/**", // test data
"scripts/**", "tools/**", // one-off scripts
"**/third_party/**", "public/vendor/**", // vendored code
"**/*.d.ts", // type declarations
"docs/**/*.js" // examples in docs
](Comments are shown for explanation only; the real file must be plain JSON.)
Pattern tips:
folder/**matches that folder at any depth. Use/folder/**to match only at the root.*.extwithout a/matches the file name anywhere.- No negation (
!), no{a,b}, no[abc]. Write separate patterns instead.
Generated files
Keep excludeGenerated: true (default). To mark generated files explicitly:
# .gitattributes
src/api/client.ts linguist-generated
proto/*.ts linguist-generatedOr put // @generated / # DO NOT EDIT in the first five lines of the file.
Should tests be analyzed?
- Exclude them (
react/pythonpresets do) if you want scores to reflect production code only. - Include them if test quality matters to you. Consider lowering
detectDuplicateCodetolow: tests are often repetitive by design.
Migrations
excludeMigrations: true (default) skips **/migrations/**. Set it to false
only if your migrations are hand-written and reviewed like application code.
4. Scoring
Scores come from weighted points per 1,000 lines, mapped through
100 × e^(−density / scale). See 08 — Scoring for the full formula.
Lever 1 — rule severity (the main weight control)
A violation's weight comes from its rule's severity: low 1, medium 3, high 5,
critical 8. This is the way to make a rule count more or less.
Lever 2 — category scales (overall leniency)
scoring.categoryScales sets how much density a category tolerates. Larger
scale = more lenient.
"scoring": { "categoryScales": { "structure": 60 } }Pick a scale from a target: "a developer with a density of d should get
sub-score s":
scale = d / −ln(s / 100)Target sub-score s | −ln(s/100) | So scale ≈ |
|---|---|---|
| 90 | 0.105 | 9.5 × d |
| 80 | 0.223 | 4.5 × d |
| 75 | 0.288 | 3.5 × d |
| 70 | 0.357 | 2.8 × d |
| 50 | 0.693 | 1.44 × d |
Example: your best developers have a structure density of about 12. You want
them to score 80 on structure → scale = 4.5 × 12 = 54.
Lever 3 — category weights (what matters most)
scoring.weights sets each sub-score's share in the final score. Keep the sum at 1.
| Focus | duplication | structure | hygiene |
|---|---|---|---|
| Default (balanced) | 0.30 | 0.45 | 0.25 |
| Security / reliability | 0.20 | 0.35 | 0.45 |
| Maintainability | 0.30 | 0.55 | 0.15 |
| DRY-focused | 0.45 | 0.35 | 0.20 |
If the weights do not sum to 1, scores are scaled up or down (and clamped to 0–100), which makes them hard to compare. Avoid that.
Lever 4 — ranking threshold
scoring.minLinesForRanking (default 1000):
- Small team / short window: lower it to 300–500 so people are ranked at all.
- Large team / formal review: raise it to 2,000–5,000 so only well-supported scores are compared.
In VS Code, set cleanlens.scoring.minLinesForRanking: that setting overrides
the config file (see section 13).
Not configurable (source only)
The shrinkage strength (PRIOR_KLOC = 1), the confidence-level thresholds
(300 / 1,000 / 5,000 lines) and which attribution confidences are scored
(high + medium). See section 12.
5. Developer identities
People often commit with several emails or name spellings. CleanLens merges them in three ways, from the most to the least explicit:
a) Explicit list in the config (recommended for teams)
"developers": [
{ "displayName": "Musa Alahmed", "emails": ["musa@company.com", "12345+musa@users.noreply.github.com"] },
{ "displayName": "Ayman Khalil", "emails": ["ayman@company.com", "ayman.k@gmail.com"] }
]- All listed emails become one developer, shown with
displayName. - Email matching is case-insensitive.
b) .mailmap (Git-level, also fixes git log / git shortlog)
Musa Alahmed <musa@company.com> <musa.personal@gmail.com>
Musa Alahmed <musa@company.com> MUSAALAHMED4 <12345+MUSAALAHMED4@users.noreply.github.com>c) Automatic merging (mergeSameNameAuthors, default on)
Merges identities with the same name, the same name ignoring spaces, punctuation and trailing digits, or the same distinctive email handle. Details: 04 — Git layer.
Turn it off if two different people share a name, then list real merges explicitly:
"mergeSameNameAuthors": false(In VS Code, set cleanlens.mergeSameNameAuthors to false too; the setting wins.)
Bots
Bots (Dependabot, Renovate, CI) appear as developers. They usually only touch
lock files and config, so they have 0 analyzed lines and a "no analysed code"
score that is not ranked. There is no option to hide them. You can ignore them
in the report, or group them under one name with the developers list.
6. History window
Only violations introduced inside the window are attributed. Older ones show
as existing and are charged to nobody.
| Goal | Setting |
|---|---|
| Everyday use (default) | maxCommits: 500 |
| Full, fair picture of the whole project | maxCommits: 0 (CLI: --full-history) |
| Quarterly / sprint review | since: "2026-07-01" or since: "3 months ago" |
| Only recent work of a new team | since = the team's start date |
| Fast feedback in CI | maxCommits: 100 |
When both are set, Git applies both: commits after since, then the newest maxCommits.
The wider the window, the more code each developer has, the higher the score
confidence, and the fewer existing violations. With the cache enabled, only
the first wide run is slow.
7. Editor experience
- Live diagnostics (
cleanlens.liveDiagnostics, default on): underlines problems as you type, using the file-level rules. Turn it off if it is distracting or slow on huge files; the full-analysis diagnostics remain. - Diagnostic level follows severity:
critical/high→ Error (red),medium→ Warning (yellow),low→ Information (blue). To make a rule quieter in the editor, lower its severity (this also lowers its score weight). - Live diagnostics do not apply the exclude list: an excluded file you open still gets live underlines. Full-analysis results are not affected.
8. CI quality gates
Use the CLI's exit codes (0 = pass, 1 = gate failed, 2 = error):
# Fail on any high or critical violation in the current code
cleanlens --fail-on high
# Reproducible gate that ignores personal overrides and uses a known ruleset
cleanlens --preset django --fail-on critical --no-cache
# Publish a report as a CI artifact
cleanlens --markdown > cleanlens-report.md
cleanlens --json > cleanlens-report.jsonThings to know:
--fail-onchecks all violations in the current code, includingexistingones. For a legacy codebase, start with--fail-on critical(for example, secrets only) and tighten over time.- In CI, the local override file does not exist (it is git-ignored), so CI uses the team config. That is what you want.
- Use
--max-commitsto keep CI fast; attribution does not affect--fail-on.
Full examples (GitHub Actions, pre-commit hook): 13 — CLI.
9. Personal overrides
To experiment without affecting your team, create .clean-code-tracker.local.json:
{
"rules": { "maxFunctionLines": { "limit": 60 } },
"exclude": ["sandbox/**"]
}Only rules (merged per rule) and exclude (appended) are read. The file is
not validated, so check the rule ids carefully. Make sure it is in your
.gitignore.
10. Ready-made team profiles
Copy one, then adjust. Only the differences from the default preset are shown;
merge them into your file's rules / scoring.
Startup / fast-moving product
Looser structure limits, focus on real bugs and secrets.
{
"rules": {
"maxFunctionLines": { "enabled": true, "severity": "low", "limit": 60 },
"maxFileLines": { "enabled": true, "severity": "low", "limit": 600 },
"maxComplexity": { "enabled": true, "severity": "medium", "limit": 12 },
"requireDocumentationForComplexCode": { "enabled": false, "severity": "low" },
"requireErrorHandling": { "enabled": true, "severity": "low" }
},
"scoring": { "categoryScales": { "structure": 60 } }
}Enterprise / regulated
Strict limits, documentation required, larger evidence before ranking.
{
"rules": {
"maxFunctionLines": { "enabled": true, "severity": "medium", "limit": 30 },
"maxComplexity": { "enabled": true, "severity": "high", "limit": 8 },
"maxNestingDepth": { "enabled": true, "severity": "high", "limit": 3 },
"forbidEmptyCatchBlocks": { "enabled": true, "severity": "critical" },
"requireDocumentationForComplexCode": { "enabled": true, "severity": "medium", "options": { "complexityThreshold": 10 } }
},
"analysis": { "maxCommits": 0 },
"scoring": { "minLinesForRanking": 3000 }
}Security-first
{
"rules": {
"forbidHardcodedSecrets": { "enabled": true, "severity": "critical" },
"forbidEmptyCatchBlocks": { "enabled": true, "severity": "critical" },
"requireErrorHandling": { "enabled": true, "severity": "high" },
"forbidDebugStatements": { "enabled": true, "severity": "medium" }
},
"scoring": { "weights": { "duplication": 0.2, "structure": 0.35, "hygiene": 0.45 } }
}Legacy codebase (first months)
Only charge new work, keep old debt visible but unscored, tighten over time.
{
"rules": {
"maxFileLines": { "enabled": true, "severity": "low", "limit": 1000 },
"maxFunctionLines": { "enabled": true, "severity": "low", "limit": 80 },
"detectDuplicateCode": { "enabled": true, "severity": "low", "options": { "minLines": 10 } }
},
"analysis": { "since": "2026-09-01" }
}since = the date the team started the cleanup: everything older is existing.
Python / Django backend
{
"rules": {
"maxNestingDepth": { "enabled": true, "severity": "high", "limit": 5 },
"requireSingleResponsibility": { "enabled": true, "severity": "medium", "options": { "maxMethods": 15 } },
"maxFileLines": { "enabled": true, "severity": "medium", "limit": 500 }
}
}11. Calibrating on your own repository
A repeatable way to fit CleanLens to a team:
- Run it wide on the real repository:
bash cleanlens --full-history --markdown > baseline.md - Read the "Severity breakdown" and "Files with violations" sections. Find the rules with the most findings.
- Fix the noise first. Open 10 findings of the noisiest rule. If most are
not real problems for your team, change that rule's
limitorseverity, or disable it (section 2). Exclude non-owned code (section 3). - Check identities. Are people split in two? Add
developersor a.mailmap(section 5). - Only then look at the scores. If one category is low for everyone, the ruleset or scale is too strict for your context. Use the formula in section 4 to adjust that category's scale.
- Freeze the configuration and commit it. Changing rules every week makes scores incomparable over time (and clears the cache).
- Communicate it. Share the ruleset and the limitations (17) with the team. Scores are for improving code, not for ranking people.
12. Customizing in source code
Some behavior is fixed in code. Change it, rebuild (npm run build), and
bump ENGINE_VERSION in src/scoring/scoringConfig.ts if the change
affects analysis results, so old caches are discarded.
| What | Where | Default |
|---|---|---|
| Severity weights | SEVERITY_WEIGHT, src/scoring/scoringConfig.ts | 1 / 3 / 5 / 8 |
| Default scales and weights | CATEGORY_SCALE, CATEGORY_WEIGHT, same file | 15/45/38, 0.30/0.45/0.25 |
| Shrinkage strength | PRIOR_KLOC, same file | 1 |
| Score confidence thresholds | confidenceLevel(), same file | 300 / 1,000 / 5,000 |
| Which attribution confidences are scored | SCORING_CONFIDENCES, same file | high, medium |
| Rule → category | RULE_CATEGORY, src/configuration/types.ts | see 06 |
| Preset rules and excludes | BASE_RULES, BASE_EXCLUDE, buildPreset(), src/configuration/presets.ts | |
| Built-in excludes, generated markers | DEFAULT_EXCLUDE, GENERATED_MARKERS, src/configuration/excludes.ts | |
| Allowed short / technical names, vague words | src/analyzers/names.ts | |
| Secret patterns, placeholders | PATTERNS, PLACEHOLDER, src/analyzers/secrets.ts | |
| Debug calls (JS) | forbidDebugStatements regex in src/analyzers/jsAnalyzer.ts | console.log/debug/info/trace/dir/table |
| "Risky" Python calls | RISKY, src/analyzers/pyAnalyzer.ts | open, requests.*, … |
| Duplicate line filter | normalize(), src/analyzers/duplicateCode.ts | min 12 chars |
| Commit window default | DEFAULT_MAX_COMMITS, src/core/analyzeRepository.ts | 500 |
| Parallelism | COMMIT_CONCURRENCY, src/attribution/commitAttribution.ts | 12 |
| Max file size | MAX_FILE_BYTES, src/analyzers/fileScanner.ts | 1 MB |
| Generic email handles | GENERIC_HANDLES, src/git/contributors.ts | |
| Dashboard page size | PAGE_SIZE, src/views/dashboardHtml.ts | 10 |
Adding a new rule or a new language is explained in 15 — Development guide.
13. Keep VS Code and CLI in sync
In the extension, these VS Code settings override the config file even at
their default values: excludeGenerated, excludeMigrations,
excludeLockFiles, mergeSameNameAuthors, analysis.maxCommits,
scoring.minLinesForRanking. (Details:
10 — Precedence.)
To get identical numbers from the extension, the CLI and CI:
- Put the team configuration in
.clean-code-tracker.json. - Commit a
.vscode/settings.jsonwith the same values for the settings above, e.g.:json { "cleanlens.analysis.maxCommits": 0, "cleanlens.scoring.minLinesForRanking": 3000, "cleanlens.mergeSameNameAuthors": true } - Do not use
--presetin CI unless you use it everywhere. - Keep personal overrides out of shared reports.