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.
- refactorlens-0.1.0/.gitignore +11 -0
- refactorlens-0.1.0/FUTURE.md +39 -0
- refactorlens-0.1.0/LICENSE +21 -0
- refactorlens-0.1.0/PKG-INFO +286 -0
- refactorlens-0.1.0/README.md +236 -0
- refactorlens-0.1.0/env.example +12 -0
- refactorlens-0.1.0/examples/messy_project/SMELLS.md +73 -0
- refactorlens-0.1.0/examples/messy_project/god.py +185 -0
- refactorlens-0.1.0/examples/messy_project/models.py +77 -0
- refactorlens-0.1.0/examples/messy_project/reporting.py +39 -0
- refactorlens-0.1.0/examples/messy_project/rlens.yaml +6 -0
- refactorlens-0.1.0/examples/messy_project/services.py +67 -0
- refactorlens-0.1.0/examples/messy_project/tests/conftest.py +13 -0
- refactorlens-0.1.0/examples/messy_project/tests/test_god.py +237 -0
- refactorlens-0.1.0/examples/messy_project/tests/test_models.py +68 -0
- refactorlens-0.1.0/examples/messy_project/tests/test_reporting.py +41 -0
- refactorlens-0.1.0/examples/messy_project/tests/test_services.py +71 -0
- refactorlens-0.1.0/examples/messy_project/tests/test_utils.py +63 -0
- refactorlens-0.1.0/examples/messy_project/utils.py +51 -0
- refactorlens-0.1.0/examples/sample_reports/README.md +10 -0
- refactorlens-0.1.0/examples/sample_reports/scan-messy_project.json +634 -0
- refactorlens-0.1.0/pyproject.toml +64 -0
- refactorlens-0.1.0/src/rlens/__init__.py +8 -0
- refactorlens-0.1.0/src/rlens/analysis/__init__.py +1 -0
- refactorlens-0.1.0/src/rlens/analysis/class_metrics.py +483 -0
- refactorlens-0.1.0/src/rlens/analysis/func_metrics.py +258 -0
- refactorlens-0.1.0/src/rlens/analysis/model.py +88 -0
- refactorlens-0.1.0/src/rlens/analysis/parser.py +211 -0
- refactorlens-0.1.0/src/rlens/analysis/scanner.py +92 -0
- refactorlens-0.1.0/src/rlens/cli.py +143 -0
- refactorlens-0.1.0/src/rlens/config.py +340 -0
- refactorlens-0.1.0/src/rlens/report/__init__.py +1 -0
- refactorlens-0.1.0/src/rlens/report/files.py +104 -0
- refactorlens-0.1.0/src/rlens/report/terminal.py +251 -0
- refactorlens-0.1.0/tests/test_class_metrics.py +536 -0
- refactorlens-0.1.0/tests/test_cli.py +129 -0
- refactorlens-0.1.0/tests/test_config.py +148 -0
- refactorlens-0.1.0/tests/test_func_metrics.py +293 -0
- refactorlens-0.1.0/tests/test_parser.py +174 -0
- refactorlens-0.1.0/tests/test_report.py +278 -0
- refactorlens-0.1.0/tests/test_scanner.py +135 -0
|
@@ -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.
|