gitlink-cli/skills/gitlink-gatekeeper/SKILL.md

371 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: gitlink-gatekeeper
version: 1.0.0
description: "Policy-as-Code 的 PR 合并门禁:按版本化的 gatekeeper.yaml 策略对一个 Pull Request 聚合多路信号、算出 0-100 评分卡、给出三态裁决PASS / REQUEST_CHANGES / COMMENT并把结论作为结构化评论回写、打标签。当用户需要判断 PR 能否合并、做合并门禁/质量闸门、生成可复现的 PR 裁决,或问『这个 PR 达标了吗』时触发。默认 dry-run绝不自动合并。"
metadata:
requires:
bins: ["gitlink-cli"]
cliHelp: "gitlink-cli pr --help"
---
# gitlink-gatekeeperPolicy-as-Code PR 合并门禁)
**CRITICAL — 开始前必须先阅读 [`gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 GitLink API 注意事项PR list state 过滤不准、主分支是 master、issue 更新需带回 subject/description 等)。**
**CRITICAL — 任何写操作(回写评论、打标签、合并)前,必须向用户复述将要做什么并得到确认;默认 dry-run 不写任何东西。绝不默认自动合并。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`GitHub CLI操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
> **前置条件:** 先阅读 [`gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数;本 Skill 的全字段策略与算法细节见同目录 [`REFERENCE.md`](./REFERENCE.md),故障排查见 [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md)。
---
## 定位与产物
gitlink-gatekeeper 是一个**可复现的 PR 合并门禁**:团队把合并标准写进版本化的 `gatekeeper.yaml`gatekeeper 据此对一个 PR 算出**透明的 0-100 评分卡**,给出**三态裁决**,并把结论回写。与 `gitlink-code-review`(只产出主观评论、无阈值、无结论)相比,本 Skill 的核心是**可裁决、可审计、可复现**:同一策略 + 同一 PR → 同一裁决。
| 裁决 | emoji | 触发条件 | 回写标签 |
|------|:----:|----------|----------|
| PASS | ✅ | 无硬门禁失败 且 `total ≥ thresholds.pass` | `gatekeeper:pass` |
| REQUEST_CHANGES | ❌ | 命中任一硬门禁,或 `total < thresholds.request_changes` | `gatekeeper:needs-changes` |
| COMMENT | 💬 | 介于两阈值之间且无硬门禁失败 | `gatekeeper:review` |
> GitLink 原生 PR review 的 `status` 为 `common`/`approved`/`rejected``rejected` 即「请求修改」的等价。gitlink-gatekeeper **刻意把所有自动裁决以建议性的 `common` 评论回写**(评分卡标题明确标注三态裁决),把强语义的 `approved`/`rejected` 留给人工授意;裁决状态通过标签(`gatekeeper:needs-changes` 等)承载——这正是本作品复用 `label` 命令组的原因。
---
## 工作流概览
| 阶段 | 操作 | AI Agent 角色 |
|------|------|--------------|
| ① 加载策略 | 读 `gatekeeper.yaml`,找不到则回退内置默认策略 | 解析 / 校验 / 回退 |
| ② 采集上下文 | 拉 PR 元信息、变更文件、diff、commits、CI 状态 | 执行 CLI 命令采集数据 |
| ③ 产出发现 | 逐文件审查,按 severity 分级标记问题 | AI 分析,输出发现列表 |
| ④ 逐维评分 | 五维各算 `0..weight` 得分,相加得 `total` | 确定性计算(非主观) |
| ⑤ 硬门禁 | 逐项判定 `hard_gates`,命中即拦截 | 布尔判定 |
| ⑥ 裁决 | 由硬门禁 + total + 阈值得出三态 | 确定性计算 |
| ⑦ 渲染评分卡 | 按模板渲染 Markdown 评分卡 | 模板填充 |
| ⑧ 回写(受 `--apply` | dry-run 仅打印;`--apply` 才回写评论 + 打标签 | 写操作,需确认 |
| ⑨ 安全规则 | 默认 dry-run绝不自动合并 | 守门 |
---
## 详细工作流
### Step ① 加载策略 gatekeeper.yaml
策略默认从**仓库根目录** `gatekeeper.yaml` 读取,可用 `--policy <path>` 指定。找不到时**回退到下文内置默认策略**(务必在评分卡 footer 注明用的是哪个策略来源)。
加载后做两项校验(不通过则报错并停止):
- `weights` 五项之和必须 **= 100**`review_findings + test_coverage + pr_hygiene + commit_quality + ci_status`)。
- `thresholds.pass`、`thresholds.request_changes` 为 0-100 整数,且 `pass ≥ request_changes`
**内置默认策略**(与 `examples/gatekeeper.yaml` 一致回退时使用policy 标记为 `<built-in default>@v1`
```yaml
version: 1
weights:
review_findings: 40
test_coverage: 20
pr_hygiene: 15
commit_quality: 15
ci_status: 10
hard_gates:
forbid_blocker_findings: true
require_ci_pass: true
require_tests_for_src_changes: true
require_linked_issue: false
max_changed_files: 80
severity_penalty: { blocker: 100, major: 25, minor: 5, nit: 1 }
thresholds: { pass: 85, request_changes: 60 }
labels:
pass: "gatekeeper:pass"
request_changes: "gatekeeper:needs-changes"
comment: "gatekeeper:review"
source_globs: ["**/*.go", "**/*.py", "**/*.js", "**/*.ts", "**/*.rs", "**/*.java"]
test_globs: ["**/*_test.go", "**/test_*.py", "**/*.test.*", "**/*.spec.*", "tests/**"]
behavior:
dry_run_default: true
post_comment: true
apply_label: true
auto_merge: false
merge_method: squash
```
### Step ② 采集 PR 上下文CLI 命令映射)
用以下命令采集一个 PR 的全部输入。**始终带 `--format json` 便于解析**`--owner`/`--repo` 在 git 仓库目录下可自动从 remote 解析,否则显式传入。
| 步骤 | 数据 | 命令 |
|------|------|------|
| PR 元信息 | 标题/描述/作者/关联 issue | `gitlink-cli pr +view -i <id> --format json` |
| 变更文件 | 文件路径列表 | `gitlink-cli pr +files -i <id> --format json` |
| Diff | 变更内容供 AI 审查 | `gitlink-cli pr +diff -i <id> --format json` |
| commits | commit 列表(消息供 commit_quality | `gitlink-cli api GET /:owner/:repo/pulls/:id/commits --format json` |
| CI 状态 | 构建结果 | `gitlink-cli ci +builds --format json` |
> 实测注意:`pr +files` 与 `pr +diff` 底层都打 `/pulls/:id/files`——`+files` 取路径列表,`+diff` 取含 patch 的同一份数据,按需取用即可。无 `pr +commits` 快捷命令commit 列表只能走 Raw API。CI 通过/失败需从 builds 返回的 `status` 字段判断;**无 build 记录时按「CI 未知」处理**(见 §3.5)。
```bash
PR=42
gitlink-cli pr +view -i "$PR" --format json # title / body / 关联 issue
gitlink-cli pr +files -i "$PR" --format json # changed files
gitlink-cli pr +diff -i "$PR" --format json # diff供 AI 审查)
gitlink-cli api GET /:owner/:repo/pulls/$PR/commits --format json
gitlink-cli ci +builds --format json
```
### Step ③ AI 按 severity 分级产出发现
对 diff 逐文件审查(检查项参考 `gitlink-code-review`:安全红线、错误处理、并发、资源管理、魔法数字、测试覆盖等),产出**结构化发现列表**,每条带:
```
{ severity: blocker|major|minor|nit, file: "<path>", line: <n>, message: "<问题描述>" }
```
severity 判定准则(与 §3.1 扣分表对应):
| severity | 含义 | 典型例子 |
|----------|------|----------|
| `blocker` | 必须拦截,单条即清零本维度并触发硬门禁 | 硬编码密钥/Token、SQL/命令注入、路径遍历、不安全反序列化、XSS |
| `major` | 严重缺陷,强烈建议修 | 未处理错误/异常吞掉、资源泄漏、并发竞态、明显逻辑错误 |
| `minor` | 一般问题 | 风格偏离、轻微复杂度、缺注释、魔法数字 |
| `nit` | 吹毛求疵 | 命名小瑕疵、格式建议 |
> 控制信噪比:宁可少而准。同时记录 `Strengths`(做得好的点),用于评分卡 `✅ Strengths` 区。
### Step ④ 按 SSOT 算法逐维评分(确定性)
每维产出 `0..weights[dim]`,五维相加得 `total ∈ 0..100`。**这是确定性计算,不是再次主观打分**——同输入必同分。
**4.1 review_findings默认 40**
```
penalty = Σ severity_penalty[finding.severity] # 对所有发现求和
score = weights.review_findings * max(0, 1 - penalty / weights.review_findings)
```
扣分累计达本维度权重即扣到 0blocker 单条penalty=100即清零本维度。
**4.2 test_coverage默认 20**
```
changed_src = 变更文件中匹配 source_globs 的数量
changed_tests = 变更文件中匹配 test_globs 的数量
if changed_src == 0: score = weights.test_coverage # 无源码改动,不扣
elif changed_tests == 0: score = 0
else: ratio = min(1, changed_tests / changed_src)
score = round(weights.test_coverage * (0.5 + 0.5*ratio)) # 有测试至少拿一半
```
**4.3 pr_hygiene默认 15三项各占 1/3**
- 描述非空且长度 ≥ 30 字符 → +1/3
- 关联了 IssuePR body 含 `#<n>` 或 API 标记)→ +1/3
- 体量适中(`changed_files ≤ max_changed_files/2`)→ +1/3超过一半但未超上限 → +1/6
```
score = round(weights.pr_hygiene * 命中比例)
```
**4.4 commit_quality默认 15**
```
conforming = 符合 Conventional Commits (type(scope): subject) 的 commit 数
total_c = commit 总数
score = round(weights.commit_quality * conforming / total_c) # total_c=0 给满分
```
**4.5 ci_status默认 10**
```
CI 通过 → weights.ci_status
CI 失败 → 0
CI 未知/无 → round(weights.ci_status * 0.5)
```
### Step ⑤ 硬门禁评估
`hard_gates` 逐项判定,命中任一 → `hard_gate_failed = true`(记下命中的门禁名供评分卡 `⛔ Hard gate failures` 区):
| 门禁 | 命中条件 |
|------|----------|
| `forbid_blocker_findings` | 为 true 且存在 blocker 发现 |
| `require_ci_pass` | 为 true 且 CI **明确失败**`failing``unknown`/无 build 记录**不触发**本门禁 |
| `require_tests_for_src_changes` | 为 true 且 `changed_src > 0``changed_tests == 0` |
| `require_linked_issue` | 为 true 且未关联 Issue |
| `max_changed_files` | `> 0``changed_files > max_changed_files` |
### Step ⑥ 裁决计算
```
if hard_gate_failed: verdict = REQUEST_CHANGES
elif total >= thresholds.pass: verdict = PASS
elif total < thresholds.request_changes: verdict = REQUEST_CHANGES
else: verdict = COMMENT
```
### Step ⑦ 渲染评分卡(回写到 PR 的 Markdown
按下列模板填充emojiPASS=✅REQUEST_CHANGES=❌COMMENT=💬)。无硬门禁失败时省略 `⛔` 区;各发现区无内容时省略。
```markdown
## 🛡️ Gatekeeper Report — PR #<id> <title>
**Verdict: <emoji> <PASS|REQUEST_CHANGES|COMMENT>** · Score: <total>/100 · policy: <policy_path>@v<version>
| Dimension | Weight | Score | Notes |
|-----------|:------:|:-----:|-------|
| Review findings | 40 | <s>/40 | <n blocker / n major / n minor / n nit> |
| Test coverage | 20 | <s>/20 | <changed_src> src / <changed_tests> test files |
| PR hygiene | 15 | <s>/15 | <desc / linked issue / size 命中情况> |
| Commit quality | 15 | <s>/15 | <conforming>/<total> conventional |
| CI status | 10 | <s>/10 | <passing/failing/unknown> |
### ⛔ Hard gate failures (<n>)
- `<gate>`: <说明>
### 🔴 Must fix (<n>)
- [<severity>] <message><file>:<line>
### 🟡 Should fix (<n>)
- ...
### 🔵 Nits (<n>)
### ✅ Strengths
- <做得好的点>
### Next steps
1. <按裁决给出的最高优先级行动>
---
*Generated by gitlink-gatekeeper · policy-as-code PR gate · re-run after changes*
```
### Step ⑧ 回写dry-run默认vs --apply
**默认 dry-run**:不传 `--apply` 时,只把评分卡**打印给用户**,不回写、不打标签、不合并。
**`--apply` 才写**:写操作前**先向用户复述**「将向 PR #<id> 回写评分卡评论 + 打标签 `<label>`」,确认后执行。
回写评论(二选一,优先用 `pr +review` 因其自带 `--dry-run` 预览):
```bash
# 方式 A作为 PR review 回写status 用 common即 GitLink 的 COMMENT 等价)
gitlink-cli pr +review -i "$PR" --status common --content "$(cat scorecard.md)"
# 方式 B作为普通 PR 评论回写
gitlink-cli pr +comment -i "$PR" --body "$(cat scorecard.md)"
```
> ⚠️ `pr +review --status` 只接受 `common` / `approved` / `rejected`。评分卡评论一律用 `common`(不要因为裁决=PASS 就 `approved`APPROVE 是更强的批准语义,需用户显式授意)。裁决信息已写在评分卡标题里,状态由标签承载。
打标签GitLink 无「直接给 PR 挂标签」的命令,标签挂在 PR 背后的 Issue 上):
```bash
# 1) 确保裁决对应的标签存在(首次需创建;已存在则跳过)
gitlink-cli label +list --format json # 查现有标签拿 id
gitlink-cli label +create --name "gatekeeper:needs-changes" --color "#D73A4A" \
--description "PR gate: changes requested" # 不存在才创建
# 2) 取 PR 背后的 issue idpr +view 返回里有 issue 对象)
ISSUE_ID=$(gitlink-cli pr +view -i "$PR" --format json | jq -r '.data.issue.id')
# 3) 通过 Issue 更新挂标签(必须带回当前 subject/description否则会被清空——见 gitlink-shared
gitlink-cli api POST /:owner/:repo/issues/$ISSUE_ID --body '{
"issue_tag_ids": [<tag_id>],
"done_ratio": 0,
"subject": "<原始标题>",
"description": "<原始描述>"
}'
```
### Step ⑨ 安全规则(硬性,不可绕过)
- **默认 dry-run**:不传 `--apply` 一律只打印,不产生任何写副作用。
- **绝不默认自动合并**`behavior.auto_merge` 默认 `false`;即便策略里设为 `true`,也必须**同时**满足 `verdict == PASS` **且**命令显式带 `--apply` 才允许合并,且合并前再次向用户复述确认。
- 合并命令(仅在上述全部条件满足时):
```bash
# merge_method 来自策略 behavior.merge_methodmerge|rebase|squash
gitlink-cli pr +merge -i "$PR" --method squash
```
> 注意 CLI 标志是 `--method`(底层 API 字段才是 `do`)。
- 不回显 Token遵循 `gitlink-shared/SKILL.md` 的认证与 API 注意事项401 引导重登、403 查权限)。
- REQUEST_CHANGES 永不触发合并COMMENT/PASS 默认也不合并,除非满足自动合并三条件。
---
## 完整示例
### 示例 1dry-run默认安全不写任何东西
```bash
# 在目标仓库目录下,对 PR #42 跑门禁,仅预览评分卡
PR=42
gitlink-cli pr +view -i "$PR" --format json
gitlink-cli pr +files -i "$PR" --format json
gitlink-cli pr +diff -i "$PR" --format json
gitlink-cli api GET /:owner/:repo/pulls/$PR/commits --format json
gitlink-cli ci +builds --format json
# → AI 产出发现 → 按 §4 评分 → §5 硬门禁 → §6 裁决 → §7 渲染评分卡
# → dry-run仅把评分卡打印给用户结尾提示「如需回写到 PR请加 --apply」
```
预期产出(节选):
```
**Verdict: ❌ REQUEST_CHANGES** · Score: 58/100 · policy: gatekeeper.yaml@v1
⛔ Hard gate failures (1)
- require_tests_for_src_changes: 改了 3 个源码文件但没有新增测试
dry-run未回写、未打标签、未合并
```
### 示例 2--apply用户确认后回写评论 + 打标签)
```bash
PR=42
# …(同上采集 + 评分,得到裁决=REQUEST_CHANGES渲染出 scorecard.md)…
# 写操作前向用户复述:将向 PR #42 回写评分卡评论 + 打标签 gatekeeper:needs-changes
# 用户确认后:
# 1) 回写评分卡review 形式,自带 dry-run 可先预演)
gitlink-cli pr +review -i "$PR" --status common --dry-run --content "$(cat scorecard.md)" # 预演
gitlink-cli pr +review -i "$PR" --status common --content "$(cat scorecard.md)" # 实际回写
# 2) 打标签 gatekeeper:needs-changes按 Step ⑧ 取/建 tag_id 与 issue_id 后)
gitlink-cli api POST /:owner/:repo/issues/$ISSUE_ID --body '{
"issue_tag_ids": [101], "done_ratio": 0,
"subject": "feat: add rate limiter", "description": "<原始描述>"
}'
# 注意:裁决=REQUEST_CHANGES → 绝不合并。
# 即便裁决=PASS也只有在 policy.auto_merge=true 且本次显式 --apply 时才允许:
# gitlink-cli pr +merge -i "$PR" --method squash
```
---
## 策略预设
`examples/` 提供三套可直接 `--policy` 引用的预设:
| 预设 | 特点 | 适用 |
|------|------|------|
| `gatekeeper.yaml` | 默认平衡策略pass 85 / rc 60 | 大多数仓库 |
| `gatekeeper.strict.yaml` | 高阈值、`require_linked_issue: true`、更小 `max_changed_files` | 核心库 / 发布分支 |
| `gatekeeper.lenient.yaml` | 低阈值、关掉部分硬门禁 | 早期项目 / 文档仓库 |
```bash
gitlink-cli pr +view -i 42 --format json # 采集后用严格策略评分(评分逻辑同上,仅阈值/门禁不同)
# 评分时加载:--policy examples/gatekeeper.strict.yaml
```
---
## 最佳实践
1. **先 dry-run 再 `--apply`**:永远先看评分卡内容,确认无误再回写。
2. **策略进版本库**:把 `gatekeeper.yaml` 提交到仓库,让裁决标准对所有贡献者透明、可审计。
3. **可复现优先**:评分是确定性的——若两次裁决不同,先查是不是 PR 内容或策略变了而非「AI 心情」。
4. **控制发现数量**:最严重的 3-5 条比 20 条琐碎问题更有价值nit 折叠展示。
5. **大 PR 分段处理**`pr +diff` 输出可能很大Agent 应分段读取 diff 再汇总发现。
6. **标签复用**:同一仓库的三个 `gatekeeper:*` 标签建一次即可,后续只更新挂载关系。
## 注意事项
- PR review/评论提交后会通知关注该 PR 的参与者,评分卡内容保持专业、可操作。
- `--state` 过滤 PR 列表不精确,需用 `pull_request_status` 字段客户端判断0=open,1=merged,2=closed
- GitLink 主分支是 `master`(非 `main`);合并方式由策略 `merge_method` 决定。
- 对 draft PR 应提示用户先标记为 Ready for Review 再门禁。
- 端到端「PR 看门人闭环」(路由建议 reviewer → 裁决 → 回写 + 为 REQUEST_CHANGES 自动建 tracking issue的完整工作流形态见配套独立仓库 recorder/gitlink-gatekeeper策略字段与算法见 `REFERENCE.md`