codedna 0.9.5__tar.gz → 0.9.6__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 (94) hide show
  1. {codedna-0.9.5 → codedna-0.9.6}/PKG-INFO +1 -1
  2. {codedna-0.9.5 → codedna-0.9.6}/codedna/__init__.py +1 -1
  3. {codedna-0.9.5 → codedna-0.9.6}/codedna/analyzer.py +31 -0
  4. {codedna-0.9.5 → codedna-0.9.6}/codedna/cli.py +32 -3
  5. {codedna-0.9.5 → codedna-0.9.6}/codedna/scorer.py +37 -1
  6. {codedna-0.9.5 → codedna-0.9.6}/pyproject.toml +1 -1
  7. {codedna-0.9.5 → codedna-0.9.6}/.gitignore +0 -0
  8. {codedna-0.9.5 → codedna-0.9.6}/LICENSE +0 -0
  9. {codedna-0.9.5 → codedna-0.9.6}/README.md +0 -0
  10. {codedna-0.9.5 → codedna-0.9.6}/codedna/ai.py +0 -0
  11. {codedna-0.9.5 → codedna-0.9.6}/codedna/ai_box.py +0 -0
  12. {codedna-0.9.5 → codedna-0.9.6}/codedna/ai_fingerprint.py +0 -0
  13. {codedna-0.9.5 → codedna-0.9.6}/codedna/api.py +0 -0
  14. {codedna-0.9.5 → codedna-0.9.6}/codedna/auth.py +0 -0
  15. {codedna-0.9.5 → codedna-0.9.6}/codedna/bus_factor.py +0 -0
  16. {codedna-0.9.5 → codedna-0.9.6}/codedna/db.py +0 -0
  17. {codedna-0.9.5 → codedna-0.9.6}/codedna/demo.py +0 -0
  18. {codedna-0.9.5 → codedna-0.9.6}/codedna/git_hook.py +0 -0
  19. {codedna-0.9.5 → codedna-0.9.6}/codedna/integrations/__init__.py +0 -0
  20. {codedna-0.9.5 → codedna-0.9.6}/codedna/integrations/github_bot.py +0 -0
  21. {codedna-0.9.5 → codedna-0.9.6}/codedna/integrations/jira.py +0 -0
  22. {codedna-0.9.5 → codedna-0.9.6}/codedna/integrations/lemonsqueezy.py +0 -0
  23. {codedna-0.9.5 → codedna-0.9.6}/codedna/interview.py +0 -0
  24. {codedna-0.9.5 → codedna-0.9.6}/codedna/natureco_account.py +0 -0
  25. {codedna-0.9.5 → codedna-0.9.6}/codedna/nl.py +0 -0
  26. {codedna-0.9.5 → codedna-0.9.6}/codedna/nl_tools.json +0 -0
  27. {codedna-0.9.5 → codedna-0.9.6}/codedna/onboarding.py +0 -0
  28. {codedna-0.9.5 → codedna-0.9.6}/codedna/plan.py +0 -0
  29. {codedna-0.9.5 → codedna-0.9.6}/codedna/protection.py +0 -0
  30. {codedna-0.9.5 → codedna-0.9.6}/codedna/rate_limit.py +0 -0
  31. {codedna-0.9.5 → codedna-0.9.6}/codedna/sprint_health.py +0 -0
  32. {codedna-0.9.5 → codedna-0.9.6}/codedna/survey.py +0 -0
  33. {codedna-0.9.5 → codedna-0.9.6}/codedna/tech_debt.py +0 -0
  34. {codedna-0.9.5 → codedna-0.9.6}/codedna/webhooks.py +0 -0
  35. {codedna-0.9.5 → codedna-0.9.6}/dashboard/.env.local.example +0 -0
  36. {codedna-0.9.5 → codedna-0.9.6}/dashboard/.gitignore +0 -0
  37. {codedna-0.9.5 → codedna-0.9.6}/dashboard/Dockerfile +0 -0
  38. {codedna-0.9.5 → codedna-0.9.6}/dashboard/README.md +0 -0
  39. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/ai-compare/page.tsx +0 -0
  40. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/api/auth/login/route.ts +0 -0
  41. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/api/auth/logout/route.ts +0 -0
  42. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/api/auth/me/route.ts +0 -0
  43. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/api/auth/register/route.ts +0 -0
  44. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/api/billing/checkout/route.ts +0 -0
  45. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/billing/page.tsx +0 -0
  46. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/bus-factor/page.tsx +0 -0
  47. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/commits/page.tsx +0 -0
  48. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/debt/page.tsx +0 -0
  49. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/favicon.ico +0 -0
  50. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/feedback/page.tsx +0 -0
  51. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/files/page.tsx +0 -0
  52. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/fonts/GeistMonoVF.woff +0 -0
  53. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/fonts/GeistVF.woff +0 -0
  54. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/globals.css +0 -0
  55. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/interview/page.tsx +0 -0
  56. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/layout.tsx +0 -0
  57. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/login/page.tsx +0 -0
  58. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/onboarding/page.tsx +0 -0
  59. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/page.tsx +0 -0
  60. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/pricing/page.tsx +0 -0
  61. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/protected/page.tsx +0 -0
  62. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/register/page.tsx +0 -0
  63. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/repos/page.tsx +0 -0
  64. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/settings/integrations/page.tsx +0 -0
  65. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/sprints/page.tsx +0 -0
  66. {codedna-0.9.5 → codedna-0.9.6}/dashboard/app/trends/page.tsx +0 -0
  67. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/AuthProvider.tsx +0 -0
  68. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/ClientProviders.tsx +0 -0
  69. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/CommitTable.tsx +0 -0
  70. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/CostInfoTooltip.tsx +0 -0
  71. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/ErrorBanner.tsx +0 -0
  72. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/FeatureGate.tsx +0 -0
  73. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/FileHeatmap.tsx +0 -0
  74. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/LanguageSwitcher.tsx +0 -0
  75. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/NavClient.tsx +0 -0
  76. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/OverviewTitles.tsx +0 -0
  77. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/QuickInsights.tsx +0 -0
  78. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/RiskBadge.tsx +0 -0
  79. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/SummaryCards.tsx +0 -0
  80. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/UnderstandingChart.tsx +0 -0
  81. {codedna-0.9.5 → codedna-0.9.6}/dashboard/components/UpgradeBanner.tsx +0 -0
  82. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/api.ts +0 -0
  83. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/auth.ts +0 -0
  84. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/i18n/en.ts +0 -0
  85. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/i18n/index.tsx +0 -0
  86. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/i18n/tr.ts +0 -0
  87. {codedna-0.9.5 → codedna-0.9.6}/dashboard/lib/plan.ts +0 -0
  88. {codedna-0.9.5 → codedna-0.9.6}/dashboard/next-env.d.ts +0 -0
  89. {codedna-0.9.5 → codedna-0.9.6}/dashboard/next.config.mjs +0 -0
  90. {codedna-0.9.5 → codedna-0.9.6}/dashboard/package-lock.json +0 -0
  91. {codedna-0.9.5 → codedna-0.9.6}/dashboard/package.json +0 -0
  92. {codedna-0.9.5 → codedna-0.9.6}/dashboard/postcss.config.mjs +0 -0
  93. {codedna-0.9.5 → codedna-0.9.6}/dashboard/tailwind.config.ts +0 -0
  94. {codedna-0.9.5 → codedna-0.9.6}/dashboard/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codedna
3
- Version: 0.9.5
3
+ Version: 0.9.6
4
4
  Summary: AI Code Transparency Tool — detect AI-written code, analyze commit quality, track understanding debt, sprint health, bus factor, and more
5
5
  Project-URL: Homepage, https://codedna.dev
6
6
  Project-URL: Repository, https://github.com/natureco-official/codedna
@@ -1,4 +1,4 @@
1
1
  """CodeDNA — AI Code Transparency Tool."""
2
2
 
3
- __version__ = "0.9.5"
3
+ __version__ = "0.9.6"
4
4
  __app_name__ = "codedna"
@@ -77,6 +77,7 @@ class FileAnalysisResult:
77
77
  avg_function_length: float = 0.0
78
78
  single_commit_ratio: float = 0.0
79
79
  content_ai_signal: float = 0.0 # content-based AI signal (code patterns, 0–1)
80
+ understanding_estimate: float = 0.0 # auto-estimated team understanding (1–5), 0 = not computed
80
81
  total_lines: int = 0
81
82
  function_count: int = 0
82
83
  unsupported: bool = False # kept for internal use
@@ -424,6 +425,36 @@ def _content_ai_signal(source: str) -> float:
424
425
  return 1.0 - math.exp(-4.0 * total)
425
426
 
426
427
 
428
+ def estimate_understanding(
429
+ ai_probability: float,
430
+ complexity_score: float,
431
+ author_count: int,
432
+ days_since_last: float,
433
+ top_author_share: float,
434
+ ) -> float:
435
+ """Auto-estimate how well a file is likely understood by the team (1.0–5.0,
436
+ higher = better understood) — no manual survey required.
437
+
438
+ The 'understanding debt' insight: code that is AI-heavy, complex, stale and
439
+ owned by a single person is code nobody really understands — i.e. real risk.
440
+ This is what sets CodeDNA apart from plain "% AI" detectors: it answers
441
+ "AI wrote it, but does anyone on the team actually understand it?".
442
+ """
443
+ complexity_norm = _smoothstep(complexity_score, 6.0, 30.0)
444
+ staleness = _smoothstep(days_since_last, 30.0, 365.0) # ramps over a month → a year
445
+ solo = top_author_share >= 0.85 or author_count <= 1
446
+
447
+ score = 5.0
448
+ score -= 2.3 * ai_probability # AI-heavy → likely unreviewed / not internalized
449
+ score -= 1.0 * complexity_norm # complex → harder to hold in one's head
450
+ score -= 1.3 * staleness # untouched for ages → knowledge decays
451
+ if solo:
452
+ score -= 0.7 * (0.4 + 0.6 * staleness) # bus-factor-of-1; worse when also stale
453
+ if author_count >= 3:
454
+ score += 0.4 # shared across the team → better understood
455
+ return round(max(1.0, min(5.0, score)), 2)
456
+
457
+
427
458
  def _calculate_ai_probability(result: FileAnalysisResult, source: str = "") -> float:
428
459
  """Continuous, content-aware AI probability score (0.0 – 1.0).
429
460
 
@@ -244,7 +244,8 @@ def scan(
244
244
  "ai_probability": round(s.ai_probability, 4),
245
245
  "complexity": s.complexity_label,
246
246
  "lines": s.total_lines,
247
- "understanding": understanding_scores_map.get(s.file_path),
247
+ "understanding": understanding_scores_map.get(s.file_path), # survey-confirmed (1–5) or null
248
+ "understanding_estimate": s.understanding_estimate or None, # auto-estimated (1–5)
248
249
  }
249
250
  for s in results
250
251
  ],
@@ -277,11 +278,15 @@ def scan(
277
278
  else:
278
279
  complexity = "[green]Low[/green]"
279
280
 
280
- # Read understanding score from the single-query map
281
+ # Understanding: survey-confirmed score if we have one, else the auto-estimate.
281
282
  understanding_score = understanding_scores_map.get(s.file_path)
282
283
  if understanding_score is not None:
283
284
  color = "green" if understanding_score >= 4.0 else "yellow" if understanding_score >= 2.5 else "red"
284
285
  understanding = f"[{color}]✅ {understanding_score:.1f}/5[/{color}]"
286
+ elif s.understanding_estimate:
287
+ est = s.understanding_estimate
288
+ color = "green" if est >= 4.0 else "yellow" if est >= 2.5 else "red"
289
+ understanding = f"[{color}]~{est:.1f}/5[/{color}]" # ~ = auto-estimated
285
290
  else:
286
291
  understanding = "[dim]⚠️ Unknown[/dim]"
287
292
 
@@ -296,12 +301,36 @@ def scan(
296
301
  avg_ai = (total_ai / len(results)) * 100 if results else 0
297
302
  risk_label, risk_color = _risk_label(avg_ai)
298
303
 
304
+ est_scores = [s.understanding_estimate for s in results if s.understanding_estimate]
305
+ avg_understanding = (sum(est_scores) / len(est_scores)) if est_scores else None
306
+ understanding_txt = (
307
+ f" · Avg. understanding: [bold]{avg_understanding:.1f}/5[/bold]" if avg_understanding is not None else ""
308
+ )
299
309
  console.print(
300
310
  f"\n[bold]Repo Summary:[/bold] {len(results)} files scanned · "
301
- f"Avg. AI probability: [bold]{avg_ai:.0f}[/bold] · "
311
+ f"Avg. AI probability: [bold]{avg_ai:.0f}[/bold]{understanding_txt} · "
302
312
  f"Risk: [bold {risk_color}]{risk_label}[/bold {risk_color}]\n"
303
313
  )
304
314
 
315
+ # Understanding debt — CodeDNA's differentiator: AI-heavy code the team likely
316
+ # doesn't understand (auto-estimated from git ownership, recency, AI, complexity).
317
+ debt = [
318
+ s for s in results
319
+ if s.understanding_estimate and s.understanding_estimate < 3.0 and s.ai_probability >= 0.3
320
+ ]
321
+ if debt:
322
+ debt.sort(key=lambda s: (s.understanding_estimate, -s.ai_probability))
323
+ console.print("[bold]🧠 Understanding Debt[/bold] [dim](AI-heavy code the team likely doesn't understand)[/dim]")
324
+ for s in debt[:5]:
325
+ rel_path = _shorten_path(s.file_path, str(root))
326
+ console.print(
327
+ f" [red]~{s.understanding_estimate:.1f}/5[/red] {rel_path} "
328
+ f"[dim](AI %{s.ai_probability * 100:.0f})[/dim]"
329
+ )
330
+ if len(debt) > 5:
331
+ console.print(f" [dim]... and {len(debt) - 5} more[/dim]")
332
+ console.print()
333
+
305
334
  # AI explanations for high-risk files
306
335
  high_risk = [s for s in results if s.ai_probability >= 0.4]
307
336
  if high_risk and len(high_risk) <= 5:
@@ -8,7 +8,7 @@ from typing import Optional
8
8
  from git import InvalidGitRepositoryError, Repo
9
9
  from rich.console import Console
10
10
 
11
- from codedna.analyzer import FileAnalysisResult, analyze_file
11
+ from codedna.analyzer import FileAnalysisResult, analyze_file, estimate_understanding
12
12
 
13
13
  console = Console()
14
14
 
@@ -79,6 +79,32 @@ def calculate_single_commit_ratio(repo: Repo, file_path: str) -> float:
79
79
  return 0.0
80
80
 
81
81
 
82
+ def file_git_signals(repo: Repo, file_path: str) -> dict:
83
+ """Lightweight per-file git signals for the understanding estimate:
84
+ distinct author count, days since the last commit, and the top author's
85
+ share of commits (ownership concentration / bus-factor proxy)."""
86
+ import time
87
+
88
+ fallback = {"author_count": 0, "days_since_last": 9999.0, "top_author_share": 1.0}
89
+ try:
90
+ commits = list(repo.iter_commits(paths=file_path, max_count=100))
91
+ if not commits:
92
+ return fallback
93
+ authors: dict[str, int] = {}
94
+ for c in commits:
95
+ name = c.author.name or c.author.email or "unknown"
96
+ authors[name] = authors.get(name, 0) + 1
97
+ total = sum(authors.values()) or 1
98
+ last_ts = commits[0].committed_date # iter_commits yields newest first
99
+ return {
100
+ "author_count": len(authors),
101
+ "days_since_last": max(0.0, (time.time() - last_ts) / 86400.0),
102
+ "top_author_share": max(authors.values()) / total,
103
+ }
104
+ except Exception:
105
+ return fallback
106
+
107
+
82
108
  def scan_repository(
83
109
  repo_path: Optional[Path] = None,
84
110
  max_files: int = 200,
@@ -160,6 +186,16 @@ def scan_repository(
160
186
 
161
187
  result = analyze_file(file, single_commit_ratio=single_commit_ratio)
162
188
  if not result.unsupported and result.error is None:
189
+ # Auto-estimate team understanding from git ownership + recency + AI/complexity
190
+ if repo and relative_path in tracked_files:
191
+ sig = file_git_signals(repo, relative_path)
192
+ result.understanding_estimate = estimate_understanding(
193
+ result.ai_probability,
194
+ result.complexity_score,
195
+ sig["author_count"],
196
+ sig["days_since_last"],
197
+ sig["top_author_share"],
198
+ )
163
199
  results.append(result)
164
200
  count += 1
165
201
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codedna"
3
- version = "0.9.5"
3
+ version = "0.9.6"
4
4
  description = "AI Code Transparency Tool — detect AI-written code, analyze commit quality, track understanding debt, sprint health, bus factor, and more"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes