feat(growth): add contributor growth skill with dynamic badge generation

Add gitlink-growth skill (SKILL.md) with workflow for PR/Issue activity
tracking, contributor rankings, and badge awarding. Include report template
and SQL query reference with exploration queries for data-driven badge design.
This commit is contained in:
Yingjie Shang 2026-05-26 21:37:16 +08:00
parent 42bc83295a
commit 66e230c30d
3 changed files with 648 additions and 0 deletions

View File

@ -0,0 +1,118 @@
---
name: gitlink-growth
version: 1.0.0
description: "开源贡献者成长体系:追踪 PR/Issue 活动 → 生成贡献排行 → 自动颁发徽章。当用户提到贡献者排行、贡献成长、颁发徽章、开发者活跃度排名等意图时触发。"
metadata:
requires:
bins: ["gitlink-cli", "sqlite3"]
cliHelp: "gitlink-cli health +fetch --help"
---
# gitlink-growth开源贡献者成长体系
**CRITICAL — 前置阅读:**
1. [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) — 认证、权限处理
2. [`../gitlink-health/references/queries.md`](../gitlink-health/references/queries.md) — 共享数据库表结构
3. [`../gitlink-health/SKILL.md`](../gitlink-health/SKILL.md) — 数据采集命令参考
## 与 gitlink-health 的关系
gitlink-growth **复用** gitlink-health 的 `health +fetch` 命令和同一 SQLite 数据库,但分析视角完全不同:
| 维度 | gitlink-health | gitlink-growth |
|------|---------------|----------------|
| **视角** | 项目整体健康度 | 个人贡献者成长 |
| **输出** | 项目的 Issue/PR/贡献者指标 | 贡献者排行 + 徽章 |
| **关心** | 合并率、解决时长、活跃度 | 谁贡献最多、什么类型、持续度 |
## 目录结构
```
skills/gitlink-growth/
├── SKILL.md # 本文件
├── references/
│ └── queries.md # 排名 & 徽章 SQL 查询
└── asset/
└── growth_report_template.md # 报告模板
```
> **数据库**直接读取 gitlink-health 的 SQLite 文件,不维护独立数据目录。
## 工作流程
### 1. 询问分析偏好
向用户确认以下参数,如果用户未明确指定全部参数,必须逐一询问:
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--owner` | 仓库所有者 | 自动从 git remote 推断 |
| `--repo` | 仓库名称 | 自动从 git remote 推断 |
| **时间周期** | 分析的时间范围 | **全量**(从仓库存在至今) |
时间周期选项:
- **本周** — 本周一 00:00:00 至现在
- **本月** — 本月 1 日 00:00:00 至现在
- **本年** — 今年 1 月 1 日 00:00:00 至现在
- **自定义范围** — 用户指定起止日期(如 `2026-01-01 ~ 2026-06-30`
- **全量**(默认)— 不设时间过滤,查询全部数据
> **记住偏好**:将用户选择的时间周期存入记忆,后续分析直接使用。
### 2. 采集数据
```bash
gitlink-cli health +fetch --owner <owner> --repo <repo>
```
`health +fetch` 会将 PR、Issue、用户、标签数据全部拉取到本地 SQLite 数据库中。数据库默认路径为 `~/.agents/skills/gitlink-health/data/gitlink_health.db`
> **注意:** 如果数据库已有该仓库的数据且用户只追加了少量新活动,再次 `+fetch` 会增量更新不会丢失历史。
### 3. 查询排名并生成报告
1. 打开 [`references/queries.md`](references/queries.md) 获取 SQL 查询参考
2. 确定 `repo_id`
3. 根据用户选择的时间周期替换 `<start_date>``<end_date>`
4. 按查询清单逐项执行
5. 将结果填入 [`asset/growth_report_template.md`](asset/growth_report_template.md)
6. 根据 [`references/queries.md#9-徽章生成`](references/queries.md#9-徽章生成) 中的流程分析数据并设计颁发徽章
### 4. 输出报告
将填充完毕的报告写入 markdown 文件并告知用户文件路径。
### 5. 提交报告并上传
询问用户当前报告是否合格,若通过,则通过`git add`添加报告文件,通过`git commit`提交修改,通过`git push`将报告推送至平台。
## 数据库路径
数据存储在 gitlink-health 的 SQLite 数据库中。默认路径:
- **Linux / macOS:** `~/.agents/skills/gitlink-health/data/gitlink_health.db`
- 如果 `health +fetch` 使用了 `--db` 指定了其他路径,则使用该路径
## 徽章规则
Agent 应根据仓库实际数据自行设计徽章,而非套用预定义列表。详见 [`references/queries.md#9-徽章生成`](references/queries.md#9-徽章生成)。
**原则:**
- 执行查询 1-8 和勘探查询获取完整数据画像,从数据中识别模式
- 徽章名称从数据特征派生(如标签名、行为类型),不套用固定模板
- 阈值参考数据分布(取前 N 名、高于均值、或连续 N 周活跃等)
- 达到阈值的贡献者都获得徽章,不设人数上限;贡献者平票时多人并列
- 每个徽章至少 1 人获得;无人达标则不发
> **关于"处理者"processor** 表中的 `processor_id` 即**指派人**assignee。GitLink API 不提供 `merged_by`(合入者)信息,所以用 `processor_id` 来代表负责处理该 PR/Issue 的人。
## 边界情况处理
| 情况 | 处理方式 |
|------|---------|
| 数据库不存在 | 提示用户先执行 `health +fetch`,不要试图创建空报告 |
| 时段内无数据 | 排行榜显示「本期无贡献数据」,不颁发任何徽章 |
| 贡献者平票 | 同一名次允许多人并列,徽章颁发给所有平票者 |
| 无标签数据 | 跳过"细分领域排行"章节,不输出空表 |
| 仓库无 Issue/PR | 跳过对应的排行章节 |
| `processor_id` 均为 NULL | 跳过"处理排行"章节,并说明「暂无已指派的记录」 |

View File

@ -0,0 +1,86 @@
# 开源贡献者成长报告
| 项目 | 内容 |
|------|------|
| **仓库** | {owner}/{repo} |
| **分析期间** | {start_date} ~ {end_date} |
| **生成时间** | {generated_at} |
---
## 🏅 徽章展示
本期共有 **{badge_count}** 位贡献者获得徽章:
| 徽章 | 获得者 | 说明 |
|------|--------|------|
| {badge_list} | | |
> *徽章条件由数据分布决定;达标者均颁发。平票时多人并列。*
---
## 📊 贡献排行榜
### 综合贡献排名PR + Issue
| 排名 | 贡献者 | PR 数 | Issue 数 | 合计 |
|------|--------|-------|----------|------|
| {rank} | {user_name} | {pr_count} | {issue_count} | {total} |
### PR 创建数排名
| 排名 | 贡献者 | PR 数 | 已合并 | 合并率 |
|------|--------|-------|--------|--------|
| {rank} | {user_name} | {pr_count} | {merged_count} | {merge_rate_pct}% |
### Issue 创建数排名
| 排名 | 贡献者 | Issue 数 | 已解决 |
|------|--------|----------|--------|
| {rank} | {user_name} | {issue_count} | {closed_count} |
### PR 处理数排名
| 排名 | 贡献者 | 处理 PR 数 |
|------|--------|-----------|
| {rank} | {user_name} | {processed_count} |
*{pr_processed_note}*
### Issue 处理数排名
| 排名 | 贡献者 | 处理 Issue 数 |
|------|--------|--------------|
| {rank} | {user_name} | {processed_count} |
*{issue_processed_note}*
---
## 🏷️ 细分领域排行
### PR 按标签排名
| 标签 | 贡献者 | PR 数 |
|------|--------|-------|
| {tag} | {user_name} | {count} |
{pr_tag_note}
### Issue 按标签排名
| 标签 | 贡献者 | Issue 数 |
|------|--------|----------|
| {tag} | {user_name} | {count} |
{issue_tag_note}
---
## 报告说明
- 数据来源GitLink API通过 `gitlink-cli health +fetch` 采集
- 分析期间:{start_date} ~ {end_date}
- 徽章根据仓库实际数据动态设计颁发
- {note_empty}

View File

@ -0,0 +1,444 @@
# 贡献排行 & 徽章 SQL 查询参考
所有查询使用 SQLite。将 `<repo_id>`、`<start_date>`、`<end_date>` 替换为实际值。
> **已知数据质量问题:** `health +fetch` 在采集标签时可能将跨仓库的同名标签复用为同一个 `tag_id`,导致 `pull_tags`/`issue_tags` 引用了不属于目标仓库的标签。为此,所有 JOIN `tags` 的查询都加了 `t.repo_id = <repo_id>` 防护。如果发现标签数据异常,请先执行查询 8 确认目标仓库的实际标签列表。
> **注意:** 时间范围条件需用 `date()` 包裹列名,避免字典序比较导致当日数据被排除。下文所有查询已按此规则编写。
## 表结构
gitlink-growth 与 gitlink-health 共用同一 SQLite 数据库,表结构由 `health +fetch` 创建。核心表如下:
### users
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT NOT NULL UNIQUE
);
```
### repos
```sql
CREATE TABLE repos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_name TEXT NOT NULL,
owner_id INTEGER NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id),
UNIQUE(repo_name, owner_id)
);
```
### pulls
```sql
CREATE TABLE pulls (
id INTEGER PRIMARY KEY,
repo_id INTEGER NOT NULL,
number INTEGER NOT NULL,
creater_id INTEGER,
processor_id INTEGER,
status TEXT CHECK(status IN ('merged', 'closed', 'open')),
create_time TIMESTAMP,
close_time TIMESTAMP,
FOREIGN KEY (repo_id) REFERENCES repos(id),
FOREIGN KEY (creater_id) REFERENCES users(id),
FOREIGN KEY (processor_id) REFERENCES users(id)
);
```
`status` 取值:`open`(开放)、`merged`(已合并)、`closed`(已关闭未合并)。`close_time` 始终为 NULLAPI 不提供)。
`processor_id` 表示 PR 的**指派人**assignee——因为 GitLink API 不提供 `merged_by`(合并者)信息,所以用 `processor_id` 代表"处理者"角色,即被指派来 review/merge 该 PR 的人。
### issues
```sql
CREATE TABLE issues (
id INTEGER PRIMARY KEY,
repo_id INTEGER NOT NULL,
number INTEGER,
creater_id INTEGER,
processor_id INTEGER,
create_time TIMESTAMP,
close_time TIMESTAMP,
status TEXT CHECK(status IN ('close', 'open')),
FOREIGN KEY (repo_id) REFERENCES repos(id),
FOREIGN KEY (creater_id) REFERENCES users(id),
FOREIGN KEY (processor_id) REFERENCES users(id)
);
```
`status` 取值:`open`(开放)、`close`(已关闭)。
`processor_id` 表示 Issue 的**指派人**assignee——与 PR 同理,因 API 无 `merged_by``processor_id` 代表负责处理该 Issue 的人。
### tags
```sql
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (repo_id) REFERENCES repos(id),
UNIQUE(repo_id, name)
);
```
### pull_tags / issue_tags
```sql
CREATE TABLE pull_tags (
pull_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (pull_id) REFERENCES pulls(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (pull_id, tag_id)
);
CREATE TABLE issue_tags (
issue_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (issue_id, tag_id)
);
```
> 完整字段映射说明见 [`../gitlink-health/references/queries.md`](../gitlink-health/references/queries.md#表结构)。
## 数据库路径
默认:`~/.agents/skills/gitlink-health/data/gitlink_health.db`
```bash
DB=~/.agents/skills/gitlink-health/data/gitlink_health.db
```
## 时间范围计算
| 周期 | SQL 计算 |
|------|---------|
| 本周 | `date('now', 'weekday 0', '-6 days')` ~ `date('now')` |
| 本月 | `date('now', 'start of month')` ~ `date('now')` |
| 本年 | `date('now', 'start of year')` ~ `date('now')` |
| 自定义 | `'<user-start>'` ~ `'<user-end>'` |
| 全量 | `'1970-01-01'` ~ `date('now')` |
## 0. 确定目标仓库
```bash
# 列出所有仓库
sqlite3 "$DB" "SELECT r.id, u.user_name AS owner, r.repo_name FROM repos r JOIN users u ON r.owner_id = u.id;"
# 精确查找 repo_id
sqlite3 "$DB" "SELECT r.id FROM repos r JOIN users u ON r.owner_id = u.id WHERE u.user_name = '<owner>' AND r.repo_name = '<repo>';"
```
---
## 1. 综合贡献排名PR + Issue
```sql
SELECT
u.user_name,
COALESCE(pr.cnt, 0) AS pr_count,
COALESCE(iss.cnt, 0) AS issue_count,
COALESCE(pr.cnt, 0) + COALESCE(iss.cnt, 0) AS total
FROM users u
LEFT JOIN (
SELECT creater_id, COUNT(*) AS cnt
FROM pulls
WHERE repo_id = <repo_id>
AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY creater_id
) pr ON u.id = pr.creater_id
LEFT JOIN (
SELECT creater_id, COUNT(*) AS cnt
FROM issues
WHERE repo_id = <repo_id>
AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY creater_id
) iss ON u.id = iss.creater_id
WHERE pr.creater_id IS NOT NULL OR iss.creater_id IS NOT NULL
ORDER BY total DESC;
```
---
## 2. PR 创建数排名
```sql
SELECT
u.user_name,
COUNT(*) AS pr_created,
SUM(CASE WHEN p.status = 'merged' THEN 1 ELSE 0 END) AS merged_count,
ROUND(CAST(SUM(CASE WHEN p.status = 'merged' THEN 1 ELSE 0 END) AS REAL) / NULLIF(COUNT(*), 0) * 100, 1) AS merge_rate_pct
FROM pulls p
JOIN users u ON p.creater_id = u.id
WHERE p.repo_id = <repo_id>
AND date(p.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY p.creater_id
ORDER BY pr_created DESC;
```
---
## 3. Issue 创建数排名
```sql
SELECT
u.user_name,
COUNT(*) AS issue_created,
SUM(CASE WHEN i.status = 'close' THEN 1 ELSE 0 END) AS closed_count
FROM issues i
JOIN users u ON i.creater_id = u.id
WHERE i.repo_id = <repo_id>
AND date(i.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY i.creater_id
ORDER BY issue_created DESC;
```
---
## 4. PR 处理数排名
```sql
SELECT
u.user_name,
COUNT(*) AS processed_count
FROM pulls p
JOIN users u ON p.processor_id = u.id
WHERE p.repo_id = <repo_id>
AND date(p.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY p.processor_id
ORDER BY processed_count DESC;
```
---
## 5. Issue 处理数排名
```sql
SELECT
u.user_name,
COUNT(*) AS processed_count
FROM issues i
JOIN users u ON i.processor_id = u.id
WHERE i.repo_id = <repo_id>
AND date(i.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY i.processor_id
ORDER BY processed_count DESC;
```
---
## 6. 按标签统计 PR 排名
```sql
SELECT
t.name AS tag,
u.user_name,
COUNT(*) AS pr_count
FROM pulls p
JOIN users u ON p.creater_id = u.id
JOIN pull_tags pt ON p.id = pt.pull_id
JOIN tags t ON pt.tag_id = t.id
WHERE p.repo_id = <repo_id>
AND t.repo_id = <repo_id>
AND date(p.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY t.name, p.creater_id
ORDER BY t.name, pr_count DESC;
```
---
## 7. 按标签统计 Issue 排名
```sql
SELECT
t.name AS tag,
u.user_name,
COUNT(*) AS issue_count
FROM issues i
JOIN users u ON i.creater_id = u.id
JOIN issue_tags it ON i.id = it.issue_id
JOIN tags t ON it.tag_id = t.id
WHERE i.repo_id = <repo_id>
AND t.repo_id = <repo_id>
AND date(i.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY t.name, i.creater_id
ORDER BY t.name, issue_count DESC;
```
---
## 8. 标签列表(确认仓库有哪些标签)
```sql
SELECT t.name, COUNT(pt.pull_id) + COUNT(it.issue_id) AS usage_count
FROM tags t
LEFT JOIN pull_tags pt ON t.id = pt.tag_id
LEFT JOIN issue_tags it ON t.id = it.tag_id
WHERE t.repo_id = <repo_id>
GROUP BY t.id
ORDER BY usage_count DESC;
```
---
## 9. 徽章生成
Agent 应根据仓库实际数据自行设计徽章,而非套用预定义列表。
### 流程
1. 执行查询 1-8 获取排名和标签数据
2. 从数据中识别值得表彰的模式(活跃度、质量、持续性、领域专精等)
3. 为每种模式设计徽章:命名、设定条件、确定获奖者
### 勘探查询
以下附加查询用于提取维度数据,帮助发现模式。
#### 用户综合画像
```sql
SELECT
u.user_name,
COUNT(DISTINCT p.id) AS pr_created,
SUM(CASE WHEN p.status = 'merged' THEN 1 ELSE 0 END) AS pr_merged,
COUNT(DISTINCT i.id) AS issue_created,
COUNT(DISTINCT proc_pr.id) AS pr_processed,
COUNT(DISTINCT proc_is.id) AS issue_processed
FROM users u
LEFT JOIN pulls p ON u.id = p.creater_id AND p.repo_id = <repo_id>
AND date(p.create_time) BETWEEN '<start_date>' AND '<end_date>'
LEFT JOIN issues i ON u.id = i.creater_id AND i.repo_id = <repo_id>
AND date(i.create_time) BETWEEN '<start_date>' AND '<end_date>'
LEFT JOIN pulls proc_pr ON u.id = proc_pr.processor_id AND proc_pr.repo_id = <repo_id>
AND date(proc_pr.create_time) BETWEEN '<start_date>' AND '<end_date>'
LEFT JOIN issues proc_is ON u.id = proc_is.processor_id AND proc_is.repo_id = <repo_id>
AND date(proc_is.create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY u.id
HAVING pr_created > 0 OR issue_created > 0 OR pr_processed > 0 OR issue_processed > 0
ORDER BY (pr_created + pr_merged + issue_created + pr_processed + issue_processed) DESC;
```
#### 新人发现
```sql
SELECT
u.user_name,
MIN(MIN(p.create_time), MIN(i.create_time)) AS first_activity,
COALESCE(pr.cnt, 0) + COALESCE(iss.cnt, 0) AS period_activity
FROM users u
LEFT JOIN pulls p ON u.id = p.creater_id AND p.repo_id = <repo_id>
LEFT JOIN issues i ON u.id = i.creater_id AND i.repo_id = <repo_id>
LEFT JOIN (
SELECT creater_id, COUNT(*) AS cnt FROM pulls
WHERE repo_id = <repo_id> AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY creater_id
) pr ON u.id = pr.creater_id
LEFT JOIN (
SELECT creater_id, COUNT(*) AS cnt FROM issues
WHERE repo_id = <repo_id> AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
GROUP BY creater_id
) iss ON u.id = iss.creater_id
WHERE first_activity IS NOT NULL
GROUP BY u.id
HAVING date(first_activity) BETWEEN '<start_date>' AND '<end_date>'
ORDER BY period_activity DESC;
```
#### 活跃持续性(按周)
```sql
SELECT
u.user_name,
COUNT(DISTINCT week) AS active_weeks,
MIN(week) AS first_week,
MAX(week) AS last_week
FROM (
SELECT creater_id, strftime('%Y-%W', create_time) AS week FROM pulls
WHERE repo_id = <repo_id> AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
UNION
SELECT creater_id, strftime('%Y-%W', create_time) FROM issues
WHERE repo_id = <repo_id> AND date(create_time) BETWEEN '<start_date>' AND '<end_date>'
) activity
JOIN users u ON activity.creater_id = u.id
GROUP BY u.id
HAVING active_weeks >= 2
ORDER BY active_weeks DESC;
```
#### 标签贡献模板
先执行查询 8 获取标签列表,再逐个代入:
```sql
-- 将 <tag_name> 替换为查询 8 返回的实际标签名
SELECT u.user_name, COUNT(*) AS tag_contribution
FROM users u
LEFT JOIN (
SELECT creater_id FROM pulls p
JOIN pull_tags pt ON p.id = pt.pull_id
JOIN tags t ON pt.tag_id = t.id
WHERE p.repo_id = <repo_id> AND t.repo_id = <repo_id>
AND t.name = '<tag_name>'
AND date(p.create_time) BETWEEN '<start_date>' AND '<end_date>'
) tag_pr ON u.id = tag_pr.creater_id
LEFT JOIN (
SELECT creater_id FROM issues i
JOIN issue_tags it ON i.id = it.issue_id
JOIN tags t ON it.tag_id = t.id
WHERE i.repo_id = <repo_id> AND t.repo_id = <repo_id>
AND t.name = '<tag_name>'
AND date(i.create_time) BETWEEN '<start_date>' AND '<end_date>'
) tag_iss ON u.id = tag_iss.creater_id
WHERE tag_pr.creater_id IS NOT NULL OR tag_iss.creater_id IS NOT NULL
GROUP BY u.id
ORDER BY tag_contribution DESC;
```
### 设计原则
- 徽章名称从数据特征派生(如标签名、行为类型),不套用固定模板
- 阈值参考数据分布(取前 N 名、高于均值、或连续 N 周活跃等)
- 每个徽章至少 1 人获得;无人达标则不发
- 在报告中说明每个徽章的数据依据
---
## 报告组装清单
**按以下顺序逐项执行查询、填入模板。每完成一项打勾,最终输出前核对所有 ✅ 是否齐全。**
### 排行榜数据
- [ ] **综合贡献排名** → 执行查询 1取全部结果
- [ ] **PR 创建数排名** → 执行查询 2取前 10 名
- [ ] **Issue 创建数排名** → 执行查询 3取前 10 名
- [ ] **PR 处理数排名** → 执行查询 4取前 10 名(结果为空则跳过此项)
- [ ] **Issue 处理数排名** → 执行查询 5取前 10 名(结果为空则跳过此项)
### 细分领域排行(仅当有标签数据时)
- [ ] **标签列表** → 执行查询 8确认仓库有哪些标签
- [ ] **按标签 PR 排名** → 执行查询 6有标签数据时执行
- [ ] **按标签 Issue 排名** → 执行查询 7有标签数据时执行
- [ ] **按标签汇总填充** → 填入模板的「细分领域排行」章节
### 徽章数据
- [ ] 执行查询 1-8 和勘探查询获取完整数据画像
- [ ] 分析数据,识别值得表彰的模式
- [ ] 为每种模式设计徽章(名称、条件、获奖者),附数据依据
- [ ] 若全维度均无突出数据,输出"本期无人获得徽章"
### 最终输出核对
- [ ] 报告包含「徽章展示」「贡献排行榜」「细分领域排行」三个主要章节
- [ ] 徽章表格不为空(即使无徽章,也显示"本期无人获得徽章"
- [ ] 排行榜表格有列名
- [ ] 报告头部包含 owner/repo、分析期间、生成时间