codedna 0.2.0__py3-none-any.whl

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.
codedna/survey.py ADDED
@@ -0,0 +1,104 @@
1
+ """Commit anlama anketi modülü."""
2
+
3
+ from typing import Optional
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.prompt import IntPrompt
8
+ from rich.text import Text
9
+
10
+ console = Console()
11
+
12
+
13
+ def _skor_al(soru: str, soru_no: int) -> Optional[int]:
14
+ """
15
+ 1-5 arasında geçerli bir skor al.
16
+
17
+ Dönüş değerleri:
18
+ - int (1-5) : kullanıcı bir skor girdi
19
+ - None : kullanıcı bilerek atladı (0 veya Enter)
20
+ veya stdin okunamadı (EOFError — ortam sorunu)
21
+
22
+ EOFError, KeyboardInterrupt ayrı yakalanır:
23
+ - KeyboardInterrupt → sessizce None (kullanıcı Ctrl+C'ye bastı)
24
+ - EOFError → uyarı yaz + None (stdin bağlı değil, ortam sorunu)
25
+ """
26
+ try:
27
+ deger = IntPrompt.ask(
28
+ f" [bold cyan]{soru_no}.[/bold cyan] {soru} [dim]\\[1-5][/dim]",
29
+ default=0,
30
+ )
31
+ if deger == 0:
32
+ return None
33
+ return max(1, min(5, deger))
34
+ except KeyboardInterrupt:
35
+ # Kullanıcı bilerek iptal etti — sessiz
36
+ return None
37
+ except EOFError:
38
+ # stdin okunamadı — kullanıcının bilerek yaptığı bir seçim değil.
39
+ # Sessiz yutma yerine bilgilendirici bir uyarı göster.
40
+ console.print(
41
+ " [dim red]⚠ Anket için terminal girdisi okunamadı "
42
+ "(stdin bağlı değil). Anket atlanıyor.[/dim red]"
43
+ )
44
+ return None
45
+
46
+
47
+ def run_survey(commit_hash: str) -> Optional[float]:
48
+ """
49
+ Post-commit hook sonrası 3 soruluk anlama anketi çalıştır.
50
+
51
+ Args:
52
+ commit_hash: Anketin bağlandığı commit hash'i
53
+
54
+ Returns:
55
+ Ortalama anlama skoru (1.0-5.0), kullanıcı atlarsa None
56
+ """
57
+ console.print()
58
+ console.print(
59
+ Panel(
60
+ Text.from_markup(
61
+ f"[bold yellow]CodeDNA[/bold yellow] — Commit [dim]{commit_hash[:8]}[/dim] için hızlı anlama anketi\n"
62
+ "[dim]Enter ile atlayabilirsin (0 = atla)[/dim]"
63
+ ),
64
+ border_style="yellow",
65
+ padding=(0, 2),
66
+ )
67
+ )
68
+
69
+ sorular = [
70
+ "Bu değişikliği 3 ay sonra açıklayabilir misin?",
71
+ "Bir hata çıksa debug edebilir misin?",
72
+ "Başkası sorsa, nasıl çalıştığını anlatabilir misin?",
73
+ ]
74
+
75
+ skorlar: list[int] = []
76
+ for i, soru in enumerate(sorular, start=1):
77
+ skor = _skor_al(soru, i)
78
+ if skor is None:
79
+ # Bilerek atladı veya EOFError zaten uyarı yazdı
80
+ console.print(" [dim]Anket atlandı.[/dim]")
81
+ return None
82
+ skorlar.append(skor)
83
+
84
+ if not skorlar:
85
+ return None
86
+
87
+ ortalama = sum(skorlar) / len(skorlar)
88
+
89
+ # Skora göre renk ve mesaj
90
+ if ortalama >= 4.0:
91
+ renk = "green"
92
+ etiket = "Harika 💪"
93
+ elif ortalama >= 2.5:
94
+ renk = "yellow"
95
+ etiket = "Orta seviye 🤔"
96
+ else:
97
+ renk = "red"
98
+ etiket = "Risk var ⚠️"
99
+
100
+ console.print(
101
+ f"\n Anlama skoru: [bold {renk}]{ortalama:.1f}/5[/bold {renk}] — {etiket}\n"
102
+ )
103
+
104
+ return ortalama
codedna/tech_debt.py ADDED
@@ -0,0 +1,232 @@
1
+ """Teknik borcu parasal değere çevirir."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from codedna.db import get_connection, get_file_scores_for_commit, get_commit_history
10
+
11
+ # Varsayılan saatlik maliyet (USD)
12
+ _VARSAYILAN_SAATLIK = 75.0
13
+
14
+ # Risk eşikleri (aylık maliyet)
15
+ _KRITIK_ESIK = 100.0 # $/ay üzeri → KRİTİK
16
+ _YUKSEK_ESIK = 50.0 # $/ay üzeri → YÜKSEK
17
+
18
+
19
+ @dataclass
20
+ class DosyaBorcu:
21
+ """Tek dosyanın teknik borç analizi."""
22
+
23
+ dosya_yolu: str
24
+ debt_saatleri: float # tahmini borç saati
25
+ aylik_maliyet_usd: float # amortismana yayılmış aylık maliyet
26
+ risk_seviyesi: str # KRİTİK / YÜKSEK / ORTA / DÜŞÜK
27
+ toplam_satir: int
28
+ ai_olasiligi: float
29
+ karmasiklik: float
30
+ anlama_skoru: Optional[float]
31
+
32
+
33
+ @dataclass
34
+ class RepoBorcu:
35
+ """Repo geneli teknik borç özeti."""
36
+
37
+ toplam_debt_saatleri: float
38
+ toplam_aylik_maliyet_usd: float
39
+ en_pahali_5: list[DosyaBorcu]
40
+ toplam_dosya: int
41
+ saatlik_ucret: float
42
+
43
+
44
+ def _risk_hesapla(aylik_maliyet: float) -> str:
45
+ """Aylık maliyete göre risk seviyesi döndür."""
46
+ if aylik_maliyet >= _KRITIK_ESIK:
47
+ return "KRİTİK"
48
+ elif aylik_maliyet >= _YUKSEK_ESIK:
49
+ return "YÜKSEK"
50
+ elif aylik_maliyet >= 20.0:
51
+ return "ORTA"
52
+ return "DÜŞÜK"
53
+
54
+
55
+ def _debt_saati_hesapla(
56
+ anlama_skoru: Optional[float],
57
+ ai_olasiligi: float,
58
+ karmasiklik: float,
59
+ toplam_satir: int,
60
+ ) -> float:
61
+ """
62
+ Dosya için teknik borç saatini hesapla.
63
+
64
+ Formül:
65
+ debt_hours = (
66
+ (1 - understanding_score/5) * 0.4 ← düşük anlama
67
+ + ai_probability * 0.35 ← AI kodu
68
+ + min(complexity/20, 1.0) * 0.25 ← karmaşıklık
69
+ ) * (lines_of_code / 100) * 2
70
+
71
+ Anlama skoru yoksa 2.5/5 varsayılır.
72
+ """
73
+ anlama = anlama_skoru if anlama_skoru is not None else 2.5
74
+ anlama_faktor = (1.0 - anlama / 5.0) * 0.40
75
+ ai_faktor = ai_olasiligi * 0.35
76
+ karmasiklik_faktor = min(karmasiklik / 20.0, 1.0) * 0.25
77
+
78
+ satir_carpan = (toplam_satir / 100.0) * 2.0
79
+
80
+ return (anlama_faktor + ai_faktor + karmasiklik_faktor) * satir_carpan
81
+
82
+
83
+ def calculate_file_debt(
84
+ file_path: str,
85
+ db_path: Path,
86
+ hourly_rate: float = _VARSAYILAN_SAATLIK,
87
+ ) -> Optional[DosyaBorcu]:
88
+ """
89
+ Tek dosya için teknik borç saatini ve dolar maliyetini hesapla.
90
+ Önce DB'den arar, bulamazsa dosyayı doğrudan analiz eder.
91
+
92
+ Args:
93
+ file_path: Dosya yolu (tam veya göreli)
94
+ db_path: SQLite veritabanı yolu
95
+ hourly_rate: Saatlik maliyet ($/saat)
96
+
97
+ Returns:
98
+ DosyaBorcu nesnesi veya dosya bulunamazsa None
99
+ """
100
+ # Önce DB'den en son skoru bul
101
+ satir = None
102
+ try:
103
+ with get_connection(db_path) as conn:
104
+ satir = conn.execute(
105
+ """
106
+ SELECT fs.ai_probability, fs.complexity_score, fs.understanding_score
107
+ FROM file_scores fs
108
+ JOIN commits c ON fs.commit_hash = c.commit_hash
109
+ WHERE fs.file_path = ?
110
+ ORDER BY c.timestamp DESC
111
+ LIMIT 1
112
+ """,
113
+ (file_path,),
114
+ ).fetchone()
115
+ except Exception:
116
+ pass
117
+
118
+ # DB'den gelen değerleri al veya dosyayı doğrudan analiz et
119
+ if satir:
120
+ ai = float(satir["ai_probability"] or 0.0)
121
+ karmasiklik = float(satir["complexity_score"] or 1.0)
122
+ anlama = float(satir["understanding_score"]) if satir["understanding_score"] is not None else None
123
+ else:
124
+ # DB'de yok — tree-sitter ile doğrudan analiz et
125
+ p = Path(file_path)
126
+ if not p.exists():
127
+ return None
128
+ try:
129
+ from codedna.analyzer import analyze_file
130
+ sonuc = analyze_file(p)
131
+ if sonuc.desteklenmiyor or sonuc.hata:
132
+ return None
133
+ ai = sonuc.ai_probability
134
+ karmasiklik = sonuc.complexity_score
135
+ anlama = None
136
+ except Exception:
137
+ return None
138
+
139
+ # Satır sayısı
140
+ toplam_satir = 100
141
+ try:
142
+ p = Path(file_path)
143
+ if p.exists():
144
+ toplam_satir = len(p.read_text(errors="replace").splitlines())
145
+ except Exception:
146
+ pass
147
+
148
+ debt_saati = _debt_saati_hesapla(anlama, ai, karmasiklik, toplam_satir)
149
+ aylik_maliyet = debt_saati * hourly_rate / 12.0
150
+
151
+ return DosyaBorcu(
152
+ dosya_yolu=file_path,
153
+ debt_saatleri=round(debt_saati, 2),
154
+ aylik_maliyet_usd=round(aylik_maliyet, 2),
155
+ risk_seviyesi=_risk_hesapla(aylik_maliyet),
156
+ toplam_satir=toplam_satir,
157
+ ai_olasiligi=round(ai, 3),
158
+ karmasiklik=round(karmasiklik, 1),
159
+ anlama_skoru=round(anlama, 2) if anlama is not None else None,
160
+ )
161
+
162
+
163
+ def calculate_repo_debt(
164
+ repo_path: Path,
165
+ db_path: Path,
166
+ hourly_rate: float = _VARSAYILAN_SAATLIK,
167
+ ) -> RepoBorcu:
168
+ """
169
+ Repo geneli toplam teknik borç özeti hesapla.
170
+
171
+ Args:
172
+ repo_path: Git repo kök dizini
173
+ db_path: SQLite veritabanı yolu
174
+ hourly_rate: Saatlik maliyet ($/saat)
175
+
176
+ Returns:
177
+ RepoBorcu özet nesnesi
178
+ """
179
+ # Repo dosyalarını tara — scan_repository artık sadece git izlenen
180
+ # dosyaları döndürüyor (.gitignore'dakiler dahil değil).
181
+ # DB yollarını KAYNAK olarak kullanmıyoruz: eski kirli veriler
182
+ # (build çıktıları vb.) hesaba katılmasın.
183
+ from codedna.scorer import scan_repository
184
+ taranan = scan_repository(repo_path, max_files=200)
185
+ tarama_yollari = {s.file_path for s in taranan}
186
+
187
+ # DB'deki anlama skorlarını sadece zenginleştirme için kullan —
188
+ # dosya listesini BELIRLEMEK için değil
189
+ tum_yollar = list(tarama_yollari)
190
+
191
+ dosya_borclar: list[DosyaBorcu] = []
192
+ for yol in tum_yollar:
193
+ # DB'de varsa DB'den al, yoksa taranan sonuçtan hesapla
194
+ borc = calculate_file_debt(yol, db_path, hourly_rate)
195
+ if borc is None:
196
+ # Taranan sonuçtan bulup hesapla
197
+ taranan_sonuc = next((s for s in taranan if s.file_path == yol), None)
198
+ if taranan_sonuc:
199
+ debt_saati = _debt_saati_hesapla(
200
+ None,
201
+ taranan_sonuc.ai_probability,
202
+ taranan_sonuc.complexity_score,
203
+ taranan_sonuc.total_lines,
204
+ )
205
+ aylik = debt_saati * hourly_rate / 12.0
206
+ borc = DosyaBorcu(
207
+ dosya_yolu=yol,
208
+ debt_saatleri=round(debt_saati, 2),
209
+ aylik_maliyet_usd=round(aylik, 2),
210
+ risk_seviyesi=_risk_hesapla(aylik),
211
+ toplam_satir=taranan_sonuc.total_lines,
212
+ ai_olasiligi=round(taranan_sonuc.ai_probability, 3),
213
+ karmasiklik=round(taranan_sonuc.complexity_score, 1),
214
+ anlama_skoru=None,
215
+ )
216
+ if borc:
217
+ dosya_borclar.append(borc)
218
+
219
+ # En pahalı 5 dosya
220
+ dosya_borclar.sort(key=lambda d: d.aylik_maliyet_usd, reverse=True)
221
+ en_pahali = dosya_borclar[:5]
222
+
223
+ toplam_saat = sum(d.debt_saatleri for d in dosya_borclar)
224
+ toplam_aylik = sum(d.aylik_maliyet_usd for d in dosya_borclar)
225
+
226
+ return RepoBorcu(
227
+ toplam_debt_saatleri=round(toplam_saat, 2),
228
+ toplam_aylik_maliyet_usd=round(toplam_aylik, 2),
229
+ en_pahali_5=en_pahali,
230
+ toplam_dosya=len(dosya_borclar),
231
+ saatlik_ucret=hourly_rate,
232
+ )
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: codedna
3
+ Version: 0.2.0
4
+ Summary: AI kod şeffaflık aracı — her commit'te AI yazım oranını ve anlama skorunu ölçer
5
+ Project-URL: Homepage, https://codedna.dev
6
+ Project-URL: Repository, https://github.com/natureco-official/codedna
7
+ Project-URL: Issues, https://github.com/natureco-official/codedna/issues
8
+ Project-URL: Changelog, https://github.com/natureco-official/codedna/blob/main/CHANGELOG.md
9
+ Author-email: NatureCo <hello@natureco.me>
10
+ License: MIT
11
+ Keywords: ai,code-analysis,developer-tools,devops,git,transparency
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: bcrypt>=5.0.0
23
+ Requires-Dist: fastapi>=0.138.0
24
+ Requires-Dist: gitpython>=3.1.50
25
+ Requires-Dist: pyjwt>=2.13.0
26
+ Requires-Dist: rich>=15.0.0
27
+ Requires-Dist: tree-sitter-javascript>=0.25.0
28
+ Requires-Dist: tree-sitter-python>=0.25.0
29
+ Requires-Dist: tree-sitter-typescript>=0.23.2
30
+ Requires-Dist: tree-sitter>=0.25.2
31
+ Requires-Dist: typer>=0.26.7
32
+ Requires-Dist: uvicorn[standard]>=0.49.0
33
+ Provides-Extra: dashboard
34
+ Requires-Dist: next-cmd>=0.5.0; extra == 'dashboard'
35
+ Provides-Extra: dev
36
+ Requires-Dist: black>=24.0.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
38
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # 🧬 CodeDNA — AI Kod Şeffaflık Aracı
43
+
44
+ Her commit'te hangi kodun AI tarafından yazıldığını tespit eden, geliştiricinin o kodu gerçekten anlayıp anlamadığını ölçen ve takım genelinde "anlama borcu" haritası çıkaran CLI aracı.
45
+
46
+ ## Kurulum
47
+
48
+ ```bash
49
+ # Geliştirme ortamı için
50
+ uv pip install -e .
51
+
52
+ # veya
53
+ pip install codedna
54
+ ```
55
+
56
+ ## Kullanım
57
+
58
+ ```bash
59
+ # Git hook'u kur ve veritabanını oluştur
60
+ codedna init
61
+
62
+ # Tüm repoyu tara
63
+ codedna scan
64
+
65
+ # Son commit skorunu göster
66
+ codedna status
67
+
68
+ # Geçmiş commit skorlarını listele
69
+ codedna history
70
+
71
+ # Hook'u kaldır
72
+ codedna uninstall
73
+ ```
74
+
75
+ ## Nasıl Çalışır?
76
+
77
+ `codedna scan` komutu her desteklenen dosyayı (`*.py`, `*.js`, `*.ts`, `*.jsx`, `*.tsx`) analiz eder:
78
+
79
+ | Metrik | Açıklama |
80
+ |--------|----------|
81
+ | `comment_ratio > 0.3` | AI kodu genelde aşırı yorum yazar → +0.20 |
82
+ | `avg_function_length > 50` | AI büyük fonksiyon blokları üretir → +0.15 |
83
+ | `single_commit_ratio > 0.7` | Toplu yapıştırma işareti → +0.30 |
84
+ | Yüksek karmaşıklık + tek commit | AI imzası → +0.25 |
85
+
86
+ ## Gereksinimler
87
+
88
+ - Python 3.10+
89
+ - Git kurulu olmalı
90
+ # test
91
+ # test2
92
+ # test4
93
+ # test5
@@ -0,0 +1,26 @@
1
+ codedna/__init__.py,sha256=vwyeFcp7eBgr9Uxbpe7bEYAiYijK6CojD8OWLUA3kxY,93
2
+ codedna/ai_fingerprint.py,sha256=xUiBF3jSjTqtu1xGAhbBbDx6PktQ7u_8KcSEz78fSSU,8107
3
+ codedna/analyzer.py,sha256=GgKd_2TCC1ZmroV6RApj8LPGAZsvrgqw9zYWdz3c0bI,7490
4
+ codedna/api.py,sha256=-X4GlQtuCqLPxerMc96KqDDtLM7XOXGCz9Hfhd9kgCM,50828
5
+ codedna/auth.py,sha256=0rPQTRX7I-6Kxg3xzYfZSiZIYJ5b7ERgbHqj6yIzL5s,11917
6
+ codedna/bus_factor.py,sha256=mgI4rRufMJmrLqTKibbrbHa3HrTaZdV9U80BMUZNRCw,8007
7
+ codedna/cli.py,sha256=qgs7Gq3ACQ586G_iq7QVtmq7_n7jquZ-mGmGey1VPTY,72661
8
+ codedna/db.py,sha256=o2-kxPQRKnXkw_p-jeiwjH0ZJXvquFUwel75_Eh5z20,11098
9
+ codedna/git_hook.py,sha256=ZyLUStLFE94bKhMTMlDFkowisDPH1PS5wm7GBG6heEw,6382
10
+ codedna/interview.py,sha256=YVQeTx1zqdlRE1-Qe8IgLuEQz5-OwvesmMY3vTm-_xk,8661
11
+ codedna/onboarding.py,sha256=hENlLV2tzdKwdN1rYSk8UpPpWlC2JhqPxsF9qGVVccA,5879
12
+ codedna/plan.py,sha256=99lcUTtT9ikIAy59k_EyF38L-6utf4Hf-fjJ5ci0X4U,5614
13
+ codedna/protection.py,sha256=ci5O9HFzTiOckrMdILQuxfXVCRuozwKOBndjsvLFpw4,6302
14
+ codedna/rate_limit.py,sha256=1JBRFoasq-JT9T0aAObplvk04u7L97mZLuE3SeByZcg,2744
15
+ codedna/scorer.py,sha256=tDBKQ3-_kZPi-Az5vh16TvURBiR1X_Au8vaJTYpC5Jw,6986
16
+ codedna/sprint_health.py,sha256=R3PFrpjGUSAw1QmwLFrp7Fo-vufcJSiXqoS_Yw6zOEk,6164
17
+ codedna/survey.py,sha256=tNM1wbaTbe4Mi7cf-A3hnmVcWW7VIBGt--_fJ_oSir0,3099
18
+ codedna/tech_debt.py,sha256=3Y_NEuLtM9FAb6GtT88BYMT9zrPl8_gSgUVHop4m3XY,7551
19
+ codedna/integrations/__init__.py,sha256=f_K_JICrNyGALXzZ8YFCauWfZejOBJRlTeDTyX_2Ogk,45
20
+ codedna/integrations/github_bot.py,sha256=CdZ6VvqpkdGYrfgCoDI02vZka6ky231MwFgGcOfVVek,7841
21
+ codedna/integrations/jira.py,sha256=1SPcBjJ4SrHmnyimWpwM38jxnRh6BhQ-1rN5h1HF4W8,5334
22
+ codedna/integrations/lemonsqueezy.py,sha256=VtZq71YRHPfZzTiulQ4rWjks3lBLlPpke6XVRzRnha8,7611
23
+ codedna-0.2.0.dist-info/METADATA,sha256=s7WQVYZRkRVW6dKog1iR7A_BaMcK0Vq6glpqA62Y_Wg,2884
24
+ codedna-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
25
+ codedna-0.2.0.dist-info/entry_points.txt,sha256=0pXR9iZgAwjwYbEGO3MSrlmYcrhMw6QXntKqq5KjWgM,45
26
+ codedna-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codedna = codedna.cli:main