refactorlens 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. refactorlens-0.1.0/.gitignore +11 -0
  2. refactorlens-0.1.0/FUTURE.md +39 -0
  3. refactorlens-0.1.0/LICENSE +21 -0
  4. refactorlens-0.1.0/PKG-INFO +286 -0
  5. refactorlens-0.1.0/README.md +236 -0
  6. refactorlens-0.1.0/env.example +12 -0
  7. refactorlens-0.1.0/examples/messy_project/SMELLS.md +73 -0
  8. refactorlens-0.1.0/examples/messy_project/god.py +185 -0
  9. refactorlens-0.1.0/examples/messy_project/models.py +77 -0
  10. refactorlens-0.1.0/examples/messy_project/reporting.py +39 -0
  11. refactorlens-0.1.0/examples/messy_project/rlens.yaml +6 -0
  12. refactorlens-0.1.0/examples/messy_project/services.py +67 -0
  13. refactorlens-0.1.0/examples/messy_project/tests/conftest.py +13 -0
  14. refactorlens-0.1.0/examples/messy_project/tests/test_god.py +237 -0
  15. refactorlens-0.1.0/examples/messy_project/tests/test_models.py +68 -0
  16. refactorlens-0.1.0/examples/messy_project/tests/test_reporting.py +41 -0
  17. refactorlens-0.1.0/examples/messy_project/tests/test_services.py +71 -0
  18. refactorlens-0.1.0/examples/messy_project/tests/test_utils.py +63 -0
  19. refactorlens-0.1.0/examples/messy_project/utils.py +51 -0
  20. refactorlens-0.1.0/examples/sample_reports/README.md +10 -0
  21. refactorlens-0.1.0/examples/sample_reports/scan-messy_project.json +634 -0
  22. refactorlens-0.1.0/pyproject.toml +64 -0
  23. refactorlens-0.1.0/src/rlens/__init__.py +8 -0
  24. refactorlens-0.1.0/src/rlens/analysis/__init__.py +1 -0
  25. refactorlens-0.1.0/src/rlens/analysis/class_metrics.py +483 -0
  26. refactorlens-0.1.0/src/rlens/analysis/func_metrics.py +258 -0
  27. refactorlens-0.1.0/src/rlens/analysis/model.py +88 -0
  28. refactorlens-0.1.0/src/rlens/analysis/parser.py +211 -0
  29. refactorlens-0.1.0/src/rlens/analysis/scanner.py +92 -0
  30. refactorlens-0.1.0/src/rlens/cli.py +143 -0
  31. refactorlens-0.1.0/src/rlens/config.py +340 -0
  32. refactorlens-0.1.0/src/rlens/report/__init__.py +1 -0
  33. refactorlens-0.1.0/src/rlens/report/files.py +104 -0
  34. refactorlens-0.1.0/src/rlens/report/terminal.py +251 -0
  35. refactorlens-0.1.0/tests/test_class_metrics.py +536 -0
  36. refactorlens-0.1.0/tests/test_cli.py +129 -0
  37. refactorlens-0.1.0/tests/test_config.py +148 -0
  38. refactorlens-0.1.0/tests/test_func_metrics.py +293 -0
  39. refactorlens-0.1.0/tests/test_parser.py +174 -0
  40. refactorlens-0.1.0/tests/test_report.py +278 -0
  41. refactorlens-0.1.0/tests/test_scanner.py +135 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .env
11
+ reports/
@@ -0,0 +1,39 @@
1
+ # FUTURE — kapsam dışı fikirlerin park alanı
2
+
3
+ Proje kapsamı teknik dokümanda kilitlidir. Buraya yazılan hiçbir şey projeye
4
+ eklenmez; amaç fikri kaybetmemek ama kapsamı şişirmemektir.
5
+
6
+ ## Dil desteği
7
+ - **C# desteği** (v2 için birincil aday). OO metrikleri C#'ta Python'dakinden
8
+ daha doğal hesaplanır — `null` dönen metrik sayısı düşer.
9
+ - Java desteği.
10
+
11
+ ## Otomasyon
12
+ - **Auto-fix / patch uygulama.** Faz 3'te araç bilerek yalnızca öneri sunar.
13
+ Otomatik uygulama, davranış testi zorunluluğunu ve "en dar yorum" kuralını
14
+ yeniden düşünmeyi gerektirir.
15
+ - GitHub Action paketi.
16
+ - Git pre-commit hook entegrasyonu (Faz 3'te README notu yeterli).
17
+
18
+ ## Raporlama
19
+ - **Tam `rlens history`** (çok noktalı trend özeti). Faz 3'te yerini
20
+ `scan --compare-last` aldı. Tam sürüm için gereken tasarım kararları:
21
+ proje kimliği, rapor isimlendirme şeması, `reports/` dizininin paylaşılabilir
22
+ hale getirilmesi.
23
+ - HTML rapor çıktısı, web arayüzü, veritabanı.
24
+ - IDE eklentisi.
25
+
26
+ ## Analiz
27
+ - Tip çıkarımı (inference) ile DCC/CAM doğruluğunu artırmak — muhtemelen
28
+ üçüncü parti bir araca bağımlılık gerektirir; M1'in "yalnızca `ast`" kararına
29
+ aykırıdır.
30
+ - Kalıtım hiyerarşisi metrikleri (DIT, NOC).
31
+ - Modül/paket düzeyi coupling metrikleri.
32
+
33
+ ## Sağlayıcılar
34
+ - Çekirdek Groq + Ollama'dır. Gemini ve Anthropic adapter'ları opsiyoneldir
35
+ (Faz 4). Diğer sağlayıcılar `providers/base.py` sözleşmesini uygulayan
36
+ ~30 satırlık dosyalardır.
37
+
38
+ ## Takım özellikleri
39
+ - Çoklu kullanıcı, paylaşılan rapor deposu, kalite kapıları (quality gates).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RefactorLens contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,286 @@
1
+ Metadata-Version: 2.5
2
+ Name: refactorlens
3
+ Version: 0.1.0
4
+ Summary: Metric-grounded AI code review CLI for Python codebases.
5
+ Project-URL: Homepage, https://github.com/okngms/refactorlens
6
+ Project-URL: Source, https://github.com/okngms/refactorlens
7
+ Project-URL: Issues, https://github.com/okngms/refactorlens/issues
8
+ Author-email: Okan GUMUS <okanngumus4@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 RefactorLens contributors
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: code-quality,llm,metrics,refactoring,static-analysis
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Programming Language :: Python :: 3.14
40
+ Classifier: Topic :: Software Development :: Quality Assurance
41
+ Requires-Python: >=3.11
42
+ Requires-Dist: httpx>=0.27
43
+ Requires-Dist: pyyaml>=6.0
44
+ Requires-Dist: rich>=13.7
45
+ Requires-Dist: typer>=0.12
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest>=8.0; extra == 'dev'
48
+ Requires-Dist: ruff>=0.6; extra == 'dev'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # RefactorLens
52
+
53
+ **Metric-grounded code review for Python.** RefactorLens computes
54
+ object-oriented design metrics from your codebase and reports them as evidence
55
+ — not opinions.
56
+
57
+ ```bash
58
+ pipx install refactorlens
59
+ rlens scan .
60
+ ```
61
+
62
+ ```
63
+ your-project/src — 12 modules, 14 classes, 68 functions
64
+
65
+ Class metrics
66
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━┳━━━━━━━┳━━━━━┳━━━━━━┳━━━━━━┓
67
+ ┃ Class ┃ NOM ┃ WMC ┃ LCOM4 ┃ DCC ┃ DAM ┃ CAM ┃
68
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━╇━━━━━━━╇━━━━━╇━━━━━━╇━━━━━━┩
69
+ │ orders:OrderManager │ 25 │ 49 │ 4 │ 8 │ 1.00 │ — │
70
+ │ reporting:ReportBuilder │ 6 │ 7 │ 2 │ 0 │ 1.00 │ — │
71
+ │ models:Customer │ 6 │ 6 │ 3 │ 0 │ 0.20 │ 1.00 │
72
+ └─────────────────────────────┴─────┴─────┴───────┴─────┴──────┴──────┘
73
+
74
+ — CAM not computed for 2 classes (no annotated parameters)
75
+ 8 items over threshold.
76
+ ```
77
+
78
+ > **Status: v0.1.0.** `scan` is complete and tested. The AI advisor (`advise`)
79
+ > and the verification loop (`verify`) are the next milestones — see
80
+ > [Roadmap](#roadmap).
81
+
82
+ ## Why this exists
83
+
84
+ Most "let an AI review my code" tools hand the model raw source and hope for the
85
+ best. RefactorLens is built on a different bet: **give the model measurements,
86
+ then check its work.**
87
+
88
+ Two layers make that concrete.
89
+
90
+ **Metric-grounded prompting.** Before asking a model for advice, compute the
91
+ numbers. A suggestion that says "this class has four disjoint responsibilities
92
+ (LCOM4 = 4) and touches eight other classes (DCC = 8)" is checkable. A
93
+ suggestion that says "this feels messy" is not.
94
+
95
+ **The verify loop.** Every suggestion must come with a *measurable prediction* —
96
+ "this change lowers LCOM4 and leaves DCC unchanged". After the change is
97
+ applied, the metrics are recomputed and two questions get answered at once: did
98
+ quality actually improve, and **was the model's own prediction correct?**
99
+
100
+ The second question is the interesting one.
101
+
102
+ ## Install
103
+
104
+ ```bash
105
+ pipx install refactorlens # recommended: isolated environment
106
+ pip install refactorlens # or into your current environment
107
+ ```
108
+
109
+ Requires Python 3.11 or newer. RefactorLens is a CLI tool rather than a library,
110
+ so `pipx` is the better fit.
111
+
112
+ ## Usage
113
+
114
+ ```bash
115
+ rlens scan . # scan the current project
116
+ rlens scan src/ --no-report # print tables only, write nothing
117
+ rlens scan . --fail-on-violation # exit 1 if anything is over threshold
118
+ rlens --version
119
+ ```
120
+
121
+ `scan` prints two tables and writes a JSON report to `reports/`:
122
+
123
+ - **Class metrics** — every class, worst first
124
+ - **Functions over threshold** — only the ones that exceed a limit, because
125
+ printing every function makes the output useless
126
+
127
+ Values shown as `—` were **not computed**, which is different from zero. The
128
+ footnote below the table says why.
129
+
130
+ ### Exit codes
131
+
132
+ | Code | Meaning |
133
+ |---|---|
134
+ | 0 | Success |
135
+ | 1 | Config error, unwritable report, or `--fail-on-violation` triggered |
136
+ | 2 | Invalid command usage |
137
+
138
+ ## Configuration
139
+
140
+ RefactorLens works with no configuration. To customise, put `rlens.yaml` in your
141
+ project root — it is searched for upward from the scanned path.
142
+
143
+ ```yaml
144
+ scan:
145
+ include: ["."] # scan everything under the given path
146
+ exclude: ["tests/", ".venv/", "migrations/"]
147
+ output_dir: reports/
148
+
149
+ metrics:
150
+ cam_min_annotation_coverage: 0.7 # below this, CAM reports null
151
+
152
+ thresholds:
153
+ cyclomatic_complexity: {warn: 10, critical: 20}
154
+ max_params: {warn: 5}
155
+ max_nesting: {warn: 4}
156
+ lcom4: {warn: 2, critical: 4}
157
+ dcc: {warn: 7}
158
+ wmc: {warn: 50}
159
+ nom: {warn: 20}
160
+ ```
161
+
162
+ Unknown keys are an **error**, not a warning. A typo like `max_nestings` would
163
+ otherwise leave you silently running on defaults.
164
+
165
+ ## Metric definitions and adaptations
166
+
167
+ The metrics are inspired by the QMOOD family and classic complexity measures.
168
+ **Only a subset of QMOOD is implemented; this is not a complete QMOOD tool.**
169
+
170
+ Python is dynamically typed, so several object-oriented metrics can only be
171
+ computed by adaptation. Those adaptations are documented here rather than
172
+ hidden.
173
+
174
+ ### Shared rule: which methods count
175
+
176
+ All six class metrics operate on the same method set: methods defined directly
177
+ in the class body, excluding dunder methods. `@property`, `@staticmethod` and
178
+ `@classmethod` are included. Nested functions and nested classes are not.
179
+
180
+ `__init__` is excluded, and this matters most for LCOM4: a constructor touches
181
+ every attribute by definition, so counting it would merge every class into a
182
+ single component and make the metric useless. Classic LCOM4 excludes
183
+ constructors for the same reason.
184
+
185
+ ### Class-level metrics
186
+
187
+ | Metric | What it counts | Adaptation |
188
+ |---|---|---|
189
+ | **NOM** | Methods in the class | Dunders excluded |
190
+ | **WMC** | Sum of cyclomatic complexity over those methods | Same method set as NOM, for consistency |
191
+ | **LCOM4** | Connected components in the method–attribute graph | See limitation below |
192
+ | **DAM** | Ratio of private attributes | Reported twice: `dam` counts `_x` and `__x`; `dam_strict` counts only `__x` |
193
+ | **DCC** | Distinct project-internal classes referenced | Name-based resolution; see below |
194
+ | **CAM** | Mean ratio of each method's parameter types to the class-wide set | Computed only when annotation coverage is sufficient |
195
+
196
+ **Attribute set (used by DAM and LCOM4)** is the union of: class-level
197
+ assignments and annotations, `self.x = ...` in *any* method (not just
198
+ `__init__`), and names listed in `__slots__`. Names that are only ever read are
199
+ not attributes.
200
+
201
+ **DCC resolution is best-effort.** Python has no static type information, so a
202
+ name appearing in a class body is counted as a reference if it matches a class
203
+ defined anywhere in the project. A local variable that shares a name with a
204
+ class will be counted. Standard-library and third-party classes are not counted.
205
+
206
+ **CAM is conditional.** The classic definition uses parameter *types*. Parameter
207
+ *names* measure something else entirely and would make the result incomparable
208
+ to the literature, so name similarity is never used as a fallback. Most Python
209
+ codebases are unannotated; forcing a number out of them would feed the model
210
+ noise dressed up as evidence. If annotation coverage falls below
211
+ `metrics.cam_min_annotation_coverage` (default 0.7), CAM reports `null` and the
212
+ report records why.
213
+
214
+ ### Known limitation: LCOM4 and data classes
215
+
216
+ LCOM4 flags well-written data-holder classes as uncohesive. A class with one
217
+ accessor per field — `rename` touching `name`, `promote` touching `tier` — has
218
+ methods that share no state, so LCOM4 counts them as separate responsibilities.
219
+
220
+ This is a property of the metric, not a bug, and it is well documented in the
221
+ literature. RefactorLens reports the number as measured rather than
222
+ special-casing it away. When reading a report, treat a high LCOM4 on a small
223
+ class as a question rather than a verdict; **WMC and DCC separate genuinely
224
+ overloaded classes from plain data holders far more reliably.**
225
+
226
+ ### Function-level metrics
227
+
228
+ Cyclomatic complexity counts `if`/`elif`, loops, `except` handlers, ternaries,
229
+ each additional `and`/`or` operand, comprehension clauses, and `match` cases.
230
+ `else`, `with`, and `try` itself add nothing — they do not branch execution.
231
+
232
+ Nesting depth treats `elif` chains as flat: a ten-branch `elif` is not ten
233
+ levels deep.
234
+
235
+ Nested function definitions are never entered. A function containing a closure
236
+ does not inherit the closure's complexity.
237
+
238
+ ## What RefactorLens does not do
239
+
240
+ - **It does not run your code.** Files are parsed with `ast`, never executed.
241
+ - **It does not fix anything.** `scan` measures; future versions will suggest.
242
+ - **It does not send anything anywhere.** `scan` is entirely local. When
243
+ `advise` arrives, it will send selected code to whichever LLM provider you
244
+ configure, and local execution via Ollama will be supported for sensitive
245
+ codebases.
246
+ - **Python only.** No Java or C#.
247
+
248
+ ## Roadmap
249
+
250
+ | Phase | Contents | Version |
251
+ |---|---|---|
252
+ | 0 | Package skeleton, config, test foundation | — |
253
+ | 1 | Metric engine (`scan`) | — |
254
+ | **2** | **PyPI release** | **v0.1.0** |
255
+ | 3 | AI advisor (`advise`) | v0.2.0 |
256
+ | 4 | Verification loop (`verify`) | v0.3.0 |
257
+ | 5 | Experiment and findings | v1.0.0 |
258
+
259
+ Ideas deliberately out of scope live in [FUTURE.md](FUTURE.md).
260
+
261
+ ## Development
262
+
263
+ ```bash
264
+ git clone https://github.com/okngms/refactorlens
265
+ cd refactorlens
266
+ python -m venv .venv && source .venv/bin/activate
267
+ pip install -e ".[dev]"
268
+
269
+ pytest tests # the package's own tests
270
+ pytest examples/messy_project/tests # the fixture's behaviour tests
271
+ ruff check . && ruff format --check .
272
+ ```
273
+
274
+ `examples/messy_project` is a deliberately badly designed sample project used as
275
+ the test fixture; every metric is verified against hand-computed gold values on
276
+ it. See [SMELLS.md](examples/messy_project/SMELLS.md) for the inventory of
277
+ intentional smells and the reasoning behind each.
278
+
279
+ Its behaviour test suite exists for a specific reason: from phase 4 onward,
280
+ refactoring suggestions get applied by hand. If a refactoring breaks the code,
281
+ the metrics improve while the program stops working. The rule is therefore
282
+ absolute — **no metric delta counts unless the behaviour tests pass.**
283
+
284
+ ## Licence
285
+
286
+ MIT
@@ -0,0 +1,236 @@
1
+ # RefactorLens
2
+
3
+ **Metric-grounded code review for Python.** RefactorLens computes
4
+ object-oriented design metrics from your codebase and reports them as evidence
5
+ — not opinions.
6
+
7
+ ```bash
8
+ pipx install refactorlens
9
+ rlens scan .
10
+ ```
11
+
12
+ ```
13
+ your-project/src — 12 modules, 14 classes, 68 functions
14
+
15
+ Class metrics
16
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━┳━━━━━━━┳━━━━━┳━━━━━━┳━━━━━━┓
17
+ ┃ Class ┃ NOM ┃ WMC ┃ LCOM4 ┃ DCC ┃ DAM ┃ CAM ┃
18
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━╇━━━━━━━╇━━━━━╇━━━━━━╇━━━━━━┩
19
+ │ orders:OrderManager │ 25 │ 49 │ 4 │ 8 │ 1.00 │ — │
20
+ │ reporting:ReportBuilder │ 6 │ 7 │ 2 │ 0 │ 1.00 │ — │
21
+ │ models:Customer │ 6 │ 6 │ 3 │ 0 │ 0.20 │ 1.00 │
22
+ └─────────────────────────────┴─────┴─────┴───────┴─────┴──────┴──────┘
23
+
24
+ — CAM not computed for 2 classes (no annotated parameters)
25
+ 8 items over threshold.
26
+ ```
27
+
28
+ > **Status: v0.1.0.** `scan` is complete and tested. The AI advisor (`advise`)
29
+ > and the verification loop (`verify`) are the next milestones — see
30
+ > [Roadmap](#roadmap).
31
+
32
+ ## Why this exists
33
+
34
+ Most "let an AI review my code" tools hand the model raw source and hope for the
35
+ best. RefactorLens is built on a different bet: **give the model measurements,
36
+ then check its work.**
37
+
38
+ Two layers make that concrete.
39
+
40
+ **Metric-grounded prompting.** Before asking a model for advice, compute the
41
+ numbers. A suggestion that says "this class has four disjoint responsibilities
42
+ (LCOM4 = 4) and touches eight other classes (DCC = 8)" is checkable. A
43
+ suggestion that says "this feels messy" is not.
44
+
45
+ **The verify loop.** Every suggestion must come with a *measurable prediction* —
46
+ "this change lowers LCOM4 and leaves DCC unchanged". After the change is
47
+ applied, the metrics are recomputed and two questions get answered at once: did
48
+ quality actually improve, and **was the model's own prediction correct?**
49
+
50
+ The second question is the interesting one.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pipx install refactorlens # recommended: isolated environment
56
+ pip install refactorlens # or into your current environment
57
+ ```
58
+
59
+ Requires Python 3.11 or newer. RefactorLens is a CLI tool rather than a library,
60
+ so `pipx` is the better fit.
61
+
62
+ ## Usage
63
+
64
+ ```bash
65
+ rlens scan . # scan the current project
66
+ rlens scan src/ --no-report # print tables only, write nothing
67
+ rlens scan . --fail-on-violation # exit 1 if anything is over threshold
68
+ rlens --version
69
+ ```
70
+
71
+ `scan` prints two tables and writes a JSON report to `reports/`:
72
+
73
+ - **Class metrics** — every class, worst first
74
+ - **Functions over threshold** — only the ones that exceed a limit, because
75
+ printing every function makes the output useless
76
+
77
+ Values shown as `—` were **not computed**, which is different from zero. The
78
+ footnote below the table says why.
79
+
80
+ ### Exit codes
81
+
82
+ | Code | Meaning |
83
+ |---|---|
84
+ | 0 | Success |
85
+ | 1 | Config error, unwritable report, or `--fail-on-violation` triggered |
86
+ | 2 | Invalid command usage |
87
+
88
+ ## Configuration
89
+
90
+ RefactorLens works with no configuration. To customise, put `rlens.yaml` in your
91
+ project root — it is searched for upward from the scanned path.
92
+
93
+ ```yaml
94
+ scan:
95
+ include: ["."] # scan everything under the given path
96
+ exclude: ["tests/", ".venv/", "migrations/"]
97
+ output_dir: reports/
98
+
99
+ metrics:
100
+ cam_min_annotation_coverage: 0.7 # below this, CAM reports null
101
+
102
+ thresholds:
103
+ cyclomatic_complexity: {warn: 10, critical: 20}
104
+ max_params: {warn: 5}
105
+ max_nesting: {warn: 4}
106
+ lcom4: {warn: 2, critical: 4}
107
+ dcc: {warn: 7}
108
+ wmc: {warn: 50}
109
+ nom: {warn: 20}
110
+ ```
111
+
112
+ Unknown keys are an **error**, not a warning. A typo like `max_nestings` would
113
+ otherwise leave you silently running on defaults.
114
+
115
+ ## Metric definitions and adaptations
116
+
117
+ The metrics are inspired by the QMOOD family and classic complexity measures.
118
+ **Only a subset of QMOOD is implemented; this is not a complete QMOOD tool.**
119
+
120
+ Python is dynamically typed, so several object-oriented metrics can only be
121
+ computed by adaptation. Those adaptations are documented here rather than
122
+ hidden.
123
+
124
+ ### Shared rule: which methods count
125
+
126
+ All six class metrics operate on the same method set: methods defined directly
127
+ in the class body, excluding dunder methods. `@property`, `@staticmethod` and
128
+ `@classmethod` are included. Nested functions and nested classes are not.
129
+
130
+ `__init__` is excluded, and this matters most for LCOM4: a constructor touches
131
+ every attribute by definition, so counting it would merge every class into a
132
+ single component and make the metric useless. Classic LCOM4 excludes
133
+ constructors for the same reason.
134
+
135
+ ### Class-level metrics
136
+
137
+ | Metric | What it counts | Adaptation |
138
+ |---|---|---|
139
+ | **NOM** | Methods in the class | Dunders excluded |
140
+ | **WMC** | Sum of cyclomatic complexity over those methods | Same method set as NOM, for consistency |
141
+ | **LCOM4** | Connected components in the method–attribute graph | See limitation below |
142
+ | **DAM** | Ratio of private attributes | Reported twice: `dam` counts `_x` and `__x`; `dam_strict` counts only `__x` |
143
+ | **DCC** | Distinct project-internal classes referenced | Name-based resolution; see below |
144
+ | **CAM** | Mean ratio of each method's parameter types to the class-wide set | Computed only when annotation coverage is sufficient |
145
+
146
+ **Attribute set (used by DAM and LCOM4)** is the union of: class-level
147
+ assignments and annotations, `self.x = ...` in *any* method (not just
148
+ `__init__`), and names listed in `__slots__`. Names that are only ever read are
149
+ not attributes.
150
+
151
+ **DCC resolution is best-effort.** Python has no static type information, so a
152
+ name appearing in a class body is counted as a reference if it matches a class
153
+ defined anywhere in the project. A local variable that shares a name with a
154
+ class will be counted. Standard-library and third-party classes are not counted.
155
+
156
+ **CAM is conditional.** The classic definition uses parameter *types*. Parameter
157
+ *names* measure something else entirely and would make the result incomparable
158
+ to the literature, so name similarity is never used as a fallback. Most Python
159
+ codebases are unannotated; forcing a number out of them would feed the model
160
+ noise dressed up as evidence. If annotation coverage falls below
161
+ `metrics.cam_min_annotation_coverage` (default 0.7), CAM reports `null` and the
162
+ report records why.
163
+
164
+ ### Known limitation: LCOM4 and data classes
165
+
166
+ LCOM4 flags well-written data-holder classes as uncohesive. A class with one
167
+ accessor per field — `rename` touching `name`, `promote` touching `tier` — has
168
+ methods that share no state, so LCOM4 counts them as separate responsibilities.
169
+
170
+ This is a property of the metric, not a bug, and it is well documented in the
171
+ literature. RefactorLens reports the number as measured rather than
172
+ special-casing it away. When reading a report, treat a high LCOM4 on a small
173
+ class as a question rather than a verdict; **WMC and DCC separate genuinely
174
+ overloaded classes from plain data holders far more reliably.**
175
+
176
+ ### Function-level metrics
177
+
178
+ Cyclomatic complexity counts `if`/`elif`, loops, `except` handlers, ternaries,
179
+ each additional `and`/`or` operand, comprehension clauses, and `match` cases.
180
+ `else`, `with`, and `try` itself add nothing — they do not branch execution.
181
+
182
+ Nesting depth treats `elif` chains as flat: a ten-branch `elif` is not ten
183
+ levels deep.
184
+
185
+ Nested function definitions are never entered. A function containing a closure
186
+ does not inherit the closure's complexity.
187
+
188
+ ## What RefactorLens does not do
189
+
190
+ - **It does not run your code.** Files are parsed with `ast`, never executed.
191
+ - **It does not fix anything.** `scan` measures; future versions will suggest.
192
+ - **It does not send anything anywhere.** `scan` is entirely local. When
193
+ `advise` arrives, it will send selected code to whichever LLM provider you
194
+ configure, and local execution via Ollama will be supported for sensitive
195
+ codebases.
196
+ - **Python only.** No Java or C#.
197
+
198
+ ## Roadmap
199
+
200
+ | Phase | Contents | Version |
201
+ |---|---|---|
202
+ | 0 | Package skeleton, config, test foundation | — |
203
+ | 1 | Metric engine (`scan`) | — |
204
+ | **2** | **PyPI release** | **v0.1.0** |
205
+ | 3 | AI advisor (`advise`) | v0.2.0 |
206
+ | 4 | Verification loop (`verify`) | v0.3.0 |
207
+ | 5 | Experiment and findings | v1.0.0 |
208
+
209
+ Ideas deliberately out of scope live in [FUTURE.md](FUTURE.md).
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ git clone https://github.com/okngms/refactorlens
215
+ cd refactorlens
216
+ python -m venv .venv && source .venv/bin/activate
217
+ pip install -e ".[dev]"
218
+
219
+ pytest tests # the package's own tests
220
+ pytest examples/messy_project/tests # the fixture's behaviour tests
221
+ ruff check . && ruff format --check .
222
+ ```
223
+
224
+ `examples/messy_project` is a deliberately badly designed sample project used as
225
+ the test fixture; every metric is verified against hand-computed gold values on
226
+ it. See [SMELLS.md](examples/messy_project/SMELLS.md) for the inventory of
227
+ intentional smells and the reasoning behind each.
228
+
229
+ Its behaviour test suite exists for a specific reason: from phase 4 onward,
230
+ refactoring suggestions get applied by hand. If a refactoring breaks the code,
231
+ the metrics improve while the program stops working. The rule is therefore
232
+ absolute — **no metric delta counts unless the behaviour tests pass.**
233
+
234
+ ## Licence
235
+
236
+ MIT
@@ -0,0 +1,12 @@
1
+ # Sağlayıcı anahtarları. Bu dosyayı .env olarak kopyalayıp doldurun.
2
+ # .env asla commit edilmez (bkz. .gitignore).
3
+
4
+ # Groq (M1 çekirdek, bulut) — https://console.groq.com
5
+ GROQ_API_KEY=
6
+
7
+ # Ollama (M1 çekirdek, lokal) — anahtar gerektirmez.
8
+ # Varsayılan dışı bir adres kullanıyorsanız rlens.yaml -> provider.base_url ayarlayın.
9
+
10
+ # Opsiyonel sağlayıcılar (Faz 4)
11
+ GEMINI_API_KEY=
12
+ ANTHROPIC_API_KEY=
@@ -0,0 +1,73 @@
1
+ # messy_project — kasıtlı koku envanteri
2
+
3
+ Bu dosya fikstürün **sözleşmesidir**. Buradaki her sayı `tests/test_class_metrics.py`
4
+ ve `tests/test_func_metrics.py` içinde altın değer olarak sabitlenmiştir; fikstür
5
+ değişirse testler kırılır.
6
+
7
+ ## Sınıf düzeyi — ölçülen değerler
8
+
9
+ | Sınıf | Dosya | NOM | WMC | LCOM4 | DCC | DAM | CAM |
10
+ |---|---|---|---|---|---|---|---|
11
+ | `OrderManager` | `god.py` | 25 | 49 | **4** | **8** | 1.0 | `null` |
12
+ | `ReportBuilder` | `reporting.py` | 6 | 7 | 2 | 0 | 1.0 | `null` |
13
+ | `Customer` | `models.py` | 6 | 6 | 3 | 0 | 0.2 | 1.0 |
14
+ | `Product` | `models.py` | 4 | 5 | 2 | 0 | 0.0 | 1.0 |
15
+ | `OrderLine` | `models.py` | 3 | 3 | 1 | 1 | 0.0 | 1.0 |
16
+ | `Order` | `services.py` | 4 | 6 | 2 | 2 | 0.0 | `null` |
17
+ | `Invoice` | `services.py` | 1 | 1 | 1 | 0 | 0.0 | `null` |
18
+ | `AuditEntry` | `services.py` | 1 | 1 | 1 | 0 | 0.0 | `null` |
19
+ | `EmailNotifier` | `services.py` | 1 | 1 | 1 | 0 | 0.0 | 1.0 |
20
+ | `ShippingCalculator` | `services.py` | 1 | 2 | 1 | 0 | 0.0 | 1.0 |
21
+
22
+ ### `OrderManager` — ana denek
23
+ Dört ayrık sorumluluk tek sınıfta. Bileşenler birbirini **hiç çağırmaz**; bu
24
+ bilinçlidir, yoksa bileşenler birleşir ve fikstür amacını kaybeder:
25
+
26
+ 1. `_orders`, `_next_id` → `place_order`, `get_order`, `cancel_order`, `order_count`, `orders_for`, `line_count`, `mark_paid`, `unpaid_orders`
27
+ 2. `_tax_rate`, `_discount_rules` → `set_discount`, `discount_for`, `bulk_discount`, `set_tax_rate`, `apply_tax`, `total_with_tax`, `shipping_for`
28
+ 3. `_log` → `log_event`, `history`, `last_event`, `log_size`, `clear_log`
29
+ 4. `_smtp_host`, `_sent` → `configure_smtp`, `notify`, `sent_count`, `outbox`, `reset_outbox`
30
+
31
+ DCC = 8: `Customer`, `Product`, `OrderLine`, `Order`, `Invoice`, `AuditEntry`,
32
+ `EmailNotifier`, `ShippingCalculator`.
33
+
34
+ > **Dikkat:** Fikstüre yeni metot eklerken iki bileşenin attribute'una birden
35
+ > dokunmayın; LCOM4 sessizce 4'ten 3'e düşer ve altın değer testleri nedeni
36
+ > anlaşılmadan kırılır.
37
+
38
+ ### LCOM4 hakkında dürüst bir not
39
+ `models.py` bilerek **temiz** yazılmıştır, ancak LCOM4 değerleri 1 değildir.
40
+ `Customer`'ın her alanı için ayrı erişimcisi vardır (`rename` → `name`,
41
+ `promote` → `tier`, `add_note` → `_notes`) ve bu metotlar birbirine dokunmaz;
42
+ LCOM4 bunu üç ayrı sorumluluk sayar.
43
+
44
+ Bu, LCOM4'ün literatürde bilinen zayıflığıdır: **veri taşıyıcı sınıfları
45
+ kohezyonsuz gösterir.** Sınıf kötü tasarlanmış değildir.
46
+
47
+ Fikstürü metriği memnun edecek şekilde değiştirmiyoruz — bu tam olarak aracın
48
+ uyardığı Goodhart tuzağı olurdu. Sınırlılık README'nin "Metric Definitions &
49
+ Adaptations" bölümünde belgelenecektir.
50
+
51
+ Temiz ile kirli arasındaki asıl ayrım LCOM4'te değil **WMC ve DCC'de** görünür:
52
+ `Customer` 6 / 0, `OrderManager` 49 / 8.
53
+
54
+ ## Fonksiyon düzeyi — ölçülen değerler (`utils.py`)
55
+
56
+ | Fonksiyon | CC | Parametre | İç içelik | Aşan eşik |
57
+ |---|---|---|---|---|
58
+ | `classify_order` | **15** | **7** | 1 | CC > 10, params > 5 |
59
+ | `deep_transform` | 8 | 1 | **7** | nesting > 4 |
60
+ | `build_shipping_label` | 2 | **7** | 0 | params > 5 |
61
+
62
+ `classify_order` yüksek karmaşıklığa rağmen **düz**dür (iç içelik 1); ardışık
63
+ `if` zinciri derinlik üretmez. İki metriğin farklı şeyler ölçtüğünün kanıtı.
64
+
65
+ ## CAM kapsama vakaları
66
+ Fikstür CAM'in **her iki yolunu** da kapsar:
67
+ - **Hesaplanır:** `models.py` (annotation kapsamı %100)
68
+ - **`null` döner:** `god.py` (kapsam %0 → `no_annotated_parameters`) ve
69
+ `reporting.py` (kapsam %33 → `insufficient_annotations`)
70
+
71
+ ## Değiştirme kuralı
72
+ Bu tablodaki bir sayıyı değiştiren her fikstür düzenlemesi, aynı commit'te altın
73
+ değer testlerini de günceller. Fikstür sessizce kaymamalıdır.