@adonis0123/weekly-report 1.0.12 → 1.0.14
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 -0
- package/SKILL.md +69 -36
- package/install-skill.js +102 -26
- package/package.json +1 -1
- package/src/git_analyzer.py +39 -21
- package/src/report_generator.py +15 -26
- package/uninstall-skill.js +83 -10
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ project-backend
|
|
|
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
|
+
- [@adonis0123/css-tailwind-styling](https://www.npmjs.com/package/@adonis0123/css-tailwind-styling) - CSS 和 Tailwind 样式规范
|
|
65
66
|
## License
|
|
66
67
|
|
|
67
68
|
MIT
|
package/SKILL.md
CHANGED
|
@@ -150,7 +150,26 @@ THIS_SUNDAY=$(TZ='Asia/Shanghai' date -v+$((7-DAY_OF_WEEK))d +%Y-%m-%d)
|
|
|
150
150
|
|
|
151
151
|
**注意**:所有显示给用户的日期都必须是中国时区的日期。
|
|
152
152
|
|
|
153
|
-
如果 `git branch -a` 看不到目标远端分支(说明本地没有对应的远端跟踪引用),需要先 `git fetch --all --prune
|
|
153
|
+
如果 `git branch -a` 看不到目标远端分支(说明本地没有对应的远端跟踪引用),需要先 `git fetch --all --prune`(在用户同意且网络可用时执行),否则无法读取到"本地不存在的分支"的提交。
|
|
154
|
+
|
|
155
|
+
### Commit Diff 分析(判断重点)
|
|
156
|
+
|
|
157
|
+
在总结前,必须通过 commit diff 判断工作重要性:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# 查看 commit 的改动规模
|
|
161
|
+
git show --stat <commit-hash>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**重点判断标准**:
|
|
165
|
+
|
|
166
|
+
| 条件 | 判定 | 处理方式 |
|
|
167
|
+
|------|------|----------|
|
|
168
|
+
| 改动文件数 > 5 或行数 > 100 | 重点工作 | 详细描述,保留子条目 |
|
|
169
|
+
| 涉及核心模块(auth, payment 等) | 重点工作 | 详细描述 |
|
|
170
|
+
| 有多次迭代/排查过程 | 难点工作 | 保留排查细节 |
|
|
171
|
+
| 只改 1-2 个文件且行数 < 20 | 普通工作 | 简略一句话 |
|
|
172
|
+
| 纯配置/样式微调 | 琐碎工作 | 过滤或合并 |
|
|
154
173
|
|
|
155
174
|
## 输出格式
|
|
156
175
|
|
|
@@ -190,21 +209,17 @@ THIS_SUNDAY=$(TZ='Asia/Shanghai' date -v+$((7-DAY_OF_WEEK))d +%Y-%m-%d)
|
|
|
190
209
|
# 周报 (2026-01-06 ~ 2026-01-12)
|
|
191
210
|
|
|
192
211
|
project-frontend
|
|
193
|
-
-
|
|
212
|
+
- 跟进用户登录系统开发
|
|
194
213
|
- 接口对接和联调
|
|
195
214
|
- 表单验证优化
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- 优化重试机制
|
|
200
|
-
- [优化] 构建工具升级
|
|
201
|
-
- [文档] 国际化文档更新
|
|
215
|
+
- 认证流程问题排查修复
|
|
216
|
+
- 排查后定位 token 过期问题
|
|
217
|
+
- 构建工具升级
|
|
202
218
|
|
|
203
219
|
project-backend
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
- [优化] 断线重连流程
|
|
220
|
+
- 完成自定义消息渲染功能
|
|
221
|
+
- 支持富文本和图片
|
|
222
|
+
- 优化断线重连流程
|
|
208
223
|
|
|
209
224
|
其他
|
|
210
225
|
- 新版国际化方案讨论
|
|
@@ -240,49 +255,67 @@ project-backend
|
|
|
240
255
|
- **重点突出**:过滤琐碎修改
|
|
241
256
|
- **按项目分组**:相同项目的工作归类
|
|
242
257
|
- **层级清晰**:用缩进表示从属关系
|
|
258
|
+
- **无标签风格**:不使用 [新功能]、[修复] 等标签,直接描述工作内容
|
|
243
259
|
|
|
244
|
-
###
|
|
260
|
+
### 输出约束(必须遵守)
|
|
245
261
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
|
249
|
-
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
| refactor | [优化] | 代码重构 |
|
|
253
|
-
| perf | [性能] | 性能优化 |
|
|
254
|
-
| docs | [文档] | 文档更新 |
|
|
255
|
-
| test | [测试] | 测试相关 |
|
|
256
|
-
| build | [构建] | 构建相关 |
|
|
262
|
+
| 约束项 | 限制 |
|
|
263
|
+
|--------|------|
|
|
264
|
+
| 每个主工作点 | 最多 2-3 个子条目 |
|
|
265
|
+
| 每个子条目 | 不超过 15 字 |
|
|
266
|
+
| 每个项目 | 最多 5-7 个主条目 |
|
|
267
|
+
| 修复类工作 | 除非是重大问题排查,否则不展开子条目 |
|
|
257
268
|
|
|
258
269
|
### 重点与难点体现
|
|
259
270
|
|
|
260
271
|
自动识别工作的重点和难点,通过内容详略体现(而非显式标记):
|
|
261
272
|
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
273
|
+
- **重点工作**(改动规模大 + 多次迭代):摘要更详细,保留 2-3 个子条目
|
|
274
|
+
- **难点工作**(多次尝试修复):保留排查过程细节
|
|
275
|
+
- **普通工作**:简洁一句话,无子条目
|
|
265
276
|
|
|
266
|
-
|
|
277
|
+
**正确示例**(good.md 风格):
|
|
267
278
|
```markdown
|
|
268
279
|
project-frontend
|
|
269
|
-
-
|
|
280
|
+
- 跟进用户登录系统开发
|
|
281
|
+
- 接口对接和联调
|
|
282
|
+
- 表单验证优化
|
|
283
|
+
- 认证流程问题排查修复
|
|
284
|
+
- 排查后定位 token 过期问题
|
|
285
|
+
- 构建工具升级
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**错误示例**(禁止):
|
|
289
|
+
```markdown
|
|
290
|
+
# 禁止:使用标签 + 子条目过多
|
|
291
|
+
- [新功能] 用户登录系统开发
|
|
270
292
|
- 接口对接和联调
|
|
271
293
|
- 表单验证优化
|
|
272
294
|
- 记住登录状态功能
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
- [优化]
|
|
295
|
+
- 第三方登录集成
|
|
296
|
+
|
|
297
|
+
# 禁止:琐碎内容单独列出
|
|
298
|
+
- [优化] 代码清理
|
|
299
|
+
- [文档] 完善相关文档
|
|
277
300
|
```
|
|
278
301
|
|
|
279
302
|
### 过滤规则
|
|
280
303
|
|
|
304
|
+
#### 强制过滤(不输出)
|
|
305
|
+
|
|
281
306
|
以下提交不会单独列出:
|
|
282
|
-
-
|
|
283
|
-
-
|
|
307
|
+
- 代码清理、格式化、lint 修复
|
|
308
|
+
- 文档完善(除非是新文档或重大更新)
|
|
309
|
+
- 移除调试日志、console.log
|
|
310
|
+
- 样式微调(除非是设计系统变更)
|
|
284
311
|
- 依赖版本小幅更新
|
|
312
|
+
- 简单的 typo 修复
|
|
285
313
|
- Merge 提交
|
|
286
|
-
|
|
314
|
+
|
|
315
|
+
#### 合并处理(归类为一条)
|
|
316
|
+
|
|
317
|
+
- 同一功能的多个小改动 → 合并为一条功能描述
|
|
318
|
+
- 多个相关修复 → 合并为"问题排查修复"
|
|
319
|
+
- 重复性的相似提交 → 合并为一条
|
|
287
320
|
|
|
288
321
|
详细格式规范见 [周报格式规范](references/WEEKLY_REPORT_FORMAT.md)
|
package/install-skill.js
CHANGED
|
@@ -23,9 +23,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// shared/src/install-skill.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
26
|
+
var import_fs3 = __toESM(require("fs"));
|
|
27
|
+
var import_path3 = __toESM(require("path"));
|
|
28
|
+
var import_os3 = __toESM(require("os"));
|
|
29
29
|
var import_child_process = require("child_process");
|
|
30
30
|
|
|
31
31
|
// shared/src/utils.ts
|
|
@@ -116,6 +116,69 @@ function readSkillConfig(dir) {
|
|
|
116
116
|
return JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// shared/src/claude-settings.ts
|
|
120
|
+
var import_fs2 = __toESM(require("fs"));
|
|
121
|
+
var import_path2 = __toESM(require("path"));
|
|
122
|
+
var import_os2 = __toESM(require("os"));
|
|
123
|
+
function getClaudeSettingsPath() {
|
|
124
|
+
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
125
|
+
}
|
|
126
|
+
function readClaudeSettings() {
|
|
127
|
+
const settingsPath = getClaudeSettingsPath();
|
|
128
|
+
if (!import_fs2.default.existsSync(settingsPath)) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const content = import_fs2.default.readFileSync(settingsPath, "utf-8");
|
|
133
|
+
return JSON.parse(content);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.warn(" \u26A0 Warning: Could not parse settings.json, treating as empty");
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function writeClaudeSettings(settings) {
|
|
140
|
+
const settingsPath = getClaudeSettingsPath();
|
|
141
|
+
const settingsDir = import_path2.default.dirname(settingsPath);
|
|
142
|
+
if (!import_fs2.default.existsSync(settingsDir)) {
|
|
143
|
+
import_fs2.default.mkdirSync(settingsDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
146
|
+
}
|
|
147
|
+
function hookMatcherExists(existingHooks, newMatcher) {
|
|
148
|
+
return existingHooks.some((hook) => hook.matcher === newMatcher.matcher);
|
|
149
|
+
}
|
|
150
|
+
function addClaudeHooks(hooksConfig, skillName) {
|
|
151
|
+
const settings = readClaudeSettings();
|
|
152
|
+
let modified = false;
|
|
153
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
154
|
+
settings.hooks = {};
|
|
155
|
+
}
|
|
156
|
+
const hooks = settings.hooks;
|
|
157
|
+
for (const [hookType, hookMatchers] of Object.entries(hooksConfig)) {
|
|
158
|
+
if (!hookMatchers || !Array.isArray(hookMatchers)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (!hooks[hookType] || !Array.isArray(hooks[hookType])) {
|
|
162
|
+
hooks[hookType] = [];
|
|
163
|
+
}
|
|
164
|
+
const existingHooks = hooks[hookType];
|
|
165
|
+
for (const matcher of hookMatchers) {
|
|
166
|
+
if (!hookMatcherExists(existingHooks, matcher)) {
|
|
167
|
+
existingHooks.push(matcher);
|
|
168
|
+
modified = true;
|
|
169
|
+
console.log(` \u2713 Added ${hookType} hook for ${skillName}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(` \u2139 ${hookType} hook already exists, skipping`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
hooks[hookType] = existingHooks;
|
|
175
|
+
}
|
|
176
|
+
if (modified) {
|
|
177
|
+
writeClaudeSettings(settings);
|
|
178
|
+
}
|
|
179
|
+
return modified;
|
|
180
|
+
}
|
|
181
|
+
|
|
119
182
|
// shared/src/install-skill.ts
|
|
120
183
|
function fetchFromRemote(tempDir, remoteSource) {
|
|
121
184
|
try {
|
|
@@ -124,7 +187,7 @@ function fetchFromRemote(tempDir, remoteSource) {
|
|
|
124
187
|
stdio: "pipe",
|
|
125
188
|
timeout: 6e4
|
|
126
189
|
});
|
|
127
|
-
if (
|
|
190
|
+
if (import_fs3.default.existsSync(import_path3.default.join(tempDir, "SKILL.md"))) {
|
|
128
191
|
console.log(" \u2713 Fetched latest version from remote");
|
|
129
192
|
return true;
|
|
130
193
|
}
|
|
@@ -146,14 +209,14 @@ function getSourceDir(config, packageDir) {
|
|
|
146
209
|
isRemote: false
|
|
147
210
|
};
|
|
148
211
|
}
|
|
149
|
-
const tempDir =
|
|
212
|
+
const tempDir = import_path3.default.join(import_os3.default.tmpdir(), `skill-fetch-${Date.now()}`);
|
|
150
213
|
const remoteSuccess = fetchFromRemote(tempDir, config.remoteSource);
|
|
151
214
|
if (remoteSuccess) {
|
|
152
215
|
return {
|
|
153
216
|
sourceDir: tempDir,
|
|
154
217
|
cleanup: () => {
|
|
155
218
|
try {
|
|
156
|
-
|
|
219
|
+
import_fs3.default.rmSync(tempDir, { recursive: true, force: true });
|
|
157
220
|
} catch {
|
|
158
221
|
}
|
|
159
222
|
},
|
|
@@ -161,7 +224,7 @@ function getSourceDir(config, packageDir) {
|
|
|
161
224
|
};
|
|
162
225
|
}
|
|
163
226
|
try {
|
|
164
|
-
|
|
227
|
+
import_fs3.default.rmSync(tempDir, { recursive: true, force: true });
|
|
165
228
|
} catch {
|
|
166
229
|
}
|
|
167
230
|
return {
|
|
@@ -172,11 +235,11 @@ function getSourceDir(config, packageDir) {
|
|
|
172
235
|
};
|
|
173
236
|
}
|
|
174
237
|
function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
175
|
-
const manifestPath =
|
|
238
|
+
const manifestPath = import_path3.default.join(skillsDir, ".skills-manifest.json");
|
|
176
239
|
let manifest = { skills: {} };
|
|
177
|
-
if (
|
|
240
|
+
if (import_fs3.default.existsSync(manifestPath)) {
|
|
178
241
|
try {
|
|
179
|
-
manifest = JSON.parse(
|
|
242
|
+
manifest = JSON.parse(import_fs3.default.readFileSync(manifestPath, "utf-8"));
|
|
180
243
|
} catch {
|
|
181
244
|
console.warn(" Warning: Could not parse existing manifest, creating new one");
|
|
182
245
|
manifest = { skills: {} };
|
|
@@ -187,55 +250,68 @@ function updateManifest(skillsDir, config, targetName, isRemote) {
|
|
|
187
250
|
version: config.version,
|
|
188
251
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
252
|
package: config.package || config.name,
|
|
190
|
-
path:
|
|
253
|
+
path: import_path3.default.join(skillsDir, skillName),
|
|
191
254
|
target: targetName,
|
|
192
255
|
...config.remoteSource && { source: config.remoteSource }
|
|
193
256
|
};
|
|
194
|
-
|
|
257
|
+
import_fs3.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
195
258
|
}
|
|
196
259
|
function installToTarget(target, config, sourceDir, isRemote) {
|
|
197
|
-
var _a;
|
|
260
|
+
var _a, _b;
|
|
198
261
|
console.log(`
|
|
199
262
|
\u{1F4E6} Installing to ${target.name}...`);
|
|
200
263
|
const isGlobal = isGlobalInstall();
|
|
201
264
|
const location = detectInstallLocation(target.paths, isGlobal);
|
|
202
265
|
const skillName = extractSkillName(config.name);
|
|
203
|
-
const targetDir =
|
|
204
|
-
const altTargetDir =
|
|
266
|
+
const targetDir = import_path3.default.join(location.base, skillName);
|
|
267
|
+
const altTargetDir = import_path3.default.join(location.base, config.name);
|
|
205
268
|
console.log(` Type: ${location.type}${isGlobal ? " (global)" : " (project)"}`);
|
|
206
269
|
console.log(` Directory: ${targetDir}`);
|
|
207
|
-
if (
|
|
270
|
+
if (import_fs3.default.existsSync(altTargetDir) && altTargetDir !== targetDir) {
|
|
208
271
|
console.log(" \u{1F9F9} Cleaning up alternative path format...");
|
|
209
272
|
removeDir(altTargetDir);
|
|
210
273
|
console.log(` \u2713 Removed directory: ${config.name}`);
|
|
211
274
|
}
|
|
212
275
|
ensureDir(targetDir);
|
|
213
|
-
const skillMdSource =
|
|
214
|
-
if (!
|
|
276
|
+
const skillMdSource = import_path3.default.join(sourceDir, "SKILL.md");
|
|
277
|
+
if (!import_fs3.default.existsSync(skillMdSource)) {
|
|
215
278
|
throw new Error("SKILL.md is required but not found");
|
|
216
279
|
}
|
|
217
|
-
|
|
280
|
+
import_fs3.default.copyFileSync(skillMdSource, import_path3.default.join(targetDir, "SKILL.md"));
|
|
218
281
|
console.log(" \u2713 Copied SKILL.md");
|
|
219
282
|
const filesToCopy = config.files || {};
|
|
220
283
|
for (const [source, dest] of Object.entries(filesToCopy)) {
|
|
221
|
-
const sourcePath =
|
|
222
|
-
if (!
|
|
284
|
+
const sourcePath = import_path3.default.join(sourceDir, source);
|
|
285
|
+
if (!import_fs3.default.existsSync(sourcePath)) {
|
|
223
286
|
console.warn(` \u26A0 Warning: ${source} not found, skipping`);
|
|
224
287
|
continue;
|
|
225
288
|
}
|
|
226
|
-
const destPath =
|
|
227
|
-
if (
|
|
289
|
+
const destPath = import_path3.default.join(targetDir, dest);
|
|
290
|
+
if (import_fs3.default.statSync(sourcePath).isDirectory()) {
|
|
228
291
|
copyDir(sourcePath, destPath);
|
|
229
292
|
console.log(` \u2713 Copied directory: ${source}`);
|
|
230
293
|
} else {
|
|
231
|
-
const destDir =
|
|
294
|
+
const destDir = import_path3.default.dirname(destPath);
|
|
232
295
|
ensureDir(destDir);
|
|
233
|
-
|
|
296
|
+
import_fs3.default.copyFileSync(sourcePath, destPath);
|
|
234
297
|
console.log(` \u2713 Copied file: ${source}`);
|
|
235
298
|
}
|
|
236
299
|
}
|
|
237
300
|
updateManifest(location.base, config, target.name, isRemote);
|
|
238
|
-
if ((_a = config.
|
|
301
|
+
if (target.name === "claude-code" && ((_a = config.claudeSettings) == null ? void 0 : _a.hooks)) {
|
|
302
|
+
console.log(" \u{1F527} \u914D\u7F6E Claude Code \u94A9\u5B50...");
|
|
303
|
+
try {
|
|
304
|
+
const skillName2 = extractSkillName(config.name);
|
|
305
|
+
const modified = addClaudeHooks(config.claudeSettings.hooks, skillName2);
|
|
306
|
+
if (modified) {
|
|
307
|
+
console.log(" \u2705 \u94A9\u5B50\u5DF2\u914D\u7F6E\u5230 ~/.claude/settings.json");
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
+
console.warn(` \u26A0 \u8B66\u544A: \u65E0\u6CD5\u914D\u7F6E\u94A9\u5B50: ${message}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if ((_b = config.hooks) == null ? void 0 : _b.postinstall) {
|
|
239
315
|
console.log(" \u{1F527} Running postinstall hook...");
|
|
240
316
|
try {
|
|
241
317
|
(0, import_child_process.execSync)(config.hooks.postinstall, {
|
package/package.json
CHANGED
package/src/git_analyzer.py
CHANGED
|
@@ -10,34 +10,56 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Any, Dict, List, Optional
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# 提交类型配置(无标签风格,直接描述工作内容)
|
|
14
14
|
COMMIT_TYPE_CONFIG = {
|
|
15
|
-
"feat": {"
|
|
16
|
-
"fix": {"
|
|
17
|
-
"refactor": {"
|
|
18
|
-
"perf": {"
|
|
19
|
-
"style": {"
|
|
20
|
-
"docs": {"
|
|
21
|
-
"test": {"
|
|
22
|
-
"chore": {"
|
|
23
|
-
"build": {"
|
|
24
|
-
"ci": {"
|
|
25
|
-
"other": {"
|
|
15
|
+
"feat": {"priority": 1, "is_highlight": True, "is_challenge": False},
|
|
16
|
+
"fix": {"priority": 2, "is_highlight": False, "is_challenge": True},
|
|
17
|
+
"refactor": {"priority": 3, "is_highlight": False, "is_challenge": False},
|
|
18
|
+
"perf": {"priority": 3, "is_highlight": True, "is_challenge": False},
|
|
19
|
+
"style": {"priority": 6, "is_highlight": False, "is_challenge": False},
|
|
20
|
+
"docs": {"priority": 5, "is_highlight": False, "is_challenge": False},
|
|
21
|
+
"test": {"priority": 4, "is_highlight": False, "is_challenge": False},
|
|
22
|
+
"chore": {"priority": 6, "is_highlight": False, "is_challenge": False},
|
|
23
|
+
"build": {"priority": 4, "is_highlight": False, "is_challenge": False},
|
|
24
|
+
"ci": {"priority": 5, "is_highlight": False, "is_challenge": False},
|
|
25
|
+
"other": {"priority": 7, "is_highlight": False, "is_challenge": False},
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# 琐碎提交的关键词(强制过滤)
|
|
30
30
|
TRIVIAL_PATTERNS = [
|
|
31
|
+
# typo 修复
|
|
31
32
|
r"^fix\s*typo",
|
|
32
33
|
r"^typo",
|
|
33
|
-
|
|
34
|
+
# 格式化/lint
|
|
35
|
+
r"^format",
|
|
36
|
+
r"^lint",
|
|
37
|
+
r"^style:",
|
|
38
|
+
r"^chore:\s*format",
|
|
39
|
+
r"^chore:\s*lint",
|
|
40
|
+
# 代码清理
|
|
41
|
+
r"^chore:\s*clean",
|
|
42
|
+
r"^refactor:\s*clean",
|
|
43
|
+
r"remove\s*(unused|debug|console)",
|
|
44
|
+
r"删除.*日志",
|
|
45
|
+
r"移除.*console",
|
|
46
|
+
# 文档完善(非重大更新)
|
|
47
|
+
r"^docs:\s*(update|fix|tweak)",
|
|
48
|
+
r"^chore:\s*docs",
|
|
49
|
+
r"完善.*文档",
|
|
50
|
+
r"更新.*readme",
|
|
51
|
+
# Merge 提交
|
|
34
52
|
r"^merge\s+branch",
|
|
35
53
|
r"^merge\s+pull\s+request",
|
|
54
|
+
# WIP 提交
|
|
36
55
|
r"^wip$",
|
|
37
56
|
r"^wip:",
|
|
38
|
-
|
|
39
|
-
r"^
|
|
40
|
-
r"
|
|
57
|
+
# 样式微调
|
|
58
|
+
r"^style:\s*(tweak|adjust|fix)",
|
|
59
|
+
r"样式.*微调",
|
|
60
|
+
# 依赖更新(小幅)
|
|
61
|
+
r"^chore\(deps\):",
|
|
62
|
+
r"bump.*version",
|
|
41
63
|
]
|
|
42
64
|
|
|
43
65
|
|
|
@@ -173,7 +195,6 @@ def get_commits(
|
|
|
173
195
|
"is_trivial": parsed["is_trivial"],
|
|
174
196
|
"is_highlight": parsed["is_highlight"],
|
|
175
197
|
"is_challenge": parsed["is_challenge"],
|
|
176
|
-
"label": parsed["label"],
|
|
177
198
|
"priority": parsed["priority"],
|
|
178
199
|
"project": get_repo_name(repo_path),
|
|
179
200
|
})
|
|
@@ -219,7 +240,6 @@ def parse_commit_message(message: str) -> Dict[str, Any]:
|
|
|
219
240
|
- is_trivial: 是否为琐碎提交
|
|
220
241
|
- is_highlight: 是否为重点(feat/perf 类型)
|
|
221
242
|
- is_challenge: 是否为难点(fix 类型)
|
|
222
|
-
- label: 类型标签(如 [新功能])
|
|
223
243
|
- priority: 优先级(用于排序)
|
|
224
244
|
"""
|
|
225
245
|
result = {
|
|
@@ -229,7 +249,6 @@ def parse_commit_message(message: str) -> Dict[str, Any]:
|
|
|
229
249
|
"is_trivial": False,
|
|
230
250
|
"is_highlight": False,
|
|
231
251
|
"is_challenge": False,
|
|
232
|
-
"label": "",
|
|
233
252
|
"priority": 7,
|
|
234
253
|
}
|
|
235
254
|
|
|
@@ -257,7 +276,6 @@ def parse_commit_message(message: str) -> Dict[str, Any]:
|
|
|
257
276
|
type_config = COMMIT_TYPE_CONFIG.get(commit_type, COMMIT_TYPE_CONFIG["other"])
|
|
258
277
|
result["is_highlight"] = type_config["is_highlight"]
|
|
259
278
|
result["is_challenge"] = type_config["is_challenge"]
|
|
260
|
-
result["label"] = type_config["label"]
|
|
261
279
|
result["priority"] = type_config["priority"]
|
|
262
280
|
|
|
263
281
|
return result
|
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
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def generate_report(
|
|
@@ -219,10 +219,11 @@ def format_project_section(
|
|
|
219
219
|
) -> str:
|
|
220
220
|
"""格式化项目部分
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
222
|
+
采用无标签风格,直接描述工作内容。
|
|
223
|
+
重点/难点通过以下方式体现:
|
|
224
|
+
- 重点工作:摘要字数更长(max_length=40),保留 2-3 个子条目
|
|
225
|
+
- 难点工作:保留排查过程细节
|
|
226
|
+
- 普通工作:简洁摘要(max_length=25),无子条目
|
|
226
227
|
|
|
227
228
|
Args:
|
|
228
229
|
project: 项目名称
|
|
@@ -234,9 +235,6 @@ def format_project_section(
|
|
|
234
235
|
lines = [project]
|
|
235
236
|
|
|
236
237
|
for commit in commits:
|
|
237
|
-
# 获取类型标签
|
|
238
|
-
label = commit.get("label", "")
|
|
239
|
-
|
|
240
238
|
# 分析重点/难点
|
|
241
239
|
significance = analyze_work_significance(commit)
|
|
242
240
|
|
|
@@ -251,22 +249,19 @@ def format_project_section(
|
|
|
251
249
|
# 普通工作:简洁摘要
|
|
252
250
|
max_len = 25
|
|
253
251
|
|
|
254
|
-
#
|
|
255
|
-
summary = summarize_commit(commit["message"], max_length=max_len
|
|
252
|
+
# 生成摘要(无标签)
|
|
253
|
+
summary = summarize_commit(commit["message"], max_length=max_len)
|
|
256
254
|
|
|
257
255
|
lines.append(f" - {summary}")
|
|
258
256
|
|
|
259
257
|
# 添加子条目细节
|
|
260
|
-
#
|
|
258
|
+
# 重点/难点保留细节,普通工作不展开
|
|
261
259
|
details = commit.get("details") or []
|
|
262
260
|
if significance["is_highlight"] or significance["is_challenge"]:
|
|
263
|
-
#
|
|
264
|
-
for detail in details[:
|
|
265
|
-
lines.append(f" - {detail}")
|
|
266
|
-
else:
|
|
267
|
-
# 普通工作:最多保留2条细节
|
|
268
|
-
for detail in details[:2]:
|
|
261
|
+
# 重点/难点:保留 2-3 条细节
|
|
262
|
+
for detail in details[:3]:
|
|
269
263
|
lines.append(f" - {detail}")
|
|
264
|
+
# 普通工作:不展开子条目
|
|
270
265
|
|
|
271
266
|
return "\n".join(lines)
|
|
272
267
|
|
|
@@ -291,9 +286,8 @@ def format_other_section(supplements: List[str]) -> str:
|
|
|
291
286
|
def summarize_commit(
|
|
292
287
|
message: str,
|
|
293
288
|
max_length: int = 30,
|
|
294
|
-
label: str = "",
|
|
295
289
|
) -> str:
|
|
296
|
-
"""
|
|
290
|
+
"""生成提交摘要(智能截断,无标签风格)
|
|
297
291
|
|
|
298
292
|
在自然断点处截断,避免割裂语义:
|
|
299
293
|
- 优先在标点符号处截断
|
|
@@ -302,11 +296,10 @@ def summarize_commit(
|
|
|
302
296
|
|
|
303
297
|
Args:
|
|
304
298
|
message: 提交信息
|
|
305
|
-
max_length:
|
|
306
|
-
label: 类型标签(如 [新功能])
|
|
299
|
+
max_length: 最大长度
|
|
307
300
|
|
|
308
301
|
Returns:
|
|
309
|
-
|
|
302
|
+
摘要文本(无标签)
|
|
310
303
|
"""
|
|
311
304
|
cleaned = clean_commit_message(message)
|
|
312
305
|
|
|
@@ -339,10 +332,6 @@ def summarize_commit(
|
|
|
339
332
|
|
|
340
333
|
cleaned = truncated
|
|
341
334
|
|
|
342
|
-
# 添加类型标签
|
|
343
|
-
if label:
|
|
344
|
-
return f"{label} {cleaned.strip()}"
|
|
345
|
-
|
|
346
335
|
return cleaned.strip()
|
|
347
336
|
|
|
348
337
|
|
package/uninstall-skill.js
CHANGED
|
@@ -23,8 +23,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// shared/src/uninstall-skill.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
26
|
+
var import_fs3 = __toESM(require("fs"));
|
|
27
|
+
var import_path3 = __toESM(require("path"));
|
|
28
28
|
|
|
29
29
|
// shared/src/utils.ts
|
|
30
30
|
var import_fs = __toESM(require("fs"));
|
|
@@ -96,17 +96,77 @@ function readSkillConfig(dir) {
|
|
|
96
96
|
return JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// shared/src/claude-settings.ts
|
|
100
|
+
var import_fs2 = __toESM(require("fs"));
|
|
101
|
+
var import_path2 = __toESM(require("path"));
|
|
102
|
+
var import_os2 = __toESM(require("os"));
|
|
103
|
+
function getClaudeSettingsPath() {
|
|
104
|
+
return import_path2.default.join(import_os2.default.homedir(), ".claude", "settings.json");
|
|
105
|
+
}
|
|
106
|
+
function readClaudeSettings() {
|
|
107
|
+
const settingsPath = getClaudeSettingsPath();
|
|
108
|
+
if (!import_fs2.default.existsSync(settingsPath)) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const content = import_fs2.default.readFileSync(settingsPath, "utf-8");
|
|
113
|
+
return JSON.parse(content);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn(" \u26A0 Warning: Could not parse settings.json, treating as empty");
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function writeClaudeSettings(settings) {
|
|
120
|
+
const settingsPath = getClaudeSettingsPath();
|
|
121
|
+
const settingsDir = import_path2.default.dirname(settingsPath);
|
|
122
|
+
if (!import_fs2.default.existsSync(settingsDir)) {
|
|
123
|
+
import_fs2.default.mkdirSync(settingsDir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
import_fs2.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
126
|
+
}
|
|
127
|
+
function removeClaudeHooks(hooksConfig, skillName) {
|
|
128
|
+
const settings = readClaudeSettings();
|
|
129
|
+
let modified = false;
|
|
130
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const hooks = settings.hooks;
|
|
134
|
+
for (const [hookType, hookMatchers] of Object.entries(hooksConfig)) {
|
|
135
|
+
if (!hookMatchers || !Array.isArray(hookMatchers)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!hooks[hookType] || !Array.isArray(hooks[hookType])) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const existingHooks = hooks[hookType];
|
|
142
|
+
const initialLength = existingHooks.length;
|
|
143
|
+
const matchersToRemove = hookMatchers.map((m) => m.matcher);
|
|
144
|
+
const filteredHooks = existingHooks.filter(
|
|
145
|
+
(hook) => !matchersToRemove.includes(hook.matcher)
|
|
146
|
+
);
|
|
147
|
+
if (filteredHooks.length < initialLength) {
|
|
148
|
+
hooks[hookType] = filteredHooks;
|
|
149
|
+
modified = true;
|
|
150
|
+
console.log(` \u2713 Removed ${hookType} hook for ${skillName}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (modified) {
|
|
154
|
+
writeClaudeSettings(settings);
|
|
155
|
+
}
|
|
156
|
+
return modified;
|
|
157
|
+
}
|
|
158
|
+
|
|
99
159
|
// shared/src/uninstall-skill.ts
|
|
100
160
|
function updateManifest(skillsDir, config) {
|
|
101
|
-
const manifestPath =
|
|
102
|
-
if (!
|
|
161
|
+
const manifestPath = import_path3.default.join(skillsDir, ".skills-manifest.json");
|
|
162
|
+
if (!import_fs3.default.existsSync(manifestPath)) {
|
|
103
163
|
return;
|
|
104
164
|
}
|
|
105
165
|
try {
|
|
106
|
-
const manifest = JSON.parse(
|
|
166
|
+
const manifest = JSON.parse(import_fs3.default.readFileSync(manifestPath, "utf-8"));
|
|
107
167
|
if (manifest.skills && manifest.skills[config.name]) {
|
|
108
168
|
delete manifest.skills[config.name];
|
|
109
|
-
|
|
169
|
+
import_fs3.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
110
170
|
console.log(" \u2713 Updated manifest");
|
|
111
171
|
}
|
|
112
172
|
} catch (error) {
|
|
@@ -115,25 +175,38 @@ function updateManifest(skillsDir, config) {
|
|
|
115
175
|
}
|
|
116
176
|
}
|
|
117
177
|
function uninstallFromTarget(target, config) {
|
|
178
|
+
var _a;
|
|
118
179
|
console.log(`
|
|
119
180
|
\u{1F5D1}\uFE0F Uninstalling from ${target.name}...`);
|
|
120
181
|
const isGlobal = isGlobalInstall();
|
|
121
182
|
const location = detectInstallLocation(target.paths, isGlobal);
|
|
122
183
|
const skillName = extractSkillName(config.name);
|
|
123
|
-
const skillNameTargetDir =
|
|
124
|
-
const fullPackageNameTargetDir =
|
|
184
|
+
const skillNameTargetDir = import_path3.default.join(location.base, skillName);
|
|
185
|
+
const fullPackageNameTargetDir = import_path3.default.join(location.base, config.name);
|
|
125
186
|
let removed = false;
|
|
126
|
-
if (
|
|
187
|
+
if (import_fs3.default.existsSync(skillNameTargetDir)) {
|
|
127
188
|
removeDir(skillNameTargetDir);
|
|
128
189
|
console.log(` \u2713 Removed skill directory: ${skillName}`);
|
|
129
190
|
removed = true;
|
|
130
191
|
}
|
|
131
|
-
if (
|
|
192
|
+
if (import_fs3.default.existsSync(fullPackageNameTargetDir) && fullPackageNameTargetDir !== skillNameTargetDir) {
|
|
132
193
|
removeDir(fullPackageNameTargetDir);
|
|
133
194
|
console.log(` \u2713 Removed skill directory: ${config.name}`);
|
|
134
195
|
removed = true;
|
|
135
196
|
}
|
|
136
197
|
updateManifest(location.base, config);
|
|
198
|
+
if (target.name === "claude-code" && removed && ((_a = config.claudeSettings) == null ? void 0 : _a.hooks)) {
|
|
199
|
+
try {
|
|
200
|
+
console.log(" \u{1F527} \u79FB\u9664 Claude Code \u94A9\u5B50...");
|
|
201
|
+
const skillName2 = extractSkillName(config.name);
|
|
202
|
+
const modified = removeClaudeHooks(config.claudeSettings.hooks, skillName2);
|
|
203
|
+
if (modified) {
|
|
204
|
+
console.log(" \u2705 \u94A9\u5B50\u5DF2\u4ECE ~/.claude/settings.json \u79FB\u9664");
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.warn(" \u26A0 \u8B66\u544A: \u65E0\u6CD5\u79FB\u9664\u94A9\u5B50\uFF08\u53EF\u5B89\u5168\u5FFD\u7565\uFF09");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
137
210
|
if (removed) {
|
|
138
211
|
console.log(` \u2705 Uninstalled from ${target.name}`);
|
|
139
212
|
return true;
|