From 9818a05afff8a00881e403dcd244ed67fea3a05f Mon Sep 17 00:00:00 2001 From: Mengz <2567587994@qq.com> Date: Sun, 26 Jul 2026 10:05:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(skills):=20=E5=85=BC=E5=AE=B9=E7=8E=B0?= =?UTF-8?q?=E6=9C=89=20CLI=20=E9=87=87=E8=AF=81=E5=B9=B6=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/gitlink-cli-contract-guard/SKILL.md | 57 ++- .../agents/openai.yaml | 2 +- .../examples/executive-contract-gate.md | 10 +- skills/gitlink-code-review/SKILL.md | 166 +++++- skills/gitlink-code-review/agents/openai.yaml | 2 +- .../examples/evidence-first-review.md | 11 +- .../examples/executive-review.md | 20 +- .../examples/pr-review-workflow.md | 8 + .../scripts/validate_review_report.py | 75 +++ skills/gitlink-maintainer-radar/SKILL.md | 53 +- .../agents/openai.yaml | 2 +- .../examples/executive-duty-board.md | 13 +- .../scripts/validate_radar_report.py | 60 +++ .../gitlink-maintenance-orchestrator/SKILL.md | 93 +++- .../agents/openai.yaml | 2 +- .../examples/fixtures/cli-contract-guard.json | 7 +- .../examples/fixtures/code-review.json | 5 + .../examples/fixtures/maintainer-radar.json | 5 + .../examples/fixtures/pr-integrator.json | 5 + .../examples/fixtures/pr-topology.json | 7 +- .../references/pipeline-contract.md | 12 +- .../scripts/run-maintenance-pipeline.ps1 | 194 ++++++- .../scripts/test-maintenance-output.ps1 | 45 ++ .../test_orchestrator_prompt_contract.py | 30 ++ .../scripts/test_validate_chinese_report.py | 34 ++ .../scripts/validate_chinese_report.py | 112 ++++ skills/gitlink-pr-integrator/SKILL.md | 61 ++- .../gitlink-pr-integrator/agents/openai.yaml | 2 +- .../examples/executive-integration.md | 12 +- skills/gitlink-pr-topology/SKILL.md | 481 +++++++++--------- skills/gitlink-pr-topology/agents/openai.yaml | 4 +- .../examples/executive-queue.md | 39 +- .../references/relationship-taxonomy.md | 44 +- .../references/maintenance-report-contract.md | 72 ++- .../scripts/validate_pr_cards.py | 107 ++++ 35 files changed, 1476 insertions(+), 376 deletions(-) create mode 100644 skills/gitlink-code-review/scripts/validate_review_report.py create mode 100644 skills/gitlink-maintainer-radar/scripts/validate_radar_report.py create mode 100644 skills/gitlink-maintenance-orchestrator/scripts/test-maintenance-output.ps1 create mode 100644 skills/gitlink-maintenance-orchestrator/scripts/test_orchestrator_prompt_contract.py create mode 100644 skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py create mode 100644 skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py create mode 100644 skills/gitlink-shared/scripts/validate_pr_cards.py diff --git a/skills/gitlink-cli-contract-guard/SKILL.md b/skills/gitlink-cli-contract-guard/SKILL.md index 32fd264..0ad336b 100644 --- a/skills/gitlink-cli-contract-guard/SKILL.md +++ b/skills/gitlink-cli-contract-guard/SKILL.md @@ -33,18 +33,23 @@ gitlink-cli workflow +review-queue --from queue.json --previous queue-previous.j - 只读运行,不评论、不 approve、不合并、不关闭、不修改远端。 - 使用 `CG-001` 起的稳定编号,记录旧行为、新行为、复现命令、严重性、证据、修复建议和验证限制。 - 首屏先显示兼容性结论、关键门禁和最多 5 项会影响现有用户或脚本的动作;blocking/high 使用颜色和粗体并保留文本标签。 +- 聊天和报告首屏按 PR 分节;参数与帮助、JSON/文本输出、错误与退出码、编码与颜色、兼容与文档、契约结论分别使用结论前置判断卡,后接解释、`依据:` 和影响/下一步。 - 一次运行只生成一份 UTF-8 Markdown,保存到 `reports/skill-runs/gitlink-cli-contract-guard/---.md`;多个 PR 在同一报告内分开结论。 -无法写入工作区时输出完整 Markdown 并标记“未落盘”。最终回复只需给出报告绝对路径、主结论和阻断数,不在聊天中重复整份报告。 +最终回复复用报告首屏的逐 PR 六方面判断卡,再给报告绝对路径;不能把多个 PR 或方面压成一段,也不能只给路径。无法写入工作区时输出完整 Markdown 并标记“未落盘”。 首屏固定先使用以下结构,再展开完整契约面: ```markdown # CLI 契约审查摘要 -**结论:** 阻断合并 **[blocked]** -**门禁:** 参数 `passed` | 帮助 `passed` | JSON `failed` | 错误 `passed` | 安全 `not_run` -**发现:** blocking 1 | high 1 | medium 0 | low 0 +## PR # +**参数与帮助:** 旧调用保持兼容 **[passed]**:新 flag 为可选且默认行为不变;依据:baseline/current `--help` 与旧调用对照;影响:现有用户无需迁移。 +**JSON/文本输出:** 机器输出已被破坏 **[failed]**:ANSI 状态文本混入 JSON;依据:golden 解析和原始字节;下一步:分离人读渲染与 JSON。 +**错误与退出码:** 错误语义稳定 **[passed]**:参数错误和远端失败仍可区分;依据:失败命令、stderr 和退出码;影响:自动化可继续判断故障。 +**编码与颜色:** 颜色边界未完整验证 **[partial]**:中文 UTF-8 正常但 `NO_COLOR` 缺测;依据:编码扫描和测试清单;下一步:补无颜色回归。 +**兼容与文档:** 文档与行为部分不一致 **[partial]**:示例未说明新增字段可选性;依据:README、帮助与实际 JSON 对照;下一步:同步说明。 +**契约结论:** 修复 JSON 后再审 **[blocked]**:存在一个 blocking 契约问题;依据:CG-001 与复现命令;下一步:修复并重跑完整矩阵。 ## 先处理这 2 项 @@ -52,6 +57,8 @@ gitlink-cli workflow +review-queue --from queue.json --previous queue-previous.j 2. [CG-002][high] 验证 header 换行和注入边界。 ``` +多个 PR 在同一报告中重复 `## PR #` 和六张判断卡,不能共享状态或证据。 + 这个 skill 的目标很窄,也很硬:**找出会把现有 CLI 用户用法搞坏的改动。** 它重点审查五类契约面: @@ -89,8 +96,13 @@ gitlink-cli workflow +review-queue --from queue.json --previous queue-previous.j ```markdown # CLI 契约审查摘要 -**结论:** 阻断合并 **[blocked]** -**门禁:** 参数通过 | 帮助通过 | JSON 失败 | 错误提示通过 | 安全未验证 +## PR # +**参数与帮助:** 默认调用兼容 **[passed]**:新参数保持旧默认值;依据:baseline/current 帮助和旧调用对照;影响:无需迁移。 +**JSON/文本输出:** JSON 已被 ANSI 破坏 **[failed]**:机器输出无法稳定解析;依据:golden 解析与原始字节;下一步:隔离渲染。 +**错误与退出码:** 错误语义稳定 **[passed]**:退出码仍可区分错误;依据:失败矩阵;影响:脚本兼容。 +**编码与颜色:** 注入和 NO_COLOR 未验证 **[partial]**:边界测试缺失;依据:测试清单;下一步:补恶意输入。 +**兼容与文档:** 文档说明不完整 **[partial]**:未说明字段可选性;依据:README 与实际输出;下一步:同步文档。 +**契约结论:** 修复 JSON 后再审 **[blocked]**:存在 blocking 问题;依据:CG-001;下一步:修复并重跑矩阵。 ## 先做这 2 件事 1. **[CG-001][blocking] 修复** JSON 输出中的 ANSI 转义,并补 golden 测试(责任:作者)。 @@ -237,28 +249,23 @@ go test ./... ### Step 6:输出契约审查结论 -推荐输出结构: +聊天和 Markdown 首屏必须使用前述逐 PR 六方面判断卡,详细 `CG-` 发现、命令和 golden 差异放在后文。保存后针对每个目标重复 `--require-pr` 并运行: -```markdown -# CLI 契约审查报告 - -## 高风险问题 -- `--header` 模板渲染后未再次校验,可能生成非法 header。 -- README.zh-CN 新增示例出现中文乱码,会污染用户可见文档。 - -## 契约面影响 -- 参数契约:`--header` 新增并改变请求构造行为。 -- 输出契约:无破坏性字段变更证据。 -- 错误契约:中文错误提示存在编码退化风险。 - -## 缺失验证 -- 缺少对 `Accept` 头覆盖行为的边界测试。 -- 缺少对渲染后非法 header 的测试。 - -## 结论 -- 需要修改后再合并。 +```bash +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py \ + --report \ + --require-pr \ + --min-cards 6 \ + --required-aspect "参数与帮助" \ + --required-aspect "JSON/文本输出" \ + --required-aspect "错误与退出码" \ + --required-aspect "编码与颜色" \ + --required-aspect "兼容与文档" \ + --required-aspect "契约结论" ``` +校验失败时必须重写,不能交付报告路径。只有本地改动且不存在 PR 编号时,可以用 `## PR #0` 表示本地候选,并在解释中注明不是远端 PR。 + ## 典型触发语句 - “帮我看这个改动会不会破坏现有 CLI 用法。” diff --git a/skills/gitlink-cli-contract-guard/agents/openai.yaml b/skills/gitlink-cli-contract-guard/agents/openai.yaml index aa5371f..684d0ba 100644 --- a/skills/gitlink-cli-contract-guard/agents/openai.yaml +++ b/skills/gitlink-cli-contract-guard/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "CLI 契约守卫" short_description: "检查 flags、help、JSON 输出和错误提示是否发生破坏性变化。" - default_prompt: "使用 $gitlink-cli-contract-guard 检查指定 PR 或本地改动。按 Skill 默认契约执行只读 CLI 契约审查,并生成关键结论前置的单一 Markdown 报告。" + default_prompt: "使用 $gitlink-cli-contract-guard 检查指定 PR 或本地改动;聊天和 Markdown 均按 PR 分节,将参数与帮助、JSON/文本输出、错误与退出码、编码与颜色、兼容与文档、契约结论分别做成结论前置判断卡,后接依据与影响,全程只读。" diff --git a/skills/gitlink-cli-contract-guard/examples/executive-contract-gate.md b/skills/gitlink-cli-contract-guard/examples/executive-contract-gate.md index 6aeab57..489445e 100644 --- a/skills/gitlink-cli-contract-guard/examples/executive-contract-gate.md +++ b/skills/gitlink-cli-contract-guard/examples/executive-contract-gate.md @@ -9,8 +9,14 @@ go run . pr +view --owner Gitlink --repo gitlink-cli --id 123 --format json 2>er ```markdown # CLI 契约审查摘要 -**结论:** 阻断合并 **[blocked]** -**门禁:** 参数通过 | 帮助通过 | JSON 失败 | 错误提示通过 | 安全未验证 + +## PR #123 +**参数与帮助:** 旧调用保持兼容 **[passed]**:新增参数为可选且默认值不变;依据:默认分支与当前 head 的帮助、旧命令和解析结果对照;影响:现有脚本无需迁移。 +**JSON/文本输出:** 机器输出契约已破坏 **[failed]**:调试文本混入 JSON 并导致解析失败;依据:相同命令的原始字节和结构化解析测试;下一步:分离人读日志与 JSON。 +**错误与退出码:** 错误语义仍可区分 **[passed]**:参数错误和远端错误保留不同退出码;依据:失败矩阵、stderr 和退出码;影响:自动化判断不受影响。 +**编码与颜色:** 注入与无颜色边界未验证 **[partial]**:中文 UTF-8 正常,但 `--header` 恶意输入和 `NO_COLOR` 缺少证据;依据:编码扫描与测试清单;下一步:补边界回归。 +**兼容与文档:** 文档说明不完整 **[partial]**:帮助未说明新增字段的可选性;依据:README、帮助和真实输出对照;下一步:同步契约说明。 +**契约结论:** 修复 JSON 后再审 **[blocked]**:存在一个会阻断脚本消费的契约问题;依据:CG-001 和稳定复现命令;下一步:修复并重跑完整矩阵。 ## 先做这 2 件事 1. **[CG-001][blocking] 修复** JSON 输出中的调试文本,并补结构化断言。 diff --git a/skills/gitlink-code-review/SKILL.md b/skills/gitlink-code-review/SKILL.md index 6b7f939..14293d0 100644 --- a/skills/gitlink-code-review/SKILL.md +++ b/skills/gitlink-code-review/SKILL.md @@ -18,10 +18,11 @@ description: "GitLink 社区智能审查:审查一个或多个 PR 的贡献价 - **只读远端**:可以生成 Review 结论、整体评论草稿和内联评论草稿,但不提交 Review、不评论、不 approve、不合并、不关闭、不分配、不改标签。 - **独立运行**:不调用其他 Skill。遇到需要专项判断的内容,可以注明验证限制,但仍完成本 Skill 能够完成的分析。 - **结论前置**:首屏先显示评审建议、阻断项、Review 履约结果和最多 5 项关键动作;blocking/high 使用颜色和粗体,并保留纯文本标签。 +- **报告结论加依据**:Markdown 中的价值、实现、Review 履约、测试和安全等关键方面先醒目显示 `passed/failed/partial/not_run`,随后用 1 至 2 句说明实际功能、判定证据和影响;状态词不能脱离描述单独出现。 - **证据可追溯**:发现使用 `CR-`,Review 履约项使用 `RV-`,健康项使用 `RH-`,Issue 分诊项使用 `IT-` 稳定编号。 - **单文件落盘**:一次运行生成一份 UTF-8 Markdown,保存到 `reports/skill-runs/gitlink-code-review/---.md`。 -无法写入工作区时,在最终回复中输出完整 Markdown 并标记“未落盘”。Markdown 不写 ANSI;凭据、cookie、token 和敏感值必须脱敏。 +最终回复的 PR 部分必须按 PR 分节,并分别显示 Review 建议、贡献价值、Review 履约、实现与逻辑、测试、安全和关键发现七张结论前置判断卡;不得压缩成一段“审查结论”。Issue 部分必须解释每个 P 级别含义、本批事项共同问题和下一步。最后声明 Review 草稿未提交并给出 Markdown 绝对路径。无法写入工作区时输出完整 Markdown 并标记“未落盘”。Markdown 不写 ANSI;凭据、cookie、token 和敏感值必须脱敏。 ## 运行模式 @@ -35,20 +36,69 @@ description: "GitLink 社区智能审查:审查一个或多个 PR 的贡献价 ### Issue 分诊模式 -用户要求 Issue 扫描或综合社区审查时启用。首屏只列各优先级的数量和 Issue 编号,详细分类统一放在报告最后。 +用户要求 Issue 扫描或综合社区审查时启用,支持三种明确范围: -## 首屏固定结构 +- **前 N 条 open Issue**:例如“处理前 40 条 open Issue”;按最近更新时间降序取 N 条,并用真实状态二次过滤。 +- **全部 open Issue**:自动翻页、按 Issue ID 去重并处理当前全部开启项;报告必须记录实际页数、条数和截断/失败情况。 +- **指定 Issue**:例如“只处理 #12、#18、#31”;逐条读取并回显真实状态,closed 项只标记为历史项,不混入 open 待办。 + +用户只说“处理 Issue”但没有范围时,默认取最近更新的前 40 条 open Issue,并在首屏明确该默认范围。只请求 PR 审查时不自动扫描 Issue,首屏省略 `Issue 待办`,报告末尾注明该模式未启用。Issue 首屏只列范围、各优先级数量和编号,详细分类统一放在报告最后。 + +## 聊天和报告首屏固定结构 + +聊天可以比详细报告短,但必须逐 PR 保留全部专项方面;每一方面独立成行,最直接结论位于最前: + +```markdown +## PR # +**Review 建议:** 修改后再审 **[action_required]**:存在 2 个影响真实使用的问题;依据:CR--001、CR--002;下一步:按发现逐项修复并复验。 +**贡献价值:** 价值成立 **[passed]**:解决 <实际问题>;依据:默认分支差异、需求和受益范围;影响:<用户或维护收益>。 +**Review 履约:** 本轮无可核对 Review **[not_applicable]**:没有有效 Review 意见;依据:Review 列表与当前 head;影响:只评估当前完整 Diff。 +**实现与逻辑:** 核心边界仍有错误 **[failed]**:<触发条件与错误行为>;依据:`path/file.go:42` 与复现命令;下一步:<具体修改>。 +**测试:** 关键失败路径缺失 **[partial]**:正常测试通过但 <场景> 未覆盖;依据:测试文件与执行结果;下一步:补回归用例。 +**安全:** 未扩大安全边界 **[passed]**:没有新增认证、执行或敏感输出路径;依据:Diff 与安全矩阵;影响:无安全阻断。 +**关键发现:** 2 项必须修改 **[high]**:CR--001、CR--002;依据:文件行号和复现证据;下一步:优先修复 high 项。 + +**Issue 分诊** + +P0(立即处置):0 条。没有发现安全事故、数据损坏或核心服务不可用事项。 +P1(本轮优先处理):#27、#26、#24。上述事项影响常用流程或阻塞维护工作,信息基本完整,应在当前维护周期确认负责人并推进。 +P2(进入计划处理):#25、#17。问题真实但不构成当前阻断,建议补充验收条件后排入迭代。 +P3(可延后或先补信息):#23、#22。影响较低或上下文不足,先请求复现信息、去重或确认需求。 +``` + +问题编号、文件位置、问题数量和 Issue 概述必须来自本轮证据,不能复制示例。closed/merged 历史 PR 仍按同样七方面输出,以 `not_applicable` 说明无需当前门禁,并在 Review 建议中写清保持关闭或历史对照的依据。 + +## Markdown 报告首屏固定结构 首屏只保留直接改变维护者决策的信息: ```markdown # GitLink 社区审查摘要 -**Review 建议:** 修改后再审 **[action_required]** -**PR 门禁:** 价值 `passed` | Review 履约 `failed` | 实现 `partial` | 测试 `failed` | 安全 `passed` -**Review 履约:** 8 条 | 已完成 6 | 部分完成 1 | 未完成 1 | 引入回归 0 -**代码发现:** blocking 0 | high 2 | medium 3 | low 4 -**Issue 待办:** P0 1 条(#81)| P1 3 条(#72、#76、#89)| P2 5 条 | P3 8 条 +## PR #123 +**Review 建议:** 修改后再审 **[action_required]**:正常流程可用,但 closed PR 会进入 open 队列;依据:`shortcuts/workflow/pr_fetch.go:403`、真实响应和缺失的回归 fixture;下一步:增加客户端二次过滤并补三类真实响应测试后复看。 + +**贡献价值:** 价值成立 **[passed]**:为维护者增加 SLA 与责任方识别;依据:默认分支没有等价输出、需求与受益范围;影响:减少人工排队。 +**Review 履约:** 没有待履约意见 **[not_applicable]**:当前没有有效 Review;依据:Review 列表与 head SHA;影响:本轮只评价完整 Diff。 +**实现与逻辑:** 核心队列结果不可靠 **[failed]**:真实 open 查询会混入 closed PR;依据:真实响应与 `pull_request_status` 归一化路径;下一步:增加客户端二次过滤。 +**测试:** 真实响应覆盖不完整 **[partial]**:构建和理想响应测试通过;依据:测试命令和现有 fixture;下一步:补服务端忽略 state、数值状态和无责任字段场景。 +**安全:** 未扩大安全边界 **[passed]**:改动为只读归一化;依据:Diff 未新增认证、权限、执行或敏感输出路径;影响:无安全阻断。 +**关键发现:** 1 项 high 必须修复 **[high]**:open 队列可能包含 closed PR;依据:CR-001 与真实响应;下一步:修复后复验。 + +**代码发现:** +- **blocking(阻止合并):0 条。** 未发现已证实的漏洞、数据破坏或不可逆回归。 +- high(本轮必须修复):1 条,CR-001。 open 队列可能包含 closed PR,直接影响维护者判断。 +- **medium(应补齐后复看):2 条,CR-002、CR-003。** 缺少真实响应测试,更新时间回退来源也未显式说明。 +- **low(可延后优化):0 条。** + +## 已关闭历史对照 +**#90:** 状态为 closed,只用于比较既有实现,不生成当前门禁、Review 建议或重新打开建议。 + +## Issue 分诊(最近更新的前 40 条 open,实际取得 15 条) +**P0(立即处置):0 条。** 没有发现需要立刻止损的安全事故、数据损坏或核心服务不可用事项。 +**P1(本轮优先处理):#81、#82、#83。** 这些事项影响常用流程或阻塞维护工作,信息基本足够,应在当前维护周期确认负责人并推进。 +**P2(进入计划处理):#84、#85。** 问题真实但没有当前阻断证据,建议补充验收条件后进入迭代计划。 +**P3(可延后或先补信息):#86、#87。** 影响较低或上下文不足,先请求复现信息、去重或确认需求。 ## 先处理这 3 项 @@ -57,7 +107,9 @@ description: "GitLink 社区智能审查:审查一个或多个 PR 的贡献价 3. [CR-004][high] 收紧权限边界:写操作缺少资源归属校验。 ``` -Issue 首屏摘要不得展开标题、原因、标签或负责人。颜色只用于最终结论、blocking/high 和关键动作;始终保留 `[action_required]`、`[high]` 等文本回退。 +示例中的编号、状态和发现仅用于定义格式,实际输出必须从本次 API、Diff、Review 和测试证据重新计算,禁止复制示例结论。 + +聊天和 Markdown 中每个 PR 都必须有独立的七方面判断卡,不能把多条 PR 或多个方面合并成一句“修改后再审”。每张卡必须以加粗、着色的结论开头,随后给出简短解释、明确的 `依据:` 和影响/下一步;任何结论都不能单独出现。closed/merged 历史项仍按七方面输出,以 `not_applicable` 说明无需当前门禁。Issue 首屏不逐条展开完整正文,但每个 P 级别必须说明级别含义、编号、本批事项的共同问题和下一步,不能只列计数。颜色只用于状态结论、总建议、blocking/high 和关键动作;始终保留 `[action_required]`、`[high]` 等文本回退。 ## 证据采集 @@ -143,10 +195,83 @@ gitlink-cli pr +reviews --owner --repo --id --f ### 6. 生成 Review 建议 -给出 `建议通过`、`修改后再审`、`暂缓合并` 或 `需要人工判断`,并生成可供维护者编辑的整体 Review 草稿;需要精确定位时生成内联评论草稿。草稿应包含正向评价、阻断问题、证据和最小修复建议。 +对每个 open PR 分别给出 `建议合并`、`修改后再审`、`暂缓合并` 或 `需要人工判断`,并在报告中生成一份可供维护者直接审核的 Review 草稿。不得只写“测试失败”“实现不完整”等泛化意见,草稿至少包含: + +1. PR 实际解决的问题和已经做对的部分。 +2. 每个必须修改的问题,包含 `CR-/RV-` 编号、文件与行号或复现命令、当前行为和用户影响。 +3. 具体修改要求,说明应改哪段逻辑、补什么边界或保持什么兼容行为,而不是只说“请优化”。 +4. 需要新增或重跑的验证,以及维护者再次 Review 时的通过条件。 +5. 若不存在阻断项,明确说明建议通过的证据和仍需关注的非阻断风险。 + +无论使用预定义结论还是根据仓库语境生成其他结论,都必须给出与结论匹配的依据。常见结论至少遵循以下要求: + +- **建议合并**:先说明 PR 解决的具体问题和实现亮点,再列出已核验的正常、失败、兼容或安全证据,明确没有必须修改的 `blocking/high` 问题;存在非阻断风险时说明为什么不影响当前合并。 +- **修改后再审**:先肯定已经成立的功能,再逐项指出不合格的文件、逻辑、触发条件和影响,给出具体修改方式、需要补充的测试以及可核验的复审通过条件。 +- **暂缓合并**:说明当前阻塞来自前置依赖、主线冲突、外部 API、发布窗口还是仓库决策,列出已有证据、继续合并的具体风险、解除阻塞的责任方和重新评估条件。 +- **需要人工判断**:列出无法由代码事实单独决定的选项和权衡,说明已经确认与仍缺失的证据,并把维护者需要回答的问题写成可执行决策点。 +- **保持关闭/拒绝**:说明能力是否已被主线或其他 PR 覆盖、问题是否不适合仓库定位,引用对照提交或重复实现证据,并说明为什么继续投入没有增量价值。 + +需要精确定位时生成内联评论草稿。多个 PR 的 Review 草稿必须分节,不能共享结论或问题编号。closed/merged 历史 PR 不生成新的 Review 草稿,除非用户明确要求复审历史实现。 + +所有 Review 草稿先使用统一结构,结论必须位于具体描述之前: + +```markdown +### PR # Review 建议草稿(未提交) +**Review 建议:** <结论> **[]**:<用 1 至 3 句说明 PR 做了什么、关键证据以及为什么得到该结论。> + +**依据与影响:** <引用 Diff、文件行号、Review、测试命令或主线对照,说明成立的功能、存在的问题和用户/维护者影响。> + +**下一步:** <合并、具体修改与复验、解除依赖或需要维护者决定的问题;没有必改项时明确写出。> +``` + +例如,“修改后再审”不能只写状态,必须落到可执行问题: + +```markdown +### PR # Review 建议草稿(未提交) +**Review 建议:** 修改后再审 **[action_required]**:这项改动解决了 <具体问题>,其中 <已验证的优点> 已有证据;但 `path/file.go:42` 在 <触发条件> 下仍会 <错误行为和影响>,当前不能合并。 + +**依据与影响:** [CR-001][high] <当前行为、证据和用户影响>;[RV-001][medium] <既有 Review 未满足部分及证据>。 + +**下一步:** 请 <具体修改要求>,新增 <正常/失败/兼容场景> 测试并运行 `<仓库命令>`;确认 <预期结果> 后重新 Review。 +``` + +草稿中的路径、行号、命令和要求必须来自当前 PR 证据;无法定位时标为 `not_verifiable` 并说明缺什么,不得填入示例占位内容。 无论结论为何,都不得调用远端写接口。最终回复必须明确说明“Review 草稿尚未提交,需人工审核”。 +### 7. 校验报告中的 Review 依据 + +PR 审查模式完成 Markdown 后必须运行: + +```bash +python -X utf8 skills/gitlink-code-review/scripts/validate_review_report.py \ + --report \ + --require-review + +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py \ + --report \ + --require-pr \ + --min-cards 7 \ + --required-aspect "Review 建议" \ + --required-aspect "贡献价值" \ + --required-aspect "Review 履约" \ + --required-aspect "实现与逻辑" \ + --required-aspect "测试" \ + --required-aspect "安全" \ + --required-aspect "关键发现" +``` + +多个 PR 重复追加 `--require-pr`。两个校验器都通过后才能交付报告并复用首屏卡片作为聊天摘要。 + +校验器会拒绝以下输出: + +- `Review 建议` 只有醒目结论和状态,没有在同一字段中紧跟依据。 +- 依据过短,或没有事实、证据、影响、验证和可执行下一步中的任何一项。 +- Review 草稿仍使用旧的 `**建议:** 修改后再审` 格式。 +- 模板占位符没有替换,或 PR 审查报告完全缺少 Review 建议。 + +校验失败时必须修改报告并重新执行,直到退出码为 0;不能把未通过校验的 Markdown 路径返回给用户。Issue-only 或仓库健康度-only 模式可以省略 `--require-review`。 + ## 仓库健康扫描 仓库模式至少检查: @@ -162,7 +287,9 @@ gitlink-cli pr +reviews --owner --repo --id --f ## 批量 Issue 分诊 -扫描 open、未分类、近期新增或长期未处理的 Issue,按以下维度建立 `IT-` 项: +先把用户输入规范化为 `first_n_open`、`all_open` 或 `issue_ids`,并在报告中记录排序、翻页、去重和最终纳入数量。open 范围必须按真实 Issue 状态二次过滤;接口失败时保留已取得页并明确 `partial`,不能用历史样例补足数量。 + +扫描纳入范围内未分类、近期新增或长期未处理的 Issue,按以下维度建立 `IT-` 项: - 类型:bug、feature、documentation、question、performance、security 或 maintenance。 - 优先级:`P0` 立即处置、`P1` 本轮处理、`P2` 计划处理、`P3` 可延后。 @@ -174,6 +301,8 @@ gitlink-cli pr +reviews --owner --repo --id --f P0/P1 必须有证据,安全问题避免在报告中复制利用细节或敏感值。默认只生成建议;标签、分配、回复、关闭和其他写操作必须经过人工审核和新的明确授权。 +聊天摘要和报告中的 Issue 分诊均按以下形式输出:`级别(处置含义):编号;本批事项概述;建议下一步`。概述必须来自本批 Issue 的标题、正文、标签、响应状态和等待方,不能复制通用定义冒充分析。相同原因可以合并描述,特殊的 P0/P1 单独指出。 + ## 发现与严重性 每条 `CR-` 发现必须包含严重性、事实类型、文件与行号或复现命令、触发条件、影响、证据、最小修复建议和验证限制。 @@ -185,12 +314,14 @@ P0/P1 必须有证据,安全问题避免在报告中复制利用细节或敏 没有精确证据的内容只能标记 `candidate`,不能升级为 blocking。纯格式偏好和可自动修复的低价值问题不得进入首屏。 +首屏按严重性给出 `级别含义 + 数量/编号 + 本批问题摘要`。数量为 0 时简要说明未发现该级别的已证实问题;数量大于 0 时至少概括最影响决策的一类问题,不能只输出 `blocking 0 | high 1`。 + ## 单一 Markdown 报告顺序 -1. 首屏审查结论、Review 建议、门禁、Review 履约统计、Issue 优先级计数和最多五项动作。 +1. 首屏按 PR 分开的实质性 Review 参考、带依据门禁、代码发现说明、Issue 分级说明和最多五项动作。 2. PR 目标、贡献价值和变更概览。 3. `RV-` Review 修改履约明细。 -4. 完整 `CR-` 代码审查、正向证据和 Review 草稿。 +4. 每个 open PR 独立的完整 `CR-` 代码审查、正向证据和可供人工审核的 Review 草稿。 5. 构建、测试、功能验证和验证限制。 6. `RH-` 仓库代码健康度。 7. `IT-` Issue 分诊详细结果。 @@ -200,11 +331,14 @@ P0/P1 必须有证据,安全问题避免在报告中复制利用细节或敏 ## 完成前自检 -- 首屏是否先给结论,且最多只有五项会改变维护者决策的动作。 +- 聊天回复和 Markdown 首屏是否都按 PR 分节,并完整保留七方面结论前置判断卡。 +- 每张卡是否在醒目结论后紧跟简短解释、明确 `依据:` 和影响/下一步;无论结论为何都没有只写状态。 - 是否完整分析实际相关维度,而不是机械限制为五项。 - 是否区分完整 PR Diff 与 Review 后增量 Diff。 - 每条有效 Review 是否有 `RV-` 状态、证据和剩余动作。 - Review 建议和草稿是否只写入报告,没有提交远端。 -- 仓库健康扫描和 Issue 分诊是否在请求时保留,Issue 详情是否位于报告最后。 +- PR 审查报告是否同时通过 `validate_review_report.py --require-review` 和 `validate_pr_cards.py` 七方面校验。 +- 仓库健康扫描和 Issue 分诊是否在请求时保留,Issue 范围是否明确为前 N 条、全部 open 或指定编号,Issue 详情是否位于报告最后。 - blocking/high 是否有可复现证据,未执行测试是否标为 `not_run`。 -- 是否生成一份 UTF-8 Markdown,并在最终回复中给出绝对路径和一句话结论。 +- Issue 的聊天直接输出是否解释 P0/P1/P2/P3 的处置含义、对应编号、本批问题概述和下一步,而不是只列级别与编号。 +- 是否生成一份 UTF-8 Markdown,并在最终回复中给出审查结论、Issue 分诊说明、Review 草稿状态和绝对路径。 diff --git a/skills/gitlink-code-review/agents/openai.yaml b/skills/gitlink-code-review/agents/openai.yaml index 631bf78..922e1fa 100644 --- a/skills/gitlink-code-review/agents/openai.yaml +++ b/skills/gitlink-code-review/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "社区智能审查" short_description: "验证 PR、Review 修改、仓库健康和 Issue 分诊,生成决策优先报告。" - default_prompt: "使用 $gitlink-code-review 审查指定 GitLink PR,验证已有 Review 的修改情况,并生成结论前置、详情完整且不回写远端的 Markdown 报告。" + default_prompt: "使用 $gitlink-code-review 审查指定 GitLink PR;聊天和 Markdown 均按 PR 分节,将 Review 建议、贡献价值、Review 履约、实现与逻辑、测试、安全和关键发现分别做成结论前置判断卡,后接依据与影响;Issue 分级另行说明,不回写远端。" diff --git a/skills/gitlink-code-review/examples/evidence-first-review.md b/skills/gitlink-code-review/examples/evidence-first-review.md index 6617a9f..3d5fe2e 100644 --- a/skills/gitlink-code-review/examples/evidence-first-review.md +++ b/skills/gitlink-code-review/examples/evidence-first-review.md @@ -24,9 +24,14 @@ $context | Set-Content .\pr-123-context.json -Encoding utf8 ## 首屏输出 ```markdown -# PR #123 代码审查摘要 -**结论:** 需要补充验证 **[action_required]** -**证据:** 当前 head `abcdef1` | CI SHA 匹配 `1/1` | 安全 `partial` +## PR #123 +**Review 建议:** 修改后再审 **[action_required]**:失败路径和错误输出仍可能影响真实用户;依据:CR-001、CR-002 以及缺失的脱敏测试;下一步:修复并按当前 head 复验。 +**贡献价值:** 目标问题真实且增量明确 **[passed]**:新增能力填补默认分支缺口;依据:Issue、baseline Diff 和受益范围;影响:减少维护者人工步骤。 +**Review 履约:** 既有意见已经满足 **[passed]**:作者修复了错误映射并补正常路径测试;依据:Review 后提交、当前代码和 RV-001;影响:没有遗留 Review 阻断。 +**实现与逻辑:** 主流程行为正确 **[passed]**:当前 head 的核心路径运行成功;依据:CI 按 SHA 匹配 `1/1` 和复现命令;影响:声明功能可用。 +**测试:** 失败路径覆盖不完整 **[partial]**:异常输入没有回归用例;依据:测试文件和测试清单;下一步:补失败与兼容测试。 +**安全:** 脱敏行为尚未证明 **[partial]**:Diff 可定位敏感输出风险;依据:错误路径和缺失的脱敏断言;下一步:验证日志不泄露敏感值。 +**关键发现:** 2 项问题需要处理 **[high]**:失败路径和敏感输出会影响合并判断;依据:CR-001、CR-002;下一步:修复后按同一 head 复验。 ## 先做这 2 件事 1. **[CR-001][high] 补充** 失败路径测试(责任:作者;证据:`E-CR-001`)。 diff --git a/skills/gitlink-code-review/examples/executive-review.md b/skills/gitlink-code-review/examples/executive-review.md index 6aee589..e567680 100644 --- a/skills/gitlink-code-review/examples/executive-review.md +++ b/skills/gitlink-code-review/examples/executive-review.md @@ -10,14 +10,28 @@ gitlink-cli pr +reviews --owner Gitlink --repo gitlink-cli --id 123 --format jso ``` ```markdown -# PR #123 代码审查摘要 -**结论:** 需要补验证 **[action_required]** -**安全门禁:** 部分完成 | **验证:** 部分完成 | **问题:** high 1 / medium 2 +## PR #123 +**Review 建议:** 修改后再审 **[action_required]**:批量操作入口和帮助文档已经形成完整主流程,但输入边界可能产生不可诊断错误或跨平台回归;依据:无权限请求、恶意路径和 Windows 中文错误输出尚未验证;下一步:补齐实现和测试后复看。 +**贡献价值:** 功能增量成立 **[passed]**:PR 补齐高频批量操作,默认分支没有等价入口;依据:需求、Diff、帮助文本和受益范围;影响:减少重复人工操作。 +**Review 履约:** 本轮没有待核对意见 **[not_applicable]**:未发现有效 Review;依据:Review 列表与当前 head SHA;影响:本轮只评价完整 Diff。 +**实现与逻辑:** 正常路径可用但边界不完整 **[partial]**:权限失败和恶意路径没有可靠处理证据;依据:实现分支与复现清单;下一步:补错误映射和输入校验。 +**测试:** 关键失败路径缺测 **[partial]**:现有测试只覆盖成功流程;依据:测试文件和执行结果;下一步:补权限、非法路径和 Windows UTF-8 回归。 +**安全:** 输入边界未完整验证 **[partial]**:改动触及路径和权限输入;依据:Diff 与安全矩阵仅覆盖部分场景;下一步:补恶意输入和无权限测试。 +**关键发现:** 1 项 high 与 2 项 medium 待处理 **[high]**:问题集中在权限失败和跨平台边界;依据:CR-001 至 CR-003;下一步:先修 high 再完成编码回归。 + +**代码发现:** +- **blocking(阻止合并):0 条。** 未发现已证实的漏洞或数据破坏。 +- **high(本轮必须修复):1 条,CR-001。** 权限失败路径缺失,可能让无权限请求得到错误结果。 +- **medium(应补齐后复看):2 条,CR-002、CR-003。** Windows 中文错误和 API 回滚行为没有验证。 +- **low(可延后优化):0 条。** ## 先做这 3 件事 1. **[CR-001][high] 补充** 恶意路径和无权限请求测试(责任:作者)。 2. **[CR-002][medium] 验证** Windows PowerShell 下的中文错误输出(责任:作者)。 3. **[CR-003][medium] 复看** API 失败时的回滚行为(责任:维护者)。 + +### PR #123 Review 建议草稿(未提交) +**Review 建议:** 修改后再审 **[action_required]**:批量操作入口和帮助文档已经形成完整主流程,但合并前需要补齐无权限请求、恶意路径和 Windows 中文错误输出,这些未验证边界会影响错误诊断和跨平台可用性。请在 `shortcuts/example/example.go:42` 保留当前正常路径,同时对无权限响应返回可诊断错误,并新增失败路径与 Windows UTF-8 回归测试。完成后运行 `go test ./shortcuts/example -count=1`,确认成功、无权限和非法路径三类场景均通过,再提交复看。 ``` ## 关键验证 diff --git a/skills/gitlink-code-review/examples/pr-review-workflow.md b/skills/gitlink-code-review/examples/pr-review-workflow.md index 98740a8..f66c0e5 100644 --- a/skills/gitlink-code-review/examples/pr-review-workflow.md +++ b/skills/gitlink-code-review/examples/pr-review-workflow.md @@ -72,6 +72,14 @@ gitlink-cli pr +reviews --id 42 --format json ```markdown ## PR #42 代码审查报告 +**Review 建议:** 修复安全阻断后再审 **[action_required]**:认证实现存在硬编码凭据和 SQL 注入风险;依据:CR-42-001、CR-42-002 与精确代码位置;下一步:完成参数化查询、密钥外置和安全回归后复看。 +**贡献价值:** 认证能力具有实际价值 **[passed]**:PR 提供登录和 Token 流程;依据:需求、模块 Diff 和主要使用路径;影响:形成可用的认证入口。 +**Review 履约:** 没有既有意见可核对 **[not_applicable]**:本轮没有有效 Review;依据:Review 列表和当前 head;影响:直接审查完整实现。 +**实现与逻辑:** 认证边界不安全 **[failed]**:查询直接拼接输入且密码处理不正确;依据:`src/auth/login.py:42`、`src/auth/login.py:88`;下一步:参数化查询并使用安全哈希。 +**测试:** 安全与边界用例不足 **[partial]**:已有测试覆盖主要成功路径;依据:`tests/test_auth.py` 和测试清单;下一步:补注入、空值、超长输入和过期 Token。 +**安全:** 存在两个阻断级风险 **[failed]**:硬编码密钥和 SQL 注入可被直接触发;依据:`src/config.py:15`、`src/auth/login.py:42`;下一步:移除凭据并使用参数化 API。 +**关键发现:** 2 项 blocking 必须先修复 **[blocking]**:CR-42-001、CR-42-002 会影响数据和凭据安全;依据:代码证据和攻击路径;下一步:阻断合并直到安全测试通过。 + ### 🔴 Critical 1. **JWT Secret 硬编码** — `src/config.py:15` diff --git a/skills/gitlink-code-review/scripts/validate_review_report.py b/skills/gitlink-code-review/scripts/validate_review_report.py new file mode 100644 index 0000000..f46aaf1 --- /dev/null +++ b/skills/gitlink-code-review/scripts/validate_review_report.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Validate that a code-review report gives evidence-backed Review advice.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +REVIEW_PREFIX = "**Review 建议:**" +LEGACY_PREFIX = "**建议:**" +REVIEW_PATTERN = re.compile( + r"^\*\*Review 建议:\*\*\s*" + r"]*>([^<]+)\s*" + r"\*\*\[([^\]]+)\]\*\*:\s*(\S.*)$" +) +REVIEW_DRAFT_HEADING = re.compile(r"^### PR #\d+ Review 建议草稿(未提交)\s*$") +EVIDENCE_MARKERS = ("依据", "测试", "Diff", "文件", "行号", "CR-", "RV-", "实现", "影响", "主线", "API", "命令", "已核验", "未发现", "通过", "失败", "缺少", "需要") +PLACEHOLDER_MARKERS = ("<结论>", "<具体", " list[str]: + errors: list[str] = [] + review_lines: list[int] = [] + lines = text.splitlines() + for line_number, line in enumerate(lines, start=1): + stripped = line.strip() + if stripped.startswith(LEGACY_PREFIX): + errors.append(f"line {line_number}: legacy conclusion-only field is not allowed") + if not stripped.startswith(REVIEW_PREFIX): + continue + review_lines.append(line_number) + match = REVIEW_PATTERN.match(stripped) + if not match: + errors.append(f"line {line_number}: Review decision requires a status tag and rationale") + continue + conclusion, status, rationale = match.groups() + if not conclusion.strip() or not status.strip() or len(rationale.strip()) < 30: + errors.append(f"line {line_number}: Review rationale is too short") + if not any(marker in rationale for marker in EVIDENCE_MARKERS): + errors.append(f"line {line_number}: Review rationale lacks evidence or an actionable next step") + if any(marker in rationale for marker in PLACEHOLDER_MARKERS): + errors.append(f"line {line_number}: unresolved template placeholder in rationale") + for index, line in enumerate(lines): + if REVIEW_DRAFT_HEADING.match(line.strip()) and not any(candidate.strip().startswith(REVIEW_PREFIX) for candidate in lines[index + 1:index + 7]): + errors.append(f"line {index + 1}: Review draft must start with a substantive Review suggestion") + if require_review and not review_lines: + errors.append("PR review mode requires at least one substantive Review decision") + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate substantive Review decisions.") + parser.add_argument("--report", required=True, type=Path) + parser.add_argument("--require-review", action="store_true") + args = parser.parse_args() + try: + text = args.report.read_text(encoding="utf-8", errors="strict") + except (OSError, UnicodeError) as exc: + print(f"review report validation failed: {exc}", file=sys.stderr) + return 2 + errors = validate_report(text, args.require_review) + if errors: + print("review report validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print("review report validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/gitlink-maintainer-radar/SKILL.md b/skills/gitlink-maintainer-radar/SKILL.md index afb9348..df958ed 100644 --- a/skills/gitlink-maintainer-radar/SKILL.md +++ b/skills/gitlink-maintainer-radar/SKILL.md @@ -32,17 +32,27 @@ gitlink-cli workflow +review-context --owner --repo --number ---.md`;完整队列与负载明细放同一文件附录。 +- 报告文件是完成条件,不是可选附件。必须先确定本轮唯一绝对路径、写入完整报告并通过 `validate_radar_report.py`,之后才能发送聊天结论。 -无法写入工作区时输出完整 Markdown 并标记“未落盘”。最终回复给出报告绝对路径、HOT 数、超 SLA 数和第一待办。 +最终回复按 PR 复用报告首屏六方面判断卡,再给报告绝对路径;不得把多个 PR 或多个治理方面压成一段。正常可写工作区中不得以“已在聊天输出”为由跳过落盘,也不得返回上一轮旧报告路径。写入失败时先创建父目录并使用明确 UTF-8;只有文件系统确实不可写时才允许输出完整 Markdown 并标记“未落盘”。 首屏固定先使用: ```markdown # 维护者值班摘要 -**结论:** 今日需处理 **[action_required]** -**队列:** HOT 4 | WATCH 6 | 超 SLA 3 | reviewer 瓶颈 1 | 安全 HOT 1 +**队列事实:** 扫描 open PR 条,目标 PR 条,数据完整性 。 +**判定依据:** 固定 `as_of`、当前状态、活动时间和分配关系;详细结论按 PR 展示。 + +## PR #123 +**响应 SLA:** 已超时 24 小时 **[hot]**:等待 review 共 96 小时;依据:创建时间、最近活动和 72 小时阈值;影响:进入今日优先队列。 +**等待方:** 当前等待 reviewer **[action_required]**:作者已经更新且没有新 Review;依据:最后提交、Review 状态和分配关系;下一步:提醒或转派 reviewer。 +**Reviewer 负载:** 当前 reviewer 过载 **[high]**:名下积压 5 条待审 PR;依据:同一快照的 reviewer 待办计数;影响:建议释放容量。 +**责任停滞:** 责任明确但长期无动作 **[hot]**:分配后 4 天没有推进;依据:assignee/reviewer 与最后活动时间;下一步:确认接单或转派。 +**安全运营优先级:** 需要优先安排安全复看 **[high]**:改动触及权限路径;依据:文件范围和已有安全标记,不代表漏洞成立;影响:优先匹配安全 reviewer。 +**维护动作:** 今天完成转派并启动复看 **[action_required]**:该 PR 同时超 SLA 且责任停滞;依据:MR-001 与上述时间/负载证据;下一步:维护者确认 reviewer。 ## 先做这 3 件事 @@ -51,6 +61,8 @@ gitlink-cli workflow +review-context --owner --repo --number [MR-003][high] 复看 PR #118;等待:maintainer。 ``` +指定多个 PR 时重复 `## PR #` 和六张卡。队列级计数仅作为范围元数据,不能代替逐 PR 判断。 + 这个 skill 不再做“把通知列表抄一遍”的弱摘要,而是把三类真正影响维护者效率的治理信号合在一起: 1. **响应时效雷达**:找出超出响应 SLA 的 Issue 和 PR。 @@ -90,14 +102,13 @@ gitlink-cli workflow +review-context --owner --repo --number 今日需处理 **[action_required]** -**队列:** HOT 4 | WATCH 6 | reviewer 瓶颈 1 | 安全 HOT 1 - -## 先做这 4 件事 -1. **[MR-001][blocking] 转派** PR #123 的安全复查,当前等待 reviewer(责任:维护者)。 -2. **[MR-002][high] 回复** Issue #87,首响已超 24 小时(责任:维护者)。 -3. **[MR-003][high] 复看** 作者更新后的 PR #118(责任:reviewer)。 -4. **[MR-004][medium] 确认** Issue #91 是否继续推进(责任:assignee)。 +## PR #123 +**响应 SLA:** 已超过 Review SLA **[hot]**:等待 96 小时;依据:固定扫描时间与最后活动;影响:今天处理。 +**等待方:** 等待 reviewer **[action_required]**:作者已更新;依据:提交和 Review 状态;下一步:安排复看。 +**Reviewer 负载:** 分配存在瓶颈 **[high]**:当前 reviewer 积压较多;依据:同一快照待审计数;下一步:考虑转派。 +**责任停滞:** 已分配但无推进 **[hot]**:责任关系存在但四天无动作;依据:分配与活动时间;下一步:确认接单。 +**安全运营优先级:** 需要安全复看 **[high]**:涉及权限路径;依据:改动文件与风险标签;影响:匹配专项 reviewer。 +**维护动作:** 今天转派并复看 **[action_required]**:同时命中超时和停滞;依据:MR-001;下一步:维护者执行分派。 ``` 如果没有 open PR 或 open Issue,明确报告“没有可分析的 open PR/Issue”;如果消息接口失败,不得用通知列表代替协作队列,也不得伪造 SLA。 @@ -259,6 +270,26 @@ gitlink-cli pr +version-diff --owner --repo -i --format 4. 今天建议先做的动作 5. 可延后的背景项 +保存后必须运行: + +```bash +python -X utf8 skills/gitlink-maintainer-radar/scripts/validate_radar_report.py \ + --report + +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py \ + --report \ + --require-pr \ + --min-cards 6 \ + --required-aspect "响应 SLA" \ + --required-aspect "等待方" \ + --required-aspect "Reviewer 负载" \ + --required-aspect "责任停滞" \ + --required-aspect "安全运营优先级" \ + --required-aspect "维护动作" +``` + +多个目标重复 `--require-pr`。校验会确认目标文件真实存在、可按严格 UTF-8 读取,并且每个 PR 都有六方面结论前置判断卡。失败时必须补写或修复报告并重新校验,不能直接结束任务。 + 推荐输出模板: ```markdown diff --git a/skills/gitlink-maintainer-radar/agents/openai.yaml b/skills/gitlink-maintainer-radar/agents/openai.yaml index 0bb4cac..7ead41d 100644 --- a/skills/gitlink-maintainer-radar/agents/openai.yaml +++ b/skills/gitlink-maintainer-radar/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "维护者雷达" short_description: "识别响应超时、review 失衡和责任停滞,生成维护者处置面板。" - default_prompt: "使用 $gitlink-maintainer-radar 扫描指定 GitLink 仓库。按 Skill 默认契约只读生成维护者优先待办,并保存关键结论前置的单一 Markdown 报告。" + default_prompt: "使用 $gitlink-maintainer-radar 扫描指定 GitLink 仓库或 PR;聊天和 Markdown 均按 PR 分节,将响应 SLA、等待方、Reviewer 负载、责任停滞、安全运营优先级、维护动作分别做成结论前置判断卡,后接依据与影响;保存并校验 UTF-8 报告,全程只读。" diff --git a/skills/gitlink-maintainer-radar/examples/executive-duty-board.md b/skills/gitlink-maintainer-radar/examples/executive-duty-board.md index fa90e03..4601b32 100644 --- a/skills/gitlink-maintainer-radar/examples/executive-duty-board.md +++ b/skills/gitlink-maintainer-radar/examples/executive-duty-board.md @@ -8,8 +8,17 @@ gitlink-cli api GET users//messages.json --query "status=1&limit=40" --fo ```markdown # 维护者值班摘要 -**结论:** 今日需处理 **[action_required]** -**队列:** HOT 3 | WATCH 5 | reviewer 瓶颈 1 | 安全 HOT 1 + +**队列事实:** 扫描 open PR 12 条、open Issue 8 条,时间基线为 `2026-07-23T08:00:00Z`。 +**判定依据:** 当前状态、创建和最后活动时间、Review、reviewer/assignee 与配置 SLA。 + +## PR #123 +**响应 SLA:** Review 已超时 **[hot]**:等待 reviewer 96 小时,超过 72 小时阈值;依据:固定扫描时间和最后有效活动;影响:进入今日优先队列。 +**等待方:** 当前等待 reviewer **[action_required]**:作者已提交修复但尚无复看结论;依据:最后提交晚于最后 Review;下一步:提醒或转派 reviewer。 +**Reviewer 负载:** 现有分配形成瓶颈 **[high]**:当前 reviewer 同时积压 5 条待审 PR;依据:同一快照内的待审计数;影响:继续等待风险较高。 +**责任停滞:** 责任明确但长期无推进 **[hot]**:分配后 4 天没有动作;依据:reviewer 分配时间与活动时间线;下一步:确认接单或释放责任。 +**安全运营优先级:** 需要优先安排安全复看 **[high]**:改动触及认证路径但不代表漏洞成立;依据:文件范围和已有安全标记;影响:应匹配安全 reviewer。 +**维护动作:** 今天完成转派并启动复看 **[action_required]**:超 SLA、负载瓶颈和安全关注同时存在;依据:MR-001 至 MR-003;下一步:维护者指定可用 reviewer。 ## 先做这 3 件事 1. **[MR-001][blocking] 转派** PR #123 的安全复查,当前等待 reviewer(责任:维护者)。 diff --git a/skills/gitlink-maintainer-radar/scripts/validate_radar_report.py b/skills/gitlink-maintainer-radar/scripts/validate_radar_report.py new file mode 100644 index 0000000..1e44f3b --- /dev/null +++ b/skills/gitlink-maintainer-radar/scripts/validate_radar_report.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Validate that maintainer-radar saves a usable UTF-8 Markdown report.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +REQUIRED_MARKERS = ( + "# 维护者值班摘要", + "**队列事实:**", + "**判定依据:**", + "## PR #", + "**响应 SLA:**", + "**等待方:**", + "**Reviewer 负载:**", + "**责任停滞:**", + "**安全运营优先级:**", + "**维护动作:**", + "MR-", +) + + +def validate_report(text: str) -> list[str]: + errors: list[str] = [] + if "\ufffd" in text or "\x00" in text or "\x1b" in text: + errors.append("report contains invalid encoding or ANSI control characters") + for marker in REQUIRED_MARKERS: + if marker not in text: + errors.append(f"missing radar report marker: {marker}") + cjk_count = len(re.findall(r"[\u3400-\u9fff]", text)) + if cjk_count < 60: + errors.append(f"radar narrative is incomplete: found {cjk_count} CJK characters, need at least 60") + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate a saved maintainer-radar report.") + parser.add_argument("--report", required=True, type=Path) + args = parser.parse_args() + try: + text = args.report.read_text(encoding="utf-8", errors="strict") + except (OSError, UnicodeError) as exc: + print(f"radar report validation failed: {exc}", file=sys.stderr) + return 2 + errors = validate_report(text) + if errors: + print("radar report validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print(f"radar report validation passed: {args.report.resolve()}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/gitlink-maintenance-orchestrator/SKILL.md b/skills/gitlink-maintenance-orchestrator/SKILL.md index 61982dc..680710a 100644 --- a/skills/gitlink-maintenance-orchestrator/SKILL.md +++ b/skills/gitlink-maintenance-orchestrator/SKILL.md @@ -11,9 +11,9 @@ description: "五个 GitLink 维护 Skill 的只读编排器:用户只需点 | 阶段 | Skill | 输出重点 | |---|---|---| -| 并行 | `gitlink-code-review` | `CR-` 代码正确性、测试覆盖和代码级安全问题 | +| 并行 | `gitlink-code-review` | `CR-/RV-` 贡献价值、代码正确性、Review 履约、测试、安全,以及按需健康扫描/Issue 分诊 | | 并行 | `gitlink-cli-contract-guard` | `CG-` flags、help、JSON、错误、退出码和 CLI 边界契约 | -| 并行 | `gitlink-pr-topology` | `TP-` PR 之间的依赖、重叠、替代、冲突和处理顺序 | +| 并行 | `gitlink-pr-topology` | `TP-` 目标 PR 与主线源码、全部 open/merged PR 的依赖、继承、重叠、替代、冲突、互补和处理顺序 | | 串行 | `gitlink-pr-integrator` | `IN-` 合并态、构建测试、安全和集成门禁 | | 串行 | `gitlink-maintainer-radar` | `MR-` 等待方、SLA、reviewer 负载和维护者待办 | @@ -28,19 +28,41 @@ description: "五个 GitLink 维护 Skill 的只读编排器:用户只需点 - 共享一次队列快照和每个目标 PR 的固定 head 上下文,编排五个专项 Skill;不要求用户逐条重复五个 Skill 的提示词。 - 全流程只读,不评论、不 approve、不合并、不关闭、不分配、不修改标签或权限。 - 单 PR 和多 PR 都支持;多 PR 先做队列级拓扑/维护排序,再对重点 PR 逐条做代码、契约和集成检查,不能混合 Diff。 -- 首屏只保留一个最终决策、关键门禁、最多 5 项跨专项去重后的动作和责任方;直接影响评审的 blocking/high 使用颜色和粗体。 +- 报告首屏先按 PR 分节;每条 PR 分别显示代码审查、CLI 契约、仓库关系、集成门禁、维护状态和最终结论六张判断卡。聊天结论也按 PR 和这六个方面组织,但必须重新提炼成更短的维护者语言。 +- 每张卡先显示醒目加粗结论,再写简短解释、明确 `依据:` 和影响/下一步;不得把五个专项压成一段或把多个 PR 合并总结。 +- 人读 `final-report.md` 必须使用中文叙述;命令名、路径、稳定编号和机器状态枚举可以保留英文。每个阶段的 `assessment.fact` 与 `assessment.basis` 在交给 finalize 前先转成忠实的中文摘要,不能把整份英文阶段报告直接拼入。 - 一次运行只生成一份主要人读报告 `/final-report.md`。五个专项 JSON 和 `final-report.json` 作为机器证据附件,不再让维护者阅读五份独立长 Markdown。 -最终回复只给出 `final-report.md` 的绝对路径、最终决策、阻断数和第一动作。若无法落盘,输出完整 Markdown 并标记“未落盘”。 +最终回复必须由执行 Skill 的 Agent 在读完 `final-report.md` 和 `final-report.json` 后重新提炼,不能复制报告首屏、不能机械删除 Markdown/HTML 格式、不能把卡片原文改成纯文本后直接输出。聊天摘要要比报告更短,按每个 PR 输出六个方面的结论、最关键依据和解释/影响,每个方面一至两句完整自然语言;省略路径清单、阶段索引、分页过程、机器状态标签和次要证据。全部 PR 摘要后再给最多五项跨专项动作,并用可点击链接或当前 Agent 平台的文件附件交付 `final-report.md`。若无法落盘,输出完整 Markdown 并标记“未落盘”。 + +聊天摘要示例只表达风格,不是可复制模板: + +```text +PR #430 +代码审查:建议修改后再审。它补齐维护队列的真实状态过滤,但当前 fixture 仍显示 closed 项会进入 open 队列,这会直接误导维护者的待审范围。 +CLI 契约:需要先稳定字段语义。新增 changes、waiting_on 和 SLA 字段有实际价值,但空值、兼容回退和错误提示还要和主线帮助/JSON 输出对齐。 +仓库关系:适合排在 #431 前面评审。#431 会消费 #430 的队列字段,所以先确认 #430 的运行时契约,可以减少后续 Skill 协议返工。 +集成门禁:暂时不能进入合并队列。构建和基础测试可以作为正向证据,但真实队列过滤失败仍是集成前必须修复的问题。 +维护状态:当前主要等待作者修复。维护者最需要检查的是过滤逻辑、字段空值和回归测试是否补齐。 +最终结论:先修复再复审。这个 PR 的方向成立,但真实队列可信度是它能否合入的前提。 + +完整报告:[final-report.md](D:\path\to\final-report.md) +``` + +最终聊天不能只回复路径、状态计数或跨 PR 总段落。Markdown 能使用醒目颜色和加粗;聊天摘要除必要的 PR 编号、证据编号和最终报告链接外,不输出 HTML/Markdown 展示标签。 首屏固定先使用: ```markdown # PR 维护全流程摘要 -**结论:** 已阻断 **[blocked]** -**门禁:** 代码 `failed` | 契约 `passed` | 拓扑 `reorder` | 集成 `blocked` | 维护 `action_required` -**风险:** blocking 1 | high 2 | security `failed` | verification `partial` +## PR # +**代码审查:** 核心队列结果不可靠 **[action_required]**:closed 项进入 open 响应;依据:CR-001 与真实 fixture;下一步:修复过滤并补测试。 +**CLI 契约:** 新增字段保持向后兼容 **[passed]**:字段可选且旧调用不变;依据:CG 对照和 golden 输出;影响:现有脚本无需迁移。 +**仓库关系:** 需要先稳定上游契约 **[reorder]**:存在字段生产/消费关系;依据:主线、全部 open/merged PR 索引;影响:调整评审顺序。 +**集成门禁:** 当前不能进入合并队列 **[blocked]**:真实响应测试失败;依据:IN-001 和当前 head 验证;下一步:修复后重跑。 +**维护状态:** 等待作者修复后复看 **[action_required]**:责任链明确;依据:Review、提交和 SLA 快照;下一步:作者更新后通知 reviewer。 +**最终结论:** 修复真实状态过滤后重新审查 **[blocked]**:存在一个阻断和两个高风险项;依据:CR-001、IN-001、MR-001;下一步:按动作顺序处理。 ## 先处理这 3 项 @@ -49,6 +71,8 @@ description: "五个 GitLink 维护 Skill 的只读编排器:用户只需点 3. [MR-001][high] 转派 安全复查;责任:maintainer。 ``` +多个 PR 重复完整六张卡,每张卡只能引用该 PR 的证据。跨 PR 动作放在全部 PR 卡片之后。 + ## 编排流程 ```mermaid @@ -84,14 +108,23 @@ flowchart TD ### 2. 采集一次、复用证据 -优先使用 CLI 的组合上下文接口,减少五个 Skill 对同一 PR 的重复请求: +当当前 CLI 已提供下列增强接口时,优先使用它们,减少五个 Skill 对同一 PR 的重复请求: ```bash gitlink-cli workflow +review-queue --owner --repo --format json gitlink-cli workflow +review-context --owner --repo --number --include-commits=true --include-ci=true --format json ``` -队列级运行只需采集一次 open PR 快照;单 PR 深审再补充该 PR 的上下文。真实数据写入运行目录后,子 Skill 只消费快照和证据,不重新猜测当前状态。 +若 `workflow +review-queue`、`workflow +review-context` 或其增强参数不可用,必须回退到当前 CLI 已有的只读接口,不得把命令不存在误写成 JSON 解析错误,也不得虚构 SLA、等待方、提交或 CI 证据: + +```bash +gitlink-cli pr +list --owner --repo --state open --page 1 --limit 100 --format json +gitlink-cli pr +view --owner --repo -i --format json +gitlink-cli pr +files --owner --repo -i --format json +gitlink-cli pr +reviews --owner --repo -i --format json +``` + +`pr +reviews` 本身不存在时,保留 `pr +view/+files` 结果,并在 `collection-manifest.json` 与专项报告中标记 Review 证据为 `not_run`。队列级运行只需采集一次 open PR 快照;单 PR 深审再补充该 PR 的上下文。拓扑阶段是例外:指定 PR 只限制目标,不限制对照范围,必须额外固定默认分支 baseline、建立源码索引并完整分页采集 merged PR 元数据。真实数据写入运行目录后,子 Skill 只消费快照和证据,不重新猜测当前状态。 ### 3. 并行执行专项检查 @@ -105,6 +138,24 @@ cli-contract-guard.json pr-topology.json ``` +每个阶段 JSON 还必须包含简短的解释性判断,供聊天结论和总报告复用: + +```json +{ + "assessment": { + "conclusion": "一句可直接决定本专项状态的短结论", + "fact": "PR 实际增加或改变了什么,以及发现的关键问题", + "basis": "与默认分支、Diff、Review、测试或队列时间证据的比较方式" + }, + "decision": "action_required" +} +``` + +`conclusion` 必须是可独立阅读的专项判断,不能只写“通过”“观察”或“需要处理”; +`fact` 和 `basis` 必须针对当前目标,不能复制状态词。兼容旧阶段结果时,编排器可从 +`fact` 的第一条事实生成结论,并从首条 finding/action 和 evidence 生成降级说明,但必须 +保留“证据受限”提示。 + 阶段失败时仍写出 `status: failed` 或 `status: not_run` 和失败证据,禁止静默跳过。缺少专项结果时,后续只能降级为 `blocked` 或 `observe`。 ### 4. 集成门禁与维护排序 @@ -120,12 +171,12 @@ pr-topology.json ### 5. 生成维护者首屏 -最终报告必须先展示维护者能立即执行的信息: - -1. **最终决策、阻断数、高风险数、安全门禁、验证状态和扫描时间**。 -2. **最多五项待办**,每项包含对象、责任方、下一动作、严重性和一个主证据。 -3. 五个阶段的状态和关键结论。 -4. 完整 findings、证据台账、限制和下一次复查条件放入附录或 JSON。 +最终报告必须先按目标 PR 展示六张判断卡,顺序固定为代码审查、CLI 契约、仓库关系、 +集成门禁、维护状态、最终结论。每张卡先给醒目结论,再写当前 PR 的事实、`依据:` +以及影响或下一步;不能把五个阶段压缩成一段总体叙述。全部目标 PR 的判断卡展示完后, +再给最多五项跨专项待办,每项包含对象、责任方、下一动作、严重性和一个主证据。 +阻断数、高风险数、安全门禁和验证状态可以作为卡片之后的索引,不能替代解释。 +完整 findings、证据台账、限制和下一次复查条件放入附录或 JSON。 Markdown 使用醒目的颜色和加粗,同时保留 `[blocking]`、`[high]`、`[pass]` 等纯文本回退;JSON 不得包含 HTML、ANSI 或颜色控制符。推荐颜色:blocking `#B42318`、high `#B54708`、pass `#067647`、observe `#175CD3`。 @@ -147,8 +198,16 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\skills\gitlink-maintenance # 五个 Skill 完成后,校验同一运行上下文并生成最终摘要 powershell -NoProfile -ExecutionPolicy Bypass -File .\skills\gitlink-maintenance-orchestrator\scripts\run-maintenance-pipeline.ps1 ` -Mode finalize -RunPath .\maintenance-runs\ + +# 再次确认最终人读报告为中文,且没有退化为英文模板 +python -X utf8 .\skills\gitlink-maintenance-orchestrator\scripts\validate_chinese_report.py ` + --report .\maintenance-runs\\final-report.md ` + --require-pr 123 ``` +多 PR 报告为每个目标重复 `--require-pr`。任何目标缺少完整六张判断卡、卡片没有明确 +`依据:`、中文被写成连续问号或报告退化为英文模板时,校验失败且不得交付路径。 + `collect` 只执行 `gitlink-cli` 的读操作,不发布评论、不添加标签、不分配 reviewer、不关闭或合并 PR。若 PowerShell 禁止执行 `gitlink-cli.ps1`,传入可执行的 `gitlink-cli.exe` 或 `gitlink-cli.cmd` 到 `-CliPath`。 ## 输出目录 @@ -157,6 +216,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\skills\gitlink-maintenance / ├── run.json # 运行键、目标和时间 ├── queue-snapshot.json # open PR 队列快照 +├── merged-pr-index.json # 全部 merged PR 元数据与分页覆盖 +├── baseline-source-index.json # 默认分支 SHA、源码路径和能力索引 ├── pr-context-.json # 单 PR 组合上下文 ├── code-review.json # CR 阶段原始结果 ├── cli-contract-guard.json # CG 阶段原始结果 @@ -169,6 +230,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\skills\gitlink-maintenance 最终报告只保留一个主决策;专项报告仍作为附件保留,便于定位责任而不是让维护者重复阅读。脚本会检查 UTF-8、替换字符、NUL、重复证据 ID、凭据样式内容、运行键不一致和缺失阶段。 +`finalize` 已内置中文报告校验。不得在 finalize 成功后用手写英文摘要覆盖 `final-report.md`;如果需要补充内容,应更新阶段 JSON 中的中文 `assessment` 后重新 finalize。最终回复前再次运行 `validate_chinese_report.py`,失败时不得交付报告路径。 + ## 安全与写入边界 - 默认只读;编排器不自动 `pr +review`、`pr +comment`、`pr +merge`、关闭、分配或改标签。 diff --git a/skills/gitlink-maintenance-orchestrator/agents/openai.yaml b/skills/gitlink-maintenance-orchestrator/agents/openai.yaml index 18d018e..883e0fd 100644 --- a/skills/gitlink-maintenance-orchestrator/agents/openai.yaml +++ b/skills/gitlink-maintenance-orchestrator/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "GitLink 维护审查编排器" short_description: "编排五个维护 Skill 生成可验证的 PR 维护摘要" - default_prompt: "使用 $gitlink-maintenance-orchestrator 分析指定仓库或一个/多个 PR。自动应用只读编排、证据一致性和首屏高亮规则,生成一份最终 Markdown 总报告。" + default_prompt: "使用 $gitlink-maintenance-orchestrator 分析指定仓库或一个/多个 PR;完整证据保存为中文 Markdown,聊天结论由执行 Agent 读完报告后重新提炼,按 PR 分别概括代码审查、CLI 契约、仓库关系、集成门禁、维护状态和最终结论,每方面说明结论、关键依据与影响,不复制报告卡片原文、不输出 HTML/Markdown 展示标签,全程只读。" diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/cli-contract-guard.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/cli-contract-guard.json index 1b821eb..cbc7621 100644 --- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/cli-contract-guard.json +++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/cli-contract-guard.json @@ -2,11 +2,16 @@ "schema_version": "1.0", "producer": "gitlink-cli-contract-guard", "status": "completed", - "decision": "observe", + "decision": "merge", "run": {"run_id": "gitlink-maintenance-orchestrator:Gitlink/gitlink-cli:123:abc1234:executive", "as_of": "2026-07-21T10:00:00Z"}, "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abc1234"}, "security_gate": "passed", "verification": "complete", + "assessment": { + "conclusion": "CLI 外部契约保持兼容", + "fact": "新增命令保持原有参数、帮助、JSON 类型和错误语义,未观察到用户可感知的契约破坏", + "basis": "对默认分支与当前 head 执行相同帮助和结构化输出测试,契约测试完整通过" + }, "findings": [], "top_actions": [], "evidence": [ diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json index 1a89bc1..9f8d2ce 100644 --- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json +++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json @@ -7,6 +7,11 @@ "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abc1234"}, "security_gate": "passed", "verification": "partial", + "assessment": { + "conclusion": "错误路径缺测,修复后再审", + "fact": "PR 为示例命令增加批量处理能力,但错误路径缺少回归测试,核心失败行为尚未被证明可靠", + "basis": "对照默认分支与当前 Diff,并执行 go test ./shortcuts/example;正常路径通过,失败路径覆盖不完整" + }, "findings": [ {"id": "CR-001", "severity": "high", "status": "open", "summary": "错误路径缺少回归测试", "evidence": ["E-CR-001"], "related_ids": []} ], diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/maintainer-radar.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/maintainer-radar.json index aea063a..bf7bfa1 100644 --- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/maintainer-radar.json +++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/maintainer-radar.json @@ -7,6 +7,11 @@ "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abc1234"}, "security_gate": "passed", "verification": "complete", + "assessment": { + "conclusion": "当前等待作者补测", + "fact": "PR #123 已有明确修复动作但仍等待作者补测,继续占用维护者复看队列", + "basis": "队列快照显示 waiting_on=author,当前 head 和 Review 后续动作尚未变化" + }, "findings": [], "top_actions": [ {"id": "MR-001", "owner": "maintainer", "severity": "medium", "action": "安排维护者复看 PR #123,当前等待作者补测", "evidence": ["queue:pr-123"]} diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-integrator.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-integrator.json index 3b62b76..1297330 100644 --- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-integrator.json +++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-integrator.json @@ -7,6 +7,11 @@ "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abc1234"}, "security_gate": "passed", "verification": "partial", + "assessment": { + "conclusion": "失败路径门禁未通过,暂不能合并", + "fact": "贡献方向与仓库需求一致,但代码审查发现的失败路径缺口尚未修复,当前实现不能安全进入合并队列", + "basis": "当前 head 的基础构建可执行,但 CR-001 未解决且全量集成验证只完成部分门禁" + }, "findings": [], "top_actions": [ {"id": "IN-001", "owner": "author", "severity": "high", "action": "修复 CR-001 后重新执行合并态验证", "evidence": ["CR-001"]} diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-topology.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-topology.json index 14b9e31..63e6327 100644 --- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-topology.json +++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/pr-topology.json @@ -2,11 +2,16 @@ "schema_version": "1.0", "producer": "gitlink-pr-topology", "status": "completed", - "decision": "observe", + "decision": "reorder", "run": {"run_id": "gitlink-maintenance-orchestrator:Gitlink/gitlink-cli:123:abc1234:executive", "as_of": "2026-07-21T10:00:00Z"}, "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abc1234"}, "security_gate": "passed", "verification": "complete", + "assessment": { + "conclusion": "与 #124 共享入口,需要复核合并顺序", + "fact": "PR #123 与 #124 修改同一命令入口,功能不完全重复,但合并顺序可能改变最终输出契约", + "basis": "比较两条 PR 的文件集合、队列快照和目标行为后确认存在共享入口,尚无证据判定其中一条完全替代另一条" + }, "findings": [ {"id": "TP-001", "severity": "medium", "status": "open", "summary": "与 PR #124 修改同一命令入口,建议合并顺序复核", "evidence": ["E-TP-001"], "related_ids": ["PR-124"]} ], diff --git a/skills/gitlink-maintenance-orchestrator/references/pipeline-contract.md b/skills/gitlink-maintenance-orchestrator/references/pipeline-contract.md index 0bc545b..c51cca0 100644 --- a/skills/gitlink-maintenance-orchestrator/references/pipeline-contract.md +++ b/skills/gitlink-maintenance-orchestrator/references/pipeline-contract.md @@ -41,6 +41,11 @@ "target": {"owner": "Gitlink", "repo": "gitlink-cli", "number": 123, "head_sha": "abcdef1"}, "security_gate": "passed", "verification": "complete", + "assessment": { + "conclusion": "错误路径缺测,修复后再审", + "fact": "核心成功路径可用,但远端失败没有回归证据", + "basis": "当前 head 的 Diff、专项测试和失败路径测试清单" + }, "findings": [], "top_actions": [], "evidence": [], @@ -48,7 +53,11 @@ } ``` -`status` 可为 `completed`、`partial`、`failed`、`not_run`、`stale`。`decision` 可为 `merge`、`action_required`、`reorder`、`observe`、`blocked`。阶段之间不得篡改其他 Skill 的 finding,只通过 `related_ids` 关联。 +`assessment.conclusion` 是首屏加粗的直接判断,不能只重复 `decision` 状态词; +`assessment.fact` 解释实际行为,`assessment.basis` 给出核验证据。`status` 可为 +`completed`、`partial`、`failed`、`not_run`、`stale`。`decision` 可为 `merge`、 +`action_required`、`reorder`、`observe`、`blocked`。阶段之间不得篡改其他 Skill 的 +finding,只通过 `related_ids` 关联。 ## 最终决策规则 @@ -82,4 +91,3 @@ - 重复的 action/finding/evidence 能去重而不丢失来源。 - blocking、安全失败、CI 未关联和测试未执行会正确降级。 - 中文报告为 UTF-8,不能出现替换字符、NUL 或凭据样式内容。 - diff --git a/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1 b/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1 index e6afc83..94f2f3a 100644 --- a/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1 +++ b/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1 @@ -101,8 +101,37 @@ function Get-StageSummary { if (Has-Property $Artifact 'findings') { $findings = @($Artifact.findings) } $topActions = @() if (Has-Property $Artifact 'top_actions') { $topActions = @($Artifact.top_actions) } + $evidence = @() + if (Has-Property $Artifact 'evidence') { $evidence = @($Artifact.evidence) } + $limitations = @() + if (Has-Property $Artifact 'limitations') { $limitations = @($Artifact.limitations) } + $assessment = if (Has-Property $Artifact 'assessment') { $Artifact.assessment } else { $null } $blocking = @($findings | Where-Object { (Get-DisplaySeverity (Get-Value $_ 'severity' '')) -eq 'blocking' }).Count $high = @($findings | Where-Object { (Get-DisplaySeverity (Get-Value $_ 'severity' '')) -eq 'high' }).Count + $focus = if ($null -ne $assessment -and -not [string]::IsNullOrWhiteSpace([string](Get-Value $assessment 'fact' ''))) { + [string](Get-Value $assessment 'fact' '') + } elseif ($findings.Count -gt 0) { + [string](Get-Value $findings[0] 'summary' '发现需要维护者复核的问题') + } elseif ($topActions.Count -gt 0) { + [string](Get-Value $topActions[0] 'action' '存在待处理动作') + } else { + '在已采集范围内未发现需要立即处理的问题' + } + $basis = if ($null -ne $assessment -and -not [string]::IsNullOrWhiteSpace([string](Get-Value $assessment 'basis' ''))) { + [string](Get-Value $assessment 'basis' '') + } elseif ($evidence.Count -gt 0) { + $firstEvidence = $evidence[0] + "$(Get-Value $firstEvidence 'source' 'unknown source') / $(Get-Value $firstEvidence 'ref' 'unknown ref')($(Get-Value $firstEvidence 'status' 'unknown'))" + } elseif ($limitations.Count -gt 0) { + "证据受限:$($limitations[0])" + } else { + '没有可引用的专项证据,结论置信度受限' + } + $conclusion = if ($null -ne $assessment -and -not [string]::IsNullOrWhiteSpace([string](Get-Value $assessment 'conclusion' ''))) { + [string](Get-Value $assessment 'conclusion' '') + } else { + ([string]$focus -split '[,;。]', 2)[0].Trim() + } return [ordered]@{ producer = $Producer status = [string](Get-Value $Artifact 'status' 'not_run') @@ -113,6 +142,9 @@ function Get-StageSummary { blocking_count = $blocking high_count = $high top_action_count = $topActions.Count + conclusion = $conclusion + focus = $focus + basis = $basis } } @@ -287,6 +319,13 @@ function New-FinalReport { $validator = Join-Path $PSScriptRoot '..\..\gitlink-shared\examples\validate-maintenance-report.ps1' if (-not (Test-Path -LiteralPath $validator)) { throw "missing maintenance report validator: $validator" } & $validator -Path (Join-Path $Path 'final-report.json') | Out-Null + $markdownValidator = Join-Path $PSScriptRoot 'validate_chinese_report.py' + if (-not (Test-Path -LiteralPath $markdownValidator)) { throw "missing Chinese report validator: $markdownValidator" } + $markdownArgs = @('-X', 'utf8', $markdownValidator, '--report', (Join-Path $Path 'final-report.md')) + $targetNumber = Get-Value $run.target 'number' $null + if ($null -ne $targetNumber) { $markdownArgs += @('--require-pr', [string]$targetNumber) } + & python @markdownArgs | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'final-report.md must be a complete Chinese report' } return $report } @@ -305,15 +344,69 @@ function Get-ColorLabel { } } +function Get-ConclusionLabel { + param([string]$Conclusion, [string]$Decision) + $color = switch ($Decision) { + 'merge' { '#067647' } + 'action_required' { '#B54708' } + 'blocked' { '#B42318' } + 'reorder' { '#175CD3' } + 'observe' { '#175CD3' } + default { '#175CD3' } + } + $safeConclusion = [Net.WebUtility]::HtmlEncode($Conclusion) + return "$safeConclusion **[$Decision]**" +} + +function Get-StageAspect { + param([string]$Producer) + switch ($Producer) { + 'gitlink-code-review' { return '代码审查' } + 'gitlink-cli-contract-guard' { return 'CLI 契约' } + 'gitlink-pr-topology' { return '仓库关系' } + 'gitlink-pr-integrator' { return '集成门禁' } + 'gitlink-maintainer-radar' { return '维护状态' } + default { return '专项判断' } + } +} + +function Get-DecisionImpact { + param([string]$Decision) + switch ($Decision) { + 'merge' { return '当前专项没有阻止进入合并队列的动作' } + 'action_required' { return '完成高优先级动作后重新评估' } + 'blocked' { return '当前不能进入合并队列' } + 'reorder' { return '需要调整评审或合并顺序' } + 'observe' { return '证据不足,保留观察并补充验证' } + default { return '按专项证据决定下一步' } + } +} + function Write-MarkdownReport { param([string]$Path, [object]$Report) $lines = New-Object Collections.Generic.List[string] $lines.Add('# PR 维护全流程摘要') $lines.Add('') - $lines.Add("**结论:** $(Get-ColorLabel $Report.decision)") $lines.Add("**范围:** $($Report.scope.owner)/$($Report.scope.repo) | **运行时间:** $($Report.run.as_of) | **运行 ID:** ``$($Report.run.run_id)``") $lines.Add("**风险:** 阻断 $($Report.counts.blocking) | 高风险 $($Report.counts.high) | 中风险 $($Report.counts.medium) | 低风险 $($Report.counts.low) | **安全门禁:** ``$($Report.security_gate)`` | **验证:** ``$($Report.verification)``") $lines.Add('') + $targetNumber = Get-Value $Report.run.target 'number' 0 + if ($null -eq $targetNumber) { $targetNumber = 0 } + $lines.Add("## PR #$targetNumber") + foreach ($stage in @($Report.stages)) { + $aspect = Get-StageAspect ([string]$stage.producer) + $impact = Get-DecisionImpact ([string]$stage.decision) + $lines.Add("**${aspect}:** $(Get-ConclusionLabel ([string]$stage.conclusion) ([string]$stage.decision)):$($stage.focus);依据:$($stage.basis);影响:$impact。") + } + $finalReason = if (@($Report.top_actions).Count -gt 0) { + [string]$Report.top_actions[0].action + } else { + '当前没有需要立即处理的高优先级动作' + } + $finalNext = Get-DecisionImpact ([string]$Report.decision) + $finalConclusion = if (@($Report.top_actions).Count -gt 0) { $finalReason } else { $finalNext } + $lines.Add("**最终结论:** $(Get-ConclusionLabel $finalConclusion ([string]$Report.decision)):该动作决定当前集成状态;依据:阻断 $($Report.counts.blocking) 项、高风险 $($Report.counts.high) 项,安全门禁 ``$($Report.security_gate)``、验证 ``$($Report.verification)``;下一步:$finalNext。") + $lines.Add('') $lines.Add('## 先处理这几项') if (@($Report.top_actions).Count -eq 0) { $lines.Add('暂无需要立即处理的动作。') @@ -326,7 +419,7 @@ function Write-MarkdownReport { } } $lines.Add('') - $lines.Add('## 五个专项结果') + $lines.Add('## 五个专项状态索引') $lines.Add('| 专项 | 状态 | 决策 | 发现 | 关键动作 |') $lines.Add('|---|---|---|---:|---:|') foreach ($stage in @($Report.stages)) { @@ -345,8 +438,46 @@ function Invoke-GitLinkJson { $output = & $Executable @Arguments 2> $ErrorPath | Out-String $exitCode = $LASTEXITCODE Write-Utf8Text -Path $OutputPath -Text $output - if ($exitCode -ne 0) { throw "gitlink-cli command failed with exit code $exitCode; see $ErrorPath" } - try { return ($output | ConvertFrom-Json) } catch { throw "gitlink-cli returned invalid JSON; see $OutputPath" } + $command = "$Executable $($Arguments -join ' ')" + if ($exitCode -ne 0) { + $errorSummary = if (Test-Path -LiteralPath $ErrorPath) { + ((Get-Content -LiteralPath $ErrorPath -Raw -Encoding utf8).Trim() -replace "[\r\n]+", ' ') + } else { + 'no stderr output' + } + throw "gitlink-cli command failed with exit code ${exitCode}: $command; stderr: $errorSummary" + } + try { + return ($output | ConvertFrom-Json) + } catch { + throw "gitlink-cli command returned non-JSON output: $command; this can mean an unsupported command or unexpected CLI output; see $OutputPath" + } +} + +function Try-InvokeGitLinkJson { + param([string]$Executable, [string[]]$Arguments, [string]$OutputPath, [string]$ErrorPath) + try { + return [pscustomobject]@{ + Succeeded = $true + Value = Invoke-GitLinkJson -Executable $Executable -Arguments $Arguments -OutputPath $OutputPath -ErrorPath $ErrorPath + Failure = '' + } + } catch { + return [pscustomobject]@{ + Succeeded = $false + Value = $null + Failure = $_.Exception.Message + } + } +} + +function Write-CollectionManifest { + param([string]$Path, [string]$QueueSource, [string]$ContextSource, [string[]]$Limitations) + Write-JsonFile -Path (Join-Path $Path 'collection-manifest.json') -Value ([ordered]@{ + queue_source = $QueueSource + context_source = $ContextSource + limitations = @($Limitations) + }) } function New-RunContext { @@ -370,13 +501,62 @@ function Start-Collect { $run = New-RunContext -Path $path -RunTrigger $Trigger -Timestamp $timestamp -TargetNumber $Number $queuePath = Join-Path $path 'queue-snapshot.json' $queueErrorPath = Join-Path $path 'queue-snapshot.stderr.log' - Invoke-GitLinkJson -Executable $CliPath -Arguments @('workflow', '+review-queue', '--owner', $Owner, '--repo', $Repo, '--format', 'json') -OutputPath $queuePath -ErrorPath $queueErrorPath | Out-Null + $limitations = @() + $queue = Try-InvokeGitLinkJson -Executable $CliPath -Arguments @('workflow', '+review-queue', '--owner', $Owner, '--repo', $Repo, '--format', 'json') -OutputPath $queuePath -ErrorPath $queueErrorPath + $queueSource = 'workflow +review-queue' + if (-not $queue.Succeeded) { + $workflowQueueFailure = $queue.Failure + $queue = Try-InvokeGitLinkJson -Executable $CliPath -Arguments @('pr', '+list', '--owner', $Owner, '--repo', $Repo, '--state', 'open', '--page', '1', '--limit', '100', '--format', 'json') -OutputPath $queuePath -ErrorPath $queueErrorPath + if (-not $queue.Succeeded) { + throw "unable to collect open PR queue. workflow attempt: $workflowQueueFailure; pr +list fallback: $($queue.Failure)" + } + $queueSource = 'pr +list fallback' + $limitations += 'workflow +review-queue is unavailable; queue snapshot is raw pr +list output without queue delta, SLA, or waiting_on fields.' + } + + $contextSource = 'not requested' if ($Number) { $contextPath = Join-Path $path "pr-context-$Number.json" $contextErrorPath = Join-Path $path "pr-context-$Number.stderr.log" - Invoke-GitLinkJson -Executable $CliPath -Arguments @('workflow', '+review-context', '--owner', $Owner, '--repo', $Repo, '--number', $Number, '--include-commits=true', '--include-ci=true', '--format', 'json') -OutputPath $contextPath -ErrorPath $contextErrorPath | Out-Null + $context = Try-InvokeGitLinkJson -Executable $CliPath -Arguments @('workflow', '+review-context', '--owner', $Owner, '--repo', $Repo, '--number', $Number, '--format', 'json') -OutputPath $contextPath -ErrorPath $contextErrorPath + $contextSource = 'workflow +review-context' + if (-not $context.Succeeded) { + $viewPath = Join-Path $path "pr-view-$Number.json" + $viewErrorPath = Join-Path $path "pr-view-$Number.stderr.log" + $view = Try-InvokeGitLinkJson -Executable $CliPath -Arguments @('pr', '+view', '--owner', $Owner, '--repo', $Repo, '-i', $Number, '--format', 'json') -OutputPath $viewPath -ErrorPath $viewErrorPath + if (-not $view.Succeeded) { + throw "unable to collect PR #$Number context. workflow attempt: $($context.Failure); pr +view fallback: $($view.Failure)" + } + $fallbackContext = [ordered]@{ + repository = "$Owner/$Repo" + pull_request = [int]$Number + source = 'pr +view/+files/+reviews fallback' + pr = $view.Value + files = $null + reviews = $null + } + foreach ($part in @( + @{ Name = 'files'; Arguments = @('pr', '+files', '--owner', $Owner, '--repo', $Repo, '-i', $Number, '--format', 'json') }, + @{ Name = 'reviews'; Arguments = @('pr', '+reviews', '--owner', $Owner, '--repo', $Repo, '-i', $Number, '--format', 'json') } + )) { + $partPath = Join-Path $path "pr-$($part.Name)-$Number.json" + $partErrorPath = Join-Path $path "pr-$($part.Name)-$Number.stderr.log" + $partResult = Try-InvokeGitLinkJson -Executable $CliPath -Arguments $part.Arguments -OutputPath $partPath -ErrorPath $partErrorPath + if (-not $partResult.Succeeded) { + $limitations += "pr +$($part.Name) fallback was unavailable: $($partResult.Failure)" + } else { + $fallbackContext[$part.Name] = $partResult.Value + } + } + Write-JsonFile -Path $contextPath -Value $fallbackContext + $contextSource = 'pr +view/+files/+reviews fallback' + $limitations += 'workflow +review-context is unavailable; commits and CI evidence were not collected and must be marked not_run or partial.' + } else { + $limitations += 'Current review-context output does not include commit or CI evidence; mark those gates not_run or partial unless separate evidence is collected.' + } } - Write-Output "collected read-only evidence: $path" + Write-CollectionManifest -Path $path -QueueSource $queueSource -ContextSource $contextSource -Limitations $limitations + Write-Output "collected read-only evidence: $path (queue: $queueSource; context: $contextSource)" } function Start-Fixture { diff --git a/skills/gitlink-maintenance-orchestrator/scripts/test-maintenance-output.ps1 b/skills/gitlink-maintenance-orchestrator/scripts/test-maintenance-output.ps1 new file mode 100644 index 0000000..0444759 --- /dev/null +++ b/skills/gitlink-maintenance-orchestrator/scripts/test-maintenance-output.ps1 @@ -0,0 +1,45 @@ +$ErrorActionPreference = 'Stop' + +$pipeline = Join-Path $PSScriptRoot 'run-maintenance-pipeline.ps1' +$validator = Join-Path $PSScriptRoot '..\..\gitlink-shared\examples\validate-maintenance-report.ps1' +$chineseReportValidatorTests = Join-Path $PSScriptRoot 'test_validate_chinese_report.py' +$orchestratorPromptTests = Join-Path $PSScriptRoot 'test_orchestrator_prompt_contract.py' +$chineseReportValidator = Join-Path $PSScriptRoot 'validate_chinese_report.py' +$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gitlink-maintenance-output-" + [Guid]::NewGuid().ToString('N')) + +try { + & $pipeline -Mode fixture -RunRoot $tempRoot -Force | Out-Null + $markdown = Get-ChildItem -LiteralPath $tempRoot -Recurse -Filter final-report.md | Select-Object -First 1 + $json = Get-ChildItem -LiteralPath $tempRoot -Recurse -Filter final-report.json | Select-Object -First 1 + if ($null -eq $markdown -or $null -eq $json) { throw 'pipeline did not generate final reports' } + + & $validator -Path $json.FullName | Out-Null + $utf8 = New-Object Text.UTF8Encoding($false, $true) + $content = [IO.File]::ReadAllText($markdown.FullName, $utf8) + if ($content.Contains([char]0xfffd) -or $content.Contains([char]0)) { throw 'Markdown contains invalid encoding characters' } + foreach ($producer in @('gitlink-code-review', 'gitlink-cli-contract-guard', 'gitlink-pr-topology', 'gitlink-pr-integrator', 'gitlink-maintainer-radar')) { + if (-not $content.Contains("| $producer |")) { throw "missing stage index entry: $producer" } + } + foreach ($fixtureName in @('code-review.json', 'cli-contract-guard.json', 'pr-topology.json', 'pr-integrator.json', 'maintainer-radar.json')) { + $fixture = Get-Content -LiteralPath (Join-Path $PSScriptRoot "..\examples\fixtures\$fixtureName") -Raw -Encoding utf8 | ConvertFrom-Json + $expectedConclusion = [Net.WebUtility]::HtmlEncode([string]$fixture.assessment.conclusion) + if (-not $content.Contains("$expectedConclusion")) { throw "missing direct stage conclusion from fixture: $fixtureName" } + } + $decisionPattern = '[^<]+ \*\*\[(merge|action_required|reorder|observe|blocked)\]\*\*' + $decisions = [regex]::Matches($content, $decisionPattern) + $actionsIndex = $content.IndexOf('1. **[') + if ($decisions.Count -lt 6 -or $actionsIndex -lt 0 -or $decisions[5].Index -gt $actionsIndex) { throw 'final conclusion must appear after five stage decisions and before actions' } + + & python -X utf8 $orchestratorPromptTests | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'orchestrator prompt contract tests failed' } + & python -X utf8 $chineseReportValidatorTests | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'orchestrator Chinese report validator tests failed' } + & python -X utf8 $chineseReportValidator --report $markdown.FullName --require-pr 123 | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'generated report does not satisfy the Chinese per-PR card contract' } + + Write-Output 'maintenance output tests passed: Chinese per-PR cards, UTF-8, agent synthesis contract' +} finally { + if (Test-Path -LiteralPath $tempRoot) { + Remove-Item -LiteralPath $tempRoot -Recurse -Force + } +} diff --git a/skills/gitlink-maintenance-orchestrator/scripts/test_orchestrator_prompt_contract.py b/skills/gitlink-maintenance-orchestrator/scripts/test_orchestrator_prompt_contract.py new file mode 100644 index 0000000..779bc69 --- /dev/null +++ b/skills/gitlink-maintenance-orchestrator/scripts/test_orchestrator_prompt_contract.py @@ -0,0 +1,30 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def read_utf8(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_chat_summary_requires_agent_synthesis() -> None: + content = read_utf8(ROOT / "SKILL.md") + required_markers = [ + "必须由执行 Skill 的 Agent", + "重新提炼", + "不能复制报告首屏", + "不能机械删除 Markdown/HTML 格式", + "每个方面一至两句完整自然语言", + "可点击链接或当前 Agent 平台的文件附件", + ] + missing = [marker for marker in required_markers if marker not in content] + assert not missing, f"missing orchestrator chat synthesis markers: {missing}" + assert "最终回复直接复用" not in content + + +def test_default_agent_prompt_requires_a_plain_summary() -> None: + content = read_utf8(ROOT / "agents" / "openai.yaml") + required_markers = ["重新提炼", "不复制报告卡片原文", "不输出 HTML/Markdown 展示标签"] + missing = [marker for marker in required_markers if marker not in content] + assert not missing, f"missing agent prompt markers: {missing}" diff --git a/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py b/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py new file mode 100644 index 0000000..15422eb --- /dev/null +++ b/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import unittest + +from validate_chinese_report import validate_report + + +class ChineseReportValidatorTests(unittest.TestCase): + def test_accepts_complete_chinese_report(self) -> None: + report = """# PR 维护全流程摘要 +**范围:** Gitlink/gitlink-cli +**风险:** 阻断 1 项,高风险 2 项,以下内容用于帮助维护者快速确认处理顺序和责任人。 +## PR #123 +**代码审查:** 需要修改 **[action_required]**:失败路径缺少覆盖;依据:当前 Diff 与专项测试;下一步:补回归测试。 +**CLI 契约:** 兼容性部分成立 **[partial]**:旧调用正常但 JSON 缺边界;依据:帮助和输出对照;下一步:补 golden。 +**仓库关系:** 需要调整顺序 **[reorder]**:目标消费上游字段;依据:主线、open 和 merged 索引;影响:先稳定上游。 +**集成门禁:** 当前被阻断 **[blocked]**:关键测试未完成;依据:合并态和验证账本;下一步:补测试。 +**维护状态:** 需要维护者接单 **[action_required]**:责任方尚未确认;依据:时间和分配快照;下一步:安排 reviewer。 +**最终结论:** 修复阻断问题后重新审查 **[blocked]**:存在未解决高风险项;依据:CR-001 与 IN-001;下一步:修复并重跑。 +## 先处理这几项 +先修复真实响应错误,再补充测试,然后重新执行完整验证并由维护者复看。 +## 五个专项状态索引 +gitlink-code-review | gitlink-cli-contract-guard | gitlink-pr-topology | gitlink-pr-integrator | gitlink-maintainer-radar +## 完整证据与限制 +当前结论来自固定时间快照、目标提交差异、构建测试和只读平台数据;未验证内容已明确标记。""" + self.assertEqual([], validate_report(report, [123])) + + def test_rejects_english_template(self) -> None: + errors = validate_report("# PR Maintenance Summary\n## Core Judgment\n**Final decision:** blocked") + self.assertTrue(any("English report template" in error for error in errors)) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py b/skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py new file mode 100644 index 0000000..2b00376 --- /dev/null +++ b/skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Validate that the orchestrator saves a complete Chinese Markdown report.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +REQUIRED_MARKERS = ( + "# PR 维护全流程摘要", + "**范围:**", + "**风险:**", + "## PR #", + "**最终结论:**", + "## 先处理这几项", + "## 五个专项状态索引", + "## 完整证据与限制", +) +REQUIRED_ASPECTS = ( + "代码审查", + "CLI 契约", + "仓库关系", + "集成门禁", + "维护状态", + "最终结论", +) +FORBIDDEN_ENGLISH_TEMPLATES = ( + "# PR Maintenance Summary", + "## Core Judgment", + "## Top Actions", + "## Stage Index", + "## Limits", + "**Scope:**", + "**Risk:**", + "**Final decision:**", +) +PR_HEADING = re.compile(r"^## PR #(\d+)(?:\s.*)?$", re.MULTILINE) +CARD_PATTERN = re.compile( + r"^\*\*([^*\n]+):\*\*\s*" + r"]*>([^<]+)\s*" + r"\*\*\[([^\]]+)\]\*\*:\s*(\S.*)$", + re.MULTILINE, +) + + +def validate_report(text: str, required_prs: list[int] | None = None) -> list[str]: + errors: list[str] = [] + if "\ufffd" in text or "\x00" in text or "\x1b" in text: + errors.append("report contains invalid encoding or terminal control characters") + if re.search(r"\?{2,}", text): + errors.append("report contains repeated question marks indicating encoding loss") + for marker in REQUIRED_MARKERS: + if marker not in text: + errors.append(f"missing Chinese report marker: {marker}") + for marker in FORBIDDEN_ENGLISH_TEMPLATES: + if marker in text: + errors.append(f"English report template is not allowed: {marker}") + if len(re.findall(r"[\u3400-\u9fff]", text)) < 100: + errors.append("Chinese narrative is insufficient") + + sections = {int(match.group(1)): match.start() for match in PR_HEADING.finditer(text)} + targets = required_prs or sorted(sections) + if not targets: + errors.append("report contains no PR section") + return errors + for number in targets: + start = sections.get(number) + if start is None: + errors.append(f"missing PR section: #{number}") + continue + next_heading = PR_HEADING.search(text, start + 1) + end = next_heading.start() if next_heading else len(text) + cards = CARD_PATTERN.findall(text[start:end]) + aspects = {card[0] for card in cards} + if len(cards) < len(REQUIRED_ASPECTS): + errors.append(f"PR #{number} has {len(cards)} judgment cards, need 6") + for aspect in REQUIRED_ASPECTS: + if aspect not in aspects: + errors.append(f"PR #{number} is missing aspect card: {aspect}") + for aspect, conclusion, status, rationale in cards: + if not conclusion.strip() or not status.strip() or len(rationale.strip()) < 20: + errors.append(f"PR #{number} aspect {aspect} is not substantive") + if "依据:" not in rationale: + errors.append(f"PR #{number} aspect {aspect} is missing evidence") + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate a Chinese orchestrator report.") + parser.add_argument("--report", required=True, type=Path) + parser.add_argument("--require-pr", action="append", type=int, default=[]) + args = parser.parse_args() + try: + text = args.report.read_text(encoding="utf-8", errors="strict") + except (OSError, UnicodeError) as exc: + print(f"orchestrator report validation failed: {exc}", file=sys.stderr) + return 2 + errors = validate_report(text, args.require_pr) + if errors: + print("orchestrator report validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print("orchestrator report validation passed: Chinese human-readable report") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/gitlink-pr-integrator/SKILL.md b/skills/gitlink-pr-integrator/SKILL.md index 2adb86a..15f67b1 100644 --- a/skills/gitlink-pr-integrator/SKILL.md +++ b/skills/gitlink-pr-integrator/SKILL.md @@ -21,7 +21,7 @@ CI 门禁必须读取 `ci_summary`:`match_mode=sha` 优先,`branch` 只能 **CRITICAL - GitLink 平台数据采集和回写只使用 `gitlink-cli`,不要改用 `gh` 或其他平台 CLI。** **CRITICAL - 默认只做读取、验证和报告;只有用户明确要求时才回写评论或 review。** **CRITICAL - 不要在用户当前的脏工作树里做合并验证。优先使用独立 worktree、临时 clone 或明确指定的检出目录。** -**CRITICAL - 在 Windows PowerShell 中保存中文报告前,先切到 UTF-8 输出链路,否则中文可能被写成 `?`。** +**CRITICAL - 在 Codex 中优先使用 `apply_patch` 写中文报告;不要通过 Windows PowerShell 5.1 here-string/变量管道写入,否则中文可能永久变成 `?`。** ## 默认调用契约 @@ -32,19 +32,24 @@ CI 门禁必须读取 `ci_summary`:`match_mode=sha` 优先,`branch` 只能 - 独立判断贡献价值和集成就绪度,不调用其他 Skill;不重新做完整代码审查,不输出完整 PR 替代图谱,也不判断维护者 SLA。 - 只读远端;允许在隔离 worktree 中执行本地验证,但不评论、不 approve、不合并、不关闭、不修改远端。 - 使用 `IN-001` 起的稳定编号,记录门禁、当前 head SHA、命令、退出码、证据、下一动作和限制。 -- 首屏先给出“贡献是否值得合入”和“现在能否进入 merge queue”,显示贡献价值与六项技术门禁及最多 5 项动作;失败或高风险使用颜色和粗体。 +- 聊天和报告首屏按 PR 分节,将贡献价值、合并态、构建、测试、契约、安全与发布、集成结论分别做成判断卡;每张卡先显示醒目结论,再写解释、`依据:` 和影响/下一步。 - 一次运行只生成一份 UTF-8 Markdown,保存到 `reports/skill-runs/gitlink-pr-integrator/---.md`;多个 PR 先给队列摘要,再分别给每条 PR 的门禁。 -无法写入工作区时输出完整 Markdown 并标记“未落盘”。最终回复给出报告绝对路径、可进入队列数量、阻断数量和第一下一动作。 +最终回复复用报告首屏的逐 PR 七方面判断卡,再给报告绝对路径;不得把多个 PR 或多个门禁压成一段。无法写入工作区时输出完整 Markdown 并标记“未落盘”。 首屏固定先使用: ```markdown -# PR # 集成摘要 +# PR 集成摘要 -**结论:** 需补验证 **[action_required]** -**贡献价值:** 值得合入 **[passed]**:需求明确、增量有效,维护成本可接受 -**门禁:** 价值 `passed` | 合并态 `passed` | 构建 `passed` | 测试 `not_run` | 契约 `partial` | 安全 `not_run` | 冲突 `low` +## PR # +**贡献价值:** 值得进入社区 **[passed]**:消除维护者手工关联构建步骤;依据:默认分支无等价命令、需求与受益范围;影响:降低日常操作成本。 +**合并态:** 可干净应用到最新主线 **[passed]**:当前 head 没有文本冲突;依据:固定 head/base SHA 与隔离 worktree 合并结果;影响:无合并阻断。 +**构建:** 构建通过 **[passed]**:仓库构建命令退出码为 0;依据:当前 head 的命令、时间和日志摘要;影响:编译链路可用。 +**测试:** 全量测试尚未执行 **[not_run]**:只有专项测试证据;依据:测试账本缺少 `go test ./...`;下一步:补全量测试。 +**契约:** 兼容证据不完整 **[partial]**:新输出字段尚未覆盖旧调用;依据:帮助与 JSON 对照缺口;下一步:补兼容回归。 +**安全与发布:** 安全边界尚未验证 **[not_run]**:涉及权限路径但未运行边界测试;依据:Diff 安全矩阵与测试账本;下一步:补无权和恶意输入验证。 +**集成结论:** 补齐测试和安全后再集成 **[action_required]**:价值成立但证据门禁不完整;依据:IN-001、IN-002 与上述门禁;下一步:完成验证后重新运行。 ## 先处理这 2 项 @@ -72,16 +77,39 @@ CI 门禁必须读取 `ci_summary`:`match_mode=sha` 优先,`branch` 只能 推荐的首屏格式: ```markdown -# PR # 集成摘要 -**结论:** 需补验证 **[action_required]** -**贡献价值:** 值得合入 **[passed]**:解决高频维护问题,现有命令没有等价能力 -**门禁:** 价值通过 | 合并态通过 | 构建通过 | 测试未执行 | 安全未验证 | 冲突低 +# PR 集成摘要 +## PR # +**贡献价值:** 价值成立 **[passed]**:解决高频维护问题;依据:需求、默认分支差异和受益范围;影响:减少重复操作。 +**合并态:** 当前无冲突 **[passed]**:head 可应用到 baseline;依据:隔离 worktree 合并;影响:无文本阻断。 +**构建:** 构建通过 **[passed]**:仓库构建成功;依据:当前 SHA 的命令和退出码;影响:编译可用。 +**测试:** 测试未执行 **[not_run]**:没有全量结果;依据:验证账本为空;下一步:运行仓库测试。 +**契约:** 仅部分验证 **[partial]**:旧调用仍需确认;依据:帮助和 JSON 对照;下一步:补兼容测试。 +**安全与发布:** 边界未验证 **[not_run]**:权限输入缺证据;依据:安全矩阵与测试缺口;下一步:执行安全场景。 +**集成结论:** 补齐验证后再进入队列 **[action_required]**:关键门禁不完整;依据:IN-001、IN-002;下一步:完成后重跑。 ## 先做这 2 件事 1. **[IN-001][high] 验证** `go test ./...`(责任:作者/维护者确认命令)。 2. **[IN-002][high] 复查** `internal/auth/` 的权限边界(责任:reviewer)。 ``` +保存报告后,针对每个目标重复 `--require-pr` 并运行: + +```bash +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py \ + --report \ + --require-pr \ + --min-cards 7 \ + --required-aspect "贡献价值" \ + --required-aspect "合并态" \ + --required-aspect "构建" \ + --required-aspect "测试" \ + --required-aspect "契约" \ + --required-aspect "安全与发布" \ + --required-aspect "集成结论" +``` + +报告通过后,聊天直接复用逐 PR 七张卡;校验失败或出现连续 `???` 时必须重写,不能交付路径。 + 只有贡献价值和六项技术门禁都有充分证据且无 `blocking/high` 未解决项,才可使用 `merge`。大型 PR 先做价值证据、文件/目录重叠和安全热点筛选,低价值或高度重复候选先交维护者判断,高风险候选再进入独立 worktree 的完整合并验证,避免批量扫描浪费维护者时间。 ## 贡献价值门禁 @@ -323,9 +351,14 @@ gitlink-cli pr +list --owner --repo --state open --page 1 --limit ## PR # 集成就绪报告 -**结论:** 需要维护者判断 **[observe]** -**贡献价值:** 证据不完整 **[partial]** -**门禁:** 价值 `partial` | 合并态 `passed` | 构建 `passed` | 测试 `passed` | 契约 `passed` | 安全 `passed` | 冲突 `low` +**贡献价值:** 价值证据部分成立 **[partial]**:PR 解决了可复现问题且实现完整,但尚未排除等价能力;依据:需求、Diff 和默认分支已核对,open/merged PR 对照未完成;下一步:补等价能力检查。 +**合并态:** 可干净应用到最新主线 **[passed]**:当前 head 没有文本冲突;依据:固定 base/head SHA 的隔离合并;影响:无文本阻断。 +**构建:** 构建通过 **[passed]**:仓库构建命令成功;依据:当前 head 的命令、退出码与日志;影响:编译链路可用。 +**测试:** 测试通过 **[passed]**:专项与全量测试均成功;依据:同一 head 的测试账本;影响:已验证核心行为。 +**契约:** 外部契约保持兼容 **[passed]**:旧调用和结构化输出未破坏;依据:帮助、JSON 与 baseline 对照;影响:调用方无需迁移。 +**安全与发布:** 安全与发布检查通过 **[passed]**:没有阻断项;依据:安全矩阵、依赖和发布影响检查;影响:无额外前置。 +**集成结论:** 先确认功能增量再决定入队 **[observe]**:技术门禁通过但价值证据不完整;依据:IN-VALUE-03 和上述门禁;下一步:完成仓库能力对照后重评。 +**状态索引:** 价值 `partial` | 合并态 `passed` | 构建 `passed` | 测试 `passed` | 契约 `passed` | 安全 `passed` | 冲突 `low` **merge_readiness:** medium **integration_risk:** medium **conflict_risk:** high diff --git a/skills/gitlink-pr-integrator/agents/openai.yaml b/skills/gitlink-pr-integrator/agents/openai.yaml index 7b3e174..a5c8eae 100644 --- a/skills/gitlink-pr-integrator/agents/openai.yaml +++ b/skills/gitlink-pr-integrator/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "PR 价值与集成检查" short_description: "以详细证据评估贡献价值,并验证 PR 能否安全并入主线。" - default_prompt: "使用 $gitlink-pr-integrator 评估指定 GitLink PR 的贡献价值和集成条件,生成结论前置、依据完整且不修改远端的 Markdown 报告。" + default_prompt: "使用 $gitlink-pr-integrator 评估指定 GitLink PR;聊天和 Markdown 均按 PR 分节,将贡献价值、合并态、构建、测试、契约、安全与发布、集成结论分别做成结论前置判断卡,后接依据与影响,不修改远端。" diff --git a/skills/gitlink-pr-integrator/examples/executive-integration.md b/skills/gitlink-pr-integrator/examples/executive-integration.md index d64d6c6..bc25844 100644 --- a/skills/gitlink-pr-integrator/examples/executive-integration.md +++ b/skills/gitlink-pr-integrator/examples/executive-integration.md @@ -9,9 +9,15 @@ gitlink-cli ci +builds --owner Gitlink --repo gitlink-cli --format json ```markdown # PR #123 集成摘要 -**结论:** 可进入合并队列 **[merge]** -**贡献价值:** 值得合入 **[passed]**:解决高频批量操作缺口,默认分支无等价能力 -**门禁:** 价值通过 | 合并态通过 | 构建通过 | 测试通过 | 契约通过 | 安全通过 | 冲突低 + +## PR #123 +**贡献价值:** 功能增量值得合入 **[passed]**:补齐高频批量操作缺口;依据:默认分支无等价能力、需求、Diff 和受益范围;影响:减少重复人工操作。 +**合并态:** 可干净应用到最新主线 **[passed]**:当前 head 没有文本冲突;依据:固定 base/head SHA 的隔离 worktree 合并结果;影响:无合并阻断。 +**构建:** 构建链路通过 **[passed]**:仓库规定的构建命令退出码为零;依据:当前 head、命令和日志摘要;影响:编译产物可生成。 +**测试:** 专项与全量测试通过 **[passed]**:正常、失败和兼容路径均有回归;依据:同一 head 的测试账本和测试结果;影响:核心行为可复验。 +**契约:** 外部契约保持兼容 **[passed]**:旧调用、帮助和 JSON 均未破坏;依据:baseline/current 对照与 golden 测试;影响:现有调用方无需迁移。 +**安全与发布:** 安全与发布门禁通过 **[passed]**:未发现凭据、权限或危险输入阻断;依据:安全矩阵、依赖和发布影响检查;影响:无额外发布前置。 +**集成结论:** 可进入合并队列 **[merge]**:价值和全部技术门禁均成立;依据:IN-001 至 IN-006 与同一 SHA 的验证证据;下一步:维护者执行最终合并。 ## 需要记录的动作 1. **[IN-001][low] 更新** 发布说明(责任:维护者)。 diff --git a/skills/gitlink-pr-topology/SKILL.md b/skills/gitlink-pr-topology/SKILL.md index 4af609b..31ee0bd 100644 --- a/skills/gitlink-pr-topology/SKILL.md +++ b/skills/gitlink-pr-topology/SKILL.md @@ -1,322 +1,325 @@ --- name: gitlink-pr-topology -description: "GitLink open PR 队列关系专项分析:识别依赖、继承、重叠、替代、冲突、互补和可打包评审关系,比较重叠实现的完整性并给出处理顺序,生成带 TP 编号和证据置信度的只读 Markdown 报告。用户只需点名 gitlink-pr-topology 并提供仓库或指定 PR 编号集合;默认不调用其他 Skill、不修改远端。" +description: "GitLink PR 仓库关系专项分析:把一个或多个目标 PR 分别与当前默认分支源码、全部 open PR 和全部 merged PR 对照,识别已实现、扩展、依赖、继承、重叠、替代、冲突、互补和联合评审关系,生成带 TP 编号、覆盖统计和证据置信度的只读 Markdown 报告。指定 PR 只限制目标,不缩小仓库对照范围;默认不调用其他 Skill、不修改远端。" --- -## 已合并功能的增量证据 - -配套基础能力 PR #429 提供统一的 PR 文件、提交和 Review 证据,PR #430 提供队列变化。它们未合并或命令不可用时,必须回退到现有只读接口并标记限制;可用时先取一次队列快照,再为候选关系补取证据: - -```bash -gitlink-cli workflow +review-queue --owner --repo --previous queue-previous.json --format json -gitlink-cli workflow +review-context --owner --repo --number --include-commits=true --format json -``` - -本 Skill 只输出 `TP-` 关系、证据、置信度和处理顺序,不把队列变化直接解释为代码缺陷,也不替代 `gitlink-code-review` 和 `gitlink-pr-integrator` 的结论。 - -队列差异应先按 `new`、`resolved` 和排名/优先级变化缩小候选集,再对候选 PR 比较文件、命令入口和输出字段。`stale` 或 `waiting_on` 是维护协作信号,不构成功能继承或重叠证据;只有存在文件、API、分支或正文证据时,才能输出 `depends_on`、`overlaps` 或 `supersedes`。 - -# gitlink-pr-topology +# GitLink PR 仓库关系分析 **CRITICAL - 开始前先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)。** -**CRITICAL - GitLink 平台数据采集和回写只使用 `gitlink-cli`。** -**CRITICAL - 这个 skill 关注的是 PR 与 PR 之间的关系,不代替单条 PR 的代码审查或合并验收。** -**CRITICAL - 如果需要把中文报告写入文件或重定向输出,在 Windows PowerShell 中先切到 UTF-8 输出链路。** +**CRITICAL - GitLink 平台数据采集只使用 `gitlink-cli`;本地源码分析可以使用 `git`、`rg` 和仓库自带工具。** +**CRITICAL - 指定的 PR 编号只定义目标 PR,不得把比较范围缩成这些 PR 之间。** +**CRITICAL - 本 Skill 判断功能关系,不代替代码缺陷审查、合并验收或 SLA 治理。** + +## 解决的问题 + +维护者需要知道的不是“目标 PR 彼此有没有关系”,而是每个目标 PR 相对于仓库当前能力处在什么位置: + +1. 主线源码是否已经实现相同能力,目标 PR 是重复、补缺、扩展还是回归。 +2. 全部 open PR 中是否存在上游依赖、竞争实现、互补能力或潜在冲突。 +3. 全部 merged PR 中是否存在被继承的基础能力、已经合入的同类实现或演进来源。 +4. 如果存在重叠实现,哪一条覆盖更完整、测试更充分、与现有架构更一致。 +5. 维护者应独立评审、联合评审、调整顺序、择一保留还是先确认产品方向。 ## 默认调用契约 -用户只需说“使用 `gitlink-pr-topology` 分析 `/` 的 open PR”,也可以列出若干 PR 编号限制范围。默认扫描当前 open 队列;除非仓库无法确定,不要求用户重复说明关系类型、输出格式或保存位置。 +用户可以提供仓库和一个或多个目标 PR,例如“分析 `Gitlink/gitlink-cli` 的 #430、#431”。调用范围解释为: + +- **目标集合**:`#430`、`#431`,报告必须分别给出结论。 +- **仓库对照宇宙**:当前默认分支源码 + 全部 open PR + 全部 merged PR。 +- **目标间关系**:只是 open/merged 关系图中的附加边,不能代替目标与整个仓库的分析。 + +用户没有指定编号时,把当前全部 open PR 作为目标集合,并使用相同仓库对照宇宙。用户明确要求仅做局部快速检查时才允许缩小对照范围,报告必须醒目标为 `partial`,不能称为仓库全量关系分析。 点名后默认自动执行: -- 只分析 PR 之间的关系,不调用其他 Skill,不做单 PR 代码缺陷、CLI 契约、合并门禁或 SLA 判断。 -- 只读运行,不评论、不关闭、不合并、不分配、不修改远端。 -- 使用 `TP-001` 起的稳定编号,记录关系类型、相关 PR、证据、置信度、比较结论、建议动作和限制。 -- 首屏先显示最能减少重复评审的关系簇、冲突热点和建议顺序,最多 5 项;高置信度阻断/高风险使用颜色和粗体。 -- 一次运行只生成一份 UTF-8 Markdown,保存到 `reports/skill-runs/gitlink-pr-topology/---.md`;完整边列表放同一文件附录,不再额外生成第二份人读报告。 +- 自动翻页取得全部 open 和 merged PR;服务端状态过滤不可信时按真实状态、合并时间和关闭时间二次校验。 +- 固定默认分支名称与 baseline SHA,分析该 SHA 下的源码、测试、帮助、文档和历史。 +- 对每个目标 PR 独立建立“主线、open、merged”三层关系结论,不混用证据。 +- 使用 `TP-001` 起的稳定编号,记录目标、对照对象、关系、证据、置信度、影响和建议动作。 +- 全程只读,不评论、不关闭、不合并、不分配、不修改标签或远端内容。 +- 生成一份 UTF-8 Markdown,保存到 `reports/skill-runs/gitlink-pr-topology/---.md`。 +- Markdown 首屏按目标 PR 分节,每个方面先给醒目加粗结论,再写解释、`依据:` 和影响/下一步。 +- 聊天摘要按相同 PR 和方面输出纯文本精简版,去除 HTML、Markdown 和机器状态标签。每个方面用一至两句完整自然语言重新提炼直接结论、最关键依据和解释/影响,详细证据留在报告。 -无法写入工作区时输出完整 Markdown 并标记“未落盘”。最终回复给出报告绝对路径、关系簇数量和第一处理顺序。 +最终回复按目标 PR 分节,由执行本 Skill 的 Agent 在理解完整报告后重新归纳主线、open 队列、merged 历史、综合关系和处理建议。每个方面最多两句,必须同时让维护者知道结论、主要依据以及为什么重要;禁止复制报告卡片、文件清单、长证据链和覆盖过程,禁止用省略号截断半句话。除报告链接外不输出 HTML、Markdown 展示标记或机器状态标签,也不能把多个方面合并成一段连续文字。 -首屏固定先使用: +## 强制分析范围 -```markdown -# PR 队列关系摘要 +### 1. 当前主线源码 -**结论:** 需要重排 **[reorder]** -**范围:** open PR 18 | 关系簇 4 | 冲突热点 2 | 安全热点 1 +先固定默认分支和 baseline SHA: -## 先处理这 3 项 - -1. [TP-001][blocking] 先处理 #61,再处理 #63;证据:共享接口依赖。 -2. [TP-002][high] 择一评审 #71/#74;先比较测试与兼容性。 -3. [TP-003][high] 集中复看 #80/#82 的权限热点。 +```bash +gitlink-cli repo +info --owner --repo --format json +git rev-parse / +rg --files ``` -这个 skill 解决的是“PR 太多,维护者看不出它们彼此是什么关系”的问题。 +对每个目标 PR 的声明功能和变更路径,至少检查: -它不只回答“有没有重复”,还要回答: +- 主线中是否已有同名命令、flag、API 包装、结构体、JSON 字段或帮助入口。 +- 主线已有行为是否由其他路径实现,不能只按文件名判断“尚未实现”。 +- 目标 PR 是补齐主线缺口、扩展已有能力、重复已有实现,还是会覆盖或退化现有行为。 +- 主线测试、文档和调用方是否证明目标能力已经存在或形成兼容约束。 -1. 哪些 PR 有明显的先后依赖,像 stacked PR 一样要按顺序处理。 -2. 哪些 PR 实际上在解决同一个需求、同一个 bug、同一个命令入口。 -3. 如果两条 PR 目标重叠,哪一条更完整、更稳、更值得保留。 -4. 哪些 PR 虽然不完全重复,但会在同一文件、同一命令、同一输出契约上互相打架。 -5. 哪些 PR 应该一起评审,避免维护者重复进入同一上下文。 -6. 当前 open PR 队列最合理的处理顺序是什么。 +本地仓库不在固定 baseline、存在用户未提交改动或无法取得默认分支时,使用只读 worktree 或 `git show :`,不得覆盖用户工作区。无法取得完整源码时把主线层标记为 `partial`。 -## 效率版队列输出 +### 2. 全部 open PR -默认遵循 [`../gitlink-shared/references/maintenance-report-contract.md`](../gitlink-shared/references/maintenance-report-contract.md),输出“关系摘要”而不是完整的两两比较表: +自动翻页直到没有下一页,不得只读取默认第一页或最近 20-50 条: -运行键、证据台账、刷新和自动回写边界遵循 [`../gitlink-shared/references/maintenance-run-protocol.md`](../gitlink-shared/references/maintenance-run-protocol.md)。 - -1. 先给 open PR 数、关系簇数量、冲突热点、安全热点和建议处理顺序。 -2. 只展示会改变维护决策的最多 5 条关系;相同关系簇合并成一项,完整边列表放附录或 JSON。 -3. 每条关系使用 `TP-xxx` 稳定编号,写明证据、置信度和建议动作;没有足够证据时标为 `candidate`,不能断言重复或 supersedes。 -4. 对同时修改认证、权限、命令执行、路径处理、依赖或输出敏感数据的 PR,增加 `security_hotspot` 关系,要求先完成安全审查再排序。 - -## 关系判定的证据等级 - -关系图谱先建立候选边,再判定关系,不允许仅凭标题相似就断言重复: - -| 关系 | 最低证据 | 输出动作 | -|------|----------|----------| -| `depends_on` / `stacked_on` | 分支、提交、API 字段或文件引用形成明确先后 | 先处理被依赖 PR | -| `overlaps` / `duplicate_candidate` | 同一命令、文件、Issue 或行为目标,且 Diff 有交集 | 合并评审上下文或择一 | -| `supersedes` | 目标相同且一条覆盖另一条的功能、测试和文档 | 保留更完整者,另一条进入人工确认 | -| `conflicts` | 同一契约/代码区域存在互斥修改 | 先解决冲突再排序 | -| `complements` | 目标不同但共享稳定接口或可以串联 | 建议打包评审,不判重复 | - -置信度分为 `high`、`medium`、`candidate`。`candidate` 只能作为待核对关系,不能直接建议关闭 PR;关系动作必须引用文件、分支、字段或快照证据。队列的 `stale`、`waiting_on` 只影响维护排序,不构成功能关系。 - -关系图谱的首屏最多列 5 个关系簇,并同时给出“保留、先后处理、并行评审、人工确认”四类动作之一;完整边列表和两两评分放 JSON 附录。 - -每条关系都要登记 `TP-` 编号、置信度、关系证据和建议动作;队列快照缺失或 PR head 已变化时,将关系标为 `stale`/`candidate`,不使用旧图谱直接建议关闭或替代。 - -首屏示例: - -```markdown -# PR 队列关系摘要 -**结论:** 需要重排 **[reorder]** -**范围:** 18 个 open PR | 4 个关系簇 | 2 个高风险热点 - -## 先处理 -1. **[TP-001][blocking] 先处理** #61,再处理 #63:共享 `shortcuts/pr/`,#63 依赖 #61 的输出字段。 -2. **[TP-002][high] 择一评审** #71 / #74:目标重叠,但 #74 缺安全和回归测试,不能直接判定 supersedes。 -3. **[TP-003][high] 安全复查** #80 / #82:同时改变权限校验。 +```bash +gitlink-cli pr +list --owner --repo --state open --page --limit 100 --format json ``` -先按标题、issue、改动文件和目录做低成本候选筛选,再对候选关系读取 diff、review 和测试证据;不要对所有 PR 做完整笛卡尔积分析。 +对响应按真实状态二次过滤、按 PR 编号去重,并记录: -## 职责边界与组合协同 +- API 返回总数、分页数、去重后 open 数。 +- 被状态二次过滤排除的编号。 +- 失败页和是否存在截断。 -独立运行时,本 Skill 只判断多个 open PR 之间的关系,不判断单条 PR 的代码缺陷、契约通过与否或维护者响应是否超时。组合运行时向 `gitlink-pr-integrator` 交接 `TP-xxx` 关系和建议顺序,向 `gitlink-maintainer-radar` 交接冲突热点和待处理簇;`security_hotspot` 只表示需要安全复查,不等同于已确认漏洞。 +指定 PR 可能是 closed/merged 历史项,但不能因此把其他 open PR 排除。对每个目标,从全部 open 元数据中粗筛候选,再为候选补取文件、Diff、Review 和提交证据。 -## 不覆盖的内容 +### 3. 全部 merged PR -下面这些不属于本 skill 的职责: +同样自动翻页取得全部 merged PR,不能只看最近合并项: -- 单条 PR 的贡献价值和实现审查:交给 `gitlink-code-review`;集成价值与执行验证交给 `gitlink-pr-integrator` -- 单条 PR 是否已经具备并入主线的条件:交给 `gitlink-pr-integrator` -- 维护者值班、SLA、review 负载和停滞治理:交给 `gitlink-maintainer-radar` - -## Windows UTF-8 前置 - -如果你在 Windows PowerShell 中运行并准备保存中文报告,先执行: - -```powershell -chcp 65001 > $null -[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false) -[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) -$OutputEncoding = [Console]::OutputEncoding +```bash +gitlink-cli pr +list --owner --repo --state merged --page --limit 100 --format json ``` -保存报告时显式指定 UTF-8: +如果快捷命令不能可靠返回 merged 状态,使用 `gitlink-cli api GET` 调用对应只读列表接口,再根据真实 `merged_at`、状态字段和合并提交二次过滤。记录总页数、merged 总数、失败页和截断状态。 -```powershell -$report | Set-Content -Path .\pr-topology-report.md -Encoding utf8 +全量 merged PR 先做元数据与模块索引;再结合默认分支 `git log`、`git blame`、路径历史和目标 PR 变更定位深度候选。这样保证所有 merged PR 都进入筛选范围,同时避免对历史中每一条 PR 拉取完整 Diff。 + +## 分层取证策略 + +“全量分析”不等于对所有对象执行昂贵的笛卡尔积比较,采用两阶段方法: + +### 阶段 A:全量覆盖 + +对全部 open 和 merged PR 读取并索引: + +- 编号、标题、正文摘要、状态、关联 Issue。 +- base/head、作者、创建/更新时间、merged commit。 +- 涉及模块、命令名、API 名、flag、输出字段和主要路径关键词。 + +目标 PR 必须读取完整详情、文件列表、Diff、提交和 Review: + +```bash +gitlink-cli pr +view --owner --repo --id --format json +gitlink-cli pr +files --owner --repo --id --format json +gitlink-cli pr +diff --owner --repo --id --format json +gitlink-cli pr +reviews --owner --repo --id --format json ``` +组合上下文可用时可以使用: + +```bash +gitlink-cli workflow +review-context --owner --repo --number --include-commits=true --format json +``` + +命令不可用时回退到现有只读命令并记录限制,不能把“不支持命令”误报成“没有关系”。 + +### 阶段 B:候选深度比较 + +出现以下任一信号时进入深度比较: + +- 相同 Issue、需求目标、命令、flag、API、JSON 字段或用户行为。 +- 修改文件相同,或修改同一模块/调用链的上下游。 +- PR 正文、提交或代码显式引用另一个 PR 提供的符号或契约。 +- 主线历史表明目标路径来自某条 merged PR。 +- 一条实现提供基础信号,另一条消费该信号形成上层能力。 + +只对候选补拉完整 Diff、文件、测试和 Review。无候选关系时也要报告“已覆盖多少对象、采用哪些筛选字段、未发现何种关系”,不能静默省略。 + ## 关系类型 -先阅读 [`references/relationship-taxonomy.md`](references/relationship-taxonomy.md) 了解关系定义和证据标准。这个 skill 至少识别以下六类关系: +关系边的对照对象可以是 `mainline:`、`open:#` 或 `merged:#`。 -1. `depends_on` -表示 PR B 依赖 PR A 先落地,否则 B 难以独立评审、测试或合并。 +| 关系 | 含义 | 最低证据 | +|---|---|---| +| `already_in_mainline` | 主线已存在等价能力,目标增量可能重复 | 可定位源码行为、测试或帮助入口 | +| `extends_mainline` | 目标在主线现有能力上增加有效场景 | 主线与目标 Diff 的行为差异 | +| `fills_mainline_gap` | 主线确认缺失该能力,目标补齐空白 | 全仓搜索、调用链和测试缺口 | +| `regresses_mainline` | 目标会删除、绕过或破坏现有行为 | 主线对照和目标 Diff | +| `depends_on` / `stacked_on` | 不先具备对照 PR 的能力,目标无法独立工作 | 分支链、提交、符号或契约引用 | +| `inherits_from` | 目标沿用已 merged 或 open PR 的基础设计,但可独立演进 | 代码历史、符号和设计来源 | +| `overlaps_with` | 双方解决同一需求或改变同一行为 | 行为目标加文件/命令/Issue 证据 | +| `supersedes` | 一方完整覆盖另一方且更适合保留 | 功能、测试、文档和兼容性包含关系 | +| `conflicts_with` | 双方对同一代码或契约给出互斥修改 | 相邻 Diff、字段或默认值冲突 | +| `complements` | 目标不同但能力可组合,组合后价值更完整 | 稳定接口、生产/消费或工作流证据 | +| `review_together` | 共享上下文,联合评审能减少重复工作 | 同模块、同契约或互补链路 | +| `merge_after` | 非硬依赖,但先后处理可避免返工 | 上游契约仍可能变化 | -2. `overlaps_with` -表示两条 PR 在需求目标、命令入口、模块范围或改动文件上明显重叠。 +详细定义见 [`references/relationship-taxonomy.md`](references/relationship-taxonomy.md)。`stale`、`waiting_on` 和更新时间只属于维护信号,不能单独证明功能关系。 -3. `supersedes` -表示一条较新的 PR 在同一目标上覆盖更完整,足以替代另一条较弱 PR。 +置信度使用: -4. `conflicts_with` -表示两条 PR 即使目标不同,也会在同一文件、同一 flag、同一 JSON 字段、同一帮助文案或同一 API 包装层上互相冲突。 +- `high`:有源码、Diff、提交链、字段引用或明确正文证据。 +- `medium`:目标和模块高度一致,已有多项间接证据但缺少一项关键验证。 +- `candidate`:只有标题、关键词或目录相似,需要继续取证。 -5. `review_together` -表示几条 PR 共享足够多的上下文,维护者一起看更高效。 +`candidate` 不能用于建议关闭、替代或阻止合并。 -6. `merge_after` -表示不是严格代码依赖,但为了减少返工,建议某条 PR 排在另一条之后处理。 +## 每个目标 PR 的强制结论 -## 标准流程 +每个目标 PR 都必须独立回答: -### Step 1:拉取 open PR 队列 +1. **对主线:** 已实现、扩展、补缺、重复、回归或 `not_verifiable`,并引用源码路径/符号/测试。 +2. **对全部 open PR:** 发现的依赖、重叠、冲突和互补候选;没有关系时说明覆盖数量和筛选依据。 +3. **对全部 merged PR:** 继承来源、历史重叠、已合入替代能力;没有关系时说明覆盖数量和历史定位方式。 +4. **目标间附加关系:** 只有确有证据时再说明指定目标之间的关系。 +5. **维护动作:** 独立评审、先处理上游、联合评审、择一比较、调整设计或人工确认。 -先列出目标仓库的 open PR: +例如指定 `#430、#431` 时,不能只输出: -```bash -gitlink-cli pr +list --owner --repo --state open --page 1 --limit 50 --format json +> #431 依赖 #430,两者互补,建议一起评审。 + +必须分别输出类似: + +```markdown +## PR #431 +**对主线:** 缺少目标声明的上游契约 **[partial]**:baseline 未提供某项 workflow 能力;依据:`cmd/...`、`shortcuts/workflow/...` 和帮助输出;影响:目标暂不能独立验证。 +**对 open 队列:** 存在一个顺序依赖 **[reorder]**:#430 提供目标消费的队列字段;依据:全部 12 条 open PR 的元数据索引和候选 Diff;下一步:先稳定 #430 字段。 +**对 merged 历史:** 继承既有证据结构但没有重复实现 **[passed]**:#429 是上下文证据来源;依据:全部 86 条 merged PR 索引、Git 历史和符号来源;影响:保留演进关系。 +**关系判断:** 与 #430 互补且应联合评审 **[review_together]**:两者位于基础信号与消费层;依据:字段生产/消费关系且文件交集不是唯一标准;影响:一次确认接口稳定性。 +**处理建议:** 先稳定上游契约,再复看 #431 **[reorder]**:当前主要风险是契约漂移;依据:上述主线与 open 关系;下一步:固定字段和默认值后重新运行。 ``` -注意: +示例数字和关系不能复用,必须来自本轮证据。 -- `--state open` 的服务端过滤并不总是可靠,必须再用 `pull_request_status == 0` 做客户端过滤。 -- 队列过大时优先扫描最近活跃的前 20-50 条,而不是一次吃完整个仓库。 +## 重叠实现比较 -### Step 2:为每条 PR 建立关系画像 +发现 `overlaps_with`、`already_in_mainline` 或 `supersedes` 候选时,读取 [`references/comparison-rubric.md`](references/comparison-rubric.md),至少比较: -对每条候选 PR 至少补拉这些信息: +- 需求与边界场景覆盖。 +- 与仓库现有封装和命令结构的一致性。 +- 正常、失败、兼容和安全测试。 +- 帮助、文档、示例和变更说明。 +- 向后兼容、复杂度和维护成本。 +- 既有 Review 的吸收情况。 -```bash -gitlink-cli pr +view --owner --repo --id --format json -gitlink-cli pr +files --owner --repo --id --format json -gitlink-cli pr +reviews --owner --repo --id --format json -gitlink-cli repo +info --owner --repo --format json +不能只说“A 更全面”。必须说明 A 多实现了什么、B 缺少什么、主线已有多少、可否拆分互补部分,以及建议保留或调整的依据。 + +## 输出结构 + +Markdown 首屏使用完整的逐 PR 判断卡,不先展示跨 PR 总段落或目标间关系: + +```markdown +# PR 仓库关系摘要 + +**分析基线:** `@`;源码文件 ;open PR / 页;merged PR / 页;失败页 。 + +## PR # +**对主线:** <最直接关系结论> **[]**:<简短解释>;依据:<源码路径、符号或测试>;影响:<对评审的含义>。 +**对 open 队列:** <最直接关系结论> **[]**:<覆盖数量和关键关系>;依据:<分页、候选和 Diff>;影响:<排序或择一建议>。 +**对 merged 历史:** <最直接关系结论> **[]**:<继承、重复或无关系>;依据:<历史索引与 Git 来源>;影响:<演进含义>。 +**关系判断:** <依赖/重叠/互补结论> **[]**:<关系解释>;依据:<生产消费、行为或契约证据>;影响:<联合或独立评审>。 +**处理建议:** <可执行建议> **[]**:<为什么这样处理>;依据:<前述 TP 关系>;下一步:<明确动作>。 + +## PR # +... + +## 先处理这几项 +1. **[TP-001][high] <动作>**:<目标、对照对象、证据和原因>。 ``` -优先提取: +完整报告顺序: -- PR 标题、描述、作者、创建时间、最近更新时间 -- base/head 分支、fork 来源 -- 修改文件、核心目录、是否触及同一条命令或同一 API 封装 -- 是否修改测试、帮助文案、README、示例 -- review 争议点、是否已有人指出重复或依赖关系 -- 关联 issue、里程碑、标签 +1. baseline SHA、源码索引、open/merged 分页与覆盖统计。 +2. 每个目标 PR 的主线关系。 +3. 每个目标 PR 与全部 open PR 的关系。 +4. 每个目标 PR 与全部 merged PR 的关系。 +5. 目标间附加关系和关系簇。 +6. 重叠实现的完整性比较。 +7. 建议处理顺序、证据台账、失败页和验证限制。 -### Step 3:先做“候选关系”粗筛 +## UTF-8 安全写入与报告校验 -先不要急着得结论,先把可能有关联的 PR 成对找出来。粗筛信号包括: +使用当前 Agent 平台支持的明确 UTF-8 文件 API 写 Markdown;平台提供补丁式文件工具时优先使用。不要在 Windows PowerShell 5.1 中把含中文的 here-string、变量或命令输出通过管道传给 `Set-Content`/`Out-File`,这会在部分宿主编码下把中文永久写成 `?`。写入后必须按严格 UTF-8 重新读取。 -- 标题和描述出现同一需求词、同一命令名、同一 issue 编号 -- 修改相同文件 -- 修改同一目录或同一 shortcuts 子模块 -- 同时触碰同一 flag、同一输出字段、同一错误提示 -- 一条 PR 的描述直接提到 “基于 #xx” “依赖 #xx” “替代 #xx” -- 两条 PR 都在补同一类能力,例如 release、attachment、milestone、search +保存后针对每个目标追加一个 `--require-pr`,并执行: -只把这些候选对放进下一步,不要把所有 PR 两两做重分析。 +```bash +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py \ + --report \ + --require-pr \ + --min-cards 5 \ + --required-aspect "对主线" \ + --required-aspect "对 open 队列" \ + --required-aspect "对 merged 历史" \ + --required-aspect "关系判断" \ + --required-aspect "处理建议" +``` -### Step 4:判断具体关系类型 +多个目标在同一次命令中重复 `--require-pr`。校验器会拒绝连续 `???`、乱码、中文不足、缺少 PR 分节、结论不前置或没有明确 `依据:` 的判断卡。失败时必须重写并重新校验,不能返回损坏报告路径。 -对每个候选对,结合 [`references/relationship-taxonomy.md`](references/relationship-taxonomy.md) 给出单一主关系,必要时允许附加次关系。 +## Agent 摘要交付 -判断顺序建议如下: +报告校验通过后,必须由执行本 Skill 的 Agent 根据完整报告重新提炼摘要,不能使用字符串 +截取、去标签或复制卡片原文代替理解与归纳。每个目标 PR 固定输出五行: -1. 先看是否存在明确依赖链。 -2. 再看是否实际上在做同一件事。 -3. 再看是否已出现“更完整版本替代较弱版本”。 -4. 如果目标不同但落点冲突,则标为冲突热点。 -5. 如果只是共享上下文但不冲突,标为建议一起评审。 +```text +PR # +对主线:<结论>。<最关键依据,以及该事实为什么影响评审>。 +对 open 队列:<结论>。<最关键依据,以及联审、排序或择一含义>。 +对 merged 历史:<结论>。<最关键依据,以及继承、重复或演进含义>。 +关系判断:<结论>。<依赖、重叠、互补或冲突的简短解释>。 +处理建议:<结论>。<维护者下一步及其理由>。 +``` -没有足够证据时,写成 `possible_overlap` 或 `possible_dependency`,不要过度下结论。 +摘要质量要求: -### Step 5:在重叠 PR 中比较“谁更值得保留” +- 必须由 Agent 根据报告结论重新提炼,不得复制报告卡片原文或机械删除格式。 +- 每个方面一至两句完整句子,通常控制在 50 至 120 个中文字符,不使用省略号截断。 +- 依据只保留最能支撑结论的一项事实,例如“主线已有基础实现”“扫描全部 open PR 后只有两条高置信候选”;不复制路径和编号清单。 +- 解释必须回答“为什么维护者需要关心”,不能只换一种说法重复结论。 +- 多个 PR 分别归纳,不把共同关系写成一段跨 PR 总结。 +- 最后一行通过当前 Agent 平台支持的可点击链接或文件附件交付报告。支持 Markdown 本地链接时使用 `完整报告:[<文件名>](<绝对路径>)`;不支持时使用平台原生文件引用,不能只给不可点击的裸路径。 -如果两条或多条 PR 目标重叠,读取 [`references/comparison-rubric.md`](references/comparison-rubric.md),从以下维度比较: +关系边至少包含: -- 需求覆盖是否更完整 -- 代码路径是否更贴近现有架构 -- 测试是否更充分 -- 帮助文档、README、示例是否同步 -- 向后兼容性是否更好 -- 风险和复杂度是否更低 -- review 反馈吸收是否更充分 - -输出时不要只说“PR A 更好”,而要明确指出: - -- A 比 B 多解决了什么 -- B 缺了什么 -- B 是否还能拆成补充 PR,还是应该直接关闭 - -### Step 6:生成队列图谱和处理顺序 - -最终输出的不是一堆散点结论,而是一份维护者可执行的“队列图谱”: - -- 哪些是依赖链,先后顺序怎样 -- 哪些是一组重叠实现,需要择一保留 -- 哪些是热点文件/热点命令,应该集中处理 -- 哪些 PR 值得一起 review -- 哪些 PR 可以暂缓,因为上游未定 - -## 输出要求 - -同时产出两类结果: - -### 1. 关系边列表 - -每条关系边至少包含: - -- `source_pr` - `target_pr` +- `counterpart_type`: `mainline`、`open_pr` 或 `merged_pr` +- `counterpart` - `relation` - `confidence` - `evidence` +- `impact` - `recommended_action` -### 2. 维护者摘要报告 +## 覆盖与失败语义 -报告至少包含: +报告必须区分: -1. open PR 总数和本轮纳入分析的数量 -2. 主要依赖链 -3. 主要重叠簇 -4. 明显替代关系 -5. 冲突热点文件/模块 -6. 建议处理顺序 -7. 需要进一步切换到 `gitlink-code-review` 或 `gitlink-pr-integrator` 深挖的对象 +- `complete`:全部 open/merged 页成功,baseline 源码可读。 +- `partial`:存在失败页、权限缺口、源码不完整或命令回退。 +- `not_verifiable`:无法取得目标 Diff 或 baseline,不能形成可靠关系。 -## 报告模板 +禁止以下行为: -```markdown -# / PR 队列关系图谱 +- 只比较用户指定的目标 PR。 +- 只扫描第一页或最近 N 条,却宣称“全部 PR”。 +- 把“文件交集为 0”直接等同于“没有功能重叠”;不同层也可能实现同一行为。 +- 把“使用同一字段”自动断言为硬依赖;要判断字段是否已在主线或可由目标自行提供。 +- 把 merged PR 当作当前待处理项;它只用于来源、重复和演进对照。 +- 因某个 API 不可用而填入历史样例或猜测关系。 -扫描时间: -open PR: -纳入分析: +## 职责边界 -## 1. 依赖链 -- #41 -> #44 -> #52 - 说明:#44 基于 #41 引入的 API 包装,#52 又建立在 #44 的 CLI 参数层上。 +本 Skill 可以为了判断关系检查源码结构、测试和文档,但不输出单条 PR 的代码缺陷清单,也不决定合并门禁。需要代码质量审查时交给 `gitlink-code-review`,需要实际合并与测试验收时交给 `gitlink-pr-integrator`,需要等待时长和责任人排序时交给 `gitlink-maintainer-radar`。 -## 2. 重叠实现 -- #61 vs #63 - 共同点:都在实现同一条命令的编号搜索能力。 - 保留建议:优先保留 #63,因为测试覆盖更完整,且同时补了帮助文档和 JSON 输出。 +## 完成前自检 -## 3. 替代关系 -- #71 supersedes #58 - 说明:#71 覆盖了 #58 的核心功能,还补齐了错误处理和帮助文档;#58 可关闭或拆成子改动。 - -## 4. 冲突热点 -- `shortcuts/pr/pr.go` -- `internal/client/client.go` -- `README.md` - -## 5. 建议一起评审 -- #80, #81, #83 - 说明:都在修改 milestone 相关 CLI 行为,一起看更容易统一参数和输出契约。 - -## 6. 建议处理顺序 -1. 先处理 #41,解除后续依赖链阻塞。 -2. 在 #61 和 #63 中择一保留,避免重复 review。 -3. 将 #80、#81、#83 打包评审,统一命令体验。 -4. 暂缓 #52,等待上游 API 包装方案稳定。 -``` - -## 典型触发语句 - -- “扫描这个仓库的 open PR,找出哪些在做同一件事。” -- “帮我分析这批 PR 的依赖关系和建议合并顺序。” -- “哪些 PR 其实可以一起 review,哪些应该择一保留?” -- “如果有两条 PR 功能重叠,判断哪条实现更完整。” -- “给我一个 open PR 队列关系图谱,方便维护者决定先看谁。” +- 指定编号是否只限制目标,而没有缩小主线/open/merged 对照范围。 +- 是否记录 baseline SHA、源码索引规模、全部 open/merged 页数和去重后数量。 +- 是否为每个目标分别给出主线、open、merged 三层结论。 +- 是否把目标间关系放在附加位置,而不是充当全部结果。 +- 每条高/中置信关系是否引用源码、Diff、提交、字段、Issue 或 Review 证据。 +- “无关系”是否同时说明实际覆盖数量和筛选依据。 +- Markdown 首屏是否按 PR 分节并包含五张完整判断卡。 +- 最终摘要是否由 Agent 根据报告重新提炼,而不是复制、去标签或截断报告原文。 +- 摘要是否按 PR 和五方面输出一至两句完整自然语言,并同时包含结论、关键依据和解释/影响。 +- 最终报告是否通过当前 Agent 平台支持的可点击链接或文件附件交付。 +- Markdown 是否通过 `validate_pr_cards.py` 的全部目标和五方面校验。 diff --git a/skills/gitlink-pr-topology/agents/openai.yaml b/skills/gitlink-pr-topology/agents/openai.yaml index 0879285..e6fa24c 100644 --- a/skills/gitlink-pr-topology/agents/openai.yaml +++ b/skills/gitlink-pr-topology/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "PR 关系图谱" - short_description: "分析 open PR 之间的依赖、重叠、替代和建议处理顺序。" - default_prompt: "使用 $gitlink-pr-topology 分析指定仓库或 PR 集合。按 Skill 默认契约只读识别 PR 关系和处理顺序,并生成关键结论前置的单一 Markdown 报告。" + short_description: "分析目标 PR 与主线源码、全部 open/merged PR 的仓库关系。" + default_prompt: "使用 $gitlink-pr-topology 分析指定仓库和目标 PR;目标编号只限制分析对象,对照范围必须包含当前主线源码、全部 open PR 和全部 merged PR。Markdown 校验通过后,由执行 Skill 的 Agent 理解报告并重新提炼逐 PR 五方面摘要,每方面用一至两句完整自然语言说明结论、关键依据和解释/影响,不复制报告原文、不机械去格式、不截断句子;最后通过可点击链接或文件附件交付报告,全程只读。" diff --git a/skills/gitlink-pr-topology/examples/executive-queue.md b/skills/gitlink-pr-topology/examples/executive-queue.md index 3306ec7..3e09643 100644 --- a/skills/gitlink-pr-topology/examples/executive-queue.md +++ b/skills/gitlink-pr-topology/examples/executive-queue.md @@ -1,22 +1,35 @@ -# 轻量 PR 队列关系示例 +# 目标 PR 与仓库关系示例 -先使用列表信息筛选候选,再对候选读取 diff,避免所有 PR 两两拉取完整内容。 +指定 `#430、#431` 只表示这两条是目标,不表示只比较它们。先固定主线并完整扫描 open/merged 元数据: ```bash -gitlink-cli pr +list --owner Gitlink --repo gitlink-cli --state open --limit 50 --format json -gitlink-cli pr +files --owner Gitlink --repo gitlink-cli --id --format json -gitlink-cli pr +diff --owner Gitlink --repo gitlink-cli --id --format json +gitlink-cli repo +info --owner Gitlink --repo gitlink-cli --format json +gitlink-cli pr +list --owner Gitlink --repo gitlink-cli --state open --page 1 --limit 100 --format json +gitlink-cli pr +list --owner Gitlink --repo gitlink-cli --state merged --page 1 --limit 100 --format json +gitlink-cli pr +files --owner Gitlink --repo gitlink-cli --id --format json +gitlink-cli pr +diff --owner Gitlink --repo gitlink-cli --id --format json ``` +列表命令必须继续翻页直到结束。全部对象先进入元数据索引,只有命令、模块、Issue、字段、路径或历史来源命中的候选才补拉完整 Diff。 + ```markdown -# PR 队列关系摘要 -**结论:** 需要重排 **[reorder]** -**范围:** 12 个 open PR | 3 个关系簇 | 1 个安全热点 +# PR 仓库关系摘要 -## 先处理 -1. **[TP-001][blocking] 先处理** #61,再处理 #63:共享命令入口且 #63 使用 #61 的输出字段。 -2. **[TP-002][high] 一起评审** #71、#74:修改同一 API 包装层,需统一错误契约。 -3. **[TP-003][high] 安全复查** #80:修改权限校验,不能仅凭标题判定可合并。 +**分析基线:** `master@abcdef1`;源码 420 个文件;open PR 12 条/1 页;merged PR 86 条/2 页;失败页 0。 + +## PR #431 +**对主线:** 扩展主线维护能力 **[extends_mainline]**:baseline 没有维护编排入口但已有 Skill 目录约定;依据:主线源码和 frontmatter 契约;影响:应保持现有规范。 +**对 open 队列:** 存在一个上游契约依赖 **[reorder]**:#430 提供目标消费的队列字段;依据:全部 12 条 open 索引和候选 Diff;下一步:先稳定字段。 +**对 merged 历史:** 继承证据结构且无重复编排器 **[passed]**:#429 提供上下文证据结构;依据:全部 86 条 merged 索引与 Git 历史;影响:保留演进来源。 +**关系判断:** 与 #430 互补并适合联合评审 **[review_together]**:一条生产队列信号、一条消费信号;依据:字段与调用链;影响:一次确认契约。 +**处理建议:** 先稳定上游字段,再复看 #431 **[reorder]**:当前风险来自字段漂移;依据:TP-001 与 TP-002;下一步:固定契约后重跑。 + +## PR #430 +**对主线:** 补齐主线队列差异缺口 **[fills_mainline_gap]**:baseline 有 workflow 框架但缺少等待方输出;依据:主线命令与源码索引;影响:形成有效增量。 +**对 open 队列:** 存在互补下游且无竞争实现 **[review_together]**:#431 消费新增字段;依据:全部 12 条 open 索引与候选 Diff;影响:需要确认接口。 +**对 merged 历史:** 继承 workflow 基础但没有历史重复 **[passed]**:历史只有基础命令来源;依据:全部 86 条 merged 索引与路径历史;影响:无需择一。 +**关系判断:** 是 #431 的互补上游 **[complements]**:提供队列信号;依据:生产/消费字段关系;影响:建议联合理解。 +**处理建议:** 先独立验证字段契约 **[reorder]**:下游依赖稳定字段;依据:TP-001;下一步:通过契约测试后处理 #431。 ``` -关系证据不足时写 `candidate`,并说明还缺哪些 diff、review 或测试证据。 +示例数字和关系只定义格式。实际运行必须重新分页、固定 baseline、读取源码并生成当前证据,不能复用示例判断。 diff --git a/skills/gitlink-pr-topology/references/relationship-taxonomy.md b/skills/gitlink-pr-topology/references/relationship-taxonomy.md index 845072b..f28b5bd 100644 --- a/skills/gitlink-pr-topology/references/relationship-taxonomy.md +++ b/skills/gitlink-pr-topology/references/relationship-taxonomy.md @@ -1,6 +1,24 @@ # 关系分类与证据标准 -在 `gitlink-pr-topology` 中,不要把“有点像”直接写成“重复”。先按下面的证据标准分型。 +在 `gitlink-pr-topology` 中,不要把“有点像”直接写成“重复”。关系的起点始终是目标 PR,对照对象可以是当前主线能力、open PR 或 merged PR。先标记 `counterpart_type`,再按下面的证据标准分型。 + +## 0. 主线关系 + +### already_in_mainline + +主线已经存在等价用户行为。必须引用可执行入口、源码符号、测试或帮助,不能只因为出现相同关键词就判定重复。 + +### extends_mainline + +目标在主线既有能力上增加新的有效场景,同时保持原契约。证据应同时展示主线行为与目标增量。 + +### fills_mainline_gap + +主线确认缺少目标能力。必须完成全仓搜索和调用链检查,避免漏掉不同目录中的等价实现。 + +### regresses_mainline + +目标会删除、绕过或破坏主线已有行为。必须引用 baseline 与目标 Diff 的具体差异。 ## 1. depends_on @@ -16,6 +34,8 @@ - 两条 PR 的 head/base 明显形成链条 - 下游代码直接引用上游新增符号 +如果上游能力已经存在于主线,关系应写为 `extends_mainline` 或 `inherits_from`,不能继续把 merged PR 当作未满足的硬依赖。 + ## 2. overlaps_with 适用场景: @@ -44,6 +64,8 @@ - 更强 PR 同时补齐测试、文档、兼容性 - 较弱 PR 长期未更新,而较强 PR 已响应 review 并继续演进 +对 merged PR 使用 `supersedes` 时要谨慎:已合入能力不能“关闭”,动作应是说明目标替换或升级哪部分主线实现,并评估兼容迁移。 + ## 4. conflicts_with 适用场景: @@ -71,6 +93,16 @@ - 同一目录、同一组件、同一命令族 - 目标互补而非互斥 +## 5.1 complements + +适用场景: + +- 一条提供基础信号或 API,另一条把它用于上层工作流 +- 目标不同、文件可以不重叠,但组合后形成完整用户能力 +- 单独评审仍可进行,联合评审能确认接口和命名是否一致 + +`complements` 不自动等于 `depends_on`。只有下游无法在当前主线独立工作时才同时标记依赖。 + ## 6. merge_after 适用场景: @@ -83,6 +115,16 @@ - 上游 PR 改的是底层封装,下游 PR 改的是调用层 - 先合并下游会造成明显返工 +## 7. inherits_from + +适用场景: + +- 目标沿用某条 merged PR 引入的架构、命令或证据结构 +- 目标吸收 open PR 的设计但已经自带所需实现,不构成硬依赖 +- Git 历史、符号来源或正文可以定位明确演进链 + +输出时说明继承了什么、目标新增了什么,不能把历史来源误写成当前阻断。 + ## 低置信措辞 证据不足时,使用保守措辞: diff --git a/skills/gitlink-shared/references/maintenance-report-contract.md b/skills/gitlink-shared/references/maintenance-report-contract.md index 1178682..d65784b 100644 --- a/skills/gitlink-shared/references/maintenance-report-contract.md +++ b/skills/gitlink-shared/references/maintenance-report-contract.md @@ -2,13 +2,27 @@ 五个维护专项 Skill 和一个编排 Skill 统一遵循本协议。目标是让维护者在 30 秒内知道“先处理什么、为什么、下一步由谁做”,同时保留可追溯的证据。 -## 默认调用与单报告落盘 +## 默认调用、聊天结论与证据落盘 用户点名某个 Skill 并给出可确定的仓库、PR 或本地改动范围后,该 Skill 必须自行应用只读边界、专项职责、严重性、首屏格式和保存规则。不得要求用户重复粘贴“不要调用其他 Skill”“不要修改远端”“关键结论放前面”等固定提示词。 五个专项 Skill 默认独立运行,不自动调用其他 Skill;只有 `gitlink-maintenance-orchestrator` 可以编排五个专项。目标无法从用户输入或当前 Git remote 唯一确定时才请求补充。 -每次运行只生成一份主要人读 Markdown: +每次运行必须产生两种互补呈现,内容结论必须一致: + +1. **聊天结论**:在最终回复中直接给维护者可阅读的执行摘要,不能只返回“报告已保存”或一个文件路径。 +2. **证据报告**:将完整分析保存为一份主要人读 Markdown,作为复查、协作和答辩留存。 + +只要本轮包含 PR,聊天结论和 Markdown 首屏都必须先按 PR 分组,再按专项方面输出判断卡。不得把多个 PR 合并成一个总段落,也不得把多个方面压成一段连续叙述。聊天可以比报告更短,但不能删除方面结论和主要依据;聊天与报告的事实、编号和最终决策必须一致。 + +聊天摘要不是报告卡片的机械裁剪。执行 Skill 的 Agent 必须在读完完整报告后重新提炼维护者 +语言:按 PR 和方面用一至两句完整自然语言说明结论、最关键依据和解释/影响,省略路径清单、 +分页过程和次要证据。除最终报告链接外,聊天不输出 HTML、Markdown 展示标签或机器状态标签; +详细依据保留在 Markdown。Markdown 校验通过不代表聊天交付通过,禁止复制完整卡片、机械 +删除格式或输出被省略号截断的半句话。报告应通过当前 Agent 平台支持的可点击链接或文件附件 +交付,而不是只给裸路径。 + +Markdown 路径: ```text reports/skill-runs//---.md @@ -16,13 +30,46 @@ reports/skill-runs//---/final-report.md`。多个 PR 放在同一份报告内,但每个 PR 的证据、发现和决策必须独立;原始 API、完整 Diff、阶段 JSON 和机器证据放附件,不再生成多份互相重复的人读报告。 -Markdown 使用 UTF-8、无 ANSI,并在最终回复中给出绝对路径。无法落盘时输出完整 Markdown 并明确标记“未落盘”,不能只返回聊天摘要。 +Markdown 使用 UTF-8、无 ANSI,并在聊天结论后给出绝对路径。无法落盘时输出完整 Markdown 并明确标记“未落盘”。聊天结论不能省略,Markdown 也不能被聊天摘要替代。 + +## 解释性判断卡 + +首屏和聊天结论不能只列 `价值 passed`、`测试 failed` 等状态词。每个会影响决策的方面必须使用独立一行判断卡,回答三件事: + +1. **结论是什么**:字段开头先给醒目的粗体/颜色结论和纯文本状态回退,让维护者快速定位结果。 +2. **看到了什么**:结论后立即说明 PR 实际增加或改变了什么,队列或契约出现了什么事实。 +3. **如何判断**:引用 Diff、默认分支、Review、测试、时间戳或其他可核验证据,说明采用了什么比较标准。 + +统一结构: + +```markdown +## PR # +**价值:** 价值成立 **[passed]**:增加 open 队列 SLA 与责任方识别;依据:默认分支差异、命令帮助和受益范围;影响:减少维护者手工判断。 +**实现:** 核心结果不可靠 **[failed]**:真实响应中的关闭项仍进入 open 队列;依据:真实 API fixture 与复现命令;下一步:修复客户端二次过滤并补回归测试。 +``` + +固定顺序是“方面标签 → 醒目加粗结论 → 状态回退 → 简短解释 → `依据:` → 影响或下一步”。最直接的结论必须位于每张卡最前面,不能先写一段事实再让维护者到句尾找结论。每张卡至少包含明确的 `依据:`,并尽量控制在一行或两个短句内;完整证据放后文。 + +状态矩阵可以作为快速索引保留,但必须放在逐 PR 判断卡之后,不能代替判断依据。多个 PR 分别生成判断卡;不得把一条 PR 的证据用于另一条。最终总决策放在该 PR 全部方面之后,并使用粗体和颜色突出。 + +### 各 Skill 的默认方面 + +- `gitlink-code-review`:Review 建议、贡献价值、Review 履约、实现与逻辑、测试、安全、关键发现。 +- `gitlink-cli-contract-guard`:参数与帮助、JSON/文本输出、错误与退出码、编码与颜色、兼容与文档、契约结论。 +- `gitlink-pr-topology`:对主线、对 open 队列、对 merged 历史、关系判断、处理建议。 +- `gitlink-pr-integrator`:贡献价值、合并态、构建、测试、契约、安全与发布、集成结论。 +- `gitlink-maintainer-radar`:响应 SLA、等待方、Reviewer 负载、责任停滞、安全运营优先级、维护动作。 +- `gitlink-maintenance-orchestrator`:代码审查、CLI 契约、仓库关系、集成门禁、维护状态、最终结论。 + +没有相关改动的方面仍保留判断卡并写 `not_applicable` 及依据;证据不可用时写 `not_run/not_verifiable` 和缺失项,不能直接省略。 + +默认 open 队列必须按响应中的真实状态做客户端二次过滤。用户显式提供 closed/merged PR 时,只把它作为历史对照并注明状态,不纳入当前待审数量,也不建议重新打开,除非用户明确要求复审历史 PR。 ## 默认输出层级 默认生成 `executive` 模式;用户明确要求细节时再生成 `standard` 或 `full`。 -1. **执行摘要**:使用颜色、粗体和纯文本标签突出结论、阻断数、高风险数、安全门禁、验证状态和扫描范围。 +1. **执行摘要**:先用解释性判断卡说明事实、依据和判断,再使用颜色、粗体和纯文本标签突出最终结论、阻断数、高风险数、安全门禁、验证状态和扫描范围。 2. **今日动作**:最多 5 项,按优先级排序;每项必须包含对象、责任方、下一动作和证据引用。 3. **证据附录**:完整发现、命令输出摘要、文件/行号、时间戳和未验证项。 @@ -83,7 +130,7 @@ Markdown 使用 HTML 颜色和粗体,同时必须提供纯 Markdown 回退, 通过 **[pass]** ``` -颜色只用于结论、严重性、门禁和动作,不要给整段正文着色。JSON、CSV 和命令管道输出禁止包含 ANSI 转义、HTML 标签或 emoji;使用纯字段值。 +颜色只用于每项判断的最终结论、总决策、严重性和关键动作,不要给事实与依据整段着色。JSON、CSV 和命令管道输出禁止包含 ANSI 转义、HTML 标签或 emoji;使用纯字段值。 在支持终端颜色时,可以根据 `NO_COLOR` 约定关闭 ANSI 颜色。报告落盘默认不写 ANSI。 @@ -118,11 +165,14 @@ Markdown 使用 HTML 颜色和粗体,同时必须提供纯 Markdown 回退, $json | ConvertFrom-Json | Out-Null if ($json -match "`e\[|") { throw "JSON 含展示层标记" } -# Markdown 使用 UTF-8 保存,并检查替换字符 -$markdown | Set-Content .\maintenance-report.md -Encoding utf8 -$bytes = [IO.File]::ReadAllBytes('.\maintenance-report.md') -$text = [Text.Encoding]::UTF8.GetString($bytes) -if ($text.Contains([char]0xfffd) -or $text.Contains('?')) { throw "报告存在编码风险" } +# Markdown 必须由支持显式 UTF-8 的文件 API 保存;平台提供补丁式文件工具时优先使用。 +# 不要在 Windows PowerShell 5.1 中把中文 here-string、变量或命令输出通过管道 +# 交给 Set-Content/Out-File。保存后使用严格 UTF-8 回读并执行逐 PR 卡片校验。 +python -X utf8 skills/gitlink-shared/scripts/validate_pr_cards.py ` + --report .\maintenance-report.md ` + --require-pr ` + --min-cards ``` -最后一条检查只针对报告中预期的中文文本;如果业务数据本身包含问号,应改为检查 UTF-8 替换字符和已知乱码片段,并记录例外。 +校验器拒绝 UTF-8 替换字符、NUL、已知乱码片段、连续问号、中文叙述不足、缺少 PR 分节、 +结论未前置和缺少明确依据。单个业务问号允许保留,不能用“删除所有问号”掩盖编码损坏。 diff --git a/skills/gitlink-shared/scripts/validate_pr_cards.py b/skills/gitlink-shared/scripts/validate_pr_cards.py new file mode 100644 index 0000000..850f887 --- /dev/null +++ b/skills/gitlink-shared/scripts/validate_pr_cards.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Validate UTF-8 Markdown reports that summarize one or more PRs.""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + + +PR_HEADING = re.compile(r"^## PR #(\d+)(?:\s.*)?$") +CARD_PATTERN = re.compile( + r"^\*\*([^*\n]+):\*\*\s*" + r"]*>([^<]+)\s*" + r"\*\*\[([^\]]+)\]\*\*:\s*(\S.*)$" +) +MOJIBAKE_PATTERNS = ("\ufffd", "\x00", "\x1b") + + +def parse_pr_sections(lines: list[str]) -> dict[int, list[str]]: + sections: dict[int, list[str]] = {} + current: int | None = None + for line in lines: + match = PR_HEADING.match(line.strip()) + if match: + current = int(match.group(1)) + sections.setdefault(current, []) + continue + if current is not None and line.startswith("## "): + current = None + elif current is not None: + sections[current].append(line) + return sections + + +def validate_report( + text: str, + required_prs: list[int] | None = None, + min_cards: int = 2, + required_aspects: list[str] | None = None, +) -> list[str]: + errors: list[str] = [] + for marker in MOJIBAKE_PATTERNS: + if marker in text: + errors.append(f"report contains invalid encoding marker: {marker!r}") + if re.search(r"\?{4,}", text): + errors.append("report contains repeated question marks indicating encoding loss") + if len(re.findall(r"[\u3400-\u9fff]", text)) < 40: + errors.append("report does not contain enough readable Chinese narrative") + + sections = parse_pr_sections(text.splitlines()) + targets = required_prs or sorted(sections) + if not targets: + return errors + ["report contains no '## PR #' section"] + for number in targets: + if number not in sections: + errors.append(f"missing PR section: #{number}") + continue + cards = [] + for line in sections[number]: + match = CARD_PATTERN.match(line.strip()) + if not match: + continue + aspect, conclusion, status, rationale = match.groups() + cards.append((aspect, conclusion, status, rationale)) + if len(rationale) < 20: + errors.append(f"PR #{number} aspect '{aspect}' explanation is too short") + if "依据:" not in rationale: + errors.append(f"PR #{number} aspect '{aspect}' is missing explicit evidence") + if any(token.startswith("<") and token.endswith(">") for token in (conclusion, status)): + errors.append(f"PR #{number} aspect '{aspect}' contains a placeholder") + if len(cards) < min_cards: + errors.append(f"PR #{number} has {len(cards)} valid aspect card(s), need at least {min_cards}") + aspects = [card[0] for card in cards] + if len(set(aspects)) != len(aspects): + errors.append(f"PR #{number} contains duplicate aspect cards") + for aspect in required_aspects or []: + if aspect not in aspects: + errors.append(f"PR #{number} is missing required aspect card: {aspect}") + return errors + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate per-PR aspect cards.") + parser.add_argument("--report", required=True, type=Path) + parser.add_argument("--require-pr", action="append", type=int, default=[]) + parser.add_argument("--min-cards", type=int, default=2) + parser.add_argument("--required-aspect", action="append", default=[]) + args = parser.parse_args() + try: + text = args.report.read_text(encoding="utf-8", errors="strict") + except (OSError, UnicodeError) as exc: + print(f"PR card validation failed: {exc}", file=sys.stderr) + return 2 + errors = validate_report(text, args.require_pr, args.min_cards, args.required_aspect) + if errors: + print("PR card validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + print("PR card validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())