11

Customization guide

How to tune CleanLens for your team: recipes for strictness, noise, scoring, identities, history, CI

11 min read13 sections

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

  1. Choose a starting point
  2. Rules: strictness and noise
  3. What to analyze: exclusions
  4. Scoring
  5. Developer identities
  6. History window
  7. Editor experience
  8. CI quality gates
  9. Personal overrides
  10. Ready-made team profiles
  11. Calibrating on your own repository
  12. Customizing in source code
  13. 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 projectPresetWhy
Node, TypeScript, plain JSjavascriptBase rules and excludes
React / Next.js front endreactAlso skips stories, __tests__, *.test.*, *.spec.*
Python library or servicepythonAlso skips caches, tests/, test_*.py, conftest.py
DjangodjangoPython excludes + migrations and manage.py; maxFileLines 500
Monorepo (e.g. Django + React)django or python, then add the React excludes by handOne 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

LeverEffect on findingsEffect on the score
enabled: falseNone reportedNone
limit / optionsFewer (looser) or more (stricter)Indirect, through the count
severitySame findingsDirect: 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

RuleLenientDefaultStrict
maxFunctionLines.limit804025
maxFileLines.limit800400250
maxParameters.limit753
maxComplexity.limit15107
maxNestingDepth.limit643
detectDuplicateCode.options.minLines1064
requireSingleResponsibility.options.maxMethods20107
requireDocumentationForComplexCode.options.complexityThreshold251510

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:

  1. requireErrorHandling: if errors are handled centrally (Express error middleware, Django views, a React Query layer), lower it to low or disable it.
    json
    "requireErrorHandling": { "enabled": true, "severity": "low" }
  2. forbidDebugStatements: for CLI tools and scripts, where print / console.log is the real output, exclude those folders (section 3) instead of disabling it for the whole project.
  3. requireDocumentationForComplexCode: raise complexityThreshold to 20+, or disable it if your team documents with regular comments.
  4. requireSingleResponsibility: raise maxMethods for frameworks with large, cohesive classes (Django admin, class components, service objects).
  5. Python maxNestingDepth: Python depth counts the class and def levels, so a method body already starts at depth 2. For Python-heavy repositories use limit: 5 or 6.

Emphasizing what matters to you

  • Security first: keep forbidHardcodedSecrets at critical and raise forbidEmptyCatchBlocks to critical.
  • Maintainability first: raise maxComplexity and maxNestingDepth to critical, lower naming and debug rules to low.
  • 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

WhereUse for
exclude in .clean-code-tracker.jsonTeam-wide exclusions (commit it)
.gitignoreAlready read automatically
.gitattributes with linguist-generatedGenerated files (also helps GitHub's language stats)
cleanlens.exclude (VS Code)Workspace/personal additions, extension only
exclude in .clean-code-tracker.local.jsonPersonal additions, both front ends

Common patterns

json
"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.
  • *.ext without 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
# .gitattributes
src/api/client.ts linguist-generated
proto/*.ts        linguist-generated

Or put // @generated / # DO NOT EDIT in the first five lines of the file.

Should tests be analyzed?

  • Exclude them (react / python presets do) if you want scores to reflect production code only.
  • Include them if test quality matters to you. Consider lowering detectDuplicateCode to low: 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.

json
"scoring": { "categoryScales": { "structure": 60 } }

Pick a scale from a target: "a developer with a density of d should get sub-score s":

text
scale = d / −ln(s / 100)
Target sub-score s−ln(s/100)So scale ≈
900.1059.5 × d
800.2234.5 × d
750.2883.5 × d
700.3572.8 × d
500.6931.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.

Focusduplicationstructurehygiene
Default (balanced)0.300.450.25
Security / reliability0.200.350.45
Maintainability0.300.550.15
DRY-focused0.450.350.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:

json
"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)

text
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:

json
"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.

GoalSetting
Everyday use (default)maxCommits: 500
Full, fair picture of the whole projectmaxCommits: 0 (CLI: --full-history)
Quarterly / sprint reviewsince: "2026-07-01" or since: "3 months ago"
Only recent work of a new teamsince = the team's start date
Fast feedback in CImaxCommits: 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):

bash
# 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.json

Things to know:

  • --fail-on checks all violations in the current code, including existing ones. 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-commits to 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:

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.

json
{
  "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.

json
{
  "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

json
{
  "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.

json
{
  "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

json
{
  "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:

  1. Run it wide on the real repository:
    bash
    cleanlens --full-history --markdown > baseline.md
  2. Read the "Severity breakdown" and "Files with violations" sections. Find the rules with the most findings.
  3. Fix the noise first. Open 10 findings of the noisiest rule. If most are not real problems for your team, change that rule's limit or severity, or disable it (section 2). Exclude non-owned code (section 3).
  4. Check identities. Are people split in two? Add developers or a .mailmap (section 5).
  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.
  6. Freeze the configuration and commit it. Changing rules every week makes scores incomparable over time (and clears the cache).
  7. 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.

WhatWhereDefault
Severity weightsSEVERITY_WEIGHT, src/scoring/scoringConfig.ts1 / 3 / 5 / 8
Default scales and weightsCATEGORY_SCALE, CATEGORY_WEIGHT, same file15/45/38, 0.30/0.45/0.25
Shrinkage strengthPRIOR_KLOC, same file1
Score confidence thresholdsconfidenceLevel(), same file300 / 1,000 / 5,000
Which attribution confidences are scoredSCORING_CONFIDENCES, same filehigh, medium
Rule → categoryRULE_CATEGORY, src/configuration/types.tssee 06
Preset rules and excludesBASE_RULES, BASE_EXCLUDE, buildPreset(), src/configuration/presets.ts
Built-in excludes, generated markersDEFAULT_EXCLUDE, GENERATED_MARKERS, src/configuration/excludes.ts
Allowed short / technical names, vague wordssrc/analyzers/names.ts
Secret patterns, placeholdersPATTERNS, PLACEHOLDER, src/analyzers/secrets.ts
Debug calls (JS)forbidDebugStatements regex in src/analyzers/jsAnalyzer.tsconsole.log/debug/info/trace/dir/table
"Risky" Python callsRISKY, src/analyzers/pyAnalyzer.tsopen, requests.*, …
Duplicate line filternormalize(), src/analyzers/duplicateCode.tsmin 12 chars
Commit window defaultDEFAULT_MAX_COMMITS, src/core/analyzeRepository.ts500
ParallelismCOMMIT_CONCURRENCY, src/attribution/commitAttribution.ts12
Max file sizeMAX_FILE_BYTES, src/analyzers/fileScanner.ts1 MB
Generic email handlesGENERIC_HANDLES, src/git/contributors.ts
Dashboard page sizePAGE_SIZE, src/views/dashboardHtml.ts10

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:

  1. Put the team configuration in .clean-code-tracker.json.
  2. Commit a .vscode/settings.json with the same values for the settings above, e.g.:
    json
    {
      "cleanlens.analysis.maxCommits": 0,
      "cleanlens.scoring.minLinesForRanking": 3000,
      "cleanlens.mergeSameNameAuthors": true
    }
  3. Do not use --preset in CI unless you use it everywhere.
  4. Keep personal overrides out of shared reports.