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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: aicodestat
3
+ Version: 0.0.1
4
+ Summary: A local-first metrics tool that analyzes how you use AI coding assistants.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: fastapi>=0.104.0
8
+ Requires-Dist: uvicorn[standard]>=0.24.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: questionary>=2.0.0
12
+ Requires-Dist: httpx>=0.25.0
13
+ Requires-Dist: mcp>=1.0.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
16
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
17
+
18
+ ## CodeStat · AI Code Metrics
19
+
20
+ > Quantify how much AI actually contributes to your codebase.
21
+
22
+ [![CI](https://img.shields.io/github/actions/workflow/status/2hangchen/CodeStat/ci.yml?style=flat-square&label=CI)](https://github.com/2hangchen/CodeStat/actions)
23
+ [![License](https://img.shields.io/github/license/2hangchen/CodeStat?style=flat-square)](LICENSE)
24
+
25
+ `CodeStat` is a local metrics tool that analyzes how you use AI coding assistants:
26
+ how many lines are generated by AI, how many are kept, and how this evolves over time.
27
+
28
+ > 中文文档见:[`README.zh-CN.md`](./README.zh-CN.md)
29
+
30
+ ---
31
+
32
+ ## Features
33
+
34
+ - **Global dashboard for all data**
35
+ - AI generated lines, adopted lines, adoption & generation rates
36
+ - File count, session count, quick bar chart overview
37
+
38
+ - **Multi‑dimension queries**
39
+ - **By file**: see how much of a file comes from AI and how much you kept
40
+ - **By session**: analyze one coding session with detailed diff lines
41
+ - **By project**: aggregate metrics for an entire repository
42
+
43
+ - **Agent / model comparison**
44
+ - Compare multiple sessions (agents / models / settings) side‑by‑side
45
+ - See which one actually produces more adopted code instead of just more tokens
46
+
47
+ - **Local‑first & privacy‑friendly**
48
+ - All metrics are computed locally from your own diffs
49
+ - No source code or prompts are sent to any remote service
50
+
51
+ - **Nice CLI UX**
52
+ - Rich‑based tables & colors, arrow‑key navigation
53
+ - Minimal but informative header (MCP status + repo info)
54
+
55
+ ---
56
+
57
+ ## Demo
58
+
59
+ > TODO: add real screenshots / GIFs from your terminal
60
+
61
+ - **Global dashboard**
62
+
63
+ *(insert GIF or screenshot here)*
64
+
65
+ - **Session metrics with diff lines**
66
+
67
+ *(insert GIF or screenshot here)*
68
+
69
+ ---
70
+
71
+ ## Quickstart
72
+
73
+ ### Install
74
+
75
+ ```bash
76
+ git clone https://github.com/2hangchen/CodeStat.git
77
+ cd CodeStat
78
+ pip install -r requirements.txt
79
+ ```
80
+
81
+ > Once published to PyPI you can alternatively run:
82
+ > `pip install codestat-ai`
83
+
84
+ ### Start the CLI
85
+
86
+ ```bash
87
+ python .\cli\main.py
88
+ ```
89
+
90
+ Use `↑/↓` to move, `Enter` to confirm.
91
+ Choose **“📈 Global Dashboard (All Data)”** to see an overview of your local metrics.
92
+
93
+ ---
94
+
95
+ ## Typical Workflows
96
+
97
+ - **Measure your own AI usage**
98
+ - Record one or more coding sessions with your IDE + MCP server
99
+ - Run `CodeStat` and inspect:
100
+ - AI generated vs adopted lines
101
+ - Which files receive the most AI help
102
+
103
+ - **Compare agents / models / prompts**
104
+ - Map different sessions to different agents / models
105
+ - Use **Compare Agents** to get a per‑session comparison table
106
+
107
+ - **Project‑level health check**
108
+ - For a given repo, run project metrics to see:
109
+ - Where AI contributes the most
110
+ - Whether AI‑generated code is actually being kept
@@ -0,0 +1,34 @@
1
+ config.py,sha256=EUE6AGyiO2-gaP2USqpO8MVMwDrrAwdqtaBTRz_HNZg,3829
2
+ local_mcp_server.py,sha256=YTH4D_BH7BK7XWzhFrQgyxdPjlabNtQ0TPz9AcmcLIE,10901
3
+ logging_config.py,sha256=_BQ8zNBM9jNDIASQZeX3pvTmitQbzXqgIvPIPiUy7II,1997
4
+ main.py,sha256=LtsgvkCaSynwK4sGaDvCSd0ae2B0hBfQ3RJM9IT9ey0,5843
5
+ service_manager.py,sha256=k1eEEioAlD44Z1zWJ5JHNv6sVkOdr92xsOgvkX0lYNg,6909
6
+ cli/__init__.py,sha256=AqvbeRtWawDr_fC3owziHPI3LTZPEOMprtqhPUv6HzM,34
7
+ cli/exporter.py,sha256=9eh3TKBY2VT1AScU2RTFEElYtEOtz_M7fmuR3LHr7Dc,3567
8
+ cli/main.py,sha256=kpZxNMlb4fC1naY5PHj7zK3HfVFSrraRee3_zwItZLI,7072
9
+ cli/menus.py,sha256=6JMBD0HT5DauB9eDsR5Hy6XOoo3nDAnibR-5Ki37lLs,19379
10
+ cli/views.py,sha256=495tKFwW2PmKjDDCRweONKX_f3E3k6y-9WVBlJ1dXwg,9858
11
+ compute/__init__.py,sha256=UZBI-hgsvGzb3BFH70Ce-QvWgxs5OvM4oQ1NzbDxGsY,25
12
+ compute/cache.py,sha256=4U8bsFy0yBdq1bhsmjTnNTWFuuHK6h3_hrkGPmxxVC8,2417
13
+ compute/diff_engine.py,sha256=ySYqGZfDDYfkTaKhA4BdYad4ACaeurWgWy3jwMbDE1I,2685
14
+ compute/lcs_engine.py,sha256=zh6TJWZCzP8s-Tls2VHkVUfW2DJ63tZgSvatxtLCT78,2217
15
+ compute/metrics_service.py,sha256=jHyQV5UfK6RpzCuAmq_C6UTknsynpKEc-aKFGQ-sLHQ,11128
16
+ mcp/__init__.py,sha256=xoKlMDnbq-owd19TqBCnbAPudUyPHr3S2nGGrb-TghI,30
17
+ mcp/agent_adapter.py,sha256=gBp4eaWZJ_z1XTynOsINrGLK6rR_AYC5IOWFYke6BKs,1777
18
+ mcp/api_schemas.py,sha256=_ZkX5Q_yM-m447f2VcPqjsSAgR2cP4bn-m23oDyv1Ro,1304
19
+ mcp/routes_after.py,sha256=ZuTWxgl7bmYxRqxX8VLvwiDRY_H7AtHh3R-6rhYxXNI,4374
20
+ mcp/routes_before.py,sha256=NbNVVqZ5OnzsSY9I0gAYzDd-YFx9r01oAWrJVjtvEEo,2234
21
+ mcp/routes_tools.py,sha256=1sgoaiVEw3LM6C_FE0WyzxLmssMXyH1k3NF_PPHcdSQ,4360
22
+ storage/__init__.py,sha256=t8_StbWwdo4NOqdm0mDgisj3a1YNRET_qktZoKO_oDY,25
23
+ storage/backup.py,sha256=UUV5-bazOV6Rs4-Vg39C4-dTdw4RQSqSvLYmVr3JxA0,5715
24
+ storage/db.py,sha256=PF_ZkQcruYlMegdkCINOwjScS5FB_qVYKd_iilnOrqY,5522
25
+ storage/models.py,sha256=-2x2loKCTVX4i56-9MWsU4NPDEUMx7llvmzseRarR-4,9842
26
+ storage/scheduler.py,sha256=J9I2AHymduyaSG33bpZzxc4JfoIjRyYMNCkU2Sr0Wzc,3960
27
+ utils/__init__.py,sha256=42ItHJEKhCz4PIrPFZnXNQV-hR0B5gJ9bb9dfW_uL-U,22
28
+ utils/port_utils.py,sha256=2df9-gtSKT4yaQZapTZxkczvDDMBKL7gY5RPOTzQOxo,1907
29
+ utils/time_utils.py,sha256=ruVPmc9pTIl_-HzG2uMaluoSJo5qRxExYRcJI84i02s,1126
30
+ aicodestat-0.0.1.dist-info/METADATA,sha256=WJVZMlSmM2VohaqB3IyUXoxplqwvZ4DH6ai1GdxHzDQ,3383
31
+ aicodestat-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
32
+ aicodestat-0.0.1.dist-info/entry_points.txt,sha256=TDZ9VLa7H2-5HXrc1HTEthOGdZL4B2HBt1cHfqzaJI4,149
33
+ aicodestat-0.0.1.dist-info/top_level.txt,sha256=N8Qc1-jbxHDZ9jirkiVXwUSWxjMNySZxMaqd5_QeCx0,90
34
+ aicodestat-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ code-stat = main:main
3
+ code-stat-cli = cli.main:main
4
+ code-stat-mcp = mcp_stdio_server:main
5
+ local-mcp-server = local_mcp_server:main
@@ -0,0 +1,10 @@
1
+ cli
2
+ compute
3
+ config
4
+ local_mcp_server
5
+ logging_config
6
+ main
7
+ mcp
8
+ service_manager
9
+ storage
10
+ utils
cli/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """交互层(CLI)模块"""
2
+
cli/exporter.py ADDED
@@ -0,0 +1,111 @@
1
+ """Export query results to CSV/JSON"""
2
+ import json
3
+ import csv
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List
7
+ from datetime import datetime
8
+ from utils.time_utils import format_datetime
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def export_to_json(data: Dict[str, Any], output_path: str) -> bool:
14
+ """
15
+ Export data as JSON format
16
+
17
+ Args:
18
+ data: Data to export
19
+ output_path: Output file path
20
+
21
+ Returns:
22
+ Whether successful
23
+ """
24
+ try:
25
+ output_file = Path(output_path)
26
+ output_file.parent.mkdir(parents=True, exist_ok=True)
27
+
28
+ export_data = {
29
+ "export_time": format_datetime(datetime.now()),
30
+ "data": data
31
+ }
32
+
33
+ with open(output_file, 'w', encoding='utf-8') as f:
34
+ json.dump(export_data, f, ensure_ascii=False, indent=2)
35
+
36
+ logger.info(f"Data exported to JSON: {output_path}")
37
+ return True
38
+ except Exception as e:
39
+ logger.error(f"Failed to export to JSON: {e}")
40
+ return False
41
+
42
+
43
+ def export_to_csv(metrics: Dict[str, Any], output_path: str) -> bool:
44
+ """
45
+ Export metrics data as CSV format
46
+
47
+ Args:
48
+ metrics: Metrics data
49
+ output_path: Output file path
50
+
51
+ Returns:
52
+ Whether successful
53
+ """
54
+ try:
55
+ output_file = Path(output_path)
56
+ output_file.parent.mkdir(parents=True, exist_ok=True)
57
+
58
+ with open(output_file, 'w', newline='', encoding='utf-8') as f:
59
+ writer = csv.writer(f)
60
+
61
+ # Write header row
62
+ writer.writerow(["Metric Name", "Value"])
63
+
64
+ # Write metrics data
65
+ writer.writerow(["AI Generated Lines", metrics.get("ai_total_lines", 0)])
66
+ writer.writerow(["Adopted Lines", metrics.get("adopted_lines", 0)])
67
+ writer.writerow(["Code Adoption Rate (%)", metrics.get("adoption_rate", 0.0)])
68
+ writer.writerow(["Code Generation Rate (%)", metrics.get("generation_rate", 0.0)])
69
+
70
+ if "file_count" in metrics:
71
+ writer.writerow(["Files Involved", metrics.get("file_count", 0)])
72
+
73
+ if "session_count" in metrics:
74
+ writer.writerow(["Sessions", metrics.get("session_count", 0)])
75
+
76
+ # If there are diff lines details, write them
77
+ diff_lines = metrics.get("diff_lines", [])
78
+ if diff_lines:
79
+ writer.writerow([]) # Empty row
80
+ writer.writerow(["Diff Lines Details"])
81
+ writer.writerow(["Diff Type", "Line Number", "Code Content"])
82
+ for diff_line in diff_lines:
83
+ writer.writerow([
84
+ diff_line.get("diff_type", ""),
85
+ diff_line.get("line_number", ""),
86
+ diff_line.get("line_content", "")
87
+ ])
88
+
89
+ logger.info(f"Data exported to CSV: {output_path}")
90
+ return True
91
+ except Exception as e:
92
+ logger.error(f"Failed to export to CSV: {e}")
93
+ return False
94
+
95
+
96
+ def export_metrics(metrics: Dict[str, Any], output_path: str, format: str = "json") -> bool:
97
+ """
98
+ Export metrics data
99
+
100
+ Args:
101
+ metrics: Metrics data
102
+ output_path: Output file path
103
+ format: Export format (json or csv)
104
+
105
+ Returns:
106
+ Whether successful
107
+ """
108
+ if format.lower() == "csv":
109
+ return export_to_csv(metrics, output_path)
110
+ else:
111
+ return export_to_json(metrics, output_path)
cli/main.py ADDED
@@ -0,0 +1,213 @@
1
+ """CLI entry point, parses command line arguments"""
2
+ import argparse
3
+ import sys
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ # Add project root to Python path to ensure modules can be imported
8
+ _project_root = Path(__file__).parent.parent
9
+ if str(_project_root) not in sys.path:
10
+ sys.path.insert(0, str(_project_root))
11
+
12
+ from config import get_cli_config
13
+ from logging_config import setup_logging
14
+ from storage.db import get_db
15
+ from cli.menus import (
16
+ show_main_menu,
17
+ query_by_session,
18
+ query_by_file,
19
+ query_by_project,
20
+ compare_agents,
21
+ manage_data,
22
+ manage_service,
23
+ show_global_dashboard,
24
+ )
25
+ from cli.views import console
26
+ from cli.exporter import export_metrics
27
+ from compute.metrics_service import calculate_session_metrics, calculate_file_metrics, calculate_project_metrics
28
+ import questionary
29
+
30
+ logger = setup_logging(log_level="INFO", module_name="cli")
31
+
32
+
33
+ def print_banner():
34
+ """Minimal banner (kept for future extension, currently no-op)."""
35
+ # Intentionally kept very light; main layout is handled in the menu.
36
+ console.print()
37
+
38
+
39
+ def main():
40
+ """Main function"""
41
+ parser = argparse.ArgumentParser(description="Local MCP AI Code Statistics Tool CLI")
42
+ parser.add_argument(
43
+ "--session",
44
+ type=str,
45
+ help="Query by session ID (quick command)"
46
+ )
47
+ parser.add_argument(
48
+ "--file",
49
+ type=str,
50
+ help="Query by file path (quick command)"
51
+ )
52
+ parser.add_argument(
53
+ "--project",
54
+ type=str,
55
+ help="Query by project root directory (quick command)"
56
+ )
57
+ parser.add_argument(
58
+ "--export",
59
+ type=str,
60
+ help="Export format (json/csv), must be used with --session/--file/--project"
61
+ )
62
+ parser.add_argument(
63
+ "--output",
64
+ type=str,
65
+ help="Export file path, must be used with --export"
66
+ )
67
+ parser.add_argument(
68
+ "--start-service",
69
+ action="store_true",
70
+ help="Start MCP service (background)"
71
+ )
72
+ parser.add_argument(
73
+ "--stop-service",
74
+ action="store_true",
75
+ help="Stop MCP service"
76
+ )
77
+ parser.add_argument(
78
+ "--service-status",
79
+ action="store_true",
80
+ help="Check MCP service status"
81
+ )
82
+
83
+ args = parser.parse_args()
84
+
85
+ # Initialize database
86
+ try:
87
+ db = get_db()
88
+ db.initialize()
89
+ except Exception as e:
90
+ console.print(f"[red]❌ Database initialization failed: {e}[/red]")
91
+ sys.exit(1)
92
+
93
+ # Handle service management commands
94
+ if args.start_service:
95
+ from service_manager import get_service_manager
96
+ manager = get_service_manager()
97
+ if manager.start(background=True):
98
+ console.print("[green]✅ MCP service started successfully[/green]")
99
+ else:
100
+ console.print("[red]❌ Failed to start MCP service[/red]")
101
+ return
102
+
103
+ if args.stop_service:
104
+ from service_manager import get_service_manager
105
+ manager = get_service_manager()
106
+ if manager.stop():
107
+ console.print("[green]✅ MCP service stopped[/green]")
108
+ else:
109
+ console.print("[yellow]⚠️ MCP service is not running or failed to stop[/yellow]")
110
+ return
111
+
112
+ if args.service_status:
113
+ from service_manager import get_service_manager
114
+ from cli.views import display_service_status
115
+ manager = get_service_manager()
116
+ status = manager.get_status()
117
+ display_service_status(status)
118
+ return
119
+
120
+ # Quick command mode
121
+ if args.session:
122
+ try:
123
+ metrics = calculate_session_metrics(args.session)
124
+ from cli.views import display_metrics_table, display_session_info
125
+ if metrics.get("summaries"):
126
+ display_session_info(metrics["summaries"])
127
+ display_metrics_table(metrics)
128
+
129
+ if args.export and args.output:
130
+ export_metrics(metrics, args.output, args.export)
131
+ console.print(f"[green]✅ Data exported to: {args.output}[/green]")
132
+ except Exception as e:
133
+ console.print(f"[red]❌ Query failed: {e}[/red]")
134
+ sys.exit(1)
135
+ return
136
+
137
+ if args.file:
138
+ try:
139
+ metrics = calculate_file_metrics(args.file)
140
+ from cli.views import display_metrics_table
141
+ display_metrics_table(metrics)
142
+
143
+ if args.export and args.output:
144
+ export_metrics(metrics, args.output, args.export)
145
+ console.print(f"[green]✅ Data exported to: {args.output}[/green]")
146
+ except Exception as e:
147
+ console.print(f"[red]❌ Query failed: {e}[/red]")
148
+ sys.exit(1)
149
+ return
150
+
151
+ if args.project:
152
+ try:
153
+ metrics = calculate_project_metrics(args.project)
154
+ from cli.views import display_metrics_table
155
+ display_metrics_table(metrics)
156
+
157
+ if args.export and args.output:
158
+ export_metrics(metrics, args.output, args.export)
159
+ console.print(f"[green]✅ Data exported to: {args.output}[/green]")
160
+ except Exception as e:
161
+ console.print(f"[red]❌ Query failed: {e}[/red]")
162
+ sys.exit(1)
163
+ return
164
+
165
+ # Interactive menu mode
166
+ print_banner()
167
+
168
+ while True:
169
+ try:
170
+ choice = show_main_menu()
171
+
172
+ if choice == "exit":
173
+ console.print("\n[green]👋 Exiting tool, data is safely stored locally~[/green]")
174
+ break
175
+ elif choice == "overview":
176
+ show_global_dashboard()
177
+ elif choice == "file":
178
+ query_by_file()
179
+ elif choice == "session":
180
+ query_by_session()
181
+ elif choice == "project":
182
+ query_by_project()
183
+ elif choice == "compare":
184
+ compare_agents()
185
+ elif choice == "export":
186
+ # Export functionality is integrated in query functions
187
+ console.print("[yellow]Please select export option when querying[/yellow]")
188
+ elif choice == "service":
189
+ manage_service()
190
+ elif choice == "manage":
191
+ manage_data()
192
+
193
+ # Ask if continue
194
+ if choice != "exit":
195
+ continue_choice = questionary.confirm("Continue querying?").ask()
196
+ if not continue_choice:
197
+ console.print("\n[green]👋 Exiting tool, data is safely stored locally~[/green]")
198
+ break
199
+ print() # Empty line separator
200
+
201
+ except KeyboardInterrupt:
202
+ console.print("\n[yellow]Operation cancelled[/yellow]")
203
+ break
204
+ except Exception as e:
205
+ console.print(f"[red]❌ Error occurred: {e}[/red]")
206
+ logger.error(f"CLI error: {e}", exc_info=True)
207
+ continue_choice = questionary.confirm("Continue?").ask()
208
+ if not continue_choice:
209
+ break
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()