git-weekly 0.2.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 woaichilajiaochaorou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-weekly
3
+ Version: 0.2.0
4
+ Summary: Generate weekly reports from Git commit history, with optional AI summary
5
+ Author: woaichilajiaochaorou
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/woaichilajiaochaorou/git-weekly
8
+ Project-URL: Repository, https://github.com/woaichilajiaochaorou/git-weekly
9
+ Project-URL: Issues, https://github.com/woaichilajiaochaorou/git-weekly/issues
10
+ Keywords: git,weekly,report,changelog,productivity,ai,llm
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Version Control :: Git
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: ai
25
+ Requires-Dist: openai>=1.0; extra == "ai"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: ruff>=0.4; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # git-weekly
32
+
33
+ 从 Git 提交记录自动生成周报,支持 AI 智能总结。告别"我这周干了啥"的灵魂拷问。
34
+
35
+ ## 功能
36
+
37
+ - 扫描指定时间范围内的所有 Git 提交
38
+ - 自动分析 diff,归类为:新功能、Bug 修复、重构、文档、测试等
39
+ - 生成结构化周报(Markdown / 终端输出)
40
+ - 支持多仓库聚合
41
+ - 代码统计(新增 / 删除 / 修改的文件数)
42
+ - **AI 智能总结**:接入 OpenAI 兼容 API,自动生成连贯的工作总结
43
+
44
+ ## 安装
45
+
46
+ ```bash
47
+ pip install git-weekly
48
+ ```
49
+
50
+ 如需 AI 总结功能:
51
+
52
+ ```bash
53
+ pip install "git-weekly[ai]"
54
+ ```
55
+
56
+ 或从源码安装:
57
+
58
+ ```bash
59
+ git clone https://github.com/woaichilajiaochaorou/git-weekly.git
60
+ cd git-weekly
61
+ pip install -e ".[ai]"
62
+ ```
63
+
64
+ ## 使用
65
+
66
+ ```bash
67
+ # 生成本周周报(默认当前仓库)
68
+ git-weekly
69
+
70
+ # 指定时间范围
71
+ git-weekly --since "2025-02-24" --until "2025-02-28"
72
+
73
+ # 指定仓库路径
74
+ git-weekly --repo /path/to/your/project
75
+
76
+ # 多仓库聚合
77
+ git-weekly --repo ./project-a --repo ./project-b
78
+
79
+ # 输出为 Markdown 文件
80
+ git-weekly -o weekly-report.md
81
+
82
+ # 指定作者(默认为 git config 中的用户)
83
+ git-weekly --author "your-name"
84
+
85
+ # AI 智能总结(需要安装 git-weekly[ai])
86
+ git-weekly --ai
87
+ git-weekly --ai --api-key sk-xxx
88
+ git-weekly --ai --base-url https://api.deepseek.com/v1 --model deepseek-chat
89
+ ```
90
+
91
+ ## 输出示例
92
+
93
+ ```
94
+ ──────────────────────────────────────────────────
95
+ 📋 周报 (02/24 - 02/28)
96
+ ──────────────────────────────────────────────────
97
+
98
+ 本周工作
99
+
100
+ 🚀 新功能
101
+ • 用户登录模块:实现 JWT 鉴权
102
+ • 添加文件上传接口,支持图片和 PDF
103
+
104
+ 🐛 Bug 修复
105
+ • 修复分页查询在最后一页返回空数据的问题
106
+ • 修复并发请求下 session 丢失
107
+
108
+ ♻️ 重构
109
+ • 将数据库操作抽取为 Repository 层
110
+
111
+ 📝 文档
112
+ • 更新 API 接口文档
113
+
114
+ 代码统计
115
+
116
+ 提交次数 12 次
117
+ 文件变更 23 个文件
118
+ 新增行数 +847
119
+ 删除行数 -312
120
+
121
+ 🤖 AI 总结
122
+
123
+ 本周主要完成了用户登录模块的 JWT 鉴权实现和文件上传接口开发,
124
+ 同时修复了分页查询和并发 session 相关的两个稳定性问题。
125
+ 技术层面对数据库操作进行了 Repository 层抽取,提升了代码可维护性。
126
+ ```
127
+
128
+ ## AI 配置
129
+
130
+ API Key 支持三种配置方式(优先级从高到低):
131
+
132
+ **1. 命令行参数**
133
+
134
+ ```bash
135
+ git-weekly --ai --api-key sk-xxx
136
+ ```
137
+
138
+ **2. 环境变量**
139
+
140
+ ```bash
141
+ export GIT_WEEKLY_API_KEY=sk-xxx
142
+ export GIT_WEEKLY_BASE_URL=https://api.deepseek.com/v1 # 可选
143
+ export GIT_WEEKLY_MODEL=deepseek-chat # 可选
144
+ ```
145
+
146
+ **3. 配置文件** `~/.config/git-weekly/config.toml`
147
+
148
+ ```toml
149
+ [ai]
150
+ api_key = "sk-xxx"
151
+ base_url = "https://api.openai.com/v1"
152
+ model = "gpt-4o-mini"
153
+ ```
154
+
155
+ ### 支持的 LLM 服务
156
+
157
+ 任何兼容 OpenAI API 的服务都可以使用,例如:
158
+
159
+ | 服务 | base_url | 推荐模型 |
160
+ |------|----------|----------|
161
+ | OpenAI | `https://api.openai.com/v1` (默认) | `gpt-4o-mini` |
162
+ | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
163
+ | 通义千问 | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` |
164
+ | Ollama (本地) | `http://localhost:11434/v1` | `llama3` |
165
+
166
+ ## 开发
167
+
168
+ ```bash
169
+ # 安装开发依赖
170
+ pip install -e ".[dev]"
171
+
172
+ # 运行测试
173
+ pytest
174
+
175
+ # 代码检查
176
+ ruff check .
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT
@@ -0,0 +1,151 @@
1
+ # git-weekly
2
+
3
+ 从 Git 提交记录自动生成周报,支持 AI 智能总结。告别"我这周干了啥"的灵魂拷问。
4
+
5
+ ## 功能
6
+
7
+ - 扫描指定时间范围内的所有 Git 提交
8
+ - 自动分析 diff,归类为:新功能、Bug 修复、重构、文档、测试等
9
+ - 生成结构化周报(Markdown / 终端输出)
10
+ - 支持多仓库聚合
11
+ - 代码统计(新增 / 删除 / 修改的文件数)
12
+ - **AI 智能总结**:接入 OpenAI 兼容 API,自动生成连贯的工作总结
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install git-weekly
18
+ ```
19
+
20
+ 如需 AI 总结功能:
21
+
22
+ ```bash
23
+ pip install "git-weekly[ai]"
24
+ ```
25
+
26
+ 或从源码安装:
27
+
28
+ ```bash
29
+ git clone https://github.com/woaichilajiaochaorou/git-weekly.git
30
+ cd git-weekly
31
+ pip install -e ".[ai]"
32
+ ```
33
+
34
+ ## 使用
35
+
36
+ ```bash
37
+ # 生成本周周报(默认当前仓库)
38
+ git-weekly
39
+
40
+ # 指定时间范围
41
+ git-weekly --since "2025-02-24" --until "2025-02-28"
42
+
43
+ # 指定仓库路径
44
+ git-weekly --repo /path/to/your/project
45
+
46
+ # 多仓库聚合
47
+ git-weekly --repo ./project-a --repo ./project-b
48
+
49
+ # 输出为 Markdown 文件
50
+ git-weekly -o weekly-report.md
51
+
52
+ # 指定作者(默认为 git config 中的用户)
53
+ git-weekly --author "your-name"
54
+
55
+ # AI 智能总结(需要安装 git-weekly[ai])
56
+ git-weekly --ai
57
+ git-weekly --ai --api-key sk-xxx
58
+ git-weekly --ai --base-url https://api.deepseek.com/v1 --model deepseek-chat
59
+ ```
60
+
61
+ ## 输出示例
62
+
63
+ ```
64
+ ──────────────────────────────────────────────────
65
+ 📋 周报 (02/24 - 02/28)
66
+ ──────────────────────────────────────────────────
67
+
68
+ 本周工作
69
+
70
+ 🚀 新功能
71
+ • 用户登录模块:实现 JWT 鉴权
72
+ • 添加文件上传接口,支持图片和 PDF
73
+
74
+ 🐛 Bug 修复
75
+ • 修复分页查询在最后一页返回空数据的问题
76
+ • 修复并发请求下 session 丢失
77
+
78
+ ♻️ 重构
79
+ • 将数据库操作抽取为 Repository 层
80
+
81
+ 📝 文档
82
+ • 更新 API 接口文档
83
+
84
+ 代码统计
85
+
86
+ 提交次数 12 次
87
+ 文件变更 23 个文件
88
+ 新增行数 +847
89
+ 删除行数 -312
90
+
91
+ 🤖 AI 总结
92
+
93
+ 本周主要完成了用户登录模块的 JWT 鉴权实现和文件上传接口开发,
94
+ 同时修复了分页查询和并发 session 相关的两个稳定性问题。
95
+ 技术层面对数据库操作进行了 Repository 层抽取,提升了代码可维护性。
96
+ ```
97
+
98
+ ## AI 配置
99
+
100
+ API Key 支持三种配置方式(优先级从高到低):
101
+
102
+ **1. 命令行参数**
103
+
104
+ ```bash
105
+ git-weekly --ai --api-key sk-xxx
106
+ ```
107
+
108
+ **2. 环境变量**
109
+
110
+ ```bash
111
+ export GIT_WEEKLY_API_KEY=sk-xxx
112
+ export GIT_WEEKLY_BASE_URL=https://api.deepseek.com/v1 # 可选
113
+ export GIT_WEEKLY_MODEL=deepseek-chat # 可选
114
+ ```
115
+
116
+ **3. 配置文件** `~/.config/git-weekly/config.toml`
117
+
118
+ ```toml
119
+ [ai]
120
+ api_key = "sk-xxx"
121
+ base_url = "https://api.openai.com/v1"
122
+ model = "gpt-4o-mini"
123
+ ```
124
+
125
+ ### 支持的 LLM 服务
126
+
127
+ 任何兼容 OpenAI API 的服务都可以使用,例如:
128
+
129
+ | 服务 | base_url | 推荐模型 |
130
+ |------|----------|----------|
131
+ | OpenAI | `https://api.openai.com/v1` (默认) | `gpt-4o-mini` |
132
+ | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
133
+ | 通义千问 | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` |
134
+ | Ollama (本地) | `http://localhost:11434/v1` | `llama3` |
135
+
136
+ ## 开发
137
+
138
+ ```bash
139
+ # 安装开发依赖
140
+ pip install -e ".[dev]"
141
+
142
+ # 运行测试
143
+ pytest
144
+
145
+ # 代码检查
146
+ ruff check .
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
@@ -0,0 +1,3 @@
1
+ """git-weekly: Generate weekly reports from Git commit history."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,4 @@
1
+ """Allow running as: python -m git_weekly"""
2
+ from .cli import main
3
+
4
+ main()
@@ -0,0 +1,218 @@
1
+ """Git log parsing and diff analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import subprocess
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timedelta
9
+ from pathlib import Path
10
+
11
+ COMMIT_SEPARATOR = "\x00"
12
+ COMMIT_FORMAT = "%H%x00%an%x00%aI%x00%s"
13
+
14
+ CATEGORY_PATTERNS: dict[str, dict[str, object]] = {
15
+ "feat": {
16
+ "keywords": ["add", "feat", "feature", "new", "implement", "support", "create"],
17
+ "label": "\U0001f680 新功能",
18
+ },
19
+ "fix": {
20
+ "keywords": ["fix", "bug", "patch", "resolve", "close", "repair", "correct"],
21
+ "label": "\U0001f41b Bug 修复",
22
+ },
23
+ "refactor": {
24
+ "keywords": ["refactor", "restructure", "reorganize", "clean", "simplify", "extract",
25
+ "move", "rename", "optimize"],
26
+ "label": "\u267b\ufe0f 重构",
27
+ },
28
+ "docs": {
29
+ "keywords": ["doc", "readme", "comment", "changelog", "license"],
30
+ "label": "\U0001f4dd 文档",
31
+ },
32
+ "test": {
33
+ "keywords": ["test", "spec", "coverage", "mock", "assert"],
34
+ "label": "\U0001f9ea 测试",
35
+ },
36
+ "chore": {
37
+ "keywords": ["chore", "ci", "cd", "build", "deploy", "config", "deps", "bump",
38
+ "upgrade", "update dep", "docker", "makefile", "lint"],
39
+ "label": "\U0001f527 工程化",
40
+ },
41
+ "style": {
42
+ "keywords": ["style", "format", "indent", "whitespace", "prettier", "eslint"],
43
+ "label": "\U0001f3a8 代码风格",
44
+ },
45
+ "other": {
46
+ "keywords": [],
47
+ "label": "\U0001f4e6 其他",
48
+ },
49
+ }
50
+
51
+ CATEGORY_ORDER: list[str] = ["feat", "fix", "refactor", "docs", "test", "chore", "style", "other"]
52
+
53
+
54
+ @dataclass
55
+ class CommitInfo:
56
+ hash: str
57
+ author: str
58
+ date: datetime
59
+ message: str
60
+ files_changed: int = 0
61
+ insertions: int = 0
62
+ deletions: int = 0
63
+ files: list[str] = field(default_factory=list)
64
+
65
+
66
+ @dataclass
67
+ class RepoStats:
68
+ repo_path: str
69
+ repo_name: str
70
+ commits: list[CommitInfo] = field(default_factory=list)
71
+ total_files_changed: int = 0
72
+ total_insertions: int = 0
73
+ total_deletions: int = 0
74
+
75
+ @property
76
+ def total_commits(self) -> int:
77
+ return len(self.commits)
78
+
79
+
80
+ def _run_git(args: list[str], cwd: str) -> str:
81
+ result = subprocess.run(
82
+ ["git"] + args,
83
+ cwd=cwd,
84
+ capture_output=True,
85
+ text=True,
86
+ )
87
+ if result.returncode != 0:
88
+ raise RuntimeError(f"git command failed: git {' '.join(args)}\n{result.stderr.strip()}")
89
+ return result.stdout.strip()
90
+
91
+
92
+ def get_git_user(repo_path: str) -> str:
93
+ try:
94
+ return _run_git(["config", "user.name"], cwd=repo_path)
95
+ except RuntimeError:
96
+ return ""
97
+
98
+
99
+ def get_repo_name(repo_path: str) -> str:
100
+ path = Path(repo_path).resolve()
101
+ try:
102
+ remote = _run_git(["remote", "get-url", "origin"], cwd=repo_path)
103
+ name = remote.rstrip("/").split("/")[-1]
104
+ return name.removesuffix(".git")
105
+ except RuntimeError:
106
+ return path.name
107
+
108
+
109
+ def parse_commits(
110
+ repo_path: str,
111
+ since: str,
112
+ until: str,
113
+ author: str | None = None,
114
+ ) -> RepoStats:
115
+ """Parse git log and return structured commit data."""
116
+ repo_path = str(Path(repo_path).resolve())
117
+ repo_name = get_repo_name(repo_path)
118
+
119
+ git_args = [
120
+ "log",
121
+ f"--since={since}",
122
+ f"--until={until}",
123
+ f"--format={COMMIT_FORMAT}",
124
+ "--numstat",
125
+ ]
126
+ if author:
127
+ git_args.append(f"--author={author}")
128
+
129
+ raw = _run_git(git_args, cwd=repo_path)
130
+ if not raw:
131
+ return RepoStats(repo_path=repo_path, repo_name=repo_name)
132
+
133
+ stats = RepoStats(repo_path=repo_path, repo_name=repo_name)
134
+ commits: list[CommitInfo] = []
135
+ current_commit: CommitInfo | None = None
136
+
137
+ for line in raw.split("\n"):
138
+ if not line:
139
+ continue
140
+
141
+ if COMMIT_SEPARATOR in line:
142
+ parts = line.split(COMMIT_SEPARATOR, 3)
143
+ if len(parts) == 4 and len(parts[0]) == 40:
144
+ if current_commit:
145
+ commits.append(current_commit)
146
+ current_commit = CommitInfo(
147
+ hash=parts[0],
148
+ author=parts[1],
149
+ date=datetime.fromisoformat(parts[2]),
150
+ message=parts[3],
151
+ )
152
+ continue
153
+
154
+ if current_commit and "\t" in line:
155
+ parts = line.split("\t")
156
+ if len(parts) == 3:
157
+ added, deleted, filepath = parts
158
+ try:
159
+ ins = int(added) if added != "-" else 0
160
+ dels = int(deleted) if deleted != "-" else 0
161
+ current_commit.insertions += ins
162
+ current_commit.deletions += dels
163
+ current_commit.files_changed += 1
164
+ current_commit.files.append(filepath)
165
+ except ValueError:
166
+ pass
167
+
168
+ if current_commit:
169
+ commits.append(current_commit)
170
+
171
+ stats.commits = commits
172
+ stats.total_files_changed = len({f for c in commits for f in c.files})
173
+ stats.total_insertions = sum(c.insertions for c in commits)
174
+ stats.total_deletions = sum(c.deletions for c in commits)
175
+
176
+ return stats
177
+
178
+
179
+ def categorize_commit(commit: CommitInfo) -> str:
180
+ """Categorize a commit based on its message using keyword matching."""
181
+ msg = commit.message.lower()
182
+
183
+ prefix_match = re.match(r"^(\w+)[\(:]", msg)
184
+ if prefix_match:
185
+ prefix = prefix_match.group(1)
186
+ for cat_key in CATEGORY_PATTERNS:
187
+ if cat_key == "other":
188
+ continue
189
+ if prefix == cat_key or prefix in CATEGORY_PATTERNS[cat_key]["keywords"]:
190
+ return cat_key
191
+
192
+ extensions = {Path(f).suffix for f in commit.files}
193
+ if extensions & {".md", ".rst", ".txt"} and not (extensions - {".md", ".rst", ".txt"}):
194
+ return "docs"
195
+ if commit.files and all("test" in f.lower() or "spec" in f.lower() for f in commit.files):
196
+ return "test"
197
+
198
+ for cat_key, cat_info in CATEGORY_PATTERNS.items():
199
+ if cat_key == "other":
200
+ continue
201
+ for keyword in cat_info["keywords"]:
202
+ if keyword in msg:
203
+ return cat_key
204
+
205
+ return "other"
206
+
207
+
208
+ def get_default_since() -> str:
209
+ """Return the Monday of the current week as YYYY-MM-DD."""
210
+ today = datetime.now()
211
+ monday = today - timedelta(days=today.weekday())
212
+ return monday.strftime("%Y-%m-%d")
213
+
214
+
215
+ def get_default_until() -> str:
216
+ """Return tomorrow as YYYY-MM-DD to include today's commits."""
217
+ tomorrow = datetime.now() + timedelta(days=1)
218
+ return tomorrow.strftime("%Y-%m-%d")
@@ -0,0 +1,129 @@
1
+ """Command-line interface for git-weekly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from .analyzer import get_default_since, get_default_until, get_git_user, parse_commits
10
+ from .report import build_report, render_markdown, render_terminal
11
+
12
+
13
+ def main():
14
+ """Generate weekly report from Git commit history."""
15
+ parser = argparse.ArgumentParser(
16
+ description="Generate weekly report from Git commit history.",
17
+ formatter_class=argparse.RawDescriptionHelpFormatter,
18
+ epilog="""\
19
+ examples:
20
+ git-weekly # 当前仓库本周周报
21
+ git-weekly --since 2025-02-24 --until 2025-02-28
22
+ git-weekly --repo /path/to/project
23
+ git-weekly --repo ./proj-a --repo ./proj-b # 多仓库聚合
24
+ git-weekly -o report.md # 输出 Markdown 文件
25
+ git-weekly --all-authors # 包含所有人的提交
26
+ git-weekly --ai # AI 智能总结
27
+ git-weekly --ai --base-url https://api.deepseek.com/v1 # 用 DeepSeek
28
+ """,
29
+ )
30
+ parser.add_argument(
31
+ "--repo", "-r", action="append", default=None,
32
+ help="Git repository path (can specify multiple times, default: current directory)",
33
+ )
34
+ parser.add_argument(
35
+ "--since", "-s", default=None,
36
+ help="Start date YYYY-MM-DD (default: Monday of current week)",
37
+ )
38
+ parser.add_argument(
39
+ "--until", "-u", default=None,
40
+ help="End date YYYY-MM-DD (default: today)",
41
+ )
42
+ parser.add_argument(
43
+ "--author", "-a", default=None,
44
+ help="Filter by author name (default: git config user.name)",
45
+ )
46
+ parser.add_argument(
47
+ "--output", "-o", default=None,
48
+ help="Output to Markdown file instead of terminal",
49
+ )
50
+ parser.add_argument(
51
+ "--all-authors", action="store_true", default=False,
52
+ help="Include commits from all authors",
53
+ )
54
+ parser.add_argument(
55
+ "--ai", action="store_true", default=False,
56
+ help="Enable AI-powered summary (requires: pip install git-weekly[ai])",
57
+ )
58
+ parser.add_argument(
59
+ "--api-key", default=None,
60
+ help="LLM API key (or set GIT_WEEKLY_API_KEY env var)",
61
+ )
62
+ parser.add_argument(
63
+ "--base-url", default=None,
64
+ help="LLM API base URL (default: OpenAI; set for DeepSeek/Ollama/etc.)",
65
+ )
66
+ parser.add_argument(
67
+ "--model", default=None,
68
+ help="LLM model name (default: gpt-4o-mini)",
69
+ )
70
+
71
+ args = parser.parse_args()
72
+
73
+ repos = args.repo or ["."]
74
+ since = args.since or get_default_since()
75
+ until = args.until or get_default_until()
76
+ author = args.author
77
+
78
+ if not author and not args.all_authors:
79
+ first_repo = str(Path(repos[0]).resolve())
80
+ author = get_git_user(first_repo)
81
+ if not author:
82
+ print("Warning: could not detect git user, showing all authors", file=sys.stderr)
83
+ author = None
84
+
85
+ reports = []
86
+ for repo_path in repos:
87
+ resolved = str(Path(repo_path).resolve())
88
+ if not Path(resolved, ".git").exists():
89
+ print(f"Error: {repo_path} is not a git repository", file=sys.stderr)
90
+ sys.exit(1)
91
+
92
+ try:
93
+ stats = parse_commits(resolved, since, until, author if not args.all_authors else None)
94
+ report = build_report(stats, since, until)
95
+ reports.append(report)
96
+ except RuntimeError as e:
97
+ print(f"Error: {e}", file=sys.stderr)
98
+ sys.exit(1)
99
+
100
+ if not reports:
101
+ print("No repositories to analyze.", file=sys.stderr)
102
+ sys.exit(0)
103
+
104
+ if args.ai:
105
+ try:
106
+ from .llm import generate_summary, load_config
107
+
108
+ llm_cfg = load_config(
109
+ api_key=args.api_key,
110
+ base_url=args.base_url,
111
+ model=args.model,
112
+ )
113
+ print("Generating AI summary...", file=sys.stderr)
114
+ summary = generate_summary(reports, llm_cfg)
115
+ for report in reports:
116
+ report.ai_summary = summary
117
+ except RuntimeError as e:
118
+ print(f"AI summary failed: {e}", file=sys.stderr)
119
+
120
+ if args.output:
121
+ md = render_markdown(reports)
122
+ Path(args.output).write_text(md, encoding="utf-8")
123
+ print(f"Report saved to {args.output}")
124
+ else:
125
+ render_terminal(reports)
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()
@@ -0,0 +1,153 @@
1
+ """LLM-powered weekly report summarization (OpenAI-compatible API)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from .report import CategorizedReport
12
+
13
+ try:
14
+ import tomllib # Python 3.11+
15
+ except ModuleNotFoundError:
16
+ try:
17
+ import tomli as tomllib # type: ignore[no-redef]
18
+ except ModuleNotFoundError:
19
+ tomllib = None # type: ignore[assignment]
20
+
21
+ CONFIG_DIR = Path.home() / ".config" / "git-weekly"
22
+ CONFIG_FILE = CONFIG_DIR / "config.toml"
23
+
24
+ DEFAULT_BASE_URL = "https://api.openai.com/v1"
25
+ DEFAULT_MODEL = "gpt-4o-mini"
26
+
27
+ SYSTEM_PROMPT = """\
28
+ 你是一位专业的技术周报撰写助手。根据提供的 Git commit 数据,写一段 3-5 句话的工作总结。
29
+
30
+ 要求:
31
+ - 用中文输出
32
+ - 像开发者写给团队看的周报,语气专业但不生硬
33
+ - 突出本周重点工作和技术亮点,不要逐条罗列 commit
34
+ - 如果有 bug fix,简要提及稳定性改进
35
+ - 如果有重构,说明技术改进的意义
36
+ - 不要编造不存在的内容,只基于提供的数据总结
37
+ """
38
+
39
+
40
+ @dataclass
41
+ class LLMConfig:
42
+ api_key: str
43
+ base_url: str = DEFAULT_BASE_URL
44
+ model: str = DEFAULT_MODEL
45
+
46
+
47
+ def _load_config_file() -> dict[str, str]:
48
+ """Load config from ~/.config/git-weekly/config.toml if it exists."""
49
+ if tomllib is None or not CONFIG_FILE.exists():
50
+ return {}
51
+ try:
52
+ with open(CONFIG_FILE, "rb") as f:
53
+ data = tomllib.load(f)
54
+ return data.get("ai", {})
55
+ except Exception:
56
+ return {}
57
+
58
+
59
+ def load_config(
60
+ *,
61
+ api_key: str | None = None,
62
+ base_url: str | None = None,
63
+ model: str | None = None,
64
+ ) -> LLMConfig:
65
+ """Build LLM config from CLI args > env vars > config file."""
66
+ file_cfg = _load_config_file()
67
+
68
+ resolved_key = (
69
+ api_key
70
+ or os.environ.get("GIT_WEEKLY_API_KEY")
71
+ or file_cfg.get("api_key", "")
72
+ )
73
+ if not resolved_key:
74
+ raise RuntimeError(
75
+ "未找到 API Key。请通过以下任一方式配置:\n"
76
+ " 1. --api-key 参数\n"
77
+ " 2. 环境变量 GIT_WEEKLY_API_KEY\n"
78
+ f" 3. 配置文件 {CONFIG_FILE}\n\n"
79
+ "配置文件示例:\n"
80
+ " [ai]\n"
81
+ ' api_key = "sk-xxx"\n'
82
+ ' base_url = "https://api.openai.com/v1"\n'
83
+ ' model = "gpt-4o-mini"'
84
+ )
85
+
86
+ resolved_url = (
87
+ base_url
88
+ or os.environ.get("GIT_WEEKLY_BASE_URL")
89
+ or file_cfg.get("base_url", "")
90
+ or DEFAULT_BASE_URL
91
+ )
92
+ resolved_model = (
93
+ model
94
+ or os.environ.get("GIT_WEEKLY_MODEL")
95
+ or file_cfg.get("model", "")
96
+ or DEFAULT_MODEL
97
+ )
98
+
99
+ return LLMConfig(api_key=resolved_key, base_url=resolved_url, model=resolved_model)
100
+
101
+
102
+ def _build_prompt(reports: list[CategorizedReport]) -> str:
103
+ """Serialize reports into structured text for the LLM."""
104
+ from .analyzer import CATEGORY_PATTERNS
105
+
106
+ parts: list[str] = []
107
+ for report in reports:
108
+ parts.append(f"仓库: {report.stats.repo_name}")
109
+ parts.append(f"时间范围: {report.since} ~ {report.until}")
110
+ parts.append(f"提交次数: {report.stats.total_commits}")
111
+ parts.append(f"文件变更: {report.stats.total_files_changed} 个文件")
112
+ parts.append(f"新增: +{report.stats.total_insertions} 行, 删除: -{report.stats.total_deletions} 行")
113
+ parts.append("")
114
+
115
+ for cat_key, commits in report.categories.items():
116
+ label = CATEGORY_PATTERNS.get(cat_key, {}).get("label", cat_key)
117
+ parts.append(f"【{label}】")
118
+ for c in commits:
119
+ file_hint = ""
120
+ if c.files:
121
+ top_files = c.files[:3]
122
+ file_hint = f" ({', '.join(top_files)})"
123
+ parts.append(f" - {c.message}{file_hint}")
124
+ parts.append("")
125
+
126
+ return "\n".join(parts)
127
+
128
+
129
+ def generate_summary(reports: list[CategorizedReport], config: LLMConfig) -> str:
130
+ """Call OpenAI-compatible API to generate a narrative summary."""
131
+ try:
132
+ from openai import OpenAI
133
+ except ImportError:
134
+ raise RuntimeError(
135
+ "AI 功能需要安装 openai 库。请运行:\n"
136
+ ' pip install "git-weekly[ai]"'
137
+ )
138
+
139
+ client = OpenAI(api_key=config.api_key, base_url=config.base_url)
140
+
141
+ user_content = _build_prompt(reports)
142
+
143
+ response = client.chat.completions.create(
144
+ model=config.model,
145
+ messages=[
146
+ {"role": "system", "content": SYSTEM_PROMPT},
147
+ {"role": "user", "content": user_content},
148
+ ],
149
+ temperature=0.3,
150
+ max_tokens=1024,
151
+ )
152
+
153
+ return response.choices[0].message.content or ""
@@ -0,0 +1,147 @@
1
+ """Report generation and formatting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import defaultdict
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+
9
+ from .analyzer import CATEGORY_ORDER, CATEGORY_PATTERNS, CommitInfo, RepoStats, categorize_commit
10
+
11
+
12
+ @dataclass
13
+ class CategorizedReport:
14
+ """Commits grouped by category with summary info."""
15
+ categories: dict[str, list[CommitInfo]]
16
+ stats: RepoStats
17
+ since: str
18
+ until: str
19
+ ai_summary: str | None = None
20
+
21
+
22
+ def build_report(stats: RepoStats, since: str, until: str) -> CategorizedReport:
23
+ """Categorize commits and build a structured report."""
24
+ categories: dict[str, list[CommitInfo]] = defaultdict(list)
25
+ for commit in stats.commits:
26
+ cat = categorize_commit(commit)
27
+ categories[cat].append(commit)
28
+ return CategorizedReport(
29
+ categories=dict(categories),
30
+ stats=stats,
31
+ since=since,
32
+ until=until,
33
+ )
34
+
35
+
36
+ def _format_date_range(since: str, until: str) -> str:
37
+ try:
38
+ s = datetime.strptime(since, "%Y-%m-%d")
39
+ u = datetime.strptime(until, "%Y-%m-%d")
40
+ return f"{s.month:02d}/{s.day:02d} - {u.month:02d}/{u.day:02d}"
41
+ except ValueError:
42
+ return f"{since} ~ {until}"
43
+
44
+
45
+ def render_terminal(reports: list[CategorizedReport]) -> None:
46
+ """Render reports to terminal with ANSI colors."""
47
+ BOLD = "\033[1m"
48
+ CYAN = "\033[36m"
49
+ GREEN = "\033[32m"
50
+ RED = "\033[31m"
51
+ DIM = "\033[2m"
52
+ RESET = "\033[0m"
53
+
54
+ for report in reports:
55
+ date_range = _format_date_range(report.since, report.until)
56
+ title = f" \U0001f4cb 周报 ({date_range})"
57
+ if len(reports) > 1:
58
+ title += f" — {report.stats.repo_name}"
59
+
60
+ width = 50
61
+ print()
62
+ print(f"{CYAN}{'─' * width}{RESET}")
63
+ print(f"{BOLD}{CYAN}{title}{RESET}")
64
+ print(f"{CYAN}{'─' * width}{RESET}")
65
+
66
+ if not report.stats.commits:
67
+ print(f"\n {DIM}这段时间没有提交记录{RESET}\n")
68
+ continue
69
+
70
+ print(f"\n{BOLD}本周工作{RESET}\n")
71
+
72
+ for cat_key in CATEGORY_ORDER:
73
+ if cat_key not in report.categories:
74
+ continue
75
+ commits = report.categories[cat_key]
76
+ label = CATEGORY_PATTERNS[cat_key]["label"]
77
+ print(f" {BOLD}{label}{RESET}")
78
+ for commit in commits:
79
+ msg = commit.message
80
+ if len(msg) > 72:
81
+ msg = msg[:72] + "..."
82
+ print(f" • {msg}")
83
+ print()
84
+
85
+ print(f"{BOLD}代码统计{RESET}\n")
86
+ print(f" 提交次数 {BOLD}{report.stats.total_commits}{RESET} 次")
87
+ print(f" 文件变更 {BOLD}{report.stats.total_files_changed}{RESET} 个文件")
88
+ print(f" 新增行数 {GREEN}+{report.stats.total_insertions}{RESET}")
89
+ print(f" 删除行数 {RED}-{report.stats.total_deletions}{RESET}")
90
+ print()
91
+
92
+ if report.ai_summary:
93
+ print(f"{BOLD}\U0001f916 AI \u603b\u7ed3{RESET}\n")
94
+ for line in report.ai_summary.strip().splitlines():
95
+ print(f" {line}")
96
+ print()
97
+
98
+
99
+ def render_markdown(reports: list[CategorizedReport]) -> str:
100
+ """Render reports as Markdown string."""
101
+ lines: list[str] = []
102
+
103
+ for report in reports:
104
+ date_range = _format_date_range(report.since, report.until)
105
+ heading = f"周报 ({date_range})"
106
+ if len(reports) > 1:
107
+ heading += f" — {report.stats.repo_name}"
108
+
109
+ lines.append(f"# {heading}")
110
+ lines.append("")
111
+
112
+ if not report.stats.commits:
113
+ lines.append("_这段时间没有提交记录_")
114
+ lines.append("")
115
+ continue
116
+
117
+ lines.append("## 本周工作")
118
+ lines.append("")
119
+
120
+ for cat_key in CATEGORY_ORDER:
121
+ if cat_key not in report.categories:
122
+ continue
123
+ commits = report.categories[cat_key]
124
+ label = CATEGORY_PATTERNS[cat_key]["label"]
125
+ lines.append(f"### {label}")
126
+ lines.append("")
127
+ for commit in commits:
128
+ lines.append(f"- {commit.message}")
129
+ lines.append("")
130
+
131
+ lines.append("## 代码统计")
132
+ lines.append("")
133
+ lines.append("| 指标 | 数据 |")
134
+ lines.append("|------|------|")
135
+ lines.append(f"| 提交次数 | {report.stats.total_commits} 次 |")
136
+ lines.append(f"| 文件变更 | {report.stats.total_files_changed} 个文件 |")
137
+ lines.append(f"| 新增行数 | +{report.stats.total_insertions} |")
138
+ lines.append(f"| 删除行数 | -{report.stats.total_deletions} |")
139
+ lines.append("")
140
+
141
+ if report.ai_summary:
142
+ lines.append("## \U0001f916 AI \u603b\u7ed3")
143
+ lines.append("")
144
+ lines.append(report.ai_summary.strip())
145
+ lines.append("")
146
+
147
+ return "\n".join(lines)
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-weekly
3
+ Version: 0.2.0
4
+ Summary: Generate weekly reports from Git commit history, with optional AI summary
5
+ Author: woaichilajiaochaorou
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/woaichilajiaochaorou/git-weekly
8
+ Project-URL: Repository, https://github.com/woaichilajiaochaorou/git-weekly
9
+ Project-URL: Issues, https://github.com/woaichilajiaochaorou/git-weekly/issues
10
+ Keywords: git,weekly,report,changelog,productivity,ai,llm
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Version Control :: Git
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: ai
25
+ Requires-Dist: openai>=1.0; extra == "ai"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: ruff>=0.4; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # git-weekly
32
+
33
+ 从 Git 提交记录自动生成周报,支持 AI 智能总结。告别"我这周干了啥"的灵魂拷问。
34
+
35
+ ## 功能
36
+
37
+ - 扫描指定时间范围内的所有 Git 提交
38
+ - 自动分析 diff,归类为:新功能、Bug 修复、重构、文档、测试等
39
+ - 生成结构化周报(Markdown / 终端输出)
40
+ - 支持多仓库聚合
41
+ - 代码统计(新增 / 删除 / 修改的文件数)
42
+ - **AI 智能总结**:接入 OpenAI 兼容 API,自动生成连贯的工作总结
43
+
44
+ ## 安装
45
+
46
+ ```bash
47
+ pip install git-weekly
48
+ ```
49
+
50
+ 如需 AI 总结功能:
51
+
52
+ ```bash
53
+ pip install "git-weekly[ai]"
54
+ ```
55
+
56
+ 或从源码安装:
57
+
58
+ ```bash
59
+ git clone https://github.com/woaichilajiaochaorou/git-weekly.git
60
+ cd git-weekly
61
+ pip install -e ".[ai]"
62
+ ```
63
+
64
+ ## 使用
65
+
66
+ ```bash
67
+ # 生成本周周报(默认当前仓库)
68
+ git-weekly
69
+
70
+ # 指定时间范围
71
+ git-weekly --since "2025-02-24" --until "2025-02-28"
72
+
73
+ # 指定仓库路径
74
+ git-weekly --repo /path/to/your/project
75
+
76
+ # 多仓库聚合
77
+ git-weekly --repo ./project-a --repo ./project-b
78
+
79
+ # 输出为 Markdown 文件
80
+ git-weekly -o weekly-report.md
81
+
82
+ # 指定作者(默认为 git config 中的用户)
83
+ git-weekly --author "your-name"
84
+
85
+ # AI 智能总结(需要安装 git-weekly[ai])
86
+ git-weekly --ai
87
+ git-weekly --ai --api-key sk-xxx
88
+ git-weekly --ai --base-url https://api.deepseek.com/v1 --model deepseek-chat
89
+ ```
90
+
91
+ ## 输出示例
92
+
93
+ ```
94
+ ──────────────────────────────────────────────────
95
+ 📋 周报 (02/24 - 02/28)
96
+ ──────────────────────────────────────────────────
97
+
98
+ 本周工作
99
+
100
+ 🚀 新功能
101
+ • 用户登录模块:实现 JWT 鉴权
102
+ • 添加文件上传接口,支持图片和 PDF
103
+
104
+ 🐛 Bug 修复
105
+ • 修复分页查询在最后一页返回空数据的问题
106
+ • 修复并发请求下 session 丢失
107
+
108
+ ♻️ 重构
109
+ • 将数据库操作抽取为 Repository 层
110
+
111
+ 📝 文档
112
+ • 更新 API 接口文档
113
+
114
+ 代码统计
115
+
116
+ 提交次数 12 次
117
+ 文件变更 23 个文件
118
+ 新增行数 +847
119
+ 删除行数 -312
120
+
121
+ 🤖 AI 总结
122
+
123
+ 本周主要完成了用户登录模块的 JWT 鉴权实现和文件上传接口开发,
124
+ 同时修复了分页查询和并发 session 相关的两个稳定性问题。
125
+ 技术层面对数据库操作进行了 Repository 层抽取,提升了代码可维护性。
126
+ ```
127
+
128
+ ## AI 配置
129
+
130
+ API Key 支持三种配置方式(优先级从高到低):
131
+
132
+ **1. 命令行参数**
133
+
134
+ ```bash
135
+ git-weekly --ai --api-key sk-xxx
136
+ ```
137
+
138
+ **2. 环境变量**
139
+
140
+ ```bash
141
+ export GIT_WEEKLY_API_KEY=sk-xxx
142
+ export GIT_WEEKLY_BASE_URL=https://api.deepseek.com/v1 # 可选
143
+ export GIT_WEEKLY_MODEL=deepseek-chat # 可选
144
+ ```
145
+
146
+ **3. 配置文件** `~/.config/git-weekly/config.toml`
147
+
148
+ ```toml
149
+ [ai]
150
+ api_key = "sk-xxx"
151
+ base_url = "https://api.openai.com/v1"
152
+ model = "gpt-4o-mini"
153
+ ```
154
+
155
+ ### 支持的 LLM 服务
156
+
157
+ 任何兼容 OpenAI API 的服务都可以使用,例如:
158
+
159
+ | 服务 | base_url | 推荐模型 |
160
+ |------|----------|----------|
161
+ | OpenAI | `https://api.openai.com/v1` (默认) | `gpt-4o-mini` |
162
+ | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
163
+ | 通义千问 | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `qwen-plus` |
164
+ | Ollama (本地) | `http://localhost:11434/v1` | `llama3` |
165
+
166
+ ## 开发
167
+
168
+ ```bash
169
+ # 安装开发依赖
170
+ pip install -e ".[dev]"
171
+
172
+ # 运行测试
173
+ pytest
174
+
175
+ # 代码检查
176
+ ruff check .
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ git_weekly/__init__.py
6
+ git_weekly/__main__.py
7
+ git_weekly/analyzer.py
8
+ git_weekly/cli.py
9
+ git_weekly/llm.py
10
+ git_weekly/report.py
11
+ git_weekly.egg-info/PKG-INFO
12
+ git_weekly.egg-info/SOURCES.txt
13
+ git_weekly.egg-info/dependency_links.txt
14
+ git_weekly.egg-info/entry_points.txt
15
+ git_weekly.egg-info/requires.txt
16
+ git_weekly.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ git-weekly = git_weekly.cli:main
@@ -0,0 +1,7 @@
1
+
2
+ [ai]
3
+ openai>=1.0
4
+
5
+ [dev]
6
+ pytest>=7.0
7
+ ruff>=0.4
@@ -0,0 +1 @@
1
+ git_weekly
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "git-weekly"
7
+ version = "0.2.0"
8
+ description = "Generate weekly reports from Git commit history, with optional AI summary"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ dependencies = []
13
+ authors = [
14
+ {name = "woaichilajiaochaorou"},
15
+ ]
16
+ keywords = ["git", "weekly", "report", "changelog", "productivity", "ai", "llm"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: Version Control :: Git",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/woaichilajiaochaorou/git-weekly"
32
+ Repository = "https://github.com/woaichilajiaochaorou/git-weekly"
33
+ Issues = "https://github.com/woaichilajiaochaorou/git-weekly/issues"
34
+
35
+ [project.optional-dependencies]
36
+ ai = [
37
+ "openai>=1.0",
38
+ ]
39
+ dev = [
40
+ "pytest>=7.0",
41
+ "ruff>=0.4",
42
+ ]
43
+
44
+ [project.scripts]
45
+ git-weekly = "git_weekly.cli:main"
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ # Thin shim for editable installs with pip < 21.3 (no PEP 660 support).
2
+ # All config lives in pyproject.toml.
3
+ from setuptools import setup
4
+
5
+ setup()