Compare commits
2 Commits
master
...
Update_Wor
| Author | SHA1 | Date |
|---|---|---|
|
|
2e42d9356d | |
|
|
4b2bbbcac9 |
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
面向 GitLink 竞赛子赛题三的端到端自动化工作流项目。
|
||||
|
||||
本项目面向开源社区运营场景,使用 `gitlink-cli` 串联仓库信息、Issue、PR 和 Release 数据采集,自动生成社区周报、Release Notes 草稿和结构化摘要,并支持将摘要发布到指定 GitLink Issue。该流程覆盖“数据采集 -> 指标分析 -> 文档生成 -> 结果发布”的完整闭环。
|
||||
本项目面向开源社区运营场景,使用 `gitlink-cli` 串联 Issue 分拣、责任人分配、仓库信息、Issue、PR 和 Release 数据采集,自动生成社区周报、Release Notes 草稿和结构化摘要,并支持将摘要发布到指定 GitLink Issue。该流程覆盖“新 Issue 自动分类 -> 分配责任人 -> 数据采集 -> 指标分析 -> 文档生成 -> 结果发布”的完整闭环。
|
||||
|
||||
Issue 分类规则来自自定义 Skill:`D:\BigData\CCFer\CCF\gitlink-issue-triage-rules\SKILL.md`。工作流代码会直接读取 Skill 中的机器可读 JSON 规则块,不再依赖 `workflow +triage` 内置分类结果。
|
||||
|
||||
## 交付物
|
||||
|
||||
|
|
@ -39,15 +41,60 @@
|
|||
- `outputs/*_report.md`
|
||||
- `outputs/*_release_notes.md`
|
||||
- `outputs/*_summary.json`
|
||||
- `outputs/*_triage_plan.md`
|
||||
- `outputs/*_triage_plan.json`
|
||||
|
||||
## Issue 自动分类与派单
|
||||
|
||||
默认运行会执行 dry-run,只生成分类和派单计划,不写回真实 Issue:
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1
|
||||
```
|
||||
|
||||
确认计划无误后,显式开启写回:
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1 -ApplyTriage
|
||||
```
|
||||
|
||||
如需显式指定 Skill 路径:
|
||||
|
||||
```powershell
|
||||
python .\scripts\gitlink_workflow.py --config .\examples\demo_active_config.json --triage-skill D:\BigData\CCFer\CCF\gitlink-issue-triage-rules\SKILL.md
|
||||
```
|
||||
|
||||
写回链路会先通过 `issue +list` 拉取开放 Issue,再读取自定义 Skill 规则完成分类;随后通过 `label +list`、`issue +assigners`、`issue +view` 和 `issue +update` 完成标签、优先级和负责人写入。若配置开启 `comment`,还会追加一条审计评论。`*_triage_plan.json` 中会记录 `source=skill-json`、Skill 路径和命中的规则 ID。
|
||||
|
||||
### 负责人分配配置
|
||||
|
||||
示例配置使用显式三成员池,避免依赖运行环境是否能访问 `member +list`。负责人分配采用按类型映射:
|
||||
|
||||
```json
|
||||
{
|
||||
"security": [153579],
|
||||
"bug": [153579],
|
||||
"performance": [153579],
|
||||
"feature": [134328],
|
||||
"refactor": [134328],
|
||||
"ci": [134328],
|
||||
"question": [134450],
|
||||
"docs": [134450],
|
||||
"duplicate": [134450]
|
||||
}
|
||||
```
|
||||
|
||||
当前成员池为 `Angel123456(153579, 管理员)`、`Hsy15889521073(134328, 开发者)`、`Priziq(134450, 报告者)`。高风险维护类分配给管理员,开发实现类分配给开发者,问答/文档/重复反馈分配给报告者。数字会按 GitLink 用户 ID 直接使用;字符串会先尝试通过 `issue +assigners` 返回的 login/name 解析,再回退到 `triage.members`。
|
||||
|
||||
## 已验证仓库
|
||||
|
||||
- `puygob236/gitlink-cli`:完成仓库信息、Issue、PR、Release 采集,并完成 Issue 摘要回写验证
|
||||
- `Gitlink/gitlink-cli`:完成仓库信息、Issue、PR、Release 采集,并生成包含有效统计数据的周报、Release Notes 和结构化摘要
|
||||
- `Angel123456/gitlink-cli`:完成仓库信息、Issue、PR、Release 采集,并完成 Issue 自动分类 dry-run 验证
|
||||
- `Angel123456/gitlink-cli`:完成仓库信息、Issue、PR、Release 采集,并生成包含有效统计数据的周报、Release Notes 和结构化摘要
|
||||
|
||||
## 项目定位
|
||||
|
||||
- 满足子赛题三“端到端自动化工作流”的要求
|
||||
- 串联 4 个数据采集命令和 1 个结果发布命令
|
||||
- 串联自定义 Skill 规则读取、Issue 分拣/派单命令、4 个数据采集命令和 1 个结果发布命令
|
||||
- 新增 Issue 自动分类与负责人分配,形成社区运营前置处理闭环
|
||||
- 支持在真实 GitLink 项目上复现
|
||||
- 提供运行脚本、验证记录、示例产物和单元测试
|
||||
|
|
|
|||
|
|
@ -1,26 +1,41 @@
|
|||
# 架构说明
|
||||
|
||||
本项目采用“采集 -> 归一化 -> 分析 -> 生成 -> 发布”的五段式流程。
|
||||
本项目采用“Issue 分拣 -> 派单 -> 采集 -> 归一化 -> 分析 -> 生成 -> 发布”的七段式流程。
|
||||
|
||||

|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["issue +list 拉取新 Issue"] --> B["读取自定义 Skill: SKILL.md JSON 规则"]
|
||||
B --> C["本地规则匹配: 类型、优先级、标签候选"]
|
||||
C --> D["label +list / issue +assigners 解析 ID"]
|
||||
D --> E["dry-run 分类与派单计划"]
|
||||
E --> F["issue +view 合并人工信息"]
|
||||
F --> G["issue +update / issue +comment 写回"]
|
||||
G --> H["周报 / Release Notes / 摘要发布"]
|
||||
```
|
||||
|
||||
## 设计目标
|
||||
|
||||
- 低门槛:只依赖 `gitlink-cli` 和 Python 标准库
|
||||
- 可复现:同一配置可重复跑出同类报告
|
||||
- 可维护:采集、归一化、分析、生成和发布步骤保持清晰边界
|
||||
- 可控写回:Issue 分类与派单默认 dry-run,显式确认后才更新真实 Issue
|
||||
- 可验证:报告文件、结构化摘要和 Issue 评论均可作为运行结果核验依据
|
||||
|
||||
## 为什么选这个链路
|
||||
|
||||
子赛题三要求使用现有命令或 Skill 组合形成完整解决方案。本方案覆盖:
|
||||
|
||||
1. 仓库信息采集
|
||||
2. Issue 列表采集
|
||||
3. PR 列表采集
|
||||
4. Release 列表采集
|
||||
5. 报告生成
|
||||
6. Issue 摘要发布
|
||||
1. 新 Issue 自动分类(读取自定义 `gitlink-issue-triage-rules` Skill 中的 JSON 规则)
|
||||
2. 责任人分配计划生成(Skill 与配置优先)
|
||||
3. 标签、负责人和优先级写回(`label +list`、`issue +assigners`、`issue +view`、`issue +update`)
|
||||
4. 仓库信息采集
|
||||
5. Issue 列表采集
|
||||
6. PR 列表采集
|
||||
7. Release 列表采集
|
||||
8. 报告生成
|
||||
9. Issue 摘要发布
|
||||
|
||||
该链路满足不少于 3 个 CLI 调用的要求,并形成从数据获取到结果发布的端到端闭环。
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## 验证目标
|
||||
|
||||
`Gitlink/gitlink-cli`
|
||||
`Angel123456/gitlink-cli`
|
||||
|
||||
## 运行命令
|
||||
|
||||
|
|
@ -12,13 +12,13 @@
|
|||
|
||||
## 关键结果
|
||||
|
||||
- Issues: 15
|
||||
- PR: 20
|
||||
- Release: 11
|
||||
- Issues: 5
|
||||
- PR: 0
|
||||
- Release: 0
|
||||
- 输出文件:
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_report.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_release_notes.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_summary.json`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_report.md`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_release_notes.md`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_summary.json`
|
||||
|
||||
## 仓库内示例产物
|
||||
|
||||
|
|
@ -29,4 +29,4 @@
|
|||
|
||||
## 额外验证
|
||||
|
||||
`puygob236/gitlink-cli` 已完成仓库信息、Issue、PR 和 Release 采集验证,并完成摘要回写到 Issue 的发布验证。
|
||||
`Angel123456/gitlink-cli` 已完成仓库信息、Issue、PR 和 Release 采集验证,并完成 Issue 自动分类 dry-run 验证。
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
|
||||
脚本会自动通过 `npm exec` 找到 `@gitlink-ai/cli`,把 `gitlink-cli` 放到临时 PATH 里,再执行:
|
||||
|
||||
- 读取自定义 `gitlink-issue-triage-rules` Skill 规则
|
||||
- 新 Issue 自动分类(dry-run)
|
||||
- 责任人分配计划生成(dry-run)
|
||||
- 仓库信息采集
|
||||
- Issue 列表采集
|
||||
- PR 列表采集
|
||||
|
|
@ -17,18 +20,35 @@
|
|||
- 周报生成
|
||||
- Release Notes 草稿生成
|
||||
|
||||
默认成员池为 `Angel123456(153579)`、`Hsy15889521073(134328)`、`Priziq(134450)`,按 Issue 类型映射派单。`summary.json.triage` 会记录成员池、派单策略和按负责人统计的分配数量。
|
||||
|
||||
默认不会修改真实 Issue。确认 dry-run 计划后,使用:
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1 -ApplyTriage
|
||||
```
|
||||
|
||||
此时会额外执行 `issue +view`、`issue +update` 和可选 `issue +comment`,把分类标签、优先级和负责人写回 GitLink。
|
||||
|
||||
如需覆盖默认 Skill 路径:
|
||||
|
||||
```powershell
|
||||
python .\scripts\gitlink_workflow.py --config .\examples\demo_active_config.json --triage-skill D:\BigData\CCFer\CCF\gitlink-issue-triage-rules\SKILL.md
|
||||
```
|
||||
|
||||
## 配置切换
|
||||
|
||||
- `examples/demo_active_config.json`:公开仓库验证配置,默认指向 `Gitlink/gitlink-cli`
|
||||
- `examples/sample_config.json`:参赛仓库验证配置,默认指向 `puygob236/gitlink-cli`
|
||||
- `examples/demo_active_config.json`:公开仓库验证配置,默认指向 `Angel123456/gitlink-cli`
|
||||
- `examples/sample_config.json`:参赛仓库验证配置,默认指向 `Angel123456/gitlink-cli`
|
||||
|
||||
## 输出
|
||||
|
||||
- `outputs/*_report.md`
|
||||
- `outputs/*_release_notes.md`
|
||||
- `outputs/*_summary.json`
|
||||
- `outputs/*_triage_plan.md`
|
||||
- `outputs/*_triage_plan.json`,其中 `source` 为 `skill-json`,并记录 Skill 路径、版本和命中规则
|
||||
|
||||
## 已验证事实
|
||||
|
||||
- `puygob236/gitlink-cli` 已完成采集、报告生成和 Issue 摘要回写验证
|
||||
- `Gitlink/gitlink-cli` 可生成带统计内容的周报和 Release Notes
|
||||
- `Angel123456/gitlink-cli` 已完成采集、报告生成和 Issue 自动分类 dry-run 验证
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
- 已安装 `gitlink-cli`
|
||||
- 已完成 `gitlink-cli auth login`
|
||||
- 目标仓库有可读权限
|
||||
- 自定义 Skill 文件存在:`D:\BigData\CCFer\CCF\gitlink-issue-triage-rules\SKILL.md`
|
||||
|
||||
官方快速开始里要求的验证命令是:
|
||||
|
||||
|
|
@ -20,35 +21,67 @@ gitlink-cli user +me
|
|||
python .\scripts\gitlink_workflow.py --config .\examples\sample_config.json
|
||||
```
|
||||
|
||||
该命令默认同时生成 Issue 自动分类与派单 dry-run 计划,但不会写回真实 Issue。
|
||||
|
||||
### 2. 生成报告并发布摘要
|
||||
|
||||
```powershell
|
||||
python .\scripts\gitlink_workflow.py --config .\examples\sample_config.json --publish-issue-id 123
|
||||
```
|
||||
|
||||
### 3. 一键复现
|
||||
### 3. 执行真实 Issue 分类与派单
|
||||
|
||||
```powershell
|
||||
python .\scripts\gitlink_workflow.py --config .\examples\sample_config.json --apply-triage
|
||||
```
|
||||
|
||||
真实写回会先通过 `issue +list` 拉取待处理 Issue,并读取自定义 Skill 的 JSON 规则完成分类;再使用 `label +list`、`issue +assigners`、`issue +view` 和 `issue +update` 写入标签、优先级和负责人。配置中 `triage.comment=true` 时,会通过 `issue +comment` 写入审计说明。
|
||||
|
||||
负责人分配使用 `triage.members` 中的显式三成员池:`Angel123456(153579, 管理员)`、`Hsy15889521073(134328, 开发者)`、`Priziq(134450, 报告者)`。派单策略为 `type-map-config`:`security/bug/performance` 给管理员,`feature/refactor/ci` 给开发者,`question/docs/duplicate` 给报告者。`issue +assigners` 用于校验和解析 login/name;若显式成员未出现在该接口返回中,dry-run 计划会保留负责人并记录 warning。
|
||||
|
||||
如需显式指定 Skill 路径:
|
||||
|
||||
```powershell
|
||||
python .\scripts\gitlink_workflow.py --config .\examples\sample_config.json --triage-skill D:\BigData\CCFer\CCF\gitlink-issue-triage-rules\SKILL.md
|
||||
```
|
||||
|
||||
### 4. 一键复现
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1
|
||||
```
|
||||
|
||||
写回模式:
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1 -ApplyTriage
|
||||
```
|
||||
|
||||
## 输出文件
|
||||
|
||||
- `outputs/*_report.md`:完整周报
|
||||
- `outputs/*_release_notes.md`:Release Notes 草稿
|
||||
- `outputs/*_summary.json`:结构化摘要
|
||||
- `outputs/*_triage_plan.md`:Issue 分类与派单计划
|
||||
- `outputs/*_triage_plan.json`:结构化分类与写回结果
|
||||
|
||||
## 验证清单
|
||||
|
||||
- `repo +info` 能返回仓库信息
|
||||
- `issue +list` 能返回 Issue 列表
|
||||
- 自定义 Skill 中存在 `TRIAGE_RULES_JSON_START/END` 规则块
|
||||
- `issue +list` 能返回待分类 Issue
|
||||
- `label +list` 能解析分类标签 ID
|
||||
- `issue +assigners` 能解析负责人 ID
|
||||
- dry-run 模式不会调用 `issue +update`
|
||||
- `*_triage_plan.json` 能记录 `source=skill-json`、Skill 路径和命中规则
|
||||
- `pr +list` 能返回 PR 列表
|
||||
- `release +list` 能返回 Release 列表
|
||||
- 报告文件能落盘
|
||||
- Release Notes 草稿能落盘
|
||||
- apply 模式能把分类标签、优先级和负责人写回 Issue
|
||||
- 发布模式能把摘要写回指定 Issue
|
||||
|
||||
## 真实项目配置
|
||||
|
||||
- `examples/demo_active_config.json` 指向 `Gitlink/gitlink-cli`,用于验证活跃公开仓库的数据分析能力。
|
||||
- `examples/sample_config.json` 指向 `puygob236/gitlink-cli`,用于验证参赛仓库的采集和 Issue 回写能力。
|
||||
- `examples/demo_active_config.json` 指向 `Angel123456/gitlink-cli`,用于验证 fork 仓库的数据分析能力。
|
||||
- `examples/sample_config.json` 指向 `Angel123456/gitlink-cli`,用于验证参赛仓库的采集和 Issue 回写能力。
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
| 要求 | 本项目对应内容 |
|
||||
| --- | --- |
|
||||
| 工作流串联不少于 3 个 CLI 命令或 Skill 调用 | `scripts/gitlink_workflow.py` 串联 `repo +info`、`issue +list`、`pr +list`、`release +list`,并支持 `issue +comment` 发布摘要 |
|
||||
| 工作流串联不少于 3 个 CLI 命令或 Skill 调用 | `scripts/gitlink_workflow.py` 读取自定义 `gitlink-issue-triage-rules` Skill,并串联 `issue +list`、`label +list`、`issue +assigners`、`issue +view`、`issue +update`、`repo +info`、`pr +list`、`release +list` 等命令 |
|
||||
| 提供可复现执行脚本或 Agent 对话记录 | `scripts/run_demo.ps1` |
|
||||
| 在至少一个真实 GitLink 项目上运行并展示效果 | `docs/verification.md`、`docs/demo-output.md`、`examples/demo_outputs/` |
|
||||
| 提供工作流说明文档 | `README.md`、`docs/quickstart.md`、`docs/runbook.md` |
|
||||
| 提供架构图 | `docs/architecture.md` 引用 `docs/assets/architecture-workflow-v2.svg` |
|
||||
| 代码开源并托管到 GitLink | `https://gitlink.org.cn/puygob236/gitlink-cli` 的 `examples/workflows/community-ops-automation/` |
|
||||
| 代码开源并托管到 GitLink | `https://gitlink.org.cn/Angel123456/gitlink-cli` 的 `examples/workflows/community-ops-automation/` |
|
||||
| 提供完整中文 README | `README.md` |
|
||||
| 开源协议 | `LICENSE`,Apache 2.0 |
|
||||
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
- `python -m py_compile .\scripts\gitlink_workflow.py .\tests\test_gitlink_workflow.py`:通过
|
||||
- `python -m unittest discover -s tests`:通过
|
||||
- `.\scripts\run_demo.ps1`:已在 `Gitlink/gitlink-cli` 上跑通
|
||||
- `.\scripts\run_demo.ps1 -Config examples\sample_config.json`:已在 `puygob236/gitlink-cli` 上跑通
|
||||
- `.\scripts\run_demo.ps1 -Config examples\sample_config.json -PublishIssueId 2`:已完成 Issue 摘要回写验证
|
||||
- `.\scripts\run_demo.ps1`:已在 `Angel123456/gitlink-cli` 上跑通
|
||||
- `.\scripts\run_demo.ps1 -Config examples\sample_config.json`:已在 `Angel123456/gitlink-cli` 上跑通
|
||||
- `.\scripts\run_demo.ps1 -Config examples\sample_config.json`:已完成 Issue 自动分类 dry-run 验证
|
||||
|
||||
## 交付内容
|
||||
|
||||
|
|
|
|||
|
|
@ -8,48 +8,44 @@
|
|||
|
||||
## 已验证的真实仓库
|
||||
|
||||
### `puygob236/gitlink-cli`
|
||||
### `Angel123456/gitlink-cli`
|
||||
|
||||
- 自定义 Skill JSON 规则可生成 Issue 分类结果
|
||||
- `label +list` 可解析分类标签
|
||||
- `issue +assigners` 可解析负责人
|
||||
- dry-run 模式可生成 `*_triage_plan.md` 和 `*_triage_plan.json`
|
||||
- `triage.members` 记录 3 个项目成员:`Angel123456(153579)`、`Hsy15889521073(134328)`、`Priziq(134450)`
|
||||
- `assigners_by_type` 按类型派单:高风险维护类给管理员,开发实现类给开发者,问答/文档/重复反馈给报告者
|
||||
- `*_triage_plan.json` 可记录 `source=skill-json`、Skill 路径和命中规则
|
||||
- `repo +info` 可访问
|
||||
- `issue +list` 可访问
|
||||
- `pr +list` 可访问
|
||||
- `release +list` 可访问
|
||||
- 已完成 Issue 摘要回写验证
|
||||
- 已完成 Issue 自动分类 dry-run 验证
|
||||
|
||||
### `Gitlink/gitlink-cli`
|
||||
### `Angel123456/gitlink-cli`
|
||||
|
||||
- 自定义 Skill JSON 规则可生成 Issue 分类结果
|
||||
- `repo +info` 可访问
|
||||
- `issue +list` 可访问
|
||||
- `pr +list` 可访问
|
||||
- `release +list` 可访问
|
||||
- 当前可提取到的统计结果:
|
||||
- Issues: 15
|
||||
- PR: 20
|
||||
- Release: 11
|
||||
- Issues: 5
|
||||
- PR: 0
|
||||
- Release: 0
|
||||
|
||||
## 本地输出
|
||||
|
||||
已生成的文件:
|
||||
|
||||
- `outputs/Gitlink_gitlink-cli_20260515_040153_report.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260515_040153_summary.json`
|
||||
- `outputs/Gitlink_gitlink-cli_20260515_121523_report.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260515_121523_release_notes.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260515_121523_summary.json`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121544_report.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121544_release_notes.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121544_summary.json`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121845_report.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121845_release_notes.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260515_121845_summary.json`
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_report.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_release_notes.md`
|
||||
- `outputs/Gitlink_gitlink-cli_20260520_140525_summary.json`
|
||||
- `outputs/puygob236_gitlink-cli_20260520_143224_report.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260520_143224_release_notes.md`
|
||||
- `outputs/puygob236_gitlink-cli_20260520_143224_summary.json`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_report.md`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_release_notes.md`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_summary.json`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_triage_plan.md`
|
||||
- `outputs/Angel123456_gitlink-cli_20260706_032259_triage_plan.json`
|
||||
|
||||
其中 `20260520_140525` 对应公开仓库数据分析验证,`20260520_143224` 对应参赛仓库采集与 Issue 回写验证。
|
||||
其中 `20260706_032259` 对应 `Angel123456/gitlink-cli` 的数据采集、报告生成和 Issue 自动分类 dry-run 验证。
|
||||
|
||||
## 示例产物
|
||||
|
||||
|
|
@ -65,3 +61,11 @@
|
|||
```powershell
|
||||
.\scripts\run_demo.ps1
|
||||
```
|
||||
|
||||
真实写回验证命令:
|
||||
|
||||
```powershell
|
||||
.\scripts\run_demo.ps1 -ApplyTriage
|
||||
```
|
||||
|
||||
未传 `-ApplyTriage` 时只生成 dry-run 计划,不会调用 `issue +update`。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,70 @@
|
|||
{
|
||||
"owner": "Gitlink",
|
||||
"owner": "Angel123456",
|
||||
"repo": "gitlink-cli",
|
||||
"window_days": 7,
|
||||
"output_dir": "outputs"
|
||||
"output_dir": "outputs",
|
||||
"triage": {
|
||||
"enabled": true,
|
||||
"state": "open",
|
||||
"limit": 50,
|
||||
"lang": "zh-CN",
|
||||
"skill_path": "../../../../../gitlink-issue-triage-rules/SKILL.md",
|
||||
"labels_by_type": {
|
||||
"bug": "缺陷",
|
||||
"feature": "功能",
|
||||
"question": "疑问",
|
||||
"docs": "文档",
|
||||
"security": "缺陷",
|
||||
"performance": "缺陷",
|
||||
"ci": "测试",
|
||||
"refactor": "任务",
|
||||
"duplicate": "重复"
|
||||
},
|
||||
"assigners_by_type": {
|
||||
"security": [153579],
|
||||
"bug": [153579],
|
||||
"performance": [153579],
|
||||
"feature": [134328],
|
||||
"refactor": [134328],
|
||||
"ci": [134328],
|
||||
"question": [134450],
|
||||
"docs": [134450],
|
||||
"duplicate": [134450]
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"id": 153579,
|
||||
"login": "Angel123456",
|
||||
"name": "Angel123456",
|
||||
"role": "Manager",
|
||||
"role_name": "管理员"
|
||||
},
|
||||
{
|
||||
"id": 134328,
|
||||
"login": "Hsy15889521073",
|
||||
"name": "Hsy15889521073",
|
||||
"role": "Developer",
|
||||
"role_name": "开发者"
|
||||
},
|
||||
{
|
||||
"id": 134450,
|
||||
"login": "Priziq",
|
||||
"name": "Priziq",
|
||||
"role": "Reporter",
|
||||
"role_name": "报告者"
|
||||
}
|
||||
],
|
||||
"assignment_strategy": "type-map-config",
|
||||
"priority_ids": {
|
||||
"P0": 4,
|
||||
"P1": 3,
|
||||
"P2": 2,
|
||||
"P3": 1,
|
||||
"critical": 4,
|
||||
"high": 3,
|
||||
"normal": 2,
|
||||
"low": 1
|
||||
},
|
||||
"comment": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,70 @@
|
|||
{
|
||||
"owner": "puygob236",
|
||||
"owner": "Angel123456",
|
||||
"repo": "gitlink-cli",
|
||||
"window_days": 7,
|
||||
"output_dir": "outputs"
|
||||
"output_dir": "outputs",
|
||||
"triage": {
|
||||
"enabled": true,
|
||||
"state": "open",
|
||||
"limit": 50,
|
||||
"lang": "zh-CN",
|
||||
"skill_path": "../../../../../gitlink-issue-triage-rules/SKILL.md",
|
||||
"labels_by_type": {
|
||||
"bug": "缺陷",
|
||||
"feature": "功能",
|
||||
"question": "疑问",
|
||||
"docs": "文档",
|
||||
"security": "缺陷",
|
||||
"performance": "缺陷",
|
||||
"ci": "测试",
|
||||
"refactor": "任务",
|
||||
"duplicate": "重复"
|
||||
},
|
||||
"assigners_by_type": {
|
||||
"security": [153579],
|
||||
"bug": [153579],
|
||||
"performance": [153579],
|
||||
"feature": [134328],
|
||||
"refactor": [134328],
|
||||
"ci": [134328],
|
||||
"question": [134450],
|
||||
"docs": [134450],
|
||||
"duplicate": [134450]
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"id": 153579,
|
||||
"login": "Angel123456",
|
||||
"name": "Angel123456",
|
||||
"role": "Manager",
|
||||
"role_name": "管理员"
|
||||
},
|
||||
{
|
||||
"id": 134328,
|
||||
"login": "Hsy15889521073",
|
||||
"name": "Hsy15889521073",
|
||||
"role": "Developer",
|
||||
"role_name": "开发者"
|
||||
},
|
||||
{
|
||||
"id": 134450,
|
||||
"login": "Priziq",
|
||||
"name": "Priziq",
|
||||
"role": "Reporter",
|
||||
"role_name": "报告者"
|
||||
}
|
||||
],
|
||||
"assignment_strategy": "type-map-config",
|
||||
"priority_ids": {
|
||||
"P0": 4,
|
||||
"P1": 3,
|
||||
"P2": 2,
|
||||
"P3": 1,
|
||||
"critical": 4,
|
||||
"high": 3,
|
||||
"normal": 2,
|
||||
"low": 1
|
||||
},
|
||||
"comment": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,7 +5,10 @@ param(
|
|||
[int]$WindowDays = 7,
|
||||
[string]$OutputDir = "outputs",
|
||||
[int]$PublishIssueId = 0,
|
||||
[switch]$SkipReleases
|
||||
[switch]$SkipReleases,
|
||||
[switch]$SkipTriage,
|
||||
[switch]$ApplyTriage,
|
||||
[string]$TriageSkill = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
|
@ -26,7 +29,8 @@ $args = @(
|
|||
"scripts\gitlink_workflow.py",
|
||||
"--config", $Config,
|
||||
"--window-days", "$WindowDays",
|
||||
"--output-dir", $OutputDir
|
||||
"--output-dir", $OutputDir,
|
||||
"--cli-bin", $cliPath
|
||||
)
|
||||
|
||||
if ($Owner) {
|
||||
|
|
@ -41,5 +45,14 @@ if ($PublishIssueId -gt 0) {
|
|||
if ($SkipReleases.IsPresent) {
|
||||
$args += "--skip-releases"
|
||||
}
|
||||
if ($SkipTriage.IsPresent) {
|
||||
$args += "--skip-triage"
|
||||
}
|
||||
if ($ApplyTriage.IsPresent) {
|
||||
$args += "--apply-triage"
|
||||
}
|
||||
if ($TriageSkill) {
|
||||
$args += @("--triage-skill", $TriageSkill)
|
||||
}
|
||||
|
||||
python @args
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from scripts.gitlink_workflow import (
|
||||
apply_triage_plan,
|
||||
build_triage_plan,
|
||||
build_issue_comment_command,
|
||||
classify_issue_with_skill_rules,
|
||||
load_skill_triage_rules,
|
||||
normalize_issues,
|
||||
normalize_prs,
|
||||
normalize_releases,
|
||||
render_markdown_report,
|
||||
render_release_notes,
|
||||
run_triage_workflow,
|
||||
summarize_triage_plan,
|
||||
summarize_workflow,
|
||||
)
|
||||
|
||||
|
|
@ -23,6 +32,123 @@ class WorkflowTests(unittest.TestCase):
|
|||
"default_branch": "master",
|
||||
}
|
||||
|
||||
def write_skill(self, directory: str) -> Path:
|
||||
ruleset = {
|
||||
"version": 1,
|
||||
"mode": "rule",
|
||||
"skill": {"name": "gitlink-issue-triage-rules", "version": "9.9.9"},
|
||||
"defaults": {"dry_run": True, "priority": "P3"},
|
||||
"priority_ids": {"P0": 4, "P1": 3, "P2": 2, "P3": 1, "critical": 4, "high": 3, "normal": 2, "low": 1},
|
||||
"labels_by_type": {
|
||||
"bug": ["缺陷", "bug"],
|
||||
"feature": ["功能", "enhancement"],
|
||||
"question": ["疑问", "question"],
|
||||
"docs": ["文档", "documentation"],
|
||||
"security": ["缺陷", "security"],
|
||||
},
|
||||
"assigners_by_type": {
|
||||
"bug": [153579],
|
||||
"feature": [153579],
|
||||
"question": [153579],
|
||||
"docs": [153579],
|
||||
"security": [153579],
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"id": "security-sensitive",
|
||||
"type": "security",
|
||||
"label": ["缺陷", "security"],
|
||||
"priority": "critical",
|
||||
"match": {"any_keyword": ["token", "漏洞"]},
|
||||
},
|
||||
{
|
||||
"id": "bug-crash",
|
||||
"type": "bug",
|
||||
"label": ["缺陷", "bug"],
|
||||
"priority": "high",
|
||||
"match": {"any_keyword": ["crash", "崩溃"]},
|
||||
},
|
||||
{
|
||||
"id": "feature-request",
|
||||
"type": "feature",
|
||||
"label": ["功能", "enhancement"],
|
||||
"priority": "normal",
|
||||
"match": {"any_keyword": ["建议", "feature"]},
|
||||
},
|
||||
{
|
||||
"id": "question-default",
|
||||
"type": "question",
|
||||
"label": ["疑问", "question"],
|
||||
"priority": "low",
|
||||
"match": {"any_keyword": ["请问", "how to"]},
|
||||
},
|
||||
{
|
||||
"id": "docs-default",
|
||||
"type": "docs",
|
||||
"label": ["文档", "documentation"],
|
||||
"priority": "low",
|
||||
"match": {"any_keyword": ["README", "typo"]},
|
||||
},
|
||||
],
|
||||
}
|
||||
path = Path(directory) / "SKILL.md"
|
||||
path.write_text(
|
||||
"---\n"
|
||||
"name: gitlink-issue-triage-rules\n"
|
||||
"version: 9.9.9\n"
|
||||
"---\n\n"
|
||||
"<!-- TRIAGE_RULES_JSON_START -->\n"
|
||||
"```json\n"
|
||||
f"{json.dumps(ruleset, ensure_ascii=False, indent=2)}\n"
|
||||
"```\n"
|
||||
"<!-- TRIAGE_RULES_JSON_END -->\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return path
|
||||
|
||||
def triage_members(self) -> list[dict[str, object]]:
|
||||
return [
|
||||
{
|
||||
"id": 153579,
|
||||
"login": "Angel123456",
|
||||
"name": "Angel123456",
|
||||
"role": "Manager",
|
||||
"role_name": "管理员",
|
||||
},
|
||||
{
|
||||
"id": 134328,
|
||||
"login": "Hsy15889521073",
|
||||
"name": "Hsy15889521073",
|
||||
"role": "Developer",
|
||||
"role_name": "开发者",
|
||||
},
|
||||
{
|
||||
"id": 134450,
|
||||
"login": "Priziq",
|
||||
"name": "Priziq",
|
||||
"role": "Reporter",
|
||||
"role_name": "报告者",
|
||||
},
|
||||
]
|
||||
|
||||
def assignment_config(self) -> dict[str, object]:
|
||||
return {
|
||||
"members": self.triage_members(),
|
||||
"assignment_strategy": "type-map-config",
|
||||
"assigners_by_type": {
|
||||
"security": [153579],
|
||||
"bug": [153579],
|
||||
"performance": [153579],
|
||||
"feature": [134328],
|
||||
"refactor": [134328],
|
||||
"ci": [134328],
|
||||
"question": [134450],
|
||||
"docs": [134450],
|
||||
"duplicate": [134450],
|
||||
},
|
||||
"priority_ids": {"P1": 3, "P2": 2, "P3": 1, "high": 3, "normal": 2, "low": 1},
|
||||
}
|
||||
|
||||
def test_normalize_issue_payload(self) -> None:
|
||||
payload = {
|
||||
"data": {
|
||||
|
|
@ -128,6 +254,256 @@ class WorkflowTests(unittest.TestCase):
|
|||
self.assertEqual(command, ["issue", "+comment", "--number", "2", "--body", "demo"])
|
||||
self.assertNotIn("-i", command)
|
||||
|
||||
def test_load_skill_triage_rules_extracts_json_block(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
skill_path = self.write_skill(tmp)
|
||||
ruleset = load_skill_triage_rules(skill_path)
|
||||
|
||||
self.assertEqual(ruleset["skill"]["name"], "gitlink-issue-triage-rules")
|
||||
self.assertEqual(ruleset["skill"]["version"], "9.9.9")
|
||||
self.assertEqual(len(ruleset["rules"]), 5)
|
||||
|
||||
def test_classify_issue_with_skill_rules_matches_common_types(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
ruleset = load_skill_triage_rules(self.write_skill(tmp))
|
||||
|
||||
cases = [
|
||||
("token 泄露漏洞", "security", "security-sensitive"),
|
||||
("CLI crash when upload", "bug", "bug-crash"),
|
||||
("建议新增报表导出", "feature", "feature-request"),
|
||||
("请问 how to 配置项目", "question", "question-default"),
|
||||
("README typo", "docs", "docs-default"),
|
||||
]
|
||||
for title, expected_type, expected_rule in cases:
|
||||
result = classify_issue_with_skill_rules(
|
||||
{"id": "1", "title": title, "labels": [], "raw": {"description": title}},
|
||||
ruleset,
|
||||
)
|
||||
self.assertEqual(result["detected_type"], expected_type)
|
||||
self.assertEqual(result["rule_id"], expected_rule)
|
||||
|
||||
def test_classify_issue_with_skill_rules_marks_unknown(self) -> None:
|
||||
with TemporaryDirectory() as tmp:
|
||||
ruleset = load_skill_triage_rules(self.write_skill(tmp))
|
||||
|
||||
result = classify_issue_with_skill_rules(
|
||||
{"id": "9", "title": "General note", "labels": [], "raw": {"description": "nothing special"}},
|
||||
ruleset,
|
||||
)
|
||||
|
||||
self.assertEqual(result["detected_type"], "unknown")
|
||||
self.assertEqual(result["source"], "skill-json")
|
||||
|
||||
def test_build_triage_plan_resolves_label_and_assigner(self) -> None:
|
||||
plan = build_triage_plan(
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"issue": {"number": 1, "title": "CLI crash"},
|
||||
"detected_type": "bug",
|
||||
"priority": "P1",
|
||||
"confidence": 88,
|
||||
"matched_rules": ["matched keyword: crash"],
|
||||
}
|
||||
]
|
||||
},
|
||||
{"data": {"issue_tags": [{"id": 10, "name": "缺陷"}]}},
|
||||
{"data": {"assigners": [{"id": 153579, "login": "Angel123456", "name": "Angel"}]}},
|
||||
{
|
||||
"labels_by_type": {"bug": "缺陷"},
|
||||
"assigners_by_type": {"bug": ["Angel123456"]},
|
||||
"priority_ids": {"P1": 3},
|
||||
},
|
||||
owner="owner",
|
||||
repo="repo",
|
||||
)
|
||||
|
||||
self.assertEqual(plan["analyzed"], 1)
|
||||
item = plan["items"][0]
|
||||
self.assertEqual(item["status"], "planned")
|
||||
self.assertEqual(item["label_id"], 10)
|
||||
self.assertEqual(item["assigner_ids"], [153579])
|
||||
self.assertEqual(item["priority_id"], 3)
|
||||
|
||||
def test_build_triage_plan_uses_three_member_type_mapping(self) -> None:
|
||||
results = []
|
||||
for index, issue_type in enumerate(
|
||||
["bug", "security", "performance", "feature", "refactor", "ci", "question", "docs", "duplicate"],
|
||||
start=1,
|
||||
):
|
||||
results.append(
|
||||
{
|
||||
"issue": {"number": index, "title": f"{issue_type} issue"},
|
||||
"detected_type": issue_type,
|
||||
"priority": "P2",
|
||||
}
|
||||
)
|
||||
plan = build_triage_plan(
|
||||
{"results": results},
|
||||
{"data": {"issue_tags": []}},
|
||||
{"data": {"assigners": [{"id": 153579, "login": "Angel123456", "name": "Angel123456"}]}},
|
||||
self.assignment_config(),
|
||||
owner="Angel123456",
|
||||
repo="gitlink-cli",
|
||||
)
|
||||
|
||||
by_type = {item["detected_type"]: item for item in plan["items"]}
|
||||
for issue_type in ("bug", "security", "performance"):
|
||||
self.assertEqual(by_type[issue_type]["assigner_ids"], [153579])
|
||||
for issue_type in ("feature", "refactor", "ci"):
|
||||
self.assertEqual(by_type[issue_type]["assigner_ids"], [134328])
|
||||
self.assertEqual(by_type[issue_type]["assigner_details"][0]["login"], "Hsy15889521073")
|
||||
for issue_type in ("question", "docs", "duplicate"):
|
||||
self.assertEqual(by_type[issue_type]["assigner_ids"], [134450])
|
||||
self.assertEqual(by_type[issue_type]["assigner_details"][0]["role"], "Reporter")
|
||||
self.assertEqual(by_type["feature"]["assignment_source"], "type-map-config")
|
||||
self.assertTrue(any("issue +assigners" in warning and "Hsy15889521073" in warning for warning in plan["warnings"]))
|
||||
|
||||
summary = summarize_triage_plan(plan)
|
||||
counts = {item["id"]: item["count"] for item in summary["assignment_counts_by_member"]}
|
||||
self.assertEqual(counts[153579], 3)
|
||||
self.assertEqual(counts[134328], 3)
|
||||
self.assertEqual(counts[134450], 3)
|
||||
|
||||
def test_build_triage_plan_resolves_configured_member_login_and_name(self) -> None:
|
||||
config = self.assignment_config()
|
||||
members = self.triage_members()
|
||||
members[1]["name"] = "Hsy Developer"
|
||||
config["members"] = members
|
||||
config["assigners_by_type"] = {
|
||||
"feature": ["Hsy15889521073"],
|
||||
"docs": ["Priziq"],
|
||||
"question": ["Hsy Developer"],
|
||||
}
|
||||
plan = build_triage_plan(
|
||||
{
|
||||
"results": [
|
||||
{"issue": {"number": 1, "title": "feature"}, "detected_type": "feature", "priority": "P2"},
|
||||
{"issue": {"number": 2, "title": "docs"}, "detected_type": "docs", "priority": "P3"},
|
||||
{"issue": {"number": 3, "title": "question"}, "detected_type": "question", "priority": "P3"},
|
||||
]
|
||||
},
|
||||
{"data": {"issue_tags": []}},
|
||||
{"data": {"assigners": []}},
|
||||
config,
|
||||
)
|
||||
|
||||
self.assertEqual(plan["items"][0]["assigner_ids"], [134328])
|
||||
self.assertEqual(plan["items"][1]["assigner_ids"], [134450])
|
||||
self.assertEqual(plan["items"][2]["assigner_ids"], [134328])
|
||||
|
||||
def test_build_triage_plan_warns_without_interrupting(self) -> None:
|
||||
plan = build_triage_plan(
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"issue": {"number": 2, "title": "General note"},
|
||||
"detected_type": "unknown",
|
||||
"priority": "P3",
|
||||
},
|
||||
{
|
||||
"issue": {"number": 3, "title": "Slow API"},
|
||||
"detected_type": "performance",
|
||||
"priority": "P2",
|
||||
},
|
||||
]
|
||||
},
|
||||
{"data": {"issue_tags": []}},
|
||||
{"data": {"assigners": []}},
|
||||
{
|
||||
"labels_by_type": {"performance": "缺陷"},
|
||||
"assigners_by_type": {"performance": ["missing-user"]},
|
||||
"priority_ids": {"P2": 2, "P3": 1},
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(plan["items"][0]["status"], "skipped")
|
||||
self.assertEqual(plan["items"][1]["status"], "planned")
|
||||
self.assertGreaterEqual(len(plan["warnings"]), 3)
|
||||
|
||||
def test_apply_triage_plan_merges_existing_metadata_and_comments(self) -> None:
|
||||
plan = build_triage_plan(
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"issue": {"number": 4, "title": "Bug: upload crash"},
|
||||
"detected_type": "bug",
|
||||
"priority": "P1",
|
||||
"confidence": 91,
|
||||
}
|
||||
]
|
||||
},
|
||||
{"data": {"issue_tags": [{"id": 10, "name": "缺陷"}]}},
|
||||
{"data": {"assigners": [{"id": 153579, "login": "Angel123456"}]}},
|
||||
{
|
||||
"labels_by_type": {"bug": "缺陷"},
|
||||
"assigners_by_type": {"bug": [153579]},
|
||||
"priority_ids": {"P1": 3},
|
||||
},
|
||||
)
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def fake_runner(command: list[str], owner: str, repo: str, cli_bin: str | None = None):
|
||||
calls.append(command)
|
||||
if command[:2] == ["issue", "+view"]:
|
||||
return {
|
||||
"data": {
|
||||
"subject": "Bug: upload crash",
|
||||
"description": "existing body",
|
||||
"tags": [{"id": 99, "name": "已有"}],
|
||||
"assigners": [{"id": 88, "login": "maintainer"}],
|
||||
}
|
||||
}
|
||||
return {"ok": True}
|
||||
|
||||
applied = apply_triage_plan(plan, "owner", "repo", fake_runner, comment=True)
|
||||
self.assertEqual(applied["items"][0]["status"], "applied")
|
||||
self.assertEqual(calls[0][:2], ["issue", "+view"])
|
||||
self.assertEqual(calls[1][:2], ["issue", "+update"])
|
||||
self.assertEqual(calls[2][:2], ["issue", "+comment"])
|
||||
self.assertIn("--tag-ids", calls[1])
|
||||
self.assertIn("99,10", calls[1])
|
||||
self.assertIn("--assigner-ids", calls[1])
|
||||
self.assertIn("88,153579", calls[1])
|
||||
self.assertIn("--priority-id", calls[1])
|
||||
self.assertIn("3", calls[1])
|
||||
|
||||
def test_run_triage_workflow_dry_run_does_not_update_issue(self) -> None:
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def fake_runner(command: list[str], owner: str, repo: str, cli_bin: str | None = None):
|
||||
calls.append(command)
|
||||
if command[:2] == ["issue", "+list"]:
|
||||
return {"data": {"issues": [{"project_issues_index": 1, "subject": "CLI crash", "description": "crash"}]}}
|
||||
if command[:2] == ["label", "+list"]:
|
||||
return {"data": {"issue_tags": [{"id": 10, "name": "缺陷"}]}}
|
||||
if command[:2] == ["issue", "+assigners"]:
|
||||
return {"data": {"assigners": [{"id": 153579, "login": "Angel123456"}]}}
|
||||
raise AssertionError(f"unexpected command in dry-run: {command}")
|
||||
|
||||
with TemporaryDirectory() as tmp:
|
||||
plan, markdown_path, json_path = run_triage_workflow(
|
||||
"owner",
|
||||
"repo",
|
||||
{
|
||||
"labels_by_type": {"bug": "缺陷"},
|
||||
"assigners_by_type": {"bug": [153579]},
|
||||
"priority_ids": {"P1": 3},
|
||||
},
|
||||
apply_triage=False,
|
||||
output_dir=Path(tmp),
|
||||
base_name="demo",
|
||||
triage_skill=self.write_skill(tmp),
|
||||
runner=fake_runner,
|
||||
)
|
||||
|
||||
self.assertTrue(plan["dry_run"])
|
||||
self.assertEqual(plan["source"], "skill-json")
|
||||
self.assertEqual(plan["items"][0]["rule_id"], "bug-crash")
|
||||
self.assertTrue(markdown_path.name.endswith("_triage_plan.md"))
|
||||
self.assertTrue(json_path.name.endswith("_triage_plan.json"))
|
||||
self.assertNotIn(["issue", "+update"], [call[:2] for call in calls])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue