@adonis0123/weekly-report 1.0.6
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.
- package/.claude-skill.json +46 -0
- package/LICENSE +21 -0
- package/README.md +453 -0
- package/SKILL.md +174 -0
- package/install-skill.js +207 -0
- package/package.json +45 -0
- package/references/WEEKLY_REPORT_FORMAT.md +116 -0
- package/src/__init__.py +3 -0
- package/src/config_manager.py +171 -0
- package/src/date_utils.py +272 -0
- package/src/git_analyzer.py +342 -0
- package/src/report_generator.py +257 -0
- package/src/storage.py +491 -0
- package/uninstall-skill.js +118 -0
- package/utils.js +94 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""周报生成器模块
|
|
2
|
+
|
|
3
|
+
根据 Git 提交记录生成结构化周报。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from src.git_analyzer import group_commits_by_project
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_report(
|
|
13
|
+
commits: List[Dict[str, Any]],
|
|
14
|
+
supplements: Optional[List[str]] = None,
|
|
15
|
+
) -> str:
|
|
16
|
+
"""生成周报
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
commits: 提交记录列表
|
|
20
|
+
supplements: 补充内容列表
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Markdown 格式的周报内容
|
|
24
|
+
"""
|
|
25
|
+
if not commits and not supplements:
|
|
26
|
+
return ""
|
|
27
|
+
|
|
28
|
+
# 过滤琐碎提交
|
|
29
|
+
filtered_commits = filter_trivial_commits(commits)
|
|
30
|
+
|
|
31
|
+
# 按项目分组
|
|
32
|
+
grouped = group_commits_by_project(filtered_commits)
|
|
33
|
+
|
|
34
|
+
# 生成周报内容
|
|
35
|
+
sections = []
|
|
36
|
+
|
|
37
|
+
# 按项目生成各部分
|
|
38
|
+
for project, project_commits in sorted(grouped.items()):
|
|
39
|
+
# 合并相关提交
|
|
40
|
+
merged = merge_related_commits(project_commits)
|
|
41
|
+
section = format_project_section(project, merged)
|
|
42
|
+
sections.append(section)
|
|
43
|
+
|
|
44
|
+
# 添加"其他"部分(补充内容)
|
|
45
|
+
if supplements:
|
|
46
|
+
other_section = format_other_section(supplements)
|
|
47
|
+
sections.append(other_section)
|
|
48
|
+
|
|
49
|
+
return "\n\n".join(sections)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def filter_trivial_commits(
|
|
53
|
+
commits: List[Dict[str, Any]]
|
|
54
|
+
) -> List[Dict[str, Any]]:
|
|
55
|
+
"""过滤琐碎提交
|
|
56
|
+
|
|
57
|
+
过滤规则:
|
|
58
|
+
- typo 修复
|
|
59
|
+
- 纯格式化/lint 调整
|
|
60
|
+
- merge 提交
|
|
61
|
+
- WIP 提交
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
commits: 提交记录列表
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
过滤后的提交列表
|
|
68
|
+
"""
|
|
69
|
+
return [c for c in commits if not c.get("is_trivial", False)]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def merge_related_commits(
|
|
73
|
+
commits: List[Dict[str, Any]]
|
|
74
|
+
) -> List[Dict[str, Any]]:
|
|
75
|
+
"""合并相关提交
|
|
76
|
+
|
|
77
|
+
合并规则:
|
|
78
|
+
- 同一功能的多次迭代合并为一条
|
|
79
|
+
- 问题排查和解决归为一条
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
commits: 提交记录列表
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
合并后的提交列表
|
|
86
|
+
"""
|
|
87
|
+
if not commits:
|
|
88
|
+
return []
|
|
89
|
+
if len(commits) <= 1:
|
|
90
|
+
single = commits[0].copy()
|
|
91
|
+
single.setdefault("details", [])
|
|
92
|
+
return [single]
|
|
93
|
+
|
|
94
|
+
# 按关键词分组
|
|
95
|
+
groups: Dict[str, List[Dict[str, Any]]] = {}
|
|
96
|
+
|
|
97
|
+
for commit in commits:
|
|
98
|
+
# 提取关键词
|
|
99
|
+
keywords = extract_keywords(commit["message"])
|
|
100
|
+
key = frozenset(keywords) if keywords else commit["message"]
|
|
101
|
+
|
|
102
|
+
# 转换为字符串 key
|
|
103
|
+
str_key = str(sorted(keywords)) if keywords else commit["message"]
|
|
104
|
+
|
|
105
|
+
if str_key not in groups:
|
|
106
|
+
groups[str_key] = []
|
|
107
|
+
groups[str_key].append(commit)
|
|
108
|
+
|
|
109
|
+
# 合并同组提交(保留主条目 + 子条目细节,避免信息丢失)
|
|
110
|
+
merged: List[Dict[str, Any]] = []
|
|
111
|
+
for group_commits in groups.values():
|
|
112
|
+
main_commit = group_commits[0].copy()
|
|
113
|
+
for c in group_commits:
|
|
114
|
+
if c.get("type") == "feat":
|
|
115
|
+
main_commit = c.copy()
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
details = []
|
|
119
|
+
for c in group_commits:
|
|
120
|
+
details.append(clean_commit_message(c.get("message", "")))
|
|
121
|
+
|
|
122
|
+
# 去重并保持顺序
|
|
123
|
+
seen = set()
|
|
124
|
+
uniq_details = []
|
|
125
|
+
for d in details:
|
|
126
|
+
key = d.strip()
|
|
127
|
+
if not key or key in seen:
|
|
128
|
+
continue
|
|
129
|
+
seen.add(key)
|
|
130
|
+
uniq_details.append(d.strip())
|
|
131
|
+
|
|
132
|
+
main_commit["details"] = uniq_details if len(uniq_details) > 1 else []
|
|
133
|
+
merged.append(main_commit)
|
|
134
|
+
|
|
135
|
+
return merged
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def extract_keywords(message: str) -> List[str]:
|
|
139
|
+
"""从提交信息中提取关键词
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
message: 提交信息
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
关键词列表
|
|
146
|
+
"""
|
|
147
|
+
# 去除前缀
|
|
148
|
+
cleaned = re.sub(r"^(\w+)(\([^)]+\))?\s*:\s*", "", message)
|
|
149
|
+
|
|
150
|
+
# 提取中文词语和英文单词
|
|
151
|
+
chinese_words = re.findall(r"[\u4e00-\u9fff]+", cleaned)
|
|
152
|
+
english_words = re.findall(r"[a-zA-Z]{3,}", cleaned)
|
|
153
|
+
|
|
154
|
+
keywords = chinese_words + [w.lower() for w in english_words]
|
|
155
|
+
|
|
156
|
+
# 过滤常见无意义词
|
|
157
|
+
stop_words = {"the", "and", "for", "with", "this", "that", "from", "into"}
|
|
158
|
+
keywords = [k for k in keywords if k.lower() not in stop_words]
|
|
159
|
+
|
|
160
|
+
return keywords[:3] # 只保留前3个关键词
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def clean_commit_message(message: str) -> str:
|
|
164
|
+
"""清理提交信息为可读描述(去除 conventional 前缀)"""
|
|
165
|
+
return re.sub(r"^(\w+)(\([^)]+\))?\s*:\s*", "", message).strip()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def format_project_section(
|
|
169
|
+
project: str,
|
|
170
|
+
commits: List[Dict[str, Any]],
|
|
171
|
+
) -> str:
|
|
172
|
+
"""格式化项目部分
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
project: 项目名称
|
|
176
|
+
commits: 提交记录列表
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
格式化的 Markdown 内容
|
|
180
|
+
"""
|
|
181
|
+
lines = [project]
|
|
182
|
+
|
|
183
|
+
for commit in commits:
|
|
184
|
+
summary = summarize_commit(commit["message"])
|
|
185
|
+
lines.append(f" - {summary}")
|
|
186
|
+
details = commit.get("details") or []
|
|
187
|
+
for detail in details:
|
|
188
|
+
lines.append(f" - {detail}")
|
|
189
|
+
|
|
190
|
+
return "\n".join(lines)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def format_other_section(supplements: List[str]) -> str:
|
|
194
|
+
"""格式化"其他"部分
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
supplements: 补充内容列表
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
格式化的 Markdown 内容
|
|
201
|
+
"""
|
|
202
|
+
lines = ["其他"]
|
|
203
|
+
|
|
204
|
+
for item in supplements:
|
|
205
|
+
lines.append(f" - {item}")
|
|
206
|
+
|
|
207
|
+
return "\n".join(lines)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def summarize_commit(message: str, max_length: int = 20) -> str:
|
|
211
|
+
"""生成提交摘要
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
message: 提交信息
|
|
215
|
+
max_length: 最大长度
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
摘要文本
|
|
219
|
+
"""
|
|
220
|
+
cleaned = clean_commit_message(message)
|
|
221
|
+
|
|
222
|
+
# 截断过长的文本
|
|
223
|
+
if len(cleaned) > max_length:
|
|
224
|
+
cleaned = cleaned[:max_length - 3] + "..."
|
|
225
|
+
|
|
226
|
+
return cleaned.strip()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def generate_full_report(
|
|
230
|
+
commits_by_project: Dict[str, List[Dict[str, Any]]],
|
|
231
|
+
supplements: Optional[List[str]] = None,
|
|
232
|
+
date_range: Optional[str] = None,
|
|
233
|
+
) -> str:
|
|
234
|
+
"""生成完整周报
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
commits_by_project: 按项目分组的提交记录
|
|
238
|
+
supplements: 补充内容列表
|
|
239
|
+
date_range: 日期范围描述
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
完整的 Markdown 周报
|
|
243
|
+
"""
|
|
244
|
+
# 合并所有提交
|
|
245
|
+
all_commits = []
|
|
246
|
+
for commits in commits_by_project.values():
|
|
247
|
+
all_commits.extend(commits)
|
|
248
|
+
|
|
249
|
+
# 生成报告内容
|
|
250
|
+
content = generate_report(all_commits, supplements)
|
|
251
|
+
|
|
252
|
+
# 添加标题(如果有日期范围)
|
|
253
|
+
if date_range:
|
|
254
|
+
header = f"# 周报 ({date_range})\n\n"
|
|
255
|
+
content = header + content
|
|
256
|
+
|
|
257
|
+
return content
|