From 017e18f3d1635739e11f896ce31271d4e37b8f30 Mon Sep 17 00:00:00 2001
From: Mengz <2567587994@qq.com>
Date: Sun, 26 Jul 2026 11:59:58 +0800
Subject: [PATCH] =?UTF-8?q?fix(skills):=20=E5=B1=95=E5=BC=80=E7=BC=96?=
=?UTF-8?q?=E6=8E=92=E6=8A=A5=E5=91=8A=E4=B8=AD=E7=9A=84=E8=AF=81=E6=8D=AE?=
=?UTF-8?q?=E6=91=98=E8=A6=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../gitlink-maintenance-orchestrator/SKILL.md | 6 +-
.../examples/fixtures/code-review.json | 2 +-
.../scripts/run-maintenance-pipeline.ps1 | 74 ++++++++++++++++++-
.../scripts/test_validate_chinese_report.py | 39 ++++++++--
.../scripts/validate_chinese_report.py | 5 ++
5 files changed, 113 insertions(+), 13 deletions(-)
diff --git a/skills/gitlink-maintenance-orchestrator/SKILL.md b/skills/gitlink-maintenance-orchestrator/SKILL.md
index 680710a..b239665 100644
--- a/skills/gitlink-maintenance-orchestrator/SKILL.md
+++ b/skills/gitlink-maintenance-orchestrator/SKILL.md
@@ -151,6 +151,10 @@ pr-topology.json
}
```
+每条 `evidence` 不能只有 `E-xxx`、`CR-xxx` 等编号,至少还要提供 `summary`、`source`、`ref`、`status` 和可选的 `scope`。`summary` 必须说明实际观察到什么,例如“正常路径测试通过但无权限路径未覆盖”,不能只重复结论。编排器会把这些字段直接写入六张判断卡、待办和证据台账;没有可读证据时必须写明限制,而不是只显示编号。
+
+`top_actions.evidence` 和 `findings.evidence` 可以引用证据或发现 ID 以便追溯,但阶段结果必须同时保留对应的可读 `evidence` 对象。编排器在渲染时必须将发现 ID 展开为问题摘要和直接证据,不能在待办或最终结论中只显示 `CR-001`、`E-001` 等标识。
+
`conclusion` 必须是可独立阅读的专项判断,不能只写“通过”“观察”或“需要处理”;
`fact` 和 `basis` 必须针对当前目标,不能复制状态词。兼容旧阶段结果时,编排器可从
`fact` 的第一条事实生成结论,并从首条 finding/action 和 evidence 生成降级说明,但必须
@@ -176,7 +180,7 @@ pr-topology.json
以及影响或下一步;不能把五个阶段压缩成一段总体叙述。全部目标 PR 的判断卡展示完后,
再给最多五项跨专项待办,每项包含对象、责任方、下一动作、严重性和一个主证据。
阻断数、高风险数、安全门禁和验证状态可以作为卡片之后的索引,不能替代解释。
-完整 findings、证据台账、限制和下一次复查条件放入附录或 JSON。
+完整 findings、可读证据台账、限制和下一次复查条件放入附录或 JSON。证据编号仅用于追溯,不能替代证据摘要;维护者不打开 JSON 也应能在 Markdown 中看到观察事实、来源、命令或文件位置、状态和适用范围。
Markdown 使用醒目的颜色和加粗,同时保留 `[blocking]`、`[high]`、`[pass]` 等纯文本回退;JSON 不得包含 HTML、ANSI 或颜色控制符。推荐颜色:blocking `#B42318`、high `#B54708`、pass `#067647`、observe `#175CD3`。
diff --git a/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json b/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json
index 9f8d2ce..dd0e3b9 100644
--- a/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json
+++ b/skills/gitlink-maintenance-orchestrator/examples/fixtures/code-review.json
@@ -19,7 +19,7 @@
{"id": "CR-001", "owner": "author", "severity": "high", "action": "补充错误路径回归测试", "evidence": ["shortcuts/example/example_test.go:42"]}
],
"evidence": [
- {"id": "E-CR-001", "kind": "test_output", "source": "local_worktree", "status": "partial", "ref": "go test ./shortcuts/example", "scope": "head:abc1234"}
+ {"id": "E-CR-001", "kind": "test_output", "summary": "示例命令的正常路径测试通过,但无权限和非法路径的失败场景没有覆盖", "source": "local_worktree", "status": "partial", "ref": "go test ./shortcuts/example", "scope": "head:abc1234"}
],
"limitations": ["主线合并态尚未验证"]
}
diff --git a/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1 b/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1
index 94f2f3a..dbf33f2 100644
--- a/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1
+++ b/skills/gitlink-maintenance-orchestrator/scripts/run-maintenance-pipeline.ps1
@@ -95,6 +95,52 @@ function Get-DisplaySeverity {
return $Severity.ToLowerInvariant()
}
+function Format-EvidenceItem {
+ param([object]$Evidence)
+ if ($null -eq $Evidence) { return '' }
+ $id = [string](Get-Value $Evidence 'id' '')
+ $summary = [string](Get-Value $Evidence 'summary' '')
+ if ([string]::IsNullOrWhiteSpace($summary)) { $summary = [string](Get-Value $Evidence 'observed' '') }
+ if ([string]::IsNullOrWhiteSpace($summary)) { $summary = [string](Get-Value $Evidence 'description' '') }
+ $source = [string](Get-Value $Evidence 'source' '未知来源')
+ $reference = [string](Get-Value $Evidence 'ref' '未提供位置或命令')
+ $status = [string](Get-Value $Evidence 'status' 'unknown')
+ $scope = [string](Get-Value $Evidence 'scope' '')
+ if ([string]::IsNullOrWhiteSpace($summary)) {
+ $summary = "已采集 $source 的 $reference"
+ }
+ $scopeText = if ([string]::IsNullOrWhiteSpace($scope)) { '' } else { ";范围 $scope" }
+ $idText = if ([string]::IsNullOrWhiteSpace($id)) { '' } else { "[$id] " }
+ return "$idText$summary(来源:$source;位置或命令:$reference;状态:$status$scopeText)"
+}
+
+function Resolve-EvidenceText {
+ param([object[]]$EvidenceValues, [hashtable]$EvidenceIndex, [hashtable]$FindingIndex = @{}, [int]$Maximum = 2)
+ $items = New-Object Collections.Generic.List[string]
+ foreach ($value in @($EvidenceValues)) {
+ if ($items.Count -ge $Maximum -or $null -eq $value) { continue }
+ if ($value -is [string]) {
+ $key = [string]$value
+ if ($EvidenceIndex.ContainsKey($key)) {
+ $items.Add((Format-EvidenceItem $EvidenceIndex[$key]))
+ } elseif ($FindingIndex.ContainsKey($key)) {
+ $finding = $FindingIndex[$key]
+ $summary = [string](Get-Value $finding 'summary' '未提供问题摘要')
+ $findingEvidence = @(Get-Value $finding 'evidence' @())
+ $resolvedFindingEvidence = Resolve-EvidenceText -EvidenceValues $findingEvidence -EvidenceIndex $EvidenceIndex -FindingIndex @{} -Maximum 1
+ $items.Add("问题 $key:$summary;直接证据:$resolvedFindingEvidence")
+ } else {
+ # A stage may point directly at a file, command, or API field rather than an evidence object.
+ $items.Add("观察位置或命令:$key")
+ }
+ continue
+ }
+ $items.Add((Format-EvidenceItem $value))
+ }
+ if ($items.Count -eq 0) { return '未采集到可直接展示的专项证据,结论已降级处理。' }
+ return ($items -join ';')
+}
+
function Get-StageSummary {
param([string]$Producer, [object]$Artifact)
$findings = @()
@@ -145,6 +191,7 @@ function Get-StageSummary {
conclusion = $conclusion
focus = $focus
basis = $basis
+ evidence = @($evidence | Select-Object -First 2)
}
}
@@ -385,6 +432,16 @@ function Get-DecisionImpact {
function Write-MarkdownReport {
param([string]$Path, [object]$Report)
$lines = New-Object Collections.Generic.List[string]
+ $evidenceIndex = @{}
+ foreach ($item in @($Report.evidence)) {
+ $id = [string](Get-Value $item 'id' '')
+ if (-not [string]::IsNullOrWhiteSpace($id)) { $evidenceIndex[$id] = $item }
+ }
+ $findingIndex = @{}
+ foreach ($item in @($Report.findings)) {
+ $id = [string](Get-Value $item 'id' '')
+ if (-not [string]::IsNullOrWhiteSpace($id)) { $findingIndex[$id] = $item }
+ }
$lines.Add('# PR 维护全流程摘要')
$lines.Add('')
$lines.Add("**范围:** $($Report.scope.owner)/$($Report.scope.repo) | **运行时间:** $($Report.run.as_of) | **运行 ID:** ``$($Report.run.run_id)``")
@@ -396,7 +453,8 @@ function Write-MarkdownReport {
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。")
+ $evidenceText = Resolve-EvidenceText -EvidenceValues @($stage.evidence) -EvidenceIndex $evidenceIndex -FindingIndex $findingIndex
+ $lines.Add("**${aspect}:** $(Get-ConclusionLabel ([string]$stage.conclusion) ([string]$stage.decision)):$($stage.focus);依据:$($stage.basis);证据摘录:$evidenceText;影响:$impact。")
}
$finalReason = if (@($Report.top_actions).Count -gt 0) {
[string]$Report.top_actions[0].action
@@ -405,7 +463,8 @@ function Write-MarkdownReport {
}
$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。")
+ $finalEvidence = if (@($Report.top_actions).Count -gt 0) { Resolve-EvidenceText -EvidenceValues @($Report.top_actions[0].evidence) -EvidenceIndex $evidenceIndex -FindingIndex $findingIndex -Maximum 1 } else { '没有高优先级动作,按各专项证据继续观察。' }
+ $lines.Add("**最终结论:** $(Get-ConclusionLabel $finalConclusion ([string]$Report.decision)):该动作决定当前集成状态;依据:阻断 $($Report.counts.blocking) 项、高风险 $($Report.counts.high) 项,安全门禁 ``$($Report.security_gate)``、验证 ``$($Report.verification)``;证据摘录:$finalEvidence;下一步:$finalNext。")
$lines.Add('')
$lines.Add('## 先处理这几项')
if (@($Report.top_actions).Count -eq 0) {
@@ -414,8 +473,8 @@ function Write-MarkdownReport {
$index = 0
foreach ($action in @($Report.top_actions)) {
$index++
- $evidenceText = if (@($action.evidence).Count -gt 0) { ";证据:``$($action.evidence[0])``" } else { '' }
- $lines.Add("$index. **[$($action.id)]** $(Get-ColorLabel $action.severity) $($action.action)(责任:$($action.owner))$evidenceText")
+ $evidenceText = Resolve-EvidenceText -EvidenceValues @($action.evidence) -EvidenceIndex $evidenceIndex -FindingIndex $findingIndex -Maximum 1
+ $lines.Add("$index. **[$($action.id)]** $(Get-ColorLabel $action.severity) $($action.action)(责任:$($action.owner));证据:$evidenceText")
}
}
$lines.Add('')
@@ -428,6 +487,13 @@ function Write-MarkdownReport {
$lines.Add('')
$lines.Add('## 完整证据与限制')
if (@($Report.limitations).Count -gt 0) { foreach ($item in @($Report.limitations)) { $lines.Add("- 限制:$item") } } else { $lines.Add('- 未发现额外限制。') }
+ $lines.Add('')
+ $lines.Add('## 证据台账')
+ if (@($Report.evidence).Count -eq 0) {
+ $lines.Add('- 未采集到专项证据;所有依赖该证据的结论应视为受限。')
+ } else {
+ foreach ($item in @($Report.evidence)) { $lines.Add("- $(Format-EvidenceItem $item)") }
+ }
$lines.Add("- 详细 JSON:``final-report.json``;各专项原始结果保存在同一运行目录。")
$lines.Add('- 颜色仅用于首屏强调;方括号严重性标签可在不支持 HTML 的渲染器中继续阅读。')
Write-Utf8Text -Path (Join-Path $Path 'final-report.md') -Text ($lines -join "`r`n")
diff --git a/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py b/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py
index 15422eb..24e0351 100644
--- a/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py
+++ b/skills/gitlink-maintenance-orchestrator/scripts/test_validate_chinese_report.py
@@ -11,24 +11,49 @@ class ChineseReportValidatorTests(unittest.TestCase):
**范围:** 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;下一步:修复并重跑。
+**代码审查:** 需要修改 **[action_required]**:失败路径缺少覆盖;依据:当前 Diff 与专项测试;证据摘录:无权限测试未覆盖,来源:`shortcuts/example/example_test.go:42`;下一步:补回归测试。
+**CLI 契约:** 兼容性部分成立 **[partial]**:旧调用正常但 JSON 缺边界;依据:帮助和输出对照;证据摘录:`--help` 可用但 JSON golden 缺少非法参数样例;下一步:补 golden。
+**仓库关系:** 需要调整顺序 **[reorder]**:目标消费上游字段;依据:主线、open 和 merged 索引;证据摘录:open PR #124 修改同一字段;影响:先稳定上游。
+**集成门禁:** 当前被阻断 **[blocked]**:关键测试未完成;依据:合并态和验证账本;证据摘录:`go test ./...` 尚未在合并工作树执行;下一步:补测试。
+**维护状态:** 需要维护者接单 **[action_required]**:责任方尚未确认;依据:时间和分配快照;证据摘录:队列快照中的 reviewer 与 assignee 均为空;下一步:安排 reviewer。
+**最终结论:** 修复阻断问题后重新审查 **[blocked]**:存在未解决高风险项;依据:CR-001 与 IN-001;证据摘录:失败路径缺测试且全量验证未运行;下一步:修复并重跑。
## 先处理这几项
先修复真实响应错误,再补充测试,然后重新执行完整验证并由维护者复看。
## 五个专项状态索引
gitlink-code-review | gitlink-cli-contract-guard | gitlink-pr-topology | gitlink-pr-integrator | gitlink-maintainer-radar
## 完整证据与限制
-当前结论来自固定时间快照、目标提交差异、构建测试和只读平台数据;未验证内容已明确标记。"""
+当前结论来自固定时间快照、目标提交差异、构建测试和只读平台数据;未验证内容已明确标记。
+## 证据台账
+- [E-001] 无权限路径未覆盖(来源:本地测试;位置或命令:`shortcuts/example/example_test.go:42`;状态:partial)"""
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))
+ def test_rejects_identifier_only_evidence(self) -> None:
+ report = """# PR 维护全流程摘要
+**范围:** Gitlink/gitlink-cli
+**风险:** 阻断 0 项,高风险 1 项。
+## PR #123
+**代码审查:** 需要修改 **[action_required]**:失败路径缺少覆盖;依据:当前 Diff;证据摘录:E-CR-001。
+**CLI 契约:** 兼容 **[passed]**:旧调用可用;依据:帮助;证据摘录:帮助输出正常。
+**仓库关系:** 独立 **[passed]**:无重叠;依据:open 索引;证据摘录:已扫描 open PR。
+**集成门禁:** 待验证 **[partial]**:全量测试未跑;依据:测试账本;证据摘录:测试未运行。
+**维护状态:** 待处理 **[action_required]**:等待作者;依据:队列;证据摘录:等待方为作者。
+**最终结论:** 重新审查 **[action_required]**:需要补测;依据:CR-001;证据摘录:E-CR-001。
+## 先处理这几项
+1. **[CR-001]** 补测试;证据:``E-CR-001``
+## 五个专项状态索引
+已生成。
+## 完整证据与限制
+限制已记录。
+## 证据台账
+- E-CR-001
+"""
+ errors = validate_report(report, [123])
+ self.assertTrue(any("identifier" 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
index 2b00376..87dd867 100644
--- a/skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py
+++ b/skills/gitlink-maintenance-orchestrator/scripts/validate_chinese_report.py
@@ -18,6 +18,7 @@ REQUIRED_MARKERS = (
"## 先处理这几项",
"## 五个专项状态索引",
"## 完整证据与限制",
+ "## 证据台账",
)
REQUIRED_ASPECTS = (
"代码审查",
@@ -85,6 +86,10 @@ def validate_report(text: str, required_prs: list[int] | None = None) -> list[st
errors.append(f"PR #{number} aspect {aspect} is not substantive")
if "依据:" not in rationale:
errors.append(f"PR #{number} aspect {aspect} is missing evidence")
+ if "证据摘录:" not in rationale:
+ errors.append(f"PR #{number} aspect {aspect} does not include readable evidence")
+ if re.search(r"证据:\s*``(?:E|CR|CG|TP|IN|MR)-[^`]+``", text):
+ errors.append("report exposes an evidence identifier without a readable evidence summary")
return errors