codedna 0.2.2__py3-none-any.whl → 0.2.4__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/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """CodeDNA — AI Kod Şeffaflık Aracı."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.4"
4
4
  __app_name__ = "codedna"
codedna/ai.py ADDED
@@ -0,0 +1,221 @@
1
+ """
2
+ AI Analiz Katmanı - Kullanici komut sonuclarini AI ile yorumlar.
3
+
4
+ Desteklenen provider'lar:
5
+ - Anthropic (Claude)
6
+ - MiniMax
7
+ - OpenAI
8
+
9
+ Key sadece analiz icin kullanilir, sohbet/tamamlama yok.
10
+ """
11
+ import base64
12
+ import json
13
+ import os
14
+ import urllib.error
15
+ import urllib.request
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ AI_CONFIG_PATH = Path.home() / ".codedna" / "ai_config.json"
21
+
22
+ # Provider listesi
23
+ PROVIDERS = ["anthropic", "minimax", "openai"]
24
+
25
+ # Default modeller
26
+ DEFAULT_MODELS = {
27
+ "anthropic": "claude-sonnet-4-20250514",
28
+ "minimax": "MiniMax-M2.5",
29
+ "openai": "gpt-4o-mini",
30
+ }
31
+
32
+ # Provider API endpoint
33
+ API_ENDPOINTS = {
34
+ "anthropic": "https://api.anthropic.com/v1/messages",
35
+ "minimax": "https://api.minimax.io/v1/text/chatcompletion_v2",
36
+ "openai": "https://api.openai.com/v1/chat/completions",
37
+ }
38
+
39
+
40
+ @dataclass
41
+ class AIConfig:
42
+ """AI analiz konfigurasyonu."""
43
+ provider: str
44
+ api_key: str
45
+ model: str
46
+ enabled: bool = True
47
+
48
+ @classmethod
49
+ def load(cls) -> Optional["AIConfig"]:
50
+ """Config dosyasindan yukle."""
51
+ if not AI_CONFIG_PATH.exists():
52
+ return None
53
+ try:
54
+ data = json.loads(AI_CONFIG_PATH.read_text())
55
+ return cls(
56
+ provider=data.get("provider", "anthropic"),
57
+ api_key=data.get("api_key", ""),
58
+ model=data.get("model", DEFAULT_MODELS.get(data.get("provider", "anthropic"), "")),
59
+ enabled=data.get("enabled", True),
60
+ )
61
+ except Exception:
62
+ return None
63
+
64
+ def save(self) -> None:
65
+ """Config'i dosyaya yaz."""
66
+ AI_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
67
+ data = {
68
+ "provider": self.provider,
69
+ "api_key": self.api_key,
70
+ "model": self.model,
71
+ "enabled": self.enabled,
72
+ }
73
+ AI_CONFIG_PATH.write_text(json.dumps(data, ensure_ascii=False, indent=2))
74
+ try:
75
+ os.chmod(AI_CONFIG_PATH, 0o600)
76
+ except OSError:
77
+ pass
78
+
79
+ @classmethod
80
+ def clear(cls) -> None:
81
+ """Config'i sil."""
82
+ if AI_CONFIG_PATH.exists():
83
+ AI_CONFIG_PATH.unlink()
84
+
85
+
86
+ def _call_anthropic(api_key: str, model: str, prompt: str) -> str:
87
+ """Anthropic Claude API cagirisi."""
88
+ payload = {
89
+ "model": model,
90
+ "max_tokens": 500,
91
+ "messages": [
92
+ {"role": "user", "content": prompt}
93
+ ],
94
+ }
95
+ req = urllib.request.Request(
96
+ API_ENDPOINTS["anthropic"],
97
+ data=json.dumps(payload).encode(),
98
+ headers={
99
+ "x-api-key": api_key,
100
+ "anthropic-version": "2023-06-01",
101
+ "Content-Type": "application/json",
102
+ },
103
+ method="POST",
104
+ )
105
+ with urllib.request.urlopen(req, timeout=30) as resp:
106
+ result = json.loads(resp.read())
107
+ # Response: {content: [{text: "..."}], ...}
108
+ return result.get("content", [{}])[0].get("text", "")
109
+
110
+
111
+ def _call_minimax(api_key: str, model: str, prompt: str) -> str:
112
+ """MiniMax API cagirisi."""
113
+ payload = {
114
+ "model": model,
115
+ "messages": [
116
+ {"role": "system", "content": "Sen CodeDNA'nin AI analiz asistanisin. Kullanici komut ciktilarini yorumlarsin. Sohbet etme, sadece analiz ve oneriler sun."},
117
+ {"role": "user", "content": prompt}
118
+ ],
119
+ }
120
+ req = urllib.request.Request(
121
+ API_ENDPOINTS["minimax"],
122
+ data=json.dumps(payload).encode(),
123
+ headers={
124
+ "Authorization": f"Bearer {api_key}",
125
+ "Content-Type": "application/json",
126
+ },
127
+ method="POST",
128
+ )
129
+ with urllib.request.urlopen(req, timeout=30) as resp:
130
+ result = json.loads(resp.read())
131
+ return result.get("choices", [{}])[0].get("message", {}).get("content", "")
132
+
133
+
134
+ def _call_openai(api_key: str, model: str, prompt: str) -> str:
135
+ """OpenAI API cagirisi."""
136
+ payload = {
137
+ "model": model,
138
+ "max_tokens": 500,
139
+ "messages": [
140
+ {"role": "system", "content": "Sen CodeDNA'nin AI analiz asistanisin. Kullanici komut ciktilarini yorumlarsin. Sohbet etme, sadece analiz ve oneriler sun."},
141
+ {"role": "user", "content": prompt}
142
+ ],
143
+ }
144
+ req = urllib.request.Request(
145
+ API_ENDPOINTS["openai"],
146
+ data=json.dumps(payload).encode(),
147
+ headers={
148
+ "Authorization": f"Bearer {api_key}",
149
+ "Content-Type": "application/json",
150
+ },
151
+ method="POST",
152
+ )
153
+ with urllib.request.urlopen(req, timeout=30) as resp:
154
+ result = json.loads(resp.read())
155
+ return result.get("choices", [{}])[0].get("message", {}).get("content", "")
156
+
157
+
158
+ def ai_analyze(command: str, output: str, config: Optional[AIConfig] = None) -> Optional[str]:
159
+ """
160
+ Komut ciktisini AI ile analiz et.
161
+
162
+ Returns:
163
+ Analiz metni veya None (config yoksa / hata durumunda)
164
+ """
165
+ if config is None:
166
+ config = AIConfig.load()
167
+ if config is None or not config.enabled or not config.api_key:
168
+ return None
169
+
170
+ # Plan kontrolu - sadece Pro+ kullanicilar AI analiz kullanabilir
171
+ try:
172
+ from codedna.plan import get_current_plan, Plan as PlanEnum
173
+ plan = get_current_plan()
174
+ if plan == PlanEnum.FREE:
175
+ return None # Free planda AI analiz yok
176
+ except Exception:
177
+ return None # Plan yuklenemedi, gosterme
178
+
179
+ # Prompt
180
+ prompt = f"""Sen CodeDNA'nin AI analiz katmanisin.
181
+
182
+ KOMUT: {command}
183
+ CIKTI: {output[:2000]}
184
+
185
+ Bu ciktiyi 3-4 cumlede analiz et:
186
+ 1. Sonuc ne anlama geliyor? (iyi/kotu/notr)
187
+ 2. Somut oneriler (numbered list, max 3)
188
+ 3. Varsa sorun ve cozumu
189
+
190
+ Kurallar:
191
+ - Sohbet etme, sadece yorum
192
+ - Maksimum 100 kelime
193
+ - Turkce yanit ver
194
+ - Emoji kullanabilirsin (ama az)"""
195
+
196
+ try:
197
+ if config.provider == "anthropic":
198
+ return _call_anthropic(config.api_key, config.model, prompt)
199
+ elif config.provider == "minimax":
200
+ return _call_minimax(config.api_key, config.model, prompt)
201
+ elif config.provider == "openai":
202
+ return _call_openai(config.api_key, config.model, prompt)
203
+ else:
204
+ return None
205
+ except Exception as e:
206
+ return f"AI analiz hatasi: {type(e).__name__}"
207
+
208
+
209
+ def is_enabled() -> bool:
210
+ """AI analiz aktif mi? (Sadece Pro+ uyeler)"""
211
+ config = AIConfig.load()
212
+ if config is None or not config.enabled or not config.api_key:
213
+ return False
214
+ try:
215
+ from codedna.plan import get_current_plan, Plan as PlanEnum
216
+ plan = get_current_plan()
217
+ if plan == PlanEnum.FREE:
218
+ return False
219
+ except Exception:
220
+ return False
221
+ return True
codedna/cli.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import sys
5
6
  from datetime import datetime
6
7
  from pathlib import Path
7
8
  from typing import Optional
@@ -37,6 +38,18 @@ app = typer.Typer(
37
38
  console = Console()
38
39
 
39
40
 
41
+ def _version_callback(value: bool) -> None:
42
+ """--version bayrağı için callback."""
43
+ if value:
44
+ try:
45
+ from importlib.metadata import version as _v
46
+ ver = _v("codedna")
47
+ except Exception:
48
+ ver = "0.2.3"
49
+ console.print(f"codedna {ver}")
50
+ raise typer.Exit()
51
+
52
+
40
53
  def _get_db(repo_path: Optional[Path] = None) -> Path:
41
54
  """Repo'ya özel veritabanı yolunu döndür."""
42
55
  kok = repo_path or find_git_root() or Path.cwd()
@@ -106,6 +119,233 @@ def init(
106
119
  # ---------------------------------------------------------------------------
107
120
  # codedna scan
108
121
  # ---------------------------------------------------------------------------
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # codedna doctor
125
+ # ---------------------------------------------------------------------------
126
+ @app.command()
127
+ def doctor() -> None:
128
+ """Sistem sağlık kontrolü."""
129
+ console.print()
130
+ console.print(
131
+ Panel.fit(
132
+ "[bold cyan]🧬 CodeDNA Doctor[/bold cyan]\n"
133
+ "[dim]Sistem sağlık kontrolü...[/dim]",
134
+ border_style="cyan",
135
+ )
136
+ )
137
+ console.print()
138
+
139
+ kontroller = []
140
+
141
+ py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
142
+ py_ok = sys.version_info >= (3, 10)
143
+ kontroller.append(("Python versiyonu", f"v{py_ver}", py_ok, f"v{py_ver} (>=3.10)" if py_ok else "ESKI"))
144
+
145
+ import shutil
146
+ git_var = shutil.which("git")
147
+ git_ok = git_var is not None
148
+ kontroller.append(("Git binary", git_var or "yok", git_ok, f"bulundu" if git_ok else "yok"))
149
+
150
+ try:
151
+ import tree_sitter
152
+ ts_ok = True
153
+ try:
154
+ ts_msg = f"v{tree_sitter.__version__}"
155
+ except AttributeError:
156
+ try:
157
+ from importlib.metadata import version as _v
158
+ ts_msg = f"v{_v('tree-sitter')}"
159
+ except Exception:
160
+ ts_msg = "yüklü"
161
+ except ImportError:
162
+ ts_ok = False
163
+ ts_msg = "yok"
164
+ kontroller.append(("Tree-sitter", ts_msg, ts_ok, ts_msg if ts_ok else "yüklü değil"))
165
+
166
+ try:
167
+ import fastapi
168
+ fa_ok = True
169
+ fa_msg = f"v{fastapi.__version__}"
170
+ except ImportError:
171
+ fa_ok = False
172
+ fa_msg = "yok"
173
+ kontroller.append(("FastAPI", fa_msg, fa_ok, fa_msg if fa_ok else "yüklü değil"))
174
+
175
+ try:
176
+ from codedna.auth import _auth_db
177
+ db_path = _auth_db()
178
+ db_ok = db_path.exists()
179
+ kontroller.append(("Auth veritabanı", str(db_path), db_ok, f"var ({db_path.stat().st_size} bytes)" if db_ok else "yok"))
180
+ except Exception as e:
181
+ kontroller.append(("Auth veritabanı", "hata", False, str(e)))
182
+
183
+ try:
184
+ from codedna.plan import get_current_plan
185
+ plan = get_current_plan()
186
+ kontroller.append(("Plan", plan.value.upper(), True, f"plan: {plan.value}"))
187
+ except Exception as e:
188
+ kontroller.append(("Plan", "hata", False, str(e)))
189
+
190
+ try:
191
+ from codedna.git_hook import find_git_root
192
+ git_root = find_git_root()
193
+ if git_root:
194
+ hook_path = git_root / ".git" / "hooks" / "post-commit"
195
+ hook_ok = hook_path.exists()
196
+ kontroller.append(("Post-commit hook", "var" if hook_ok else "yok", hook_ok, "kurulmus" if hook_ok else "codedna init gerekli"))
197
+ else:
198
+ kontroller.append(("Post-commit hook", "—", True, "git repo disinda"))
199
+ except Exception as e:
200
+ kontroller.append(("Post-commit hook", "hata", False, str(e)))
201
+
202
+ tablo = Table(title="[bold]Sistem Durumu[/bold]", show_header=True, header_style="bold magenta", box=None)
203
+ tablo.add_column("Bileşen", style="cyan", no_wrap=True)
204
+ tablo.add_column("Durum", justify="center")
205
+ tablo.add_column("Detay", style="dim")
206
+
207
+ tum_ok = True
208
+ for ad, deger, ok, detay in kontroller:
209
+ if ok:
210
+ emoji, renk = "[green]✓[/green]", "green"
211
+ else:
212
+ emoji, renk = "[red]✗[/red]", "red"
213
+ tum_ok = False
214
+ tablo.add_row(ad, f"{emoji} [{renk}]{deger}[/{renk}]", detay)
215
+
216
+ console.print(tablo)
217
+ console.print()
218
+ if tum_ok:
219
+ console.print("[bold green]✓ Tüm sistem sağlıklı![/bold green]")
220
+ else:
221
+ console.print("[bold yellow]⚠ Bazı bileşenlerde sorun var.[/bold yellow]")
222
+ raise typer.Exit(1)
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # codedna setup-ai — AI analiz katmanı (Pro+ plan gerekli)
227
+ # ---------------------------------------------------------------------------
228
+ @app.command(name="setup-ai")
229
+ def setup_ai(
230
+ clear: bool = typer.Option(False, "--clear", help="AI konfigürasyonunu sil"),
231
+ show: bool = typer.Option(False, "--show", help="Mevcut konfigürasyonu göster"),
232
+ enable_flag: bool = typer.Option(False, "--on", help="AI analizi etkinleştir"),
233
+ disable_flag: bool = typer.Option(False, "--off", help="AI analizi devre dışı bırak"),
234
+ ) -> None:
235
+ """AI analiz katmanı için API key ayarla (Anthropic, MiniMax, OpenAI). Pro+ plan gerekli."""
236
+ import getpass
237
+ from codedna.ai import AIConfig, AI_CONFIG_PATH, DEFAULT_MODELS, PROVIDERS
238
+
239
+ console.print()
240
+ console.print(
241
+ Panel.fit(
242
+ "[bold cyan]🧬 CodeDNA AI Setup[/bold cyan]\n"
243
+ "[dim]AI analiz katmanı — Pro+ plan gerekli[/dim]",
244
+ border_style="cyan",
245
+ )
246
+ )
247
+ console.print()
248
+
249
+ if clear:
250
+ AIConfig.clear()
251
+ console.print("[bold green]✓[/bold green] AI konfigürasyonu silindi.")
252
+ return
253
+
254
+ if show:
255
+ cfg = AIConfig.load()
256
+ if not cfg:
257
+ console.print("[yellow]AI konfigürasyonu yok.[/yellow]")
258
+ else:
259
+ masked = cfg.api_key[:8] + "..." + cfg.api_key[-4:] if len(cfg.api_key) > 12 else "***"
260
+ console.print(f"[bold]Provider:[/bold] {cfg.provider}")
261
+ console.print(f"[bold]Model:[/bold] {cfg.model}")
262
+ console.print(f"[bold]API Key:[/bold] {masked}")
263
+ console.print(f"[bold]Aktif:[/bold] {'Evet' if cfg.enabled else 'Hayır'}")
264
+ console.print(f"[bold]Config:[/bold] {AI_CONFIG_PATH}")
265
+ return
266
+
267
+ if enable_flag or disable_flag:
268
+ cfg = AIConfig.load()
269
+ if not cfg:
270
+ console.print("[red]Önce key ayarlayın: codedna setup-ai[/red]")
271
+ raise typer.Exit(1)
272
+ cfg.enabled = enable_flag
273
+ cfg.save()
274
+ status = "aktif" if enable_flag else "devre dışı"
275
+ console.print(f"[bold green]✓[/bold green] AI analiz {status}.")
276
+ return
277
+
278
+ # Plan kontrolu
279
+ try:
280
+ from codedna.plan import get_current_plan, Plan as PlanEnum
281
+ plan = get_current_plan()
282
+ if plan == PlanEnum.FREE:
283
+ console.print(
284
+ Panel.fit(
285
+ "[bold yellow]🔒 AI analiz Pro+ plan gerektirir.[/bold yellow]\n\n"
286
+ f"Mevcut plan: [cyan]{plan.value.upper()}[/cyan]\n\n"
287
+ "Demo: [cyan]codedna plan demo pro[/cyan]",
288
+ border_style="yellow",
289
+ )
290
+ )
291
+ raise typer.Exit(1)
292
+ except Exception as e:
293
+ console.print(f"[red]Plan hatası: {e}[/red]")
294
+ raise typer.Exit(1)
295
+
296
+ # Wizard
297
+ console.print("[bold]Provider seçin:[/bold]")
298
+ for i, p in enumerate(PROVIDERS, 1):
299
+ default = DEFAULT_MODELS.get(p, "")
300
+ console.print(f" [cyan]{i}[/cyan]. {p} [dim]({default})[/dim]")
301
+ console.print()
302
+ secim = typer.prompt("Numara", type=int, default=1)
303
+ if secim < 1 or secim > len(PROVIDERS):
304
+ console.print("[red]Geçersiz seçim.[/red]")
305
+ raise typer.Exit(1)
306
+ provider = PROVIDERS[secim - 1]
307
+ default_model = DEFAULT_MODELS[provider]
308
+
309
+ console.print()
310
+ console.print(f"[dim]API key girin ({provider}):[/dim]")
311
+ api_key = getpass.getpass("Key: ").strip()
312
+ if not api_key:
313
+ console.print("[red]Key boş olamaz.[/red]")
314
+ raise typer.Exit(1)
315
+
316
+ console.print()
317
+ model = typer.prompt(f"Model [varsayilan: {default_model}]", default=default_model)
318
+
319
+ console.print()
320
+ console.print("[dim]API bağlantısı test ediliyor...[/dim]")
321
+ try:
322
+ from codedna.ai import ai_analyze
323
+ test_config = AIConfig(provider=provider, api_key=api_key, model=model)
324
+ result = ai_analyze("test", "Bu bir test komutudur.", test_config)
325
+ if result and not result.startswith("AI analiz hatasi"):
326
+ console.print(f"[bold green]✓[/bold green] Bağlantı başarılı!")
327
+ else:
328
+ console.print(f"[bold red]✗[/bold red] Test başarısız: {result}")
329
+ raise typer.Exit(1)
330
+ except Exception as e:
331
+ console.print(f"[bold red]✗[/bold red] Hata: {e}")
332
+ raise typer.Exit(1)
333
+
334
+ config = AIConfig(provider=provider, api_key=api_key, model=model, enabled=True)
335
+ config.save()
336
+ console.print()
337
+ console.print(
338
+ Panel.fit(
339
+ f"[bold green]✓ AI analiz aktif![/bold green]\n\n"
340
+ f"Provider: {provider}\n"
341
+ f"Model: {model}\n"
342
+ f"Config: {AI_CONFIG_PATH}\n\n"
343
+ "[dim]Her komut çıktısından sonra AI analiz kutusu görünecek.[/dim]",
344
+ border_style="green",
345
+ )
346
+ )
347
+
348
+
109
349
  @app.command()
110
350
  def scan(
111
351
  repo: Optional[Path] = typer.Option(None, "--repo", "-r", help="Git repo dizini"),
@@ -271,7 +511,7 @@ def status(
271
511
  Panel(
272
512
  f"[bold]Commit:[/bold] [dim]{commit_hash[:8]}[/dim] [dim]{mesaj}[/dim]\n"
273
513
  f"[bold]Yazar:[/bold] {yazar}\n"
274
- f"[bold]Değişen kod dosyaları:[/bold] {len(sonuclar)}\n"
514
+ f"[bold]Değişen dosyalar:[/bold] {len(sonuclar)}\n"
275
515
  f"[bold]Ort. AI olasılığı:[/bold] [bold {risk_renk}]%{ort_ai*100:.0f} ({risk_etiketi})[/bold {risk_renk}]\n"
276
516
  f"[bold]Anlama skoru:[/bold] {anlama_goster}",
277
517
  title="[bold cyan]🧬 CodeDNA — Commit Skoru[/bold cyan]",
@@ -280,11 +520,6 @@ def status(
280
520
  )
281
521
  )
282
522
  console.print("[dim]Commit skoru kaydedildi.[/dim]\n")
283
- else:
284
- console.print(
285
- f"[bold]Commit:[/bold] [dim]{commit_hash[:8]}[/dim]\n"
286
- "[dim]Bu commit'te desteklenen kod dosyası bulunamadı.[/dim]"
287
- )
288
523
 
289
524
  # Post-commit hook: korumalı modül ihlallerini kontrol et ve uyar (bloklama YOK)
290
525
  if hook:
@@ -296,6 +531,12 @@ def status(
296
531
  except Exception:
297
532
  pass # Hook'u asla bozmaz
298
533
 
534
+ else:
535
+ console.print(
536
+ f"[bold]Commit:[/bold] [dim]{commit_hash[:8]}[/dim]\n"
537
+ "[dim]Bu commit'te desteklenen kod dosyası bulunamadı.[/dim]"
538
+ )
539
+
299
540
 
300
541
  # ---------------------------------------------------------------------------
301
542
  # codedna history
@@ -329,7 +570,7 @@ def history(
329
570
  tablo.add_column("Commit", style="dim", min_width=10)
330
571
  tablo.add_column("Yazar", min_width=15)
331
572
  tablo.add_column("Tarih", min_width=17)
332
- tablo.add_column("Kod Dosyası", justify="right", min_width=11)
573
+ tablo.add_column("Dosya", justify="right", min_width=6)
333
574
  tablo.add_column("Anlama", justify="center", min_width=12)
334
575
 
335
576
  for satir in satirlar:
@@ -1461,54 +1702,27 @@ def sprint_gecmisi(
1461
1702
  def plan(
1462
1703
  komut: Optional[str] = typer.Argument(
1463
1704
  None,
1464
- help="'activate', 'demo' veya doğrudan lisans anahtarı",
1705
+ help="'activate' veya doğrudan lisans anahtarı",
1465
1706
  ),
1466
1707
  anahtar: Optional[str] = typer.Argument(
1467
1708
  None,
1468
- help="Lisans anahtarı (activate/demo ile birlikte kullanılır)",
1709
+ help="Lisans anahtarı (activate ile birlikte kullanılır)",
1469
1710
  ),
1470
1711
  ) -> None:
1471
- """Mevcut planı göster, demo lisans aktif et veya lisans anahtarı ile plan aktif et.
1712
+ """Mevcut planı göster veya lisans anahtarı ile plan aktif et.
1472
1713
 
1473
1714
  Kullanım:
1474
1715
  codedna plan # mevcut planı göster
1475
- codedna plan activate <KEY> # lisans aktif et (CDNA-PRO/TEAM/ENT-...)
1476
- codedna plan demo # Enterprise demo lisans aktif et (hızlı test)
1477
- codedna plan demo pro # Pro demo lisans aktif et
1716
+ codedna plan activate <KEY> # lisans aktif et
1478
1717
  codedna plan <KEY> # kısa yol
1479
1718
  """
1480
1719
  from codedna.plan import (
1481
1720
  Plan as PlanEnum,
1482
1721
  get_current_plan,
1483
1722
  activate_license,
1484
- activate_demo_license,
1485
1723
  get_plan_limits,
1486
1724
  )
1487
1725
 
1488
- # "demo [plan]" syntax'ı
1489
- if komut == "demo":
1490
- demo_plan_str = (anahtar or "enterprise").lower()
1491
- try:
1492
- demo_plan = PlanEnum(demo_plan_str)
1493
- except ValueError:
1494
- console.print(f"[bold red]Hata:[/bold red] Geçersiz plan: {demo_plan_str!r}")
1495
- console.print("Geçerli planlar: free, pro, team, enterprise")
1496
- raise typer.Exit(1)
1497
- aktif_plan = activate_demo_license(demo_plan)
1498
- console.print(
1499
- Panel(
1500
- f"[bold green]✓ Demo lisans aktif edildi![/bold green]\n\n"
1501
- f"[bold]Plan:[/bold] [cyan]{aktif_plan.value.upper()}[/cyan]\n"
1502
- f"[bold yellow]⚠️ Bu bir DEMO lisanstır — production için gerçek key gerekir.[/bold yellow]\n\n"
1503
- f"Gerçek lisans için:\n"
1504
- f"[dim]codedna plan activate CDNA-{demo_plan_str.upper()[:3]}-XXXX-XXXX-XXXX[/dim]",
1505
- title="[bold cyan]🧬 CodeDNA — Demo Plan[/bold cyan]",
1506
- border_style="cyan",
1507
- padding=(1, 2),
1508
- )
1509
- )
1510
- return
1511
-
1512
1726
  # "activate <KEY>" veya doğrudan "<KEY>" syntax'ı destekle
1513
1727
  lisans_anahtari: Optional[str] = None
1514
1728
  if komut == "activate" and anahtar:
@@ -1702,233 +1916,6 @@ def uninstall(
1702
1916
  raise typer.Exit(1)
1703
1917
 
1704
1918
 
1705
- # ---------------------------------------------------------------------------
1706
- # codedna natureco (Pro+ özellik — NatureCo CLI entegrasyonu)
1707
- # ---------------------------------------------------------------------------
1708
- @app.command()
1709
- def natureco(
1710
- komut: Optional[str] = typer.Argument(
1711
- None,
1712
- help="'connect', 'sync', 'auto-scan', 'status' veya doğrudan dosya yolu",
1713
- ),
1714
- dosya: Optional[str] = typer.Argument(
1715
- None,
1716
- help="Dosya yolu (sync/auto-scan için)",
1717
- ),
1718
- ) -> None:
1719
- """NatureCo CLI ile entegre ol (Pro+ plan gerekir).
1720
-
1721
- NatureCo CLI kod ürettiğinde otomatik CodeDNA analizi yap.
1722
-
1723
- Kullanım:
1724
- codedna natureco connect # NatureCo CLI'ya bağlan
1725
- codedna natureco status # Bağlantı durumu
1726
- codedna natureco sync <file> # Dosyayı tarat
1727
- codedna natureco auto-scan # Post-hook: commit'te otomatik scan
1728
- codedna natureco disconnect # Bağlantıyı kes
1729
- """
1730
- from codedna.plan import get_current_plan, Plan as PlanEnum, is_feature_available
1731
-
1732
- # Pro+ plan gerekli
1733
- if not is_feature_available("natureco_integration"):
1734
- plan = get_current_plan()
1735
- console.print(
1736
- Panel(
1737
- f"[bold yellow]🔒 NatureCo entegrasyonu Pro+ plan gerektirir.[/bold yellow]\n\n"
1738
- f"Mevcut planınız: [cyan]{plan.value.upper()}[/cyan]\n\n"
1739
- f"Demo için:\n"
1740
- f"[dim]codedna plan demo pro[/dim]\n\n"
1741
- f"Upgrade:\n"
1742
- f"[dim]https://codedna.dev/pricing[/dim]",
1743
- title="[bold cyan]🧬 CodeDNA — NatureCo Integration[/bold cyan]",
1744
- border_style="yellow",
1745
- padding=(1, 2),
1746
- )
1747
- )
1748
- raise typer.Exit(1)
1749
-
1750
- if not komut or komut == "status":
1751
- return _natureco_status()
1752
- elif komut == "connect":
1753
- return _natureco_connect()
1754
- elif komut == "disconnect":
1755
- return _natureco_disconnect()
1756
- elif komut == "sync":
1757
- return _natureco_sync(dosya)
1758
- elif komut == "auto-scan":
1759
- return _natureco_auto_scan()
1760
- else:
1761
- console.print(f"[bold red]Bilinmeyen komut:[/bold red] {komut!r}")
1762
- console.print("Geçerli: connect, status, sync, auto-scan, disconnect")
1763
- raise typer.Exit(1)
1764
-
1765
-
1766
- def _natureco_config_path() -> Path:
1767
- """NatureCo entegrasyon ayar dosyası yolu."""
1768
- return Path.home() / ".codedna" / "natureco.json"
1769
-
1770
-
1771
- def _natureco_status() -> None:
1772
- """Bağlantı durumunu göster."""
1773
- config_path = _natureco_config_path()
1774
- if config_path.exists():
1775
- import json as _json
1776
- data = _json.loads(config_path.read_text())
1777
- console.print(
1778
- Panel(
1779
- f"[bold green]✓ NatureCo CLI bağlı[/bold green]\n\n"
1780
- f"[bold]Hook:[/bold] {'[green]Aktif[/green]' if data.get('auto_scan') else '[yellow]Pasif[/yellow]'}\n"
1781
- f"[bold]Son sync:[/bold] {data.get('last_sync', 'Hiç')}\n"
1782
- f"[bold]Toplam dosya:[/bold] {data.get('total_files', 0)}\n"
1783
- f"[bold]Bağlandı:[/bold] {data.get('connected_at', 'Bilinmiyor')}",
1784
- title="[bold cyan]🧬 CodeDNA ↔ NatureCo[/bold cyan]",
1785
- border_style="green",
1786
- padding=(1, 2),
1787
- )
1788
- )
1789
- else:
1790
- console.print(
1791
- Panel(
1792
- "[bold yellow]NatureCo CLI henüz bağlı değil.[/bold yellow]\n\n"
1793
- "Bağlamak için:\n"
1794
- "[dim]codedna natureco connect[/dim]",
1795
- title="[bold cyan]🧬 CodeDNA ↔ NatureCo[/bold cyan]",
1796
- border_style="yellow",
1797
- padding=(1, 2),
1798
- )
1799
- )
1800
-
1801
-
1802
- def _natureco_connect() -> None:
1803
- """NatureCo CLI ile bağlantı kur."""
1804
- import json as _json
1805
- import time as _time
1806
-
1807
- config_path = _natureco_config_path()
1808
- config_path.parent.mkdir(parents=True, exist_ok=True)
1809
-
1810
- # NatureCo CLI yüklü mü kontrol et
1811
- nc_path = None
1812
- for p in ["/usr/local/bin/natureco", "/opt/homebrew/bin/natureco",
1813
- str(Path.home() / ".npm-global/bin/natureco")]:
1814
- if Path(p).exists():
1815
- nc_path = p
1816
- break
1817
-
1818
- # PATH'te ara
1819
- if not nc_path:
1820
- import shutil
1821
- nc_path = shutil.which("natureco")
1822
-
1823
- if nc_path:
1824
- console.print(f"[green]✓[/green] NatureCo CLI bulundu: [dim]{nc_path}[/dim]")
1825
- else:
1826
- console.print("[yellow]⚠[/yellow] NatureCo CLI PATH'te bulunamadı")
1827
- console.print("[dim] Yine de entegrasyon kuruldu — NatureCo CLI kurulunca çalışacak[/dim]")
1828
-
1829
- config = {
1830
- "connected": True,
1831
- "natureco_cli_path": nc_path,
1832
- "auto_scan": True, # Varsayılan açık
1833
- "last_sync": None,
1834
- "total_files": 0,
1835
- "connected_at": _time.strftime("%Y-%m-%d %H:%M:%S"),
1836
- }
1837
- config_path.write_text(_json.dumps(config, indent=2))
1838
-
1839
- console.print(
1840
- Panel(
1841
- "[bold green]✓ NatureCo CLI bağlantısı kuruldu![/bold green]\n\n"
1842
- "Artık NatureCo CLI ile üretilen her kod otomatik taranacak.\n\n"
1843
- "[bold]Sonraki adımlar:[/bold]\n"
1844
- "1. NatureCo CLI'da kod üretin (`natureco code`)\n"
1845
- "2. CodeDNA otomatik scan yapar\n"
1846
- "3. `codedna natureco status` ile durumu kontrol edin",
1847
- title="[bold green]🧬 CodeDNA ↔ NatureCo Bağlandı[/bold green]",
1848
- border_style="green",
1849
- padding=(1, 2),
1850
- )
1851
- )
1852
-
1853
-
1854
- def _natureco_disconnect() -> None:
1855
- """Bağlantıyı kes."""
1856
- config_path = _natureco_config_path()
1857
- if config_path.exists():
1858
- config_path.unlink()
1859
- console.print("[green]✓[/green] NatureCo bağlantısı kesildi.")
1860
- else:
1861
- console.print("[yellow]Zaten bağlı değil.[/yellow]")
1862
-
1863
-
1864
- def _natureco_sync(dosya: Optional[str]) -> None:
1865
- """Bir dosyayı CodeDNA ile tara."""
1866
- import json as _json
1867
- import time as _time
1868
-
1869
- if not dosya:
1870
- console.print("[bold red]Hata:[/bold red] Dosya yolu gerekli")
1871
- console.print("Kullanım: [dim]codedna natureco sync <dosya>[/dim]")
1872
- raise typer.Exit(1)
1873
-
1874
- file_path = Path(dosya)
1875
- if not file_path.exists():
1876
- console.print(f"[bold red]Hata:[/bold red] Dosya bulunamadı: {dosya}")
1877
- raise typer.Exit(1)
1878
-
1879
- # Repo kökünü bul
1880
- kok = find_git_root() or Path.cwd()
1881
- db_yolu = _get_db(kok)
1882
-
1883
- # Scoring yap (analyzer kullanarak)
1884
- from codedna.analyzer import analyze_file
1885
-
1886
- console.print(f"[dim]Taranıyor:[/dim] {file_path.name}")
1887
- result = analyze_file(file_path, single_commit_ratio=0.0)
1888
-
1889
- if result:
1890
- console.print(
1891
- Panel(
1892
- f"[bold green]✓ Tarama tamamlandı[/bold green]\n\n"
1893
- f"[bold]Dosya:[/bold] {file_path.name}\n"
1894
- f"[bold]AI olasılığı:[/bold] [cyan]%{result.ai_probability * 100:.0f}[/cyan]\n"
1895
- f"[bold]Karmaşıklık:[/bold] {result.complexity_score:.1f}\n"
1896
- f"[bold]Yorum oranı:[/bold] {result.comment_ratio * 100:.0f}%\n",
1897
- title="[bold cyan]🧬 Tarama Sonucu[/bold cyan]",
1898
- border_style="cyan",
1899
- padding=(1, 2),
1900
- )
1901
- )
1902
-
1903
- # Config'i güncelle
1904
- config_path = _natureco_config_path()
1905
- if config_path.exists():
1906
- cfg = _json.loads(config_path.read_text())
1907
- cfg["last_sync"] = _time.strftime("%Y-%m-%d %H:%M:%S")
1908
- cfg["total_files"] = cfg.get("total_files", 0) + 1
1909
- config_path.write_text(_json.dumps(cfg, indent=2))
1910
- else:
1911
- console.print("[yellow]Dosya analiz edilemedi.[/yellow]")
1912
-
1913
-
1914
- def _natureco_auto_scan() -> None:
1915
- """Auto-scan hook'unu aktif/pasif yap."""
1916
- import json as _json
1917
-
1918
- config_path = _natureco_config_path()
1919
- if not config_path.exists():
1920
- console.print("[bold red]Hata:[/bold red] Önce connect gerekli")
1921
- console.print("[dim]codedna natureco connect[/dim]")
1922
- raise typer.Exit(1)
1923
-
1924
- cfg = _json.loads(config_path.read_text())
1925
- cfg["auto_scan"] = not cfg.get("auto_scan", False)
1926
- config_path.write_text(_json.dumps(cfg, indent=2))
1927
-
1928
- state = "[green]aktif[/green]" if cfg["auto_scan"] else "[yellow]pasif[/yellow]"
1929
- console.print(f"[green]✓[/green] Auto-scan {state}")
1930
-
1931
-
1932
1919
  # ---------------------------------------------------------------------------
1933
1920
  # Yardımcı fonksiyonlar
1934
1921
  # ---------------------------------------------------------------------------
@@ -1958,6 +1945,15 @@ def _risk_etiketi(yuzde: float) -> tuple[str, str]:
1958
1945
 
1959
1946
  def main() -> None:
1960
1947
  """CLI giriş noktası."""
1948
+ # --version veya -V kontrolu (Typer'in tanimadigi)
1949
+ if "--version" in sys.argv or "-V" in sys.argv:
1950
+ try:
1951
+ from importlib.metadata import version as _v
1952
+ ver = _v("codedna")
1953
+ except Exception:
1954
+ ver = "0.2.3"
1955
+ console.print(f"codedna {ver}")
1956
+ sys.exit(0)
1961
1957
  app()
1962
1958
 
1963
1959
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedna
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: AI Code Transparency Tool - detect AI-written code and measure developer understanding
5
5
  Project-URL: Homepage, https://codedna.dev
6
6
  Project-URL: Repository, https://github.com/natureco-official/codedna
@@ -1,10 +1,11 @@
1
- codedna/__init__.py,sha256=vwyeFcp7eBgr9Uxbpe7bEYAiYijK6CojD8OWLUA3kxY,93
1
+ codedna/__init__.py,sha256=7G00zlcjHJcAUQDtrKy_XkZJhBV3iF7ce-miIlhZJXA,93
2
+ codedna/ai.py,sha256=h7cwQ6ArGEHWOoMDOY7v9ARSRm_7dHyszGdRYfFiE9Q,6701
2
3
  codedna/ai_fingerprint.py,sha256=xUiBF3jSjTqtu1xGAhbBbDx6PktQ7u_8KcSEz78fSSU,8107
3
4
  codedna/analyzer.py,sha256=GgKd_2TCC1ZmroV6RApj8LPGAZsvrgqw9zYWdz3c0bI,7490
4
5
  codedna/api.py,sha256=-X4GlQtuCqLPxerMc96KqDDtLM7XOXGCz9Hfhd9kgCM,50828
5
6
  codedna/auth.py,sha256=0rPQTRX7I-6Kxg3xzYfZSiZIYJ5b7ERgbHqj6yIzL5s,11917
6
7
  codedna/bus_factor.py,sha256=mgI4rRufMJmrLqTKibbrbHa3HrTaZdV9U80BMUZNRCw,8007
7
- codedna/cli.py,sha256=qgs7Gq3ACQ586G_iq7QVtmq7_n7jquZ-mGmGey1VPTY,72661
8
+ codedna/cli.py,sha256=bImIUXtNRnIlJrgswzhe9Wj8ZXr2bOS339l9SwUNJv4,72189
8
9
  codedna/db.py,sha256=o2-kxPQRKnXkw_p-jeiwjH0ZJXvquFUwel75_Eh5z20,11098
9
10
  codedna/git_hook.py,sha256=ZyLUStLFE94bKhMTMlDFkowisDPH1PS5wm7GBG6heEw,6382
10
11
  codedna/interview.py,sha256=YVQeTx1zqdlRE1-Qe8IgLuEQz5-OwvesmMY3vTm-_xk,8661
@@ -20,8 +21,8 @@ codedna/integrations/__init__.py,sha256=f_K_JICrNyGALXzZ8YFCauWfZejOBJRlTeDTyX_2
20
21
  codedna/integrations/github_bot.py,sha256=CdZ6VvqpkdGYrfgCoDI02vZka6ky231MwFgGcOfVVek,7841
21
22
  codedna/integrations/jira.py,sha256=1SPcBjJ4SrHmnyimWpwM38jxnRh6BhQ-1rN5h1HF4W8,5334
22
23
  codedna/integrations/lemonsqueezy.py,sha256=VtZq71YRHPfZzTiulQ4rWjks3lBLlPpke6XVRzRnha8,7611
23
- codedna-0.2.2.dist-info/METADATA,sha256=9K-h1obT66DwAIL8ZL7q48uwVzjcyuGtcVaHa1Kt2VI,18957
24
- codedna-0.2.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
25
- codedna-0.2.2.dist-info/entry_points.txt,sha256=0pXR9iZgAwjwYbEGO3MSrlmYcrhMw6QXntKqq5KjWgM,45
26
- codedna-0.2.2.dist-info/licenses/LICENSE,sha256=-XHiSAtpzZ_2gcli9hDggy41vDYoZrAqvLhiJ_5L35I,1065
27
- codedna-0.2.2.dist-info/RECORD,,
24
+ codedna-0.2.4.dist-info/METADATA,sha256=VWKXNQBBkrbM4hbbCsj1rtfgKvzQykAHXrXea8_sC3U,18957
25
+ codedna-0.2.4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
26
+ codedna-0.2.4.dist-info/entry_points.txt,sha256=0pXR9iZgAwjwYbEGO3MSrlmYcrhMw6QXntKqq5KjWgM,45
27
+ codedna-0.2.4.dist-info/licenses/LICENSE,sha256=-XHiSAtpzZ_2gcli9hDggy41vDYoZrAqvLhiJ_5L35I,1065
28
+ codedna-0.2.4.dist-info/RECORD,,