@adonis0123/weekly-report 1.0.10 → 1.0.12
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/README.md +1 -1
- package/SKILL.md +124 -10
- package/package.json +3 -2
- package/src/git_analyzer.py +43 -2
- package/src/report_generator.py +168 -48
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ project-backend
|
|
|
61
61
|
- [@adonis0123/commit](https://www.npmjs.com/package/@adonis0123/commit) - 提交信息生成
|
|
62
62
|
- [@adonis0123/staged-changes-review](https://www.npmjs.com/package/@adonis0123/staged-changes-review) - 代码审查
|
|
63
63
|
- [@adonis0123/create-skill](https://www.npmjs.com/package/@adonis0123/create-skill) - 创建新技能包
|
|
64
|
-
|
|
64
|
+
- [@adonis0123/code-doc-generator](https://www.npmjs.com/package/@adonis0123/code-doc-generator) - 代码文档生成
|
|
65
65
|
## License
|
|
66
66
|
|
|
67
67
|
MIT
|
package/SKILL.md
CHANGED
|
@@ -40,7 +40,58 @@ metadata:
|
|
|
40
40
|
- 自定义周报(输入周一日期)
|
|
41
41
|
- 自定义时间段(输入起始日期,截止到今天)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
**重要**:
|
|
44
|
+
- 所有日期计算必须使用 **中国时区 (UTC+8 / Asia/Shanghai)**,而不是系统默认时区
|
|
45
|
+
- 选择时必须显示具体的日期范围,让用户确认是否正确
|
|
46
|
+
|
|
47
|
+
**自定义日期输入规范(必须严格遵守)**:
|
|
48
|
+
|
|
49
|
+
⚠️ **严禁使用 AskUserQuestion 工具收集日期输入** ⚠️
|
|
50
|
+
|
|
51
|
+
**问题原因**:AskUserQuestion 工具会将数字键识别为选项快捷键,导致用户输入 "2026-01-06" 时,按下 "2" 会被当作选择了选项 2,而不是日期的一部分。
|
|
52
|
+
|
|
53
|
+
**使用场景区分**:
|
|
54
|
+
| 场景 | 工具 | 用户操作 |
|
|
55
|
+
|------|------|----------|
|
|
56
|
+
| 选项选择(本周/上周/自定义等) | AskUserQuestion | 按数字快捷键或点击选项 |
|
|
57
|
+
| 自由文本输入(日期、补充说明等) | 不使用任何工具,直接输出文本 | 输入内容后按 **Enter** 发送 |
|
|
58
|
+
|
|
59
|
+
**正确流程**:
|
|
60
|
+
1. 用 AskUserQuestion 显示时间范围选项(本周/上周/前半年/自定义周报/自定义时间段)
|
|
61
|
+
2. 如果用户选择了"自定义周报"或"自定义时间段":
|
|
62
|
+
- **立即停止,不要调用任何工具**
|
|
63
|
+
- 直接输出纯文本提示,例如:"请输入周报的周一日期(格式:YYYY-MM-DD,如 2026-01-06):"
|
|
64
|
+
- 等待用户在对话框中输入日期并按 Enter 发送
|
|
65
|
+
|
|
66
|
+
**正确的交互示例**:
|
|
67
|
+
```
|
|
68
|
+
第一步(使用 AskUserQuestion 工具选择选项):
|
|
69
|
+
[AskUserQuestion 工具显示选项]
|
|
70
|
+
请选择时间范围:
|
|
71
|
+
1. 本周 (2026-01-13 ~ 2026-01-19)
|
|
72
|
+
2. 上周 (2026-01-06 ~ 2026-01-12)
|
|
73
|
+
3. 前半年 (2025-07-19 ~ 2026-01-19)
|
|
74
|
+
4. 自定义周报
|
|
75
|
+
5. 自定义时间段
|
|
76
|
+
|
|
77
|
+
用户选择: 4
|
|
78
|
+
|
|
79
|
+
第二步(直接输出文本,不调用任何工具,等待用户回复):
|
|
80
|
+
Claude 直接输出文本: "请输入周报的周一日期(格式:YYYY-MM-DD,如 2026-01-06):"
|
|
81
|
+
|
|
82
|
+
用户直接在对话框中输入: 2026-01-06
|
|
83
|
+
[用户按 Enter 发送消息]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**错误示例(严禁)**:
|
|
87
|
+
```
|
|
88
|
+
# 错误:使用 AskUserQuestion 工具要求用户输入日期
|
|
89
|
+
# 原因:用户按数字键会触发选项选择,无法正常输入日期
|
|
90
|
+
AskUserQuestion({
|
|
91
|
+
question: "请输入日期",
|
|
92
|
+
options: [...] // 这种方式无法让用户自由输入日期!
|
|
93
|
+
})
|
|
94
|
+
```
|
|
44
95
|
|
|
45
96
|
2. **选择仓库**(如已配置多仓库)
|
|
46
97
|
- 显示已配置的仓库列表
|
|
@@ -61,16 +112,17 @@ metadata:
|
|
|
61
112
|
|
|
62
113
|
## Git 提交读取(重要)
|
|
63
114
|
|
|
64
|
-
|
|
115
|
+
为避免"只读取当前分支而漏掉其它分支(例如 `credits-lite*`)"的问题,读取提交时必须使用 `--all`(覆盖本地分支 + 远端跟踪分支),并确保截止时间包含结束日当天:
|
|
65
116
|
|
|
66
117
|
```bash
|
|
67
118
|
# 关键点:
|
|
68
119
|
# - 用 --all 覆盖所有本地 refs(包含 remotes/origin/*)
|
|
69
|
-
# - --until 用
|
|
120
|
+
# - --until 用 "结束日 23:59:59" 避免漏掉结束日当天提交
|
|
70
121
|
# - --author 建议用 name + email 联合匹配,避免不同身份写法漏掉本人提交
|
|
122
|
+
# - 必须使用中国时区 (UTC+8)
|
|
71
123
|
|
|
72
124
|
AUTHOR_PATTERN="(your-name|your@email.com)" # 或仅用你的 name/email
|
|
73
|
-
git log --all \
|
|
125
|
+
TZ='Asia/Shanghai' git log --all \
|
|
74
126
|
--author="$AUTHOR_PATTERN" \
|
|
75
127
|
--since="$START_DATE 00:00:00" \
|
|
76
128
|
--until="$END_DATE 23:59:59" \
|
|
@@ -78,6 +130,26 @@ git log --all \
|
|
|
78
130
|
--date=short
|
|
79
131
|
```
|
|
80
132
|
|
|
133
|
+
### 日期计算(中国时区)
|
|
134
|
+
|
|
135
|
+
计算"本周"、"上周"等日期时,必须基于中国时区 (UTC+8):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 获取中国时区的当前日期
|
|
139
|
+
TODAY=$(TZ='Asia/Shanghai' date +%Y-%m-%d)
|
|
140
|
+
|
|
141
|
+
# 获取中国时区的星期几(1=周一, 7=周日)
|
|
142
|
+
DAY_OF_WEEK=$(TZ='Asia/Shanghai' date +%u)
|
|
143
|
+
|
|
144
|
+
# 计算本周一(中国时区)
|
|
145
|
+
THIS_MONDAY=$(TZ='Asia/Shanghai' date -v-$((DAY_OF_WEEK-1))d +%Y-%m-%d)
|
|
146
|
+
|
|
147
|
+
# 计算本周日(中国时区)
|
|
148
|
+
THIS_SUNDAY=$(TZ='Asia/Shanghai' date -v+$((7-DAY_OF_WEEK))d +%Y-%m-%d)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**注意**:所有显示给用户的日期都必须是中国时区的日期。
|
|
152
|
+
|
|
81
153
|
如果 `git branch -a` 看不到目标远端分支(说明本地没有对应的远端跟踪引用),需要先 `git fetch --all --prune`(在用户同意且网络可用时执行),否则无法读取到“本地不存在的分支”的提交。
|
|
82
154
|
|
|
83
155
|
## 输出格式
|
|
@@ -118,14 +190,21 @@ git log --all \
|
|
|
118
190
|
# 周报 (2026-01-06 ~ 2026-01-12)
|
|
119
191
|
|
|
120
192
|
project-frontend
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
|
|
193
|
+
- [新功能] 用户登录系统开发,支持多种登录方式
|
|
194
|
+
- 接口对接和联调
|
|
195
|
+
- 表单验证优化
|
|
196
|
+
- 记住登录状态功能
|
|
197
|
+
- [修复] 认证流程问题排查修复
|
|
198
|
+
- 定位 token 过期问题
|
|
199
|
+
- 优化重试机制
|
|
200
|
+
- [优化] 构建工具升级
|
|
201
|
+
- [文档] 国际化文档更新
|
|
125
202
|
|
|
126
203
|
project-backend
|
|
127
|
-
-
|
|
128
|
-
|
|
204
|
+
- [新功能] 自定义类型化消息渲染,支持多种格式
|
|
205
|
+
- 富文本渲染
|
|
206
|
+
- 图片消息支持
|
|
207
|
+
- [优化] 断线重连流程
|
|
129
208
|
|
|
130
209
|
其他
|
|
131
210
|
- 新版国际化方案讨论
|
|
@@ -162,6 +241,41 @@ project-backend
|
|
|
162
241
|
- **按项目分组**:相同项目的工作归类
|
|
163
242
|
- **层级清晰**:用缩进表示从属关系
|
|
164
243
|
|
|
244
|
+
### 类型标签
|
|
245
|
+
|
|
246
|
+
根据提交类型自动添加标签:
|
|
247
|
+
|
|
248
|
+
| 类型 | 标签 | 说明 |
|
|
249
|
+
|------|------|------|
|
|
250
|
+
| feat | [新功能] | 新增功能 |
|
|
251
|
+
| fix | [修复] | 问题修复 |
|
|
252
|
+
| refactor | [优化] | 代码重构 |
|
|
253
|
+
| perf | [性能] | 性能优化 |
|
|
254
|
+
| docs | [文档] | 文档更新 |
|
|
255
|
+
| test | [测试] | 测试相关 |
|
|
256
|
+
| build | [构建] | 构建相关 |
|
|
257
|
+
|
|
258
|
+
### 重点与难点体现
|
|
259
|
+
|
|
260
|
+
自动识别工作的重点和难点,通过内容详略体现(而非显式标记):
|
|
261
|
+
|
|
262
|
+
- **重点工作**(feat/perf 类型 + 多次迭代):摘要更详细,保留更多子条目
|
|
263
|
+
- **难点工作**(fix 类型 + 多次尝试修复):保留完整的排查过程细节
|
|
264
|
+
- **普通工作**:简洁摘要,精简细节
|
|
265
|
+
|
|
266
|
+
**示例**:
|
|
267
|
+
```markdown
|
|
268
|
+
project-frontend
|
|
269
|
+
- [新功能] 用户登录系统开发,支持多种登录方式
|
|
270
|
+
- 接口对接和联调
|
|
271
|
+
- 表单验证优化
|
|
272
|
+
- 记住登录状态功能
|
|
273
|
+
- [修复] 认证流程问题排查修复
|
|
274
|
+
- 定位 token 过期问题
|
|
275
|
+
- 优化重试机制
|
|
276
|
+
- [优化] 构建工具升级
|
|
277
|
+
```
|
|
278
|
+
|
|
165
279
|
### 过滤规则
|
|
166
280
|
|
|
167
281
|
以下提交不会单独列出:
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis0123/weekly-report",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Claude Code Skill - 自动读取 Git 提交记录生成周报,支持多仓库汇总和智能过滤",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"postinstall": "node install-skill.js",
|
|
7
7
|
"preuninstall": "node uninstall-skill.js",
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "python3 -m pytest tests/ -v --tb=short",
|
|
9
|
+
"test:install": "node install-skill.js && echo 'Installation test completed.'"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"SKILL.md",
|
package/src/git_analyzer.py
CHANGED
|
@@ -10,6 +10,22 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Any, Dict, List, Optional
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
# 提交类型配置
|
|
14
|
+
COMMIT_TYPE_CONFIG = {
|
|
15
|
+
"feat": {"label": "[新功能]", "priority": 1, "is_highlight": True, "is_challenge": False},
|
|
16
|
+
"fix": {"label": "[修复]", "priority": 2, "is_highlight": False, "is_challenge": True},
|
|
17
|
+
"refactor": {"label": "[优化]", "priority": 3, "is_highlight": False, "is_challenge": False},
|
|
18
|
+
"perf": {"label": "[性能]", "priority": 3, "is_highlight": True, "is_challenge": False},
|
|
19
|
+
"style": {"label": "[样式]", "priority": 6, "is_highlight": False, "is_challenge": False},
|
|
20
|
+
"docs": {"label": "[文档]", "priority": 5, "is_highlight": False, "is_challenge": False},
|
|
21
|
+
"test": {"label": "[测试]", "priority": 4, "is_highlight": False, "is_challenge": False},
|
|
22
|
+
"chore": {"label": "[杂项]", "priority": 6, "is_highlight": False, "is_challenge": False},
|
|
23
|
+
"build": {"label": "[构建]", "priority": 4, "is_highlight": False, "is_challenge": False},
|
|
24
|
+
"ci": {"label": "[CI]", "priority": 5, "is_highlight": False, "is_challenge": False},
|
|
25
|
+
"other": {"label": "", "priority": 7, "is_highlight": False, "is_challenge": False},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
13
29
|
# 琐碎提交的关键词
|
|
14
30
|
TRIVIAL_PATTERNS = [
|
|
15
31
|
r"^fix\s*typo",
|
|
@@ -155,6 +171,10 @@ def get_commits(
|
|
|
155
171
|
"date": parts[3],
|
|
156
172
|
"type": parsed["type"],
|
|
157
173
|
"is_trivial": parsed["is_trivial"],
|
|
174
|
+
"is_highlight": parsed["is_highlight"],
|
|
175
|
+
"is_challenge": parsed["is_challenge"],
|
|
176
|
+
"label": parsed["label"],
|
|
177
|
+
"priority": parsed["priority"],
|
|
158
178
|
"project": get_repo_name(repo_path),
|
|
159
179
|
})
|
|
160
180
|
|
|
@@ -192,13 +212,25 @@ def parse_commit_message(message: str) -> Dict[str, Any]:
|
|
|
192
212
|
message: 提交信息
|
|
193
213
|
|
|
194
214
|
Returns:
|
|
195
|
-
|
|
215
|
+
解析后的提交信息字典,包含:
|
|
216
|
+
- type: 提交类型
|
|
217
|
+
- scope: 作用域
|
|
218
|
+
- description: 描述
|
|
219
|
+
- is_trivial: 是否为琐碎提交
|
|
220
|
+
- is_highlight: 是否为重点(feat/perf 类型)
|
|
221
|
+
- is_challenge: 是否为难点(fix 类型)
|
|
222
|
+
- label: 类型标签(如 [新功能])
|
|
223
|
+
- priority: 优先级(用于排序)
|
|
196
224
|
"""
|
|
197
225
|
result = {
|
|
198
226
|
"type": "other",
|
|
199
227
|
"scope": None,
|
|
200
228
|
"description": message,
|
|
201
229
|
"is_trivial": False,
|
|
230
|
+
"is_highlight": False,
|
|
231
|
+
"is_challenge": False,
|
|
232
|
+
"label": "",
|
|
233
|
+
"priority": 7,
|
|
202
234
|
}
|
|
203
235
|
|
|
204
236
|
# 检查是否为琐碎提交
|
|
@@ -213,11 +245,20 @@ def parse_commit_message(message: str) -> Dict[str, Any]:
|
|
|
213
245
|
match = re.match(conventional_pattern, message)
|
|
214
246
|
|
|
215
247
|
if match:
|
|
216
|
-
|
|
248
|
+
commit_type = match.group(1).lower()
|
|
249
|
+
result["type"] = commit_type
|
|
217
250
|
result["scope"] = match.group(2)
|
|
218
251
|
result["description"] = match.group(3)
|
|
219
252
|
else:
|
|
220
253
|
result["description"] = message
|
|
254
|
+
commit_type = "other"
|
|
255
|
+
|
|
256
|
+
# 从配置中获取类型属性
|
|
257
|
+
type_config = COMMIT_TYPE_CONFIG.get(commit_type, COMMIT_TYPE_CONFIG["other"])
|
|
258
|
+
result["is_highlight"] = type_config["is_highlight"]
|
|
259
|
+
result["is_challenge"] = type_config["is_challenge"]
|
|
260
|
+
result["label"] = type_config["label"]
|
|
261
|
+
result["priority"] = type_config["priority"]
|
|
221
262
|
|
|
222
263
|
return result
|
|
223
264
|
|
package/src/report_generator.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import re
|
|
7
7
|
from typing import Any, Dict, List, Optional
|
|
8
8
|
|
|
9
|
-
from src.git_analyzer import group_commits_by_project
|
|
9
|
+
from src.git_analyzer import group_commits_by_project, COMMIT_TYPE_CONFIG
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def generate_report(
|
|
@@ -75,8 +75,9 @@ def merge_related_commits(
|
|
|
75
75
|
"""合并相关提交
|
|
76
76
|
|
|
77
77
|
合并规则:
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
1. 先按 commit 类型分组(feat/fix/docs 分开)
|
|
79
|
+
2. 再按关键词合并相似提交
|
|
80
|
+
3. 按类型优先级排序输出
|
|
80
81
|
|
|
81
82
|
Args:
|
|
82
83
|
commits: 提交记录列表
|
|
@@ -89,48 +90,61 @@ def merge_related_commits(
|
|
|
89
90
|
if len(commits) <= 1:
|
|
90
91
|
single = commits[0].copy()
|
|
91
92
|
single.setdefault("details", [])
|
|
93
|
+
single.setdefault("commit_count", 1)
|
|
92
94
|
return [single]
|
|
93
95
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
# 第一步:按类型分组
|
|
97
|
+
type_groups: Dict[str, List[Dict[str, Any]]] = {}
|
|
97
98
|
for commit in commits:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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)
|
|
99
|
+
commit_type = commit.get("type", "other")
|
|
100
|
+
if commit_type not in type_groups:
|
|
101
|
+
type_groups[commit_type] = []
|
|
102
|
+
type_groups[commit_type].append(commit)
|
|
108
103
|
|
|
109
|
-
#
|
|
104
|
+
# 第二步:在每个类型组内按关键词合并
|
|
110
105
|
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
106
|
|
|
132
|
-
|
|
133
|
-
|
|
107
|
+
for commit_type, type_commits in type_groups.items():
|
|
108
|
+
# 按关键词分组
|
|
109
|
+
keyword_groups: Dict[str, List[Dict[str, Any]]] = {}
|
|
110
|
+
|
|
111
|
+
for commit in type_commits:
|
|
112
|
+
keywords = extract_keywords(commit["message"])
|
|
113
|
+
str_key = str(sorted(keywords)) if keywords else commit["message"]
|
|
114
|
+
|
|
115
|
+
if str_key not in keyword_groups:
|
|
116
|
+
keyword_groups[str_key] = []
|
|
117
|
+
keyword_groups[str_key].append(commit)
|
|
118
|
+
|
|
119
|
+
# 合并同组提交
|
|
120
|
+
for group_commits in keyword_groups.values():
|
|
121
|
+
main_commit = group_commits[0].copy()
|
|
122
|
+
# 优先选择 feat 类型作为主条目
|
|
123
|
+
for c in group_commits:
|
|
124
|
+
if c.get("type") == "feat":
|
|
125
|
+
main_commit = c.copy()
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
details = []
|
|
129
|
+
for c in group_commits:
|
|
130
|
+
details.append(clean_commit_message(c.get("message", "")))
|
|
131
|
+
|
|
132
|
+
# 去重并保持顺序
|
|
133
|
+
seen = set()
|
|
134
|
+
uniq_details = []
|
|
135
|
+
for d in details:
|
|
136
|
+
key = d.strip()
|
|
137
|
+
if not key or key in seen:
|
|
138
|
+
continue
|
|
139
|
+
seen.add(key)
|
|
140
|
+
uniq_details.append(d.strip())
|
|
141
|
+
|
|
142
|
+
main_commit["details"] = uniq_details if len(uniq_details) > 1 else []
|
|
143
|
+
main_commit["commit_count"] = len(group_commits)
|
|
144
|
+
merged.append(main_commit)
|
|
145
|
+
|
|
146
|
+
# 第三步:按优先级排序(优先级数字越小越靠前)
|
|
147
|
+
merged.sort(key=lambda x: (x.get("priority", 7), x.get("message", "")))
|
|
134
148
|
|
|
135
149
|
return merged
|
|
136
150
|
|
|
@@ -165,12 +179,51 @@ def clean_commit_message(message: str) -> str:
|
|
|
165
179
|
return re.sub(r"^(\w+)(\([^)]+\))?\s*:\s*", "", message).strip()
|
|
166
180
|
|
|
167
181
|
|
|
182
|
+
def analyze_work_significance(commit: Dict[str, Any]) -> Dict[str, bool]:
|
|
183
|
+
"""分析工作的重点和难点
|
|
184
|
+
|
|
185
|
+
判断规则:
|
|
186
|
+
- 重点:feat 类型 + 多次迭代(>=2次提交)或显式标记 is_highlight
|
|
187
|
+
- 难点:fix 类型 + 多次尝试(>=2次提交)或显式标记 is_challenge
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
commit: 提交记录(合并后的,含 commit_count)
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
包含 is_highlight 和 is_challenge 的字典
|
|
194
|
+
"""
|
|
195
|
+
commit_count = commit.get("commit_count", 1)
|
|
196
|
+
commit_type = commit.get("type", "other")
|
|
197
|
+
|
|
198
|
+
# 判断是否为重点
|
|
199
|
+
is_highlight = commit.get("is_highlight", False)
|
|
200
|
+
if commit_type == "feat" and commit_count >= 2:
|
|
201
|
+
is_highlight = True
|
|
202
|
+
if commit_type == "perf":
|
|
203
|
+
is_highlight = True
|
|
204
|
+
|
|
205
|
+
# 判断是否为难点
|
|
206
|
+
is_challenge = commit.get("is_challenge", False)
|
|
207
|
+
if commit_type == "fix" and commit_count >= 2:
|
|
208
|
+
is_challenge = True
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"is_highlight": is_highlight,
|
|
212
|
+
"is_challenge": is_challenge,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
168
216
|
def format_project_section(
|
|
169
217
|
project: str,
|
|
170
218
|
commits: List[Dict[str, Any]],
|
|
171
219
|
) -> str:
|
|
172
220
|
"""格式化项目部分
|
|
173
221
|
|
|
222
|
+
重点/难点通过以下方式体现(而非显式标记):
|
|
223
|
+
- 重点工作:摘要字数更长(max_length=40),保留更多细节
|
|
224
|
+
- 难点工作:保留更多子条目细节
|
|
225
|
+
- 普通工作:简洁摘要(max_length=25)
|
|
226
|
+
|
|
174
227
|
Args:
|
|
175
228
|
project: 项目名称
|
|
176
229
|
commits: 提交记录列表
|
|
@@ -181,11 +234,39 @@ def format_project_section(
|
|
|
181
234
|
lines = [project]
|
|
182
235
|
|
|
183
236
|
for commit in commits:
|
|
184
|
-
|
|
237
|
+
# 获取类型标签
|
|
238
|
+
label = commit.get("label", "")
|
|
239
|
+
|
|
240
|
+
# 分析重点/难点
|
|
241
|
+
significance = analyze_work_significance(commit)
|
|
242
|
+
|
|
243
|
+
# 根据重要程度调整摘要长度
|
|
244
|
+
if significance["is_highlight"]:
|
|
245
|
+
# 重点工作:更长的摘要
|
|
246
|
+
max_len = 40
|
|
247
|
+
elif significance["is_challenge"]:
|
|
248
|
+
# 难点工作:适中的摘要
|
|
249
|
+
max_len = 35
|
|
250
|
+
else:
|
|
251
|
+
# 普通工作:简洁摘要
|
|
252
|
+
max_len = 25
|
|
253
|
+
|
|
254
|
+
# 生成摘要(含类型标签)
|
|
255
|
+
summary = summarize_commit(commit["message"], max_length=max_len, label=label)
|
|
256
|
+
|
|
185
257
|
lines.append(f" - {summary}")
|
|
258
|
+
|
|
259
|
+
# 添加子条目细节
|
|
260
|
+
# 重点/难点保留更多细节,普通工作限制细节数量
|
|
186
261
|
details = commit.get("details") or []
|
|
187
|
-
|
|
188
|
-
|
|
262
|
+
if significance["is_highlight"] or significance["is_challenge"]:
|
|
263
|
+
# 重点/难点:保留所有细节(最多5条)
|
|
264
|
+
for detail in details[:5]:
|
|
265
|
+
lines.append(f" - {detail}")
|
|
266
|
+
else:
|
|
267
|
+
# 普通工作:最多保留2条细节
|
|
268
|
+
for detail in details[:2]:
|
|
269
|
+
lines.append(f" - {detail}")
|
|
189
270
|
|
|
190
271
|
return "\n".join(lines)
|
|
191
272
|
|
|
@@ -207,21 +288,60 @@ def format_other_section(supplements: List[str]) -> str:
|
|
|
207
288
|
return "\n".join(lines)
|
|
208
289
|
|
|
209
290
|
|
|
210
|
-
def summarize_commit(
|
|
211
|
-
|
|
291
|
+
def summarize_commit(
|
|
292
|
+
message: str,
|
|
293
|
+
max_length: int = 30,
|
|
294
|
+
label: str = "",
|
|
295
|
+
) -> str:
|
|
296
|
+
"""生成提交摘要(智能截断)
|
|
297
|
+
|
|
298
|
+
在自然断点处截断,避免割裂语义:
|
|
299
|
+
- 优先在标点符号处截断
|
|
300
|
+
- 其次在空格处截断
|
|
301
|
+
- 最后才硬截断
|
|
212
302
|
|
|
213
303
|
Args:
|
|
214
304
|
message: 提交信息
|
|
215
|
-
max_length:
|
|
305
|
+
max_length: 最大长度(不含标签)
|
|
306
|
+
label: 类型标签(如 [新功能])
|
|
216
307
|
|
|
217
308
|
Returns:
|
|
218
|
-
|
|
309
|
+
带标签的摘要文本
|
|
219
310
|
"""
|
|
220
311
|
cleaned = clean_commit_message(message)
|
|
221
312
|
|
|
222
|
-
#
|
|
313
|
+
# 截断过长的文本(智能截断)
|
|
223
314
|
if len(cleaned) > max_length:
|
|
224
|
-
|
|
315
|
+
# 在最大长度范围内寻找自然断点
|
|
316
|
+
truncated = cleaned[:max_length]
|
|
317
|
+
|
|
318
|
+
# 1. 优先在中文标点处截断
|
|
319
|
+
for punct in ["。", ",", "、", ";", ":", "!", "?"]:
|
|
320
|
+
idx = truncated.rfind(punct)
|
|
321
|
+
if idx > max_length // 2: # 至少保留一半内容
|
|
322
|
+
truncated = truncated[:idx + 1]
|
|
323
|
+
break
|
|
324
|
+
else:
|
|
325
|
+
# 2. 尝试在英文标点处截断
|
|
326
|
+
for punct in [".", ",", ";", ":", "!", "?"]:
|
|
327
|
+
idx = truncated.rfind(punct)
|
|
328
|
+
if idx > max_length // 2:
|
|
329
|
+
truncated = truncated[:idx + 1]
|
|
330
|
+
break
|
|
331
|
+
else:
|
|
332
|
+
# 3. 尝试在空格处截断
|
|
333
|
+
idx = truncated.rfind(" ")
|
|
334
|
+
if idx > max_length // 2:
|
|
335
|
+
truncated = truncated[:idx]
|
|
336
|
+
else:
|
|
337
|
+
# 4. 硬截断并添加省略号
|
|
338
|
+
truncated = truncated[:max_length - 3] + "..."
|
|
339
|
+
|
|
340
|
+
cleaned = truncated
|
|
341
|
+
|
|
342
|
+
# 添加类型标签
|
|
343
|
+
if label:
|
|
344
|
+
return f"{label} {cleaned.strip()}"
|
|
225
345
|
|
|
226
346
|
return cleaned.strip()
|
|
227
347
|
|