Architecture
Layers, module map, the 7-step pipeline, data flow between modules
Layers
CleanLens is a TypeScript project with one engine and two thin front ends.
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ VS Code extension │ │ CLI │
│ src/extension.ts │ │ src/cli/index.ts │
│ src/views/* │ │ src/cli/format.ts │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
│ analyzeRepository(root, opts) │
└───────────────┬──────────────────┘
▼
┌────────────────────────────────────────────────────────────────┐
│ Core pipeline — src/core/analyzeRepository.ts (no VS Code API) │
├──────────────┬──────────────┬───────────────┬──────────────────┤
│ git/ │ configuration│ analyzers/ │ attribution/ │
│ git commands │ config, rules│ rule checks │ commit replay │
│ + identities │ presets, │ per file │ + fingerprints │
│ │ excludes │ │ │
├──────────────┴──────────────┴───────────────┼──────────────────┤
│ cache/ — per-commit results on disk │ scoring/ — score │
└─────────────────────────────────────────────┴──────────────────┘- Only
src/extension.tsandsrc/views/*importvscode. Everything else is plain Node.js, which is why the CLI and the tests can run it. - There are no runtime dependencies except
typescript(used as a parser by the JS analyzer, bundled by esbuild). All Git access isexecFile("git", …).
Module map
| Folder | File | Responsibility |
|---|---|---|
src/ | extension.ts | Activation, command registration, progress UI, reading VS Code settings, live-diagnostics wiring |
src/core/ | analyzeRepository.ts | The shared pipeline; merges settings over config; builds the cache; returns ProjectReport |
src/cli/ | index.ts | Argument parsing, exit codes, stdout/stderr handling |
format.ts | Report → text / Markdown; the --fail-on check | |
src/git/ | gitService.ts | "Is this a repo?", "Has commits?", git log for contributor stats |
contributors.ts | Groups commits into developers; merges duplicate identities | |
commitHistory.ts | Lists non-merge commits (newest N / since date), oldest first | |
diffService.ts | One git diff per commit → changed files, line ranges, blob ids; git cat-file --batch to read blobs | |
blameService.ts | git blame --line-porcelain parser. Legacy (from the old blame engine); still tested, not used by the pipeline | |
src/configuration/ | types.ts | Config types, the 14 rule ids, titles and categories, file names |
configValidator.ts | Schema validation of .clean-code-tracker.json | |
configLoader.ts | Load config (or default preset), merge the local override, write a new config | |
presets.ts | The four presets; preset auto-detection | |
excludes.ts | Built-in excludes; .gitignore / .gitattributes reading; generated-header detection | |
src/analyzers/ | fileScanner.ts | Directory walk for the HEAD scan |
glob.ts | Minimal glob → RegExp and ExcludeMatcher | |
jsAnalyzer.ts | AST-based rules for JS/TS (TypeScript compiler API) | |
pyAnalyzer.ts | Line/indentation-based rules for Python | |
duplicateCode.ts | Sliding-window duplicate block detection | |
secrets.ts, names.ts | Shared heuristics for secrets and unclear names | |
model.ts | RawFinding → Violation; rule enabled/limit/option helpers | |
runAnalyzers.ts | HEAD scan orchestration; picks the analyzer by extension | |
analyzeRevision.ts | Analyze one file revision (blob) and fingerprint each violation | |
src/attribution/ | commitAttribution.ts | Walks commits, compares before/after, classifies each violation |
attributeReport.ts | Merges the HEAD violations with the commit results; scores developers | |
fingerprint.ts | FNV-1a hash; stable violation fingerprint | |
ownership.ts | Email → developer id map (+ legacy blame helpers) | |
src/scoring/ | scoringConfig.ts | Every tunable constant: weights, scales, prior, thresholds, ENGINE_VERSION |
cleanCodeScore.ts | The score formula | |
src/cache/ | analysisCache.ts | Per-commit JSON cache + config hash |
src/types/ | index.ts | Severity, Violation, Developer, ProjectReport, statuses |
src/views/ | treeProviders.ts | Sidebar trees + ReportStore |
dashboardPanel.ts | Webview panel lifecycle and messages | |
dashboardHtml.ts | Pure HTML/CSS/JS generation for the dashboard | |
diagnostics.ts | Editor diagnostics: full-report feed and live feed |
The pipeline, step by step
analyzeRepository(root, opts) in src/core/analyzeRepository.ts:
1. Git checks git rev-parse --is-inside-work-tree, git rev-parse HEAD
2. Config loadConfig() → applySettings(VS Code / CLI overrides)
readRepoAttributes() (.gitignore, .gitattributes)
mergedExcludeGlobs(), resolveScoring(), analysisConfigHash()
3. Developers git log --no-merges → aggregateContributors() → toDevelopers()
4. HEAD scan analyzeProject(): walk files → analyzer per file → duplicates
→ sorted Violation[]
5. Commit walk attributeCommits(): newest N commits, oldest first,
12 in parallel, each: diff → blobs → analyze before/after
→ introduced / fixed / existing
6. Merge + score attributeReport(): fingerprint every HEAD violation, look up
its origin, set status/confidence/developer; compute the
project prior; score every developer
7. Display extension: tree views, dashboard, diagnostics
CLI: text / Markdown / JSON + exit codeThe stages:
- Git checks: is the folder a repository, and does it have a commit?
- Config: load the config file, merge VS Code settings or CLI options on
top, read
.gitignoreand.gitattributes, and build the exclude list, the scoring constants and the config hash. - Developers: read the Git history and group commits into developers.
- HEAD scan: analyze the current files and produce the sorted violation list.
- Commit walk: the newest N commits, oldest first, 12 in parallel; for each: diff → blobs → analyze before/after → introduced / fixed / existing.
- Merge and score: fingerprint every HEAD violation, find its origin, set its status, confidence and developer; then compute the project average and score every developer.
- Display: in the extension, the trees, dashboard and diagnostics; in the CLI, text / Markdown / JSON plus the exit code.
Steps 1–6 are identical for both front ends. Only the options differ:
| Option | Extension | CLI |
|---|---|---|
config | loaded from the repo | loaded from the repo, or --preset |
settings | from VS Code settings (cleanlens.*) | from --max-commits, --full-history, --since |
cacheDir | context.globalStorageUri | <os tmpdir>/cleanlens-cache |
cache | cleanlens.cache.enabled | off with --no-cache |
signal | the progress notification's Cancel button | none |
Expected failures
AnalysisError is thrown for problems the user can fix: not a Git
repository, no commits, invalid config. The extension shows it as an error
message; the CLI prints error: … and exits with code 2. Any other exception
is a bug.
Data flow and types
RawFinding ──toViolation()──▶ Violation (severity, category, weight, id)
│
analyzeRevision() │ fingerprintFor()
▼
RevisionViolation (+fingerprint) ─▶ CommitContribution (per commit, cached)
│
attributeCommits() aggregates
▼
DeveloperAttribution (per developer) + origins (fingerprint → commit)
│
attributeReport()
▼
ProjectReport { developers[], violations[] }The type definitions live in src/types/index.ts and src/configuration/types.ts. The report format is documented field by field in 14 — Report schema.
Two analyses, two purposes
This is the most important design point to understand:
| HEAD scan (step 4) | Commit walk (step 5) | |
|---|---|---|
| Input | files on disk right now | blobs from Git history |
| Output | the list of violations shown to you | who introduced / fixed what, and analyzed lines |
| Duplicate detection | across all files | within each file only |
| Scope | every analyzable file | only files changed by the analyzed commits |
Step 6 joins them with fingerprints. A HEAD violation whose fingerprint was
introduced in the walk gets that commit's author. A HEAD violation that never
appeared in the walk predates the analyzed window → existing.
Build outputs
esbuild bundles two independent files:
dist/extension.js— entrysrc/extension.ts,vscodekept external.dist/cli.js— entrysrc/cli/index.ts, with a#!/usr/bin/env nodebanner; exposed as thecleanlensbin.
See 15 — Development guide for the build commands.