aicodestat 0.0.1__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.
cli/views.py ADDED
@@ -0,0 +1,277 @@
1
+ """Table, bar chart, and color rendering based on rich"""
2
+ import logging
3
+ from typing import Dict, Any, List
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from rich.panel import Panel
7
+ from rich.text import Text
8
+ from rich.bar import Bar
9
+ from rich import box
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ console = Console()
14
+
15
+
16
+ def format_percentage(value: float) -> str:
17
+ """Format percentage with color markers"""
18
+ if value >= 80:
19
+ return f"[green]{value:.2f}%[/green]"
20
+ elif value >= 50:
21
+ return f"[yellow]{value:.2f}%[/yellow]"
22
+ else:
23
+ return f"[red]{value:.2f}%[/red]"
24
+
25
+
26
+ def display_service_status(status: Dict[str, Any]):
27
+ """Display MCP service status"""
28
+ table = Table(title="MCP Service Status", box=box.ROUNDED, show_header=True, header_style="bold magenta")
29
+ table.add_column("Item", style="cyan", no_wrap=True)
30
+ table.add_column("Value", style="green")
31
+
32
+ table.add_row("Running Status", "✅ Running" if status["running"] else "❌ Not Running")
33
+ if status["running"]:
34
+ table.add_row("Process ID", str(status["pid"]))
35
+ table.add_row("Listen Address", f"{status['host']}:{status['port']}")
36
+ table.add_row("PID File", status["pid_file"])
37
+
38
+ console.print(table)
39
+
40
+ if status["running"]:
41
+ # Test if service is accessible
42
+ try:
43
+ import requests
44
+ response = requests.get(
45
+ f"http://{status['host']}:{status['port']}/health",
46
+ timeout=2
47
+ )
48
+ if response.status_code == 200:
49
+ console.print("[green]✅ Service health check passed[/green]")
50
+ health_data = response.json()
51
+ console.print(f"[dim]Version: {health_data.get('version', 'unknown')}[/dim]")
52
+ else:
53
+ console.print(f"[yellow]⚠️ Service response abnormal: {response.status_code}[/yellow]")
54
+ except ImportError:
55
+ console.print("[yellow]⚠️ Cannot perform health check (requests library missing)[/yellow]")
56
+ except Exception as e:
57
+ console.print(f"[red]❌ Cannot connect to service: {e}[/red]")
58
+
59
+
60
+ def display_metrics_table(metrics: Dict[str, Any], title: str = "Metrics Statistics"):
61
+ """
62
+ Display metrics overview table
63
+
64
+ Args:
65
+ metrics: Metrics data
66
+ title: Table title
67
+ """
68
+ table = Table(title=title, box=box.ROUNDED, show_header=True, header_style="bold magenta")
69
+ table.add_column("Metric Name", style="cyan", no_wrap=True)
70
+ table.add_column("Value", style="green", justify="right")
71
+
72
+ table.add_row("AI Generated Lines", f"{metrics.get('ai_total_lines', 0)} lines")
73
+ table.add_row("Adopted Lines", f"{metrics.get('adopted_lines', 0)} lines")
74
+ table.add_row("Code Adoption Rate", format_percentage(metrics.get('adoption_rate', 0.0)))
75
+ table.add_row("Code Generation Rate", format_percentage(metrics.get('generation_rate', 0.0)))
76
+
77
+ if "file_count" in metrics:
78
+ table.add_row("Files Involved", f"{metrics.get('file_count', 0)} files")
79
+
80
+ if "session_count" in metrics:
81
+ table.add_row("Sessions", f"{metrics.get('session_count', 0)} sessions")
82
+
83
+ console.print(table)
84
+
85
+
86
+ def display_global_dashboard(metrics: Dict[str, Any]):
87
+ """Display a concise global dashboard for all local data."""
88
+ from rich.layout import Layout
89
+
90
+ layout = Layout()
91
+ layout.split_column(
92
+ Layout(name="summary", size=8),
93
+ Layout(name="details"),
94
+ )
95
+
96
+ # Top summary panel
97
+ summary_table = Table(
98
+ title="Global Summary",
99
+ box=box.ROUNDED,
100
+ show_header=False,
101
+ header_style="bold magenta",
102
+ )
103
+ summary_table.add_column("Metric", style="cyan")
104
+ summary_table.add_column("Value", style="green", justify="right")
105
+
106
+ summary_table.add_row("AI Generated Lines", f"{metrics.get('ai_total_lines', 0)}")
107
+ summary_table.add_row("Adopted Lines", f"{metrics.get('adopted_lines', 0)}")
108
+ summary_table.add_row(
109
+ "Adoption Rate", format_percentage(metrics.get("adoption_rate", 0.0))
110
+ )
111
+ summary_table.add_row(
112
+ "Generation Rate", format_percentage(metrics.get("generation_rate", 0.0))
113
+ )
114
+ summary_table.add_row(
115
+ "Files Involved", str(metrics.get("file_count", 0))
116
+ )
117
+ summary_table.add_row(
118
+ "Sessions", str(metrics.get("session_count", 0))
119
+ )
120
+
121
+ layout["summary"].update(summary_table)
122
+
123
+ # Bottom: simple bar chart for quick visual
124
+ bar_data = {
125
+ "AI Lines": float(metrics.get("ai_total_lines", 0)),
126
+ "Adopted": float(metrics.get("adopted_lines", 0)),
127
+ }
128
+ # Avoid all zeros
129
+ bar_data = {k: v for k, v in bar_data.items() if v > 0}
130
+
131
+ if bar_data:
132
+ from rich.table import Table as RichTable
133
+
134
+ max_value = max(bar_data.values())
135
+ bar_table = RichTable(
136
+ title="Adoption Overview",
137
+ box=box.SIMPLE,
138
+ show_header=False,
139
+ )
140
+ bar_table.add_column("Item", style="cyan")
141
+ bar_table.add_column("Chart", style="blue")
142
+ bar_table.add_column("Value", style="green", justify="right")
143
+
144
+ for label, value in bar_data.items():
145
+ length = int((value / max_value) * 40) if max_value > 0 else 0
146
+ bar = "█" * length
147
+ bar_table.add_row(label, bar, f"{int(value)}")
148
+
149
+ layout["details"].update(bar_table)
150
+ else:
151
+ layout["details"].update(
152
+ Panel("[yellow]No global data yet. Generate some AI code to see stats here.[/yellow]")
153
+ )
154
+
155
+ console.print(layout)
156
+
157
+
158
+ def display_session_info(summaries: List[Dict[str, Any]]):
159
+ """Display session details"""
160
+ if not summaries:
161
+ return
162
+
163
+ summary = summaries[0] # Take first session info
164
+ session_info = summary.get("session_info", "")
165
+ create_time = summary.get("create_time", "")
166
+
167
+ info_text = Text()
168
+ if session_info:
169
+ info_text.append("🤖 Agent Info: ", style="bold")
170
+ info_text.append(f"{session_info}\n", style="cyan")
171
+ if create_time:
172
+ info_text.append("📅 Operation Time: ", style="bold")
173
+ info_text.append(f"{create_time}\n", style="cyan")
174
+
175
+ if info_text:
176
+ panel = Panel(info_text, title="Session Details", border_style="blue")
177
+ console.print(panel)
178
+
179
+
180
+ def display_diff_lines_table(diff_lines: List[Dict[str, Any]], limit: int = 20):
181
+ """
182
+ Display diff lines details table
183
+
184
+ Args:
185
+ diff_lines: Diff lines list
186
+ limit: Display line limit
187
+ """
188
+ if not diff_lines:
189
+ console.print("[yellow]No diff lines data[/yellow]")
190
+ return
191
+
192
+ table = Table(title=f"Diff Lines Details (showing first {min(limit, len(diff_lines))} lines)", box=box.ROUNDED)
193
+ table.add_column("Diff Type", style="cyan", width=10)
194
+ table.add_column("Line Number", style="green", justify="right", width=8)
195
+ table.add_column("Code Content", style="white", overflow="fold")
196
+
197
+ for diff_line in diff_lines[:limit]:
198
+ diff_type = diff_line.get("diff_type", "")
199
+ line_number = diff_line.get("line_number", "")
200
+ line_content = diff_line.get("line_content", "")
201
+
202
+ # Type color markers
203
+ if diff_type == "add":
204
+ type_style = "[green]add[/green]"
205
+ elif diff_type == "modify":
206
+ type_style = "[yellow]modify[/yellow]"
207
+ else:
208
+ type_style = diff_type
209
+
210
+ table.add_row(type_style, str(line_number), line_content)
211
+
212
+ console.print(table)
213
+
214
+ if len(diff_lines) > limit:
215
+ console.print(f"[dim]... {len(diff_lines) - limit} more lines not shown[/dim]")
216
+
217
+
218
+ def display_simple_bar_chart(data: Dict[str, float], title: str = "Metrics Comparison"):
219
+ """
220
+ Display simple bar chart
221
+
222
+ Args:
223
+ data: Data dictionary, keys are labels, values are numbers
224
+ title: Chart title
225
+ """
226
+ if not data:
227
+ console.print("[yellow]No data to display[/yellow]")
228
+ return
229
+
230
+ # Calculate max value for normalization
231
+ max_value = max(data.values()) if data.values() else 1
232
+
233
+ table = Table(title=title, box=box.SIMPLE)
234
+ table.add_column("Item", style="cyan")
235
+ table.add_column("Value", style="green", justify="right")
236
+ table.add_column("Chart", style="blue")
237
+
238
+ for label, value in sorted(data.items(), key=lambda x: x[1], reverse=True):
239
+ # Calculate bar length (assuming max width of 50)
240
+ bar_length = int((value / max_value) * 50) if max_value > 0 else 0
241
+ bar = "█" * bar_length
242
+ table.add_row(label, f"{value:.2f}", bar)
243
+
244
+ console.print(table)
245
+
246
+
247
+ def display_agent_comparison(metrics_list: List[Dict[str, Any]]):
248
+ """
249
+ Display cross-agent metrics comparison
250
+
251
+ Args:
252
+ metrics_list: Metrics list, each element contains session_id and metrics data
253
+ """
254
+ if not metrics_list:
255
+ console.print("[yellow]No comparison data[/yellow]")
256
+ return
257
+
258
+ table = Table(title="Cross-Agent Metrics Comparison", box=box.ROUNDED)
259
+ table.add_column("Session ID", style="cyan")
260
+ table.add_column("AI Generated Lines", style="green", justify="right")
261
+ table.add_column("Adopted Lines", style="green", justify="right")
262
+ table.add_column("Adoption Rate", style="green", justify="right")
263
+ table.add_column("Generation Rate", style="green", justify="right")
264
+
265
+ for item in metrics_list:
266
+ session_id = item.get("session_id", "unknown")
267
+ metrics = item.get("metrics", {})
268
+
269
+ table.add_row(
270
+ session_id[:30] + "..." if len(session_id) > 30 else session_id,
271
+ str(metrics.get("ai_total_lines", 0)),
272
+ str(metrics.get("adopted_lines", 0)),
273
+ format_percentage(metrics.get("adoption_rate", 0.0)),
274
+ format_percentage(metrics.get("generation_rate", 0.0))
275
+ )
276
+
277
+ console.print(table)
compute/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """计算层模块"""
2
+
compute/cache.py ADDED
@@ -0,0 +1,90 @@
1
+ """简单内存缓存,按会话/文件维度缓存5分钟内的计算结果"""
2
+ import time
3
+ import logging
4
+ from typing import Any, Optional
5
+ from datetime import datetime, timedelta
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class SimpleCache:
11
+ """简单内存缓存实现"""
12
+
13
+ def __init__(self, default_ttl: int = 300):
14
+ """
15
+ 初始化缓存
16
+
17
+ Args:
18
+ default_ttl: 默认过期时间(秒),默认5分钟
19
+ """
20
+ self._cache: dict[str, tuple[Any, float]] = {}
21
+ self.default_ttl = default_ttl
22
+
23
+ def get(self, key: str) -> Optional[Any]:
24
+ """
25
+ 获取缓存值
26
+
27
+ Args:
28
+ key: 缓存键
29
+
30
+ Returns:
31
+ 缓存值,如果不存在或已过期则返回None
32
+ """
33
+ if key not in self._cache:
34
+ return None
35
+
36
+ value, expire_time = self._cache[key]
37
+
38
+ if time.time() > expire_time:
39
+ # 已过期,删除
40
+ del self._cache[key]
41
+ return None
42
+
43
+ return value
44
+
45
+ def set(self, key: str, value: Any, ttl: Optional[int] = None):
46
+ """
47
+ 设置缓存值
48
+
49
+ Args:
50
+ key: 缓存键
51
+ value: 缓存值
52
+ ttl: 过期时间(秒),如果为None则使用默认值
53
+ """
54
+ if ttl is None:
55
+ ttl = self.default_ttl
56
+
57
+ expire_time = time.time() + ttl
58
+ self._cache[key] = (value, expire_time)
59
+
60
+ def clear(self):
61
+ """清空所有缓存"""
62
+ self._cache.clear()
63
+
64
+ def delete(self, key: str):
65
+ """删除指定缓存"""
66
+ if key in self._cache:
67
+ del self._cache[key]
68
+
69
+ def cleanup_expired(self):
70
+ """清理过期缓存"""
71
+ current_time = time.time()
72
+ expired_keys = [
73
+ key for key, (_, expire_time) in self._cache.items()
74
+ if current_time > expire_time
75
+ ]
76
+ for key in expired_keys:
77
+ del self._cache[key]
78
+
79
+ if expired_keys:
80
+ logger.debug(f"Cleaned up {len(expired_keys)} expired cache entries")
81
+
82
+
83
+ # 全局缓存实例
84
+ _metrics_cache = SimpleCache(default_ttl=300)
85
+
86
+
87
+ def get_cache() -> SimpleCache:
88
+ """获取全局缓存实例"""
89
+ return _metrics_cache
90
+
compute/diff_engine.py ADDED
@@ -0,0 +1,69 @@
1
+ """差异提取引擎:封装 extract_diff_lines,用 difflib 实现精准差分"""
2
+ import difflib
3
+ import logging
4
+ from typing import List, Dict, Any
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def extract_diff_lines(code_before: str, code_after: str) -> List[Dict[str, Any]]:
10
+ """
11
+ 提取具体差异行(新增/修改),返回差异行列表
12
+
13
+ Args:
14
+ code_before: 编辑前的代码
15
+ code_after: 编辑后的代码
16
+
17
+ Returns:
18
+ 差异行列表,每个元素包含:
19
+ - diff_type: 'add' 或 'modify'
20
+ - line_content: 差异行内容
21
+ - line_number: 差异行在编辑后文件中的行号
22
+ """
23
+ # 拆分代码为行列表(保留空行、原始格式)
24
+ before_lines = code_before.split("\n")
25
+ after_lines = code_after.split("\n")
26
+
27
+ diff_lines = []
28
+
29
+ # 使用 SequenceMatcher 计算差异
30
+ matcher = difflib.SequenceMatcher(None, before_lines, after_lines)
31
+
32
+ # 追踪编辑后文件的行号
33
+ current_after_line = 0
34
+
35
+ for tag, i1, i2, j1, j2 in matcher.get_opcodes():
36
+ if tag == 'equal':
37
+ # 相同部分,更新行号
38
+ current_after_line += (j2 - j1)
39
+ elif tag == 'delete':
40
+ # 删除的行,不记录(因为我们要的是新增/修改)
41
+ pass
42
+ elif tag == 'insert':
43
+ # 新增的行
44
+ for idx, line in enumerate(after_lines[j1:j2]):
45
+ line_content = line.strip()
46
+ if line_content: # 过滤空的新增行
47
+ diff_lines.append({
48
+ "diff_type": "add",
49
+ "line_content": line_content,
50
+ "line_number": current_after_line + idx + 1
51
+ })
52
+ current_after_line += (j2 - j1)
53
+ elif tag == 'replace':
54
+ # 修改的行(先删后加)
55
+ # 我们记录新增的内容作为修改后的差异行
56
+ for idx, line in enumerate(after_lines[j1:j2]):
57
+ line_content = line.strip()
58
+ if line_content: # 过滤空的修改行
59
+ diff_lines.append({
60
+ "diff_type": "modify",
61
+ "line_content": line_content,
62
+ "line_number": current_after_line + idx + 1
63
+ })
64
+ current_after_line += (j2 - j1)
65
+
66
+ logger.debug(f"Extracted {len(diff_lines)} diff lines: {sum(1 for d in diff_lines if d['diff_type'] == 'add')} add, {sum(1 for d in diff_lines if d['diff_type'] == 'modify')} modify")
67
+
68
+ return diff_lines
69
+
compute/lcs_engine.py ADDED
@@ -0,0 +1,73 @@
1
+ """LCS算法引擎:实现 lcs_calculate 与批量计算接口"""
2
+ import logging
3
+ from typing import List
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def lcs_calculate(ai_diff_lines: List[str], latest_file_lines: List[str]) -> int:
9
+ """
10
+ 计算AI生成的差异行与文件最新内容的最长公共子序列(公共行数)
11
+
12
+ Args:
13
+ ai_diff_lines: AI生成的差异行列表(从code_diff_lines表读取)
14
+ latest_file_lines: 文件最新的代码行列表(读取本地文件)
15
+
16
+ Returns:
17
+ 公共行数(采纳行数)
18
+ """
19
+ # 简化处理:提取非空代码行,去除首尾空白
20
+ ai_lines = [line.strip() for line in ai_diff_lines if line.strip()]
21
+ latest_lines = [line.strip() for line in latest_file_lines if line.strip()]
22
+
23
+ if not ai_lines or not latest_lines:
24
+ return 0
25
+
26
+ # 构建LCS动态规划表
27
+ m, n = len(ai_lines), len(latest_lines)
28
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
29
+
30
+ for i in range(1, m + 1):
31
+ for j in range(1, n + 1):
32
+ if ai_lines[i-1] == latest_lines[j-1]:
33
+ dp[i][j] = dp[i-1][j-1] + 1
34
+ else:
35
+ dp[i][j] = max(dp[i-1][j], dp[i][j-1])
36
+
37
+ lcs_length = dp[m][n]
38
+ logger.debug(f"LCS calculation: {len(ai_lines)} AI lines vs {len(latest_lines)} latest lines, LCS={lcs_length}")
39
+
40
+ return lcs_length
41
+
42
+
43
+ def calculate_adoption_rate(ai_total_lines: int, adopted_lines: int) -> float:
44
+ """
45
+ 计算采纳率
46
+
47
+ Args:
48
+ ai_total_lines: AI生成总行数
49
+ adopted_lines: 采纳行数
50
+
51
+ Returns:
52
+ 采纳率(0-100)
53
+ """
54
+ if ai_total_lines == 0:
55
+ return 0.0
56
+ return round((adopted_lines / ai_total_lines) * 100, 2)
57
+
58
+
59
+ def calculate_generation_rate(ai_total_lines: int, file_total_lines: int) -> float:
60
+ """
61
+ 计算生成率
62
+
63
+ Args:
64
+ ai_total_lines: AI生成总行数
65
+ file_total_lines: 文件最新总行数
66
+
67
+ Returns:
68
+ 生成率(0-100)
69
+ """
70
+ if file_total_lines == 0:
71
+ return 0.0
72
+ return round((ai_total_lines / file_total_lines) * 100, 2)
73
+