Skip to content

Commit 17dfaa7

Browse files
refactor(code2llm): configuration management system
changes: - file: computation.py area: core added: [make_relative_path, build_context, aggregate_file_stats, compute_god_modules, compute_hub_types, scan_file_sizes, +2 more] - file: exclusion.py area: core added: [is_excluded] - file: render.py area: core added: [render_patterns, render_risks, render_metrics_target, render_header, render_history, render_next] - file: yaml_export.py area: core added: [export_to_yaml] - file: evolution_exporter.py area: core modified: [export, export_to_yaml, EvolutionExporter, _is_excluded] removed: [_compute_god_modules, _render_risks, _compute_func_data, _aggregate_file_stats, _render_next, _build_context, +8 more] dependencies: flow: "computation→exclusion" - computation.py -> exclusion.py stats: lines: "+625/-439 (net +186)" files: 7 complexity: "Large structural change (normalized)"
1 parent 08d719e commit 17dfaa7

13 files changed

Lines changed: 641 additions & 444 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
## [Unreleased]
22

3+
## [0.5.127] - 2026-04-19
4+
5+
### Other
6+
- Update code2llm/exporters/evolution/__init__.py
7+
- Update code2llm/exporters/evolution/computation.py
8+
- Update code2llm/exporters/evolution/constants.py
9+
- Update code2llm/exporters/evolution/exclusion.py
10+
- Update code2llm/exporters/evolution/render.py
11+
- Update code2llm/exporters/evolution/yaml_export.py
12+
- Update code2llm/exporters/evolution_exporter.py
13+
314
## [0.5.126] - 2026-04-19
415

516
### Other

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
## AI Cost Tracking
55

6-
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.126-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6+
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.127-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
77
![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
88

99
- 🤖 **LLM usage:** $7.5000 (166 commits)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.5.126
1+
0.5.127

code2llm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
and entity resolution with multilingual support.
99
"""
1010

11-
__version__ = "0.5.126"
11+
__version__ = "0.5.127"
1212
__author__ = "STTS Project"
1313

1414
# Core analysis components (lightweight, always needed)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Evolution exporter package — prioritized refactoring queue for iterative improvement.
2+
3+
This package provides:
4+
- constants: Thresholds and exclusion patterns
5+
- exclusion: Path filtering logic
6+
- computation: Metrics calculation (god modules, hub types, etc.)
7+
- render: Text output generation for evolution.toon
8+
- yaml_export: Structured YAML output for evolution.toon.yaml
9+
10+
All public names are re-exported here for backward compatibility
11+
with the original evolution_exporter.py module structure.
12+
"""
13+
14+
# Constants
15+
from .constants import (
16+
CC_SPLIT_THRESHOLD,
17+
FAN_OUT_THRESHOLD,
18+
GOD_MODULE_LINES,
19+
HUB_TYPE_THRESHOLD,
20+
EXCLUDE_PATTERNS,
21+
)
22+
23+
# Exclusion
24+
from .exclusion import is_excluded
25+
26+
# Computation
27+
from .computation import (
28+
compute_func_data,
29+
scan_file_sizes,
30+
aggregate_file_stats,
31+
make_relative_path,
32+
filter_god_modules,
33+
compute_god_modules,
34+
compute_hub_types,
35+
build_context,
36+
)
37+
38+
# Render
39+
from .render import (
40+
render_header,
41+
render_next,
42+
render_risks,
43+
render_metrics_target,
44+
render_patterns,
45+
render_history,
46+
)
47+
48+
# YAML Export
49+
from .yaml_export import export_to_yaml
50+
51+
__all__ = [
52+
# Constants
53+
'CC_SPLIT_THRESHOLD',
54+
'FAN_OUT_THRESHOLD',
55+
'GOD_MODULE_LINES',
56+
'HUB_TYPE_THRESHOLD',
57+
'EXCLUDE_PATTERNS',
58+
# Exclusion
59+
'is_excluded',
60+
# Computation
61+
'compute_func_data',
62+
'scan_file_sizes',
63+
'aggregate_file_stats',
64+
'make_relative_path',
65+
'filter_god_modules',
66+
'compute_god_modules',
67+
'compute_hub_types',
68+
'build_context',
69+
# Render
70+
'render_header',
71+
'render_next',
72+
'render_risks',
73+
'render_metrics_target',
74+
'render_patterns',
75+
'render_history',
76+
# YAML Export
77+
'export_to_yaml',
78+
]
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""Evolution exporter computation — metrics calculation for god modules, hub types, etc."""
2+
3+
from collections import defaultdict
4+
from pathlib import Path
5+
from typing import Any, Dict, List, Optional
6+
7+
from code2llm.core.models import AnalysisResult, FunctionInfo
8+
9+
from .constants import GOD_MODULE_LINES, HUB_TYPE_THRESHOLD, CC_SPLIT_THRESHOLD
10+
from .exclusion import is_excluded
11+
12+
13+
def compute_func_data(result: AnalysisResult) -> List[Dict]:
14+
"""Compute per-function metrics, excluding venv."""
15+
func_data = []
16+
for qname, fi in result.functions.items():
17+
if is_excluded(fi.file):
18+
continue
19+
cc = fi.complexity.get("cyclomatic_complexity", 0)
20+
fan_out = len(set(fi.calls))
21+
fan_in = len(set(fi.called_by))
22+
func_data.append({
23+
"qname": qname, "name": fi.name,
24+
"class_name": fi.class_name, "cc": cc,
25+
"fan_out": fan_out, "fan_in": fan_in,
26+
"impact": cc * max(fan_out, 1),
27+
"file": fi.file, "module": fi.module,
28+
})
29+
return sorted(func_data, key=lambda x: x["impact"], reverse=True)
30+
31+
32+
def scan_file_sizes(project_path: Optional[Path]) -> Dict[str, int]:
33+
"""Scan Python files and return line counts."""
34+
file_lines: Dict[str, int] = {}
35+
if not project_path or not project_path.is_dir():
36+
return file_lines
37+
38+
for py in project_path.rglob("*.py"):
39+
fpath = str(py)
40+
if is_excluded(fpath):
41+
continue
42+
try:
43+
lc = len(py.read_text(encoding="utf-8", errors="ignore").splitlines())
44+
file_lines[fpath] = lc
45+
except Exception:
46+
pass
47+
return file_lines
48+
49+
50+
def aggregate_file_stats(
51+
result: AnalysisResult,
52+
file_lines: Dict[str, int]
53+
) -> Dict[str, Dict]:
54+
"""Aggregate function and class data per file."""
55+
file_stats: Dict[str, Dict] = defaultdict(
56+
lambda: {"lines": 0, "funcs": 0, "classes": set(), "max_cc": 0}
57+
)
58+
59+
# Initialize with line counts
60+
for fpath, lc in file_lines.items():
61+
file_stats[fpath]["lines"] = lc
62+
63+
# Aggregate function data
64+
for qname, fi in result.functions.items():
65+
if is_excluded(fi.file):
66+
continue
67+
fs = file_stats[fi.file]
68+
fs["funcs"] += 1
69+
fs["max_cc"] = max(fs["max_cc"], fi.complexity.get("cyclomatic_complexity", 0))
70+
if fi.class_name:
71+
fs["classes"].add(fi.class_name)
72+
73+
# Aggregate class data
74+
for qname, ci in result.classes.items():
75+
if not is_excluded(ci.file):
76+
file_stats[ci.file]["classes"].add(ci.name)
77+
78+
return file_stats
79+
80+
81+
def make_relative_path(fpath: str, project_path: Optional[Path]) -> str:
82+
"""Convert absolute path to relative path."""
83+
if not project_path:
84+
return fpath
85+
try:
86+
return str(Path(fpath).relative_to(project_path))
87+
except ValueError:
88+
return fpath
89+
90+
91+
def filter_god_modules(file_stats: Dict[str, Dict], project_path: Optional[Path]) -> List[Dict]:
92+
"""Filter files to god modules (≥500 lines)."""
93+
god_modules = []
94+
for fpath, stats in file_stats.items():
95+
if stats["lines"] >= GOD_MODULE_LINES:
96+
rel = make_relative_path(fpath, project_path)
97+
god_modules.append({
98+
"file": rel,
99+
"lines": stats["lines"],
100+
"funcs": stats["funcs"],
101+
"classes": len(stats["classes"]),
102+
"max_cc": stats["max_cc"],
103+
})
104+
god_modules.sort(key=lambda x: x["lines"], reverse=True)
105+
return god_modules
106+
107+
108+
def compute_god_modules(result: AnalysisResult) -> List[Dict]:
109+
"""Identify god modules (≥500 lines) from project files."""
110+
pp = Path(result.project_path) if result.project_path else None
111+
112+
file_lines = scan_file_sizes(pp)
113+
file_stats = aggregate_file_stats(result, file_lines)
114+
return filter_god_modules(file_stats, pp)
115+
116+
117+
def compute_hub_types(result: AnalysisResult) -> List[Dict]:
118+
"""Identify hub types consumed by many functions."""
119+
type_consumers: Dict[str, int] = defaultdict(int)
120+
type_producers: Dict[str, int] = defaultdict(int)
121+
for qname, fi in result.functions.items():
122+
ret = fi.complexity.get("return_type", "")
123+
if ret:
124+
type_producers[ret] += 1
125+
for arg_type in fi.complexity.get("arg_types", []):
126+
if arg_type:
127+
type_consumers[arg_type] += 1
128+
hub_types = [
129+
{"type": t, "consumers": c, "producers": type_producers.get(t, 0)}
130+
for t, c in type_consumers.items()
131+
if c >= HUB_TYPE_THRESHOLD
132+
]
133+
hub_types.sort(key=lambda x: x["consumers"], reverse=True)
134+
return hub_types
135+
136+
137+
def build_context(result: AnalysisResult) -> Dict[str, Any]:
138+
"""Build context dict with all computed metrics."""
139+
ctx = {
140+
"result": result,
141+
}
142+
ctx["funcs"] = compute_func_data(result)
143+
ctx["god_modules"] = compute_god_modules(result)
144+
ctx["hub_types"] = compute_hub_types(result)
145+
146+
# Overall metrics
147+
all_cc = [f["cc"] for f in ctx["funcs"]]
148+
ctx["avg_cc"] = round(sum(all_cc) / len(all_cc), 1) if all_cc else 0.0
149+
ctx["max_cc"] = max(all_cc) if all_cc else 0
150+
ctx["total_funcs"] = len(all_cc)
151+
ctx["total_files"] = len(set(f["file"] for f in ctx["funcs"])) or 1
152+
ctx["high_cc_count"] = len([c for c in all_cc if c >= CC_SPLIT_THRESHOLD])
153+
ctx["critical_count"] = len([c for c in all_cc if c >= 10])
154+
155+
return ctx
156+
157+
158+
__all__ = [
159+
'compute_func_data',
160+
'scan_file_sizes',
161+
'aggregate_file_stats',
162+
'make_relative_path',
163+
'filter_god_modules',
164+
'compute_god_modules',
165+
'compute_hub_types',
166+
'build_context',
167+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Evolution exporter constants — thresholds and configuration."""
2+
3+
# Thresholds
4+
CC_SPLIT_THRESHOLD = 15
5+
FAN_OUT_THRESHOLD = 10
6+
GOD_MODULE_LINES = 500
7+
HUB_TYPE_THRESHOLD = 10
8+
9+
10+
# Exclude patterns (mirrors ToonExporter)
11+
EXCLUDE_PATTERNS = {
12+
'venv', '.venv', 'env', '.env', 'publish-env', 'test-env',
13+
'site-packages', 'node_modules', '__pycache__', '.git',
14+
'dist', 'build', 'egg-info', '.tox', '.mypy_cache',
15+
'examples', 'benchmarks', 'tests', 'scripts', 'demo_langs',
16+
}
17+
18+
19+
__all__ = [
20+
'CC_SPLIT_THRESHOLD',
21+
'FAN_OUT_THRESHOLD',
22+
'GOD_MODULE_LINES',
23+
'HUB_TYPE_THRESHOLD',
24+
'EXCLUDE_PATTERNS',
25+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Evolution exporter exclusion logic — path filtering."""
2+
3+
from .constants import EXCLUDE_PATTERNS
4+
5+
6+
def is_excluded(path: str) -> bool:
7+
"""Check if path should be excluded (venv, site-packages, etc.)."""
8+
path_lower = path.lower().replace('\\', '/')
9+
for pattern in EXCLUDE_PATTERNS:
10+
if f'/{pattern}/' in path_lower or path_lower.startswith(f'{pattern}/'):
11+
return True
12+
if pattern in path_lower.split('/'):
13+
return True
14+
return False
15+
16+
17+
__all__ = ['is_excluded']

0 commit comments

Comments
 (0)