CleanLens
English · العربية
Fair, local Clean Code analysis for a Git repository. CleanLens replays your
commit history — comparing each file before and after every commit — so each
code smell is charged to the developer who actually introduced it, never to
whoever last touched the file. Every developer gets a quality score based on the
density of the issues they introduced per 1,000 lines they wrote, with Bayesian
shrinkage so a small sample can't inflate it. Quality and contribution volume
are always shown separately.
- Runs entirely on your machine — no source code or developer data leaves it, no sign-in.
- Interactive dashboard + inline editor diagnostics as you type.
- A
cleanlens CLI with the same report for team reports, CI and pre-commit gates.
- Docs in English & Arabic.

How it works
- Git checks & rules — confirms it's a repo with commits, loads
.clean-code-tracker.json (or a built-in preset).
- Scan — 14 Clean Code rules for JS/TS/JSX/TSX (AST) and Python (heuristic),
plus duplicate-code detection.
- Replay the history — walks the newest 500 commits (configurable), and for
every changed file diffs it
--unified=0 -w -M -C and analyses the version
before and after the commit. A stable per-violation fingerprint
(rule + path + symbol + normalized context) matches issues across
line-number churn.
- Classify & attribute — a violation is
introduced (charged to the commit
author) only when it first appears on lines that commit changed (high
confidence) or in a symbol it modified (medium); otherwise existing (never
charged), fixed (credited), excluded or unattributed.
- Score — per developer, per category:
score = 100·e^(−adjustedDensity / scale) where adjustedDensity is the
weighted introduced-violation points per 1,000 lines they wrote, shrunk toward
the project mean; final = duplication·0.30 + structure·0.45 + hygiene·0.25.
Confidence and a "not ranked" flag come from how much code they wrote.
Results are cached per commit, so re-runs only analyse new commits.
Everything is configurable
- Clean Code rules — enable/disable each of the 14 rules, set its severity
(
low/medium/high/critical) and its threshold in
.clean-code-tracker.json (committed, team-wide) or a personal
.clean-code-tracker.local.json override. Presets for JavaScript, React,
Python and Django.
- Scoring & analysis — tune the severity weights, the per-category scales,
the
0.30 / 0.45 / 0.25 category weights, the ranking threshold, the commit
window (maxCommits / since) and every exclusion list — from
.clean-code-tracker.json or from VS Code settings (cleanlens.*,
see Settings).
- Exclusions — sensible defaults (
node_modules, dist, build, generated
files, lock files, migrations…) are merged with your own globs, .gitignore
and linguist-generated markers — never replaced.
Full documentation, the exact formulas and the pipeline walkthrough are on
GitHub.
Overview
Analyzes a Git repository entirely on your machine, shows every contributor and their activity, and — by replaying each commit and comparing the code before and after — attributes each Clean Code violation to the developer who actually introduced it (not whoever last touched the file). Each developer gets a fair quality score based on the density of the smells they introduced per 1,000 lines they wrote, with Bayesian shrinkage so a tiny sample can't game it.
It ships as two front-ends over one analysis core:
- a VS Code extension — an interactive dashboard, plus inline diagnostics that underline violations in the editor as you type;
- a
cleanlens CLI — the same report as text or JSON, for team reports, CI, and pre-commit gates.
Privacy: all analysis runs locally. No source code and no developer data ever leaves your machine, and no sign-in is required. See docs/PRIVACY.md.
Table of contents
What it produces
A report with project-level totals and a card per developer:
Developers: 5
Analyzed files: 287
Analyzed commits: 480
Total violations: 124
Unattributed: 3
Musa
Clean Code Score: 73/100
duplication 88 · structure 72 · hygiene 78
Analyzed Lines: 3,140
Confidence: Reliable
Contribution: 42%
New Violations: 39
Fixed Violations: 12
Existing Violations: 51 (informational)
Unattributed Violations: 0 (informational)
Weighted Violations: 118
Net Quality Impact: +27
Violation Density: 37.58 / KLOC
Active days: 24
The score is a weighted mean of three per-category sub-scores
(duplication ×0.30 + structure ×0.45 + hygiene ×0.25) so a problem in one
dimension does not read as an across-the-board failure. Only violations a
developer introduced (with high/medium confidence) count; existing,
excluded and unattributed ones are informational. Contribution (how much
code you wrote) is shown next to the score but never mixed into it. A developer
with no analysed code scores —, not 100, and one below the ranking threshold
(1,000 analysed lines by default) is shown but not ranked.
In the extension, clicking any violation opens the file at the exact line. In the CLI, the same numbers are printed to stdout (or emitted as JSON).
Two ways to run it
|
VS Code extension |
cleanlens CLI |
| Best for |
a developer at work — see your own violations, click to open the file |
scheduled team reports, CI, pre-commit gates |
| Output |
interactive dashboard: cards, search, severity filters |
text or --json on stdout |
| Needs |
the project open in VS Code |
one command; runs headless on a server |
| Automation |
no |
yes — --fail-on returns a non-zero exit code |
| Entry point |
src/extension.ts |
src/cli/index.ts |
Both call the same function, analyzeRepository — identical rules, identical numbers.
Quick start
In VS Code
- Open a folder that contains a Git repository.
- Run CleanLens: Initialize Project from the Command Palette — it detects the project type, proposes a rule preset (JavaScript / React / Python / Django), and writes
.clean-code-tracker.json after you confirm.
- Run CleanLens: Run Analysis.
- The Dashboard opens automatically; reopen it any time with CleanLens: Open Dashboard.
In the terminal
# from a package that has it installed
cleanlens
# or without installing (once published)
npx cleanlens --violations
If there is no .clean-code-tracker.json, the CLI falls back to built-in defaults (with a warning on stderr). Use --preset <name> to pick a different baseline without a config file.
How it works — the pipeline
Both front-ends call analyzeRepository(root, options). Seven steps run in order:
1. Git checks → 2. Load rules → 3. Extract developers
↓
4. Scan code files (14 rules) → 5. Attribute violations (walk each commit,
↓ diff before/after, fingerprint & classify)
6. Score → 7. Display (Dashboard / stdout)
Steps 1–6 are shared code with no VS Code dependency. Only step 7 differs between the extension and the CLI.
1. Git checks
src/git/gitService.ts
git rev-parse --is-inside-work-tree # is this a repository?
git rev-parse HEAD # does it have any commits?
If either fails the analysis stops with a clear message (not a Git repo / no commits). In code this is an AnalysisError; the extension shows it as a notification, the CLI prints error: … to stderr and exits 2.
2. Loading the rules
src/configuration/configLoader.ts · src/configuration/configValidator.ts
- Reads
.clean-code-tracker.json from the project root (skipped when the CLI is given --preset).
- Validates it:
version must be 1, every rule name must be one of the known 14, every severity must be low|medium|high|critical, every limit must be a positive number, options must be numbers/booleans. Any error stops the analysis and is shown to you.
- If
.clean-code-tracker.local.json exists, its rules are merged on top of the official ones — this is per-developer and never changes the official report. It belongs in .gitignore.
- If there is no config file at all, built-in defaults are used and a warning is emitted.
src/git/contributors.ts
git log --no-merges --format="%aN|%aE|%aI"
Each line yields name · email · ISO date. Commits are grouped by lowercased email:
| Field |
How it is computed |
| Active days |
the set of distinct calendar days (YYYY-MM-DD); 5 commits on one day counts as one day |
| First / last contribution |
min / max commit date |
Merging duplicate identities. git log respects .mailmap. On top of that:
4. Scanning the code
- Walks the directory tree recursively.
- Always skipped:
.git, node_modules, .svn, .hg.
- Skipped by
exclude: each glob from the config is matched with the small matcher in src/analyzers/glob.ts (** → any path segment(s), * → within one segment, ? → one character). Patterns are tested against both the relative path and the file's basename.
- Kept extensions:
.js .jsx .mjs .cjs .ts .tsx .py.
- Any file larger than 1 MB is skipped.
By extension: JS/TS → jsAnalyzer, Python → pyAnalyzer. Each file is read as text and passed with the resolved config; the analyzer returns a list of RawFinding objects (rule id + file + line range + message).
4c. How each file is analyzed
JavaScript / TypeScript / JSX / TSX — real AST analysis using the TypeScript compiler (ts.createSourceFile with parent pointers), then a single recursive walk over every node. Function-like nodes checked: function declarations/expressions, arrow functions, methods, constructors, accessors.
- Cyclomatic complexity starts at 1 and adds 1 for each
if, for, for..in, for..of, while, do, non-empty case, catch, ternary ?:, and each && / || / ?? — without descending into nested functions (those are counted on their own).
- Nesting depth is a counter incremented when entering
if / for / while / do / switch; else if does not add a level.
- Unused code: every identifier occurrence in the file is counted; a non-exported import / variable / function whose name appears only once (its declaration) is reported.
Python — there is no full AST; analysis is heuristic, based on indentation and text patterns (src/analyzers/pyAnalyzer.ts):
- Block boundaries come from indentation (a block ends at the first line that dedents to ≤ the header indent).
- Complexity ≈ 1 + count of
if / elif / for / while / except / and / or inside the body.
except …: followed only by pass / ... = empty. print( / breakpoint( = debug statement. Nesting depth = indent ÷ 4.
- Parameters are parsed from the
def signature (ignoring self / cls / *args).
- An
import whose bound name never appears again = unused.
Runs across all analyzed files together:
- Normalize every line (trim, collapse whitespace, drop comments and trivial lines like a lone
}).
- Slide a window of 6 lines (configurable via
options.minLines) and hash each window.
- Any hash seen in two different places → a duplication violation on the later occurrence, pointing at the first.
Each RawFinding becomes a Violation with a stable hash id (so the same issue is never counted twice), the severity taken from the config, and the file/line range. Violations are sorted by severity.
5. Attributing violations per commit
src/git/commitHistory.ts · src/git/diffService.ts · src/attribution/fingerprint.ts · src/attribution/commitAttribution.ts · src/attribution/attributeReport.ts
The engine walks the newest 500 non-merge commits (configurable — see
Settings) oldest-first. For each commit, for each changed source
file that is not excluded:
git diff --unified=0 -w -M -C <parent> <commit> -- <file> # which lines changed (whitespace-insensitive, move/copy aware)
git cat-file --batch # the before / after blobs (one spawn per commit)
Both revisions of the file are analysed with the same 14 rules. Every violation
gets a stable fingerprint — hash(ruleId + normalizedPath + symbolName + normalizedCodeContext) — so it can be matched across line-number churn without
using line numbers.
| Compared before vs after |
Status |
Effect |
| fingerprint only in after, on a line the commit added/changed |
introduced · confidence high |
charged to the commit author |
| fingerprint only in after, overlapping a symbol the commit modified |
introduced · confidence medium |
charged to the commit author |
| fingerprint only in after, no evidence the edit caused it |
introduced · confidence low |
recorded, not scored |
| fingerprint only in before |
fixed |
credited to the commit author |
| fingerprint in both |
existing |
never charged to anyone |
| a revision could not be read/parsed |
unattributed |
informational only |
| the file is generated / vendored / ignored |
excluded |
not analysed at all |
A HEAD violation that never appears entering an analysed commit predates the
window → existing. A root commit (no parent) is diffed against the empty tree,
so all of its code is genuinely new. Author emails map to developer ids via the
developer list; .mailmap is applied by Git automatically.
Per-commit results are cached by commit hash (see Performance),
so re-runs only analyse new commits. The cache is invalidated automatically when
the rules, weights or exclusions change.
6. Scoring
src/scoring/scoringConfig.ts · src/scoring/cleanCodeScore.ts
Severity weights: low=1, medium=3, high=5, critical=8.
Analyzed lines = the lines the developer actually added or modified across
analysed files (not "lines they currently own").
Each developer is scored on the density of the violations they introduced
(high/medium confidence only), per 1,000 analysed lines, per category:
| Category |
Density |
Scale (density → ~37) |
duplication (detectDuplicateCode, within-file) |
weighted introduced points ÷ analyzed KLOC |
15 |
structure (maxFunctionLines, maxFileLines, maxParameters, maxComplexity, maxNestingDepth, requireSingleResponsibility, requireDocumentationForComplexCode) |
same |
45 |
hygiene (detectUnusedCode, requireClearVariableNames, requireErrorHandling, forbidEmptyCatchBlocks, forbidHardcodedSecrets, forbidDebugStatements) |
same |
38 |
adjustedDensity(cat) = (weighted(cat) + projectDensity(cat) × 1) / (analyzedKLOC + 1)
categoryScore(cat) = round(100 × e^(−adjustedDensity(cat) / scale(cat)))
CleanCodeScore = round(dup × 0.30 + structure × 0.45 + hygiene × 0.25)
Volume fairness — Bayesian shrinkage. Each density is blended with the
project-wide density, weighted by how much code the developer wrote
(priorKLOC = 1). A 300-line contributor is pulled strongly toward the project
mean; a 25 KLOC contributor keeps their real number. This is what stops a tiny
clean sample from scoring 100 and a large honest contributor from looking worse
than a tiny one.
Confidence level, from analysed lines: <300 insufficient,
300–999 provisional, 1000–4999 reliable, ≥5000 highly_reliable. Below the
ranking threshold (minLinesForRanking, default 1000) a developer is shown
but not ranked. A developer with no analysed code scores —, not 100.
Net Quality Impact = introducedWeighted − fixedWeighted is shown per
developer for context; fixing a lot of debt is visible but cannot push the score
above 100.
Quality vs contribution stay separate. Clean Code Score (0–100) measures
code quality. Contribution (% of analysed lines written) measures volume. They
are displayed side by side and never combined into one number.
Every constant lives in src/scoring/scoringConfig.ts
and can be overridden from the config file / VS Code settings — tune there, not
at the call sites. Treat the number as comparative, not an absolute grade.
7. Display
The ProjectReport object returned by analyzeRepository is then rendered:
- Extension — stored in
ReportStore (src/views/treeProviders.ts), which pushes updates to the sidebar (Dashboard / Developers / Violations) and to the Webview panel (src/views/dashboardHtml.ts): project stats, a card per developer with a score bar, and a violations table with free-text search and severity filters. Clicking a row opens the file at the line. The report also feeds the inline editor diagnostics.
- CLI — formatted by src/cli/format.ts into the text block shown above, or serialized with
JSON.stringify when --json is given.
The 14 rules in detail
Full reference with defaults and per-language coverage: docs/RULES.md.
| Rule id |
Reported when |
Default limit |
maxFunctionLines |
a function is longer than the limit |
40 lines |
maxFileLines |
a file is longer than the limit |
400 lines |
maxParameters |
a function takes more parameters than the limit |
5 |
maxComplexity |
cyclomatic complexity exceeds the limit |
10 |
maxNestingDepth |
control-flow nesting is deeper than the limit |
4 |
detectDuplicateCode |
an identical block repeats across the project |
options.minLines = 6 |
detectUnusedCode |
an import / variable / function is never used |
— |
requireClearVariableNames |
a vague name such as data1 or x |
— |
requireErrorHandling |
a failure-prone operation with no try |
— |
forbidEmptyCatchBlocks |
an empty catch / except |
— |
forbidHardcodedSecrets |
an API key or password written in the code |
— |
forbidDebugStatements |
console.log / debugger / print / breakpoint |
— |
requireSingleResponsibility |
a class with too many methods, or a function that is both long and complex |
options.maxMethods = 10 |
requireDocumentationForComplexCode |
function complexity ≥ threshold with no JSDoc / docstring |
options.complexityThreshold = 15 |
Heuristic rules — requireErrorHandling, requireSingleResponsibility, and requireDocumentationForComplexCode are approximate by nature and can produce false positives. Lower their severity or raise their thresholds if they are noisy for your team.
Configuration file
.clean-code-tracker.json is created in the project root, committed to Git, and applied to everyone. Shape:
{
"version": 1,
"rules": {
"maxFunctionLines": { "enabled": true, "severity": "medium", "limit": 40 },
"maxComplexity": { "enabled": true, "severity": "high", "limit": 10 },
"forbidHardcodedSecrets": { "enabled": true, "severity": "critical" },
"detectDuplicateCode": { "enabled": true, "severity": "medium", "options": { "minLines": 6 } }
},
"exclude": ["fixtures/**", "*.snap"],
"excludeGenerated": true,
"excludeMigrations": true,
"excludeLockFiles": true,
"analysis": { "maxCommits": 500, "since": "" },
"scoring": {
"minLinesForRanking": 1000,
"severityWeights": { "critical": 8 },
"categoryScales": { "duplication": 15, "structure": 45, "hygiene": 38 },
"weights": { "duplication": 0.3, "structure": 0.45, "hygiene": 0.25 }
}
}
exclude is merged with the built-in defaults (node_modules/**, dist/**,
build/**, coverage/**, .next/**, vendor/**, __pycache__/**,
staticfiles/**, generated/**, *.min.js, *.map, *.generated.*), plus
**/migrations/** and the lock files unless you disable those flags, plus
anything in .gitignore and files flagged linguist-generated in
.gitattributes or carrying a @generated / DO NOT EDIT header. Excluded
files never count toward analysed lines, violations or the score. scoring and
analysis are optional; omit them for the defaults.
Each developer may add .clean-code-tracker.local.json (same shape, rules only) to tighten their own feedback without touching the official report. Add it to .gitignore.
VS Code commands
| Command |
Action |
CleanLens: Initialize Project |
detect project type, pick a preset, write .clean-code-tracker.json |
CleanLens: Configure Rules |
open the config file (offers to initialize if missing) |
CleanLens: Run Analysis |
run the full pipeline and open the Dashboard |
CleanLens: Open Dashboard |
reopen the Webview panel |
Inline editor diagnostics
Violations show up in the editor as you work — you do not have to run a full analysis first. src/views/diagnostics.ts owns a DiagnosticCollection, so each violation appears like any linter finding:
- a coloured squiggle under the offending line(s);
- a hover with the rule title and message (e.g. Long function: Function handleRequest has 63 lines (limit 40).);
- an entry in the Problems panel (
⇧⌘M), grouped by file;
- a marker in the gutter and the file's tab.
Severity maps to VS Code's levels:
| Rule severity |
Editor level |
critical, high |
Error (red) |
medium |
Warning (yellow) |
low |
Information |
Two feeds
| Feed |
Trigger |
Coverage |
| Full |
after Run Analysis |
all 14 rules, every file, including cross-file detectDuplicateCode |
| Live |
while you type (400 ms debounce), and on file open / save |
file-scoped rules for the edited file only — no detectDuplicateCode, no attribution |
The live feed re-runs just the single-file analyzer (jsAnalyzer / pyAnalyzer) for the active document, using the current .clean-code-tracker.json — a FileSystemWatcher reloads the rules the moment you edit that file. While you are editing a file, its live diagnostics temporarily replace that file's full-analysis diagnostics; the next Run Analysis restores the complete set (including duplicates).
If the buffer does not parse mid-edit, the previous diagnostics are kept rather than cleared. Closing the file, or turning cleanlens.liveDiagnostics off, swaps that file back to its full-analysis diagnostics (or clears them if no analysis has run).
Settings
| Setting |
Default |
Effect |
cleanlens.liveDiagnostics |
true |
Underline violations in the editor as you type, before running a full analysis. Set to false to only show diagnostics after Run Analysis. |
cleanlens.exclude |
[] |
Extra glob patterns to exclude, merged with the built-in defaults. |
cleanlens.excludeGenerated |
true |
Skip files flagged linguist-generated or carrying a @generated / DO NOT EDIT header. |
cleanlens.excludeMigrations |
true |
Skip **/migrations/**. |
cleanlens.excludeLockFiles |
true |
Skip package-lock.json, yarn.lock, pnpm-lock.yaml. |
cleanlens.mergeSameNameAuthors |
true |
Merge contributors that share a display name into one developer. |
cleanlens.analysis.maxCommits |
500 |
Walk only the newest N non-merge commits. 0 = whole history. |
cleanlens.analysis.since |
"" |
Only walk commits after this date (any git log --since value). |
cleanlens.scoring.minLinesForRanking |
1000 |
Analysed lines a developer needs to enter the ranking. |
cleanlens.cache.enabled |
true |
Cache per-commit results so re-runs only analyse new commits. |
Run Analysis is cancellable and shows progress (Analyzing commits (240/500)).
The extension activates on onStartupFinished, so the live feed works as soon as VS Code finishes loading — you do not need to open the CleanLens sidebar first.
CLI manual
Synopsis
cleanlens [path] [options]
path — the repository to analyze. Defaults to the current working directory. It must be inside a Git working tree with at least one commit.
Options
| Option |
Effect |
--json |
Print the full ProjectReport as pretty JSON to stdout instead of the text report. Everything (developers, violations with developerId, line ranges) is included. |
--markdown |
Print a full English report in Markdown instead of the text summary: run metadata, the scoring formula, severity and rule breakdowns, a per-developer section, a Files with violations table that lists the violating line numbers, and an All violations table. Redirect to a .md file to share. |
--violations |
In text mode, also print every violation, sorted by severity, as [severity] Rule title — path:line (status/confidence; introduced by). Ignored with --json (JSON always contains them). |
--preset <name> |
Ignore .clean-code-tracker.json and use a built-in baseline: javascript, react, python, or django. Useful in CI where you do not want to depend on a committed config, or to compare against the defaults. |
--max-commits <n> |
Walk only the newest N commits for attribution (default 500). |
--full-history |
Walk the entire commit history (overrides --max-commits). |
--since <date> |
Walk only commits after this date (any git log --since value). |
--no-cache |
Do not read or write the per-commit analysis cache ($TMPDIR/cleanlens-cache). |
--fail-on <severity> |
After printing the report, exit with code 1 if any violation is at or above <severity> (low | medium | high | critical). This is the quality gate. |
-h, --help |
Print usage and exit 0. |
Output streams
| Stream |
Contents |
| stdout |
the report only — the text block, or JSON with --json |
| stderr |
progress (Scanning files (12/87)), warning: lines, and error: lines |
So redirects stay clean:
cleanlens --json > report.json # only JSON in the file
cleanlens --violations 2> /dev/null # hide progress and warnings
Exit codes
| Code |
Meaning |
0 |
analysis completed (and --fail-on, if given, was not triggered) |
1 |
--fail-on matched — violations at or above the given severity exist |
2 |
could not analyze — not a Git repo, no commits, invalid config, or a bad option |
Examples
# human-readable report for the current repo
cleanlens
# full listing, using the Django preset instead of a config file
cleanlens ./services/api --preset django --violations
# machine-readable, for a dashboard or a diff between runs
cleanlens --json > clean-code-$(date +%F).json
# quality gate: fail the build on any high or critical violation
cleanlens --fail-on high
GitHub Actions
name: clean-code
on: [pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # commit-by-commit attribution needs history
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npx cleanlens --fail-on critical
fetch-depth: 0 matters: a shallow clone has almost no history, so attribution
falls back to a few commits and most violations show as existing.
Pre-commit hook
# .git/hooks/pre-commit (chmod +x)
#!/bin/sh
npx cleanlens --fail-on critical || {
echo "Commit blocked: critical Clean Code violations."
exit 1
}
- Analysis runs on demand only, with progress reporting and a cancel button.
- The commit walk is cached by commit hash under
$TMPDIR/cleanlens-cache
(CLI) or the extension's global storage. A re-run only analyses new commits;
the cache is invalidated automatically when rules, weights or exclusions change.
- Only the newest 500 commits are walked by default (
analysis.maxCommits).
- Git calls are batched — one
git log, then one git diff + one
git cat-file --batch per commit (not one call per changed file), and
commits are analysed 12-way in parallel. Each file revision is parsed at
most once (content-addressed by its blob sha).
node_modules, virtualenvs and generated folders are excluded before any file is read.
- Files over 1 MB and non-source extensions are skipped.
Limitations you must communicate to your team
- Attribution compares a file's violations before and after each commit. It
charges a developer only for smells that first appear on lines they changed (or
in a symbol they modified) — a one-character edit no longer transfers a whole
file. Whitespace-only reformats are ignored (
-w); moved/copied code is
tracked (-M -C), but cases Git cannot detect fall back to existing/low
and are not charged.
- Only JS/TS and Python files are analysed; other files count as not analysed.
- Violations older than the analysed commit window show as
existing and are
never charged. Widen analysis.maxCommits (or --full-history) for full coverage.
- Cross-file clone detection runs on the HEAD snapshot only; the per-commit
duplication signal is within-file.
- The Clean Code Score is an indicative signal to improve project quality,
not an absolute judgement of a developer. Always show it next to the violation
details, the confidence level and the ruleset used.
Contributing
Project structure, local development and the release process are in
CONTRIBUTING.md.
Author & license
Created and maintained by MUSAALAHMED4 (Musa Al Ahmed). Licensed under MIT — see LICENSE.txt.