feat(skills): 扩展编排器综合评估维度

This commit is contained in:
Mengz 2026-07-26 16:55:51 +08:00
parent 017e18f3d1
commit 808aa8438e
15 changed files with 389 additions and 46 deletions

View File

@ -5,7 +5,7 @@ description: "五个 GitLink 维护 Skill 的只读编排器:用户只需点
# GitLink 维护审查编排器
这个 Skill 只负责编排和校验,不替代五个专项 Skill 的判断。它把“证据采集、三路并行专项检查、集成门禁、维护者排序、首屏报告”组织成一次可复现的只读运行,并保留每个专项的独立结果,便于维护者快速处理而不是阅读五份重复长报告
这个 Skill 负责编排、交叉校验和综合决策,不替代五个专项 Skill 的原始判断。它把“证据采集、三路并行专项检查、集成门禁、维护者排序、跨专项一致性判断、综合报告”组织成一次可复现的只读运行,并保留每个专项的大部分评估维度。执行前必须读取 [`references/comprehensive-dimensions.md`](references/comprehensive-dimensions.md)
## 五个 Skill 的职责
@ -28,41 +28,54 @@ description: "五个 GitLink 维护 Skill 的只读编排器:用户只需点
- 共享一次队列快照和每个目标 PR 的固定 head 上下文,编排五个专项 Skill不要求用户逐条重复五个 Skill 的提示词。
- 全流程只读,不评论、不 approve、不合并、不关闭、不分配、不修改标签或权限。
- 单 PR 和多 PR 都支持;多 PR 先做队列级拓扑/维护排序,再对重点 PR 逐条做代码、契约和集成检查,不能混合 Diff。
- 报告首屏先按 PR 分节;每条 PR 分别显示代码审查、CLI 契约、仓库关系、集成门禁、维护状态和最终结论六张判断卡。聊天结论也按 PR 和这六个方面组织,但必须重新提炼成更短的维护者语言
- 报告按 PR 分节完整展示代码与贡献、CLI 契约、仓库关系、集成门禁、维护治理五组专项维度,再展示证据一致性、风险传播、动作责任和置信度等编排器独有判断。不得用五张阶段总评代替完整维度
- 每张卡先显示醒目加粗结论,再写简短解释、明确 `依据:` 和影响/下一步;不得把五个专项压成一段或把多个 PR 合并总结。
- 人读 `final-report.md` 必须使用中文叙述;命令名、路径、稳定编号和机器状态枚举可以保留英文。每个阶段的 `assessment.fact``assessment.basis` 在交给 finalize 前先转成忠实的中文摘要,不能把整份英文阶段报告直接拼入。
- 一次运行只生成一份主要人读报告 `<run-directory>/final-report.md`。五个专项 JSON 和 `final-report.json` 作为机器证据附件,不再让维护者阅读五份独立长 Markdown。
最终回复必须由执行 Skill 的 Agent 在读完 `final-report.md``final-report.json` 后重新提炼,不能复制报告首屏、不能机械删除 Markdown/HTML 格式、不能把卡片原文改成纯文本后直接输出。聊天摘要要比报告更短,按每个 PR 输出六个方面的结论、最关键依据和解释/影响,每个方面一至两句完整自然语言;省略路径清单、阶段索引、分页过程、机器状态标签和次要证据。全部 PR 摘要后再给最多五项跨专项动作,并用可点击链接或当前 Agent 平台的文件附件交付 `final-report.md`。若无法落盘,输出完整 Markdown 并标记“未落盘”。
最终回复必须由执行 Skill 的 Agent 在读完 `final-report.md``final-report.json` 后重新提炼,不能复制报告首屏、不能机械删除 Markdown/HTML 格式、不能把卡片原文改成纯文本后直接输出。聊天摘要要比报告短,但每个 PR 必须覆盖 [`references/comprehensive-dimensions.md`](references/comprehensive-dimensions.md) 规定的 15 项直接判断,每项一至两句,包含结论、最关键依据和影响。省略路径清单、阶段索引、分页过程、机器状态标签和次要证据。全部 PR 摘要后再给最多五项跨专项动作,并用可点击链接或当前 Agent 平台的文件附件交付 `final-report.md`。若无法落盘,输出完整 Markdown 并标记“未落盘”。
聊天摘要示例只表达风格,不是可复制模板:
```text
PR #430
代码审查:建议修改后再审。它补齐维护队列的真实状态过滤,但当前 fixture 仍显示 closed 项会进入 open 队列,这会直接误导维护者的待审范围。
CLI 契约:需要先稳定字段语义。新增 changes、waiting_on 和 SLA 字段有实际价值,但空值、兼容回退和错误提示还要和主线帮助/JSON 输出对齐。
仓库关系:适合排在 #431 前面评审。#431 会消费 #430 的队列字段,所以先确认 #430 的运行时契约,可以减少后续 Skill 协议返工。
集成门禁:暂时不能进入合并队列。构建和基础测试可以作为正向证据,但真实队列过滤失败仍是集成前必须修复的问题。
维护状态:当前主要等待作者修复。维护者最需要检查的是过滤逻辑、字段空值和回归测试是否补齐。
最终结论:先修复再复审。这个 PR 的方向成立,但真实队列可信度是它能否合入的前提。
最终建议:修改后再审。功能方向成立,但真实队列过滤失败会直接误导维护者。
贡献价值与功能增量:补齐队列变化和等待责任信号,能减少人工比对;主线没有等价完整实现。
Review 履约:当前没有足够的正式 Review 基线,不能判断作者是否已经完成全部修改要求。
逻辑、质量与可维护性:主流程沿用现有架构,但 open 状态二次过滤存在错误,需要修复后再评价稳定性。
测试、安全与性能:基础测试通过,真实状态、权限失败和大队列边界仍需补测;未发现已证实的安全阻断。
参数、帮助与兼容:新增参数具有价值,旧调用默认行为需要继续保持。
JSON、错误、编码与文档字段类型基本稳定但空值语义和错误原因不能被统一误报为 JSON 解析失败。
主线与 merged 历史关系:属于已有 workflow 队列能力的增量演进,不是重复实现。
open PR 重叠、依赖与处理顺序:与消费这些字段的下游 PR 互补,应先稳定本 PR 契约。
合并态、构建与冲突:基础构建成立,仍需在最新主线合并工作树确认无冲突。
测试、CI、安全与发布门禁当前 head 的 CI 关联和失败路径验证不完整,暂不能进入合并队列。
SLA、等待方与 Reviewer 负载:当前等待作者修复;完整 reviewer 负载需要仓库级快照。
责任停滞、Issue 和安全运营优先级:修复责任明确,超过阈值后应升级复看提醒。
证据完整性与跨专项一致性:五个阶段使用同一 head但 CI 和 Review 证据仍有缺口;各专项结论没有无法解释的冲突。
优先动作、责任方与复验条件:作者先修复过滤并补失败测试,维护者随后重跑契约和集成门禁。
完整报告:[final-report.md](D:\path\to\final-report.md)
```
最终聊天不能只回复路径、状态计数或跨 PR 总段落。Markdown 能使用醒目颜色和加粗;聊天摘要除必要的 PR 编号、证据编号和最终报告链接外,不输出 HTML/Markdown 展示标签。
最终聊天不能只回复路径、状态计数、五个阶段状态或跨 PR 总段落。Markdown 能使用醒目颜色和加粗;聊天摘要除必要的 PR 编号、证据编号和最终报告链接外,不输出 HTML/Markdown 展示标签。
首屏固定先使用:
报告中的每个维度固定使用以下卡片语法;必须重复覆盖完整维度表,不能只生成示例中的几项
```markdown
# PR 维护全流程摘要
## PR #<number>
**代码审查:** <span style="color:#B42318"><strong>核心队列结果不可靠</strong></span> **[action_required]**closed 项进入 open 响应依据CR-001 与真实 fixture下一步修复过滤并补测试。
**CLI 契约:** <span style="color:#067647"><strong>新增字段保持向后兼容</strong></span> **[passed]**字段可选且旧调用不变依据CG 对照和 golden 输出;影响:现有脚本无需迁移。
**仓库关系:** <span style="color:#175CD3"><strong>需要先稳定上游契约</strong></span> **[reorder]**:存在字段生产/消费关系;依据:主线、全部 open/merged PR 索引;影响:调整评审顺序。
**集成门禁:** <span style="color:#B42318"><strong>当前不能进入合并队列</strong></span> **[blocked]**真实响应测试失败依据IN-001 和当前 head 验证;下一步:修复后重跑。
**维护状态:** <span style="color:#B54708"><strong>等待作者修复后复看</strong></span> **[action_required]**责任链明确依据Review、提交和 SLA 快照;下一步:作者更新后通知 reviewer。
**最终结论:** <span style="color:#B42318"><strong>修复真实状态过滤后重新审查</strong></span> **[blocked]**存在一个阻断和两个高风险项依据CR-001、IN-001、MR-001下一步按动作顺序处理。
### 代码审查
**贡献价值:** <span style="color:#067647"><strong>高频维护价值成立</strong></span> **[merge]**:补齐队列变化信号;依据:主线缺口和用户流程;证据摘录:需求与 Diff影响减少人工比对。
**逻辑正确性:** <span style="color:#B42318"><strong>真实状态过滤不可靠</strong></span> **[action_required]**closed 项进入 open 响应依据CR-001 和真实 fixture证据摘录失败输出影响修复并补测试。
### CLI 契约
**JSON 与文本输出:** <span style="color:#B54708"><strong>空值语义尚未稳定</strong></span> **[observe]**新增字段可选但错误回退不完整依据golden 对照;证据摘录:结构化输出;影响:稳定后再供下游消费。
### 编排器综合判断
**证据完整性与新鲜度:** <span style="color:#B54708"><strong>CI 和 Review 证据不完整</strong></span> **[observe]**:五阶段 head 一致但两类证据缺失依据运行账本证据摘录collection manifest影响不能给出高置信度合并结论。
**最终结论:** <span style="color:#B42318"><strong>修复真实状态过滤后重新审查</strong></span> **[blocked]**存在一个阻断和两个高风险项依据CR-001、IN-001、MR-001证据摘录失败 fixture 与门禁结果;下一步:按动作顺序处理。
## 先处理这 3 项
@ -71,7 +84,7 @@ CLI 契约:需要先稳定字段语义。新增 changes、waiting_on 和 SLA
3. <span style="color:#B54708"><strong>[MR-001][high] 转派</strong></span> 安全复查责任maintainer。
```
多个 PR 重复完整六张卡,每张卡只能引用该 PR 的证据。跨 PR 动作放在全部 PR 卡片之后。
多个 PR 重复完整综合维度矩阵,每张卡只能引用该 PR 的证据。跨 PR 动作放在全部 PR 卡片之后。
## 编排流程
@ -151,7 +164,7 @@ pr-topology.json
}
```
每条 `evidence` 不能只有 `E-xxx`、`CR-xxx` 等编号,至少还要提供 `summary`、`source`、`ref`、`status` 和可选的 `scope`。`summary` 必须说明实际观察到什么,例如“正常路径测试通过但无权限路径未覆盖”,不能只重复结论。编排器会把这些字段直接写入六张判断卡、待办和证据台账;没有可读证据时必须写明限制,而不是只显示编号。
每条 `evidence` 不能只有 `E-xxx`、`CR-xxx` 等编号,至少还要提供 `summary`、`source`、`ref`、`status` 和可选的 `scope`。`summary` 必须说明实际观察到什么,例如“正常路径测试通过但无权限路径未覆盖”,不能只重复结论。编排器会把这些字段直接写入综合维度卡片、待办和证据台账;没有可读证据时必须写明限制,而不是只显示编号。
`top_actions.evidence``findings.evidence` 可以引用证据或发现 ID 以便追溯,但阶段结果必须同时保留对应的可读 `evidence` 对象。编排器在渲染时必须将发现 ID 展开为问题摘要和直接证据,不能在待办或最终结论中只显示 `CR-001`、`E-001` 等标识。
@ -173,11 +186,10 @@ pr-topology.json
- 只有拓扑关系需要调整顺序时:最终决策可为 `reorder`
- 没有阻断项但存在维护动作时:显示 `action_required`,不自动回写远端。
### 5. 生成维护者首屏
### 5. 生成完整综合报告
最终报告必须先按目标 PR 展示六张判断卡顺序固定为代码审查、CLI 契约、仓库关系、
集成门禁、维护状态、最终结论。每张卡先给醒目结论,再写当前 PR 的事实、`依据:`
以及影响或下一步;不能把五个阶段压缩成一段总体叙述。全部目标 PR 的判断卡展示完后,
最终报告必须按目标 PR 展示 [`references/comprehensive-dimensions.md`](references/comprehensive-dimensions.md) 中五个专项的完整维度,再展示编排器独有判断和最终结论。每张卡先给醒目结论,再写当前 PR 的事实、`依据:`
以及影响或下一步;不能把五个阶段压缩成五张总评。全部目标 PR 的判断卡展示完后,
再给最多五项跨专项待办,每项包含对象、责任方、下一动作、严重性和一个主证据。
阻断数、高风险数、安全门禁和验证状态可以作为卡片之后的索引,不能替代解释。
完整 findings、可读证据台账、限制和下一次复查条件放入附录或 JSON。证据编号仅用于追溯不能替代证据摘要维护者不打开 JSON 也应能在 Markdown 中看到观察事实、来源、命令或文件位置、状态和适用范围。
@ -209,7 +221,7 @@ python -X utf8 .\skills\gitlink-maintenance-orchestrator\scripts\validate_chines
--require-pr 123
```
多 PR 报告为每个目标重复 `--require-pr`。任何目标缺少完整六张判断卡、卡片没有明确
多 PR 报告为每个目标重复 `--require-pr`。任何目标缺少完整综合维度、卡片没有明确
`依据:`、中文被写成连续问号或报告退化为英文模板时,校验失败且不得交付路径。
`collect` 只执行 `gitlink-cli` 的读操作,不发布评论、不添加标签、不分配 reviewer、不关闭或合并 PR。若 PowerShell 禁止执行 `gitlink-cli.ps1`,传入可执行的 `gitlink-cli.exe``gitlink-cli.cmd``-CliPath`

View File

@ -1,4 +1,4 @@
interface:
display_name: "GitLink 维护审查编排器"
short_description: "编排五个维护 Skill 生成可验证的 PR 维护摘要"
default_prompt: "使用 $gitlink-maintenance-orchestrator 分析指定仓库或一个/多个 PR完整证据保存为中文 Markdown聊天结论由执行 Agent 读完报告后重新提炼,按 PR 分别概括代码审查、CLI 契约、仓库关系、集成门禁、维护状态和最终结论,每方面说明结论、关键依据与影响,不复制报告卡片原文、不输出 HTML/Markdown 展示标签,全程只读。"
short_description: "编排五个维护 Skill 生成全面、可验证的 PR 综合报告"
default_prompt: "使用 $gitlink-maintenance-orchestrator 分析指定仓库或一个/多个 PR完整证据保存为中文 Markdown报告覆盖五个专项的大部分评估维度以及证据一致性、风险传播、责任动作和决策置信度。聊天结论由执行 Agent 读完报告后重新提炼,按 PR 输出 15 项简明判断,每项说明结论、关键依据与影响,不复制报告卡片原文、不输出 HTML/Markdown 展示标签,全程只读。"

View File

@ -12,6 +12,13 @@
"fact": "新增命令保持原有参数、帮助、JSON 类型和错误语义,未观察到用户可感知的契约破坏",
"basis": "对默认分支与当前 head 执行相同帮助和结构化输出测试,契约测试完整通过"
},
"dimensions": [
{"aspect": "参数与帮助", "conclusion": "新增入口保持旧调用兼容", "decision": "merge", "fact": "新参数为可选项且默认行为不变", "basis": "baseline 与 head 帮助对照", "evidence": ["E-CG-001"], "impact": "现有用户无需迁移"},
{"aspect": "JSON 与文本输出", "conclusion": "机器输出结构稳定", "decision": "merge", "fact": "新增字段未改变既有字段类型,文本没有混入 JSON", "basis": "结构化输出解析测试", "evidence": ["E-CG-001"], "impact": "自动化脚本可继续解析"},
{"aspect": "错误与退出码", "conclusion": "错误语义保持可区分", "decision": "merge", "fact": "参数错误与远端失败仍返回不同错误路径", "basis": "失败命令与退出码对照", "evidence": ["E-CG-001"], "impact": "调用方无需修改判断逻辑"},
{"aspect": "编码与颜色", "conclusion": "UTF-8 和机器输出边界正常", "decision": "merge", "fact": "中文输出可严格解码JSON 不包含 ANSI", "basis": "原始字节和 JSON 解析验证", "evidence": ["E-CG-001"], "impact": "跨平台输出风险较低"},
{"aspect": "兼容与文档", "conclusion": "文档与当前行为一致", "decision": "merge", "fact": "帮助和示例覆盖新增入口及默认行为", "basis": "文档、帮助和运行结果对照", "evidence": ["E-CG-001"], "impact": "用户可按现有文档使用"}
],
"findings": [],
"top_actions": [],
"evidence": [

View File

@ -12,6 +12,14 @@
"fact": "PR 为示例命令增加批量处理能力,但错误路径缺少回归测试,核心失败行为尚未被证明可靠",
"basis": "对照默认分支与当前 Diff并执行 go test ./shortcuts/example正常路径通过失败路径覆盖不完整"
},
"dimensions": [
{"aspect": "贡献价值", "conclusion": "批量处理能力具有明确使用价值", "decision": "merge", "fact": "PR 减少重复命令调用并覆盖常用维护流程", "basis": "对照默认分支功能入口与 PR 描述", "evidence": ["E-CR-001"], "impact": "功能方向可以保留"},
{"aspect": "Review 履约", "conclusion": "本轮没有可核对的正式 Review", "decision": "observe", "fact": "当前证据没有包含有效 Review 意见与对应提交", "basis": "Review 证据未采集完整", "evidence": ["E-CR-001"], "impact": "不能判断作者是否完成 Review 修改"},
{"aspect": "逻辑正确性", "conclusion": "失败路径仍不可靠", "decision": "action_required", "fact": "正常路径可用,但远端失败和非法输入没有完整行为证据", "basis": "当前 Diff 与专项测试结果", "evidence": ["E-CR-001"], "impact": "修复错误处理后复验"},
{"aspect": "代码质量与可维护性", "conclusion": "主流程结构清晰", "decision": "merge", "fact": "批量入口沿用现有 shortcut 结构,未发现明显重复实现", "basis": "目录职责和调用链对照", "evidence": ["E-CR-001"], "impact": "当前结构不阻断合并"},
{"aspect": "测试覆盖", "conclusion": "失败和权限场景缺测", "decision": "action_required", "fact": "成功场景通过,但无权限和非法路径没有回归测试", "basis": "测试文件与执行命令对照", "evidence": ["E-CR-001"], "impact": "补齐失败路径后重跑"},
{"aspect": "安全与性能", "conclusion": "未发现已证实的安全或性能阻断", "decision": "observe", "fact": "现有范围未发现凭据泄露和无界批量循环,但恶意输入未完整验证", "basis": "Diff 安全热点和测试范围", "evidence": ["E-CR-001"], "impact": "保持观察并补输入边界测试"}
],
"findings": [
{"id": "CR-001", "severity": "high", "status": "open", "summary": "错误路径缺少回归测试", "evidence": ["E-CR-001"], "related_ids": []}
],

View File

@ -12,6 +12,13 @@
"fact": "PR #123 已有明确修复动作但仍等待作者补测,继续占用维护者复看队列",
"basis": "队列快照显示 waiting_on=author当前 head 和 Review 后续动作尚未变化"
},
"dimensions": [
{"aspect": "响应 SLA", "conclusion": "当前复看等待正在累积", "decision": "action_required", "fact": "作者补测尚未完成PR 继续占用复看队列", "basis": "队列时间与状态快照", "evidence": ["E-MR-001"], "impact": "修复后应及时通知 reviewer"},
{"aspect": "当前等待方", "conclusion": "当前明确等待作者", "decision": "action_required", "fact": "waiting_on=author 且修复动作已指向 CR-001", "basis": "队列字段和技术动作关联", "evidence": ["E-MR-001"], "impact": "维护者暂不重复 Review"},
{"aspect": "Reviewer 负载", "conclusion": "本次没有足够数据判断负载平衡", "decision": "observe", "fact": "fixture 只包含目标 PR没有完整 reviewer 队列", "basis": "扫描范围限制", "evidence": ["E-MR-001"], "impact": "需要仓库级快照后再调度"},
{"aspect": "责任停滞", "conclusion": "责任链清晰但尚未推进", "decision": "action_required", "fact": "作者有明确补测任务,当前 head 尚未变化", "basis": "waiting_on 与 head 快照", "evidence": ["E-MR-001"], "impact": "超过阈值后升级维护提醒"},
{"aspect": "安全与 Issue 优先级", "conclusion": "没有已证实的安全 HOT 事项", "decision": "observe", "fact": "当前高优先级来自测试缺口而非已确认漏洞", "basis": "安全门禁和 finding 分类", "evidence": ["E-MR-001"], "impact": "保持技术修复优先,不误报安全漏洞"}
],
"findings": [],
"top_actions": [
{"id": "MR-001", "owner": "maintainer", "severity": "medium", "action": "安排维护者复看 PR #123当前等待作者补测", "evidence": ["queue:pr-123"]}

View File

@ -12,6 +12,14 @@
"fact": "贡献方向与仓库需求一致,但代码审查发现的失败路径缺口尚未修复,当前实现不能安全进入合并队列",
"basis": "当前 head 的基础构建可执行,但 CR-001 未解决且全量集成验证只完成部分门禁"
},
"dimensions": [
{"aspect": "合并态与冲突", "conclusion": "尚未完成最新主线合并验证", "decision": "observe", "fact": "当前 fixture 没有最新主线合并工作树结果", "basis": "合并态证据缺失", "evidence": ["E-IN-001"], "impact": "合并前必须在最新主线复验"},
{"aspect": "构建", "conclusion": "基础构建可执行", "decision": "merge", "fact": "当前 head 在规定环境完成基础构建", "basis": "本地工作树构建结果", "evidence": ["E-IN-001"], "impact": "构建不构成当前阻断"},
{"aspect": "测试与功能一致性", "conclusion": "PR 声明仅部分得到验证", "decision": "action_required", "fact": "成功路径成立,但错误路径与描述中的可靠性目标不一致", "basis": "专项测试和 CR-001", "evidence": ["E-IN-001"], "impact": "补测并修复后重跑"},
{"aspect": "CI 与证据关联", "conclusion": "CI 证据未完整关联当前 head", "decision": "observe", "fact": "fixture 没有提供可核对的 head SHA 构建记录", "basis": "CI 证据关联字段缺失", "evidence": ["E-IN-001"], "impact": "不能把历史构建当作当前通过"},
{"aspect": "安全与发布影响", "conclusion": "没有已证实的发布阻断", "decision": "observe", "fact": "安全门禁通过,但失败路径和回滚证据不完整", "basis": "安全状态与限制列表", "evidence": ["E-IN-001"], "impact": "补齐验证后确认发布风险"},
{"aspect": "集成结论", "conclusion": "当前不能进入合并队列", "decision": "action_required", "fact": "CR-001 未解决且合并态、CI 和全量测试不完整", "basis": "代码审查与集成证据汇总", "evidence": ["E-IN-001"], "impact": "完成高优先级动作后重新评估"}
],
"findings": [],
"top_actions": [
{"id": "IN-001", "owner": "author", "severity": "high", "action": "修复 CR-001 后重新执行合并态验证", "evidence": ["CR-001"]}

View File

@ -12,6 +12,13 @@
"fact": "PR #123 与 #124 修改同一命令入口,功能不完全重复,但合并顺序可能改变最终输出契约",
"basis": "比较两条 PR 的文件集合、队列快照和目标行为后确认存在共享入口,尚无证据判定其中一条完全替代另一条"
},
"dimensions": [
{"aspect": "对当前主线", "conclusion": "目标是对主线能力的增量扩展", "decision": "merge", "fact": "主线已有基础入口,但没有本 PR 的批量行为", "basis": "默认分支源码与目标 Diff 对照", "evidence": ["E-TP-001"], "impact": "不存在主线完全覆盖"},
{"aspect": "对 open 队列", "conclusion": "与 #124 存在共享入口", "decision": "reorder", "fact": "两条 open PR 修改同一命令入口但目标不同", "basis": "完整 open 队列和文件语义比较", "evidence": ["E-TP-001"], "impact": "需要联合评审接口边界"},
{"aspect": "对 merged 历史", "conclusion": "继承既有 shortcut 架构", "decision": "merge", "fact": "已合入历史提供基础命令框架,但没有等价批量实现", "basis": "merged PR 索引与主线提交历史", "evidence": ["E-TP-001"], "impact": "应按增量演进评审"},
{"aspect": "完整性比较", "conclusion": "目标与 #124 互补而非替代", "decision": "observe", "fact": "两条 PR 的覆盖范围和验证重点不同", "basis": "功能范围、测试和文件路径比较", "evidence": ["E-TP-001"], "impact": "不建议仅按文件交集择一"},
{"aspect": "依赖与处理顺序", "conclusion": "先稳定共享入口再合入下游", "decision": "reorder", "fact": "合并顺序可能改变最终命令和输出契约", "basis": "入口生产与消费关系", "evidence": ["E-TP-001"], "impact": "先确认接口再处理后续 PR"}
],
"findings": [
{"id": "TP-001", "severity": "medium", "status": "open", "summary": "与 PR #124 修改同一命令入口,建议合并顺序复核", "evidence": ["E-TP-001"], "related_ids": ["PR-124"]}
],

View File

@ -0,0 +1,87 @@
# 综合维护评估维度
总报告必须覆盖下列六组维度。专项阶段应逐项写入 `dimensions`;没有证据时保留该项并标记 `observe`、`partial` 或 `not_run`,不能省略。
## 1. 代码与贡献
| 维度 | 最低判断内容 |
|---|---|
| 贡献价值 | 真实问题、受益对象、相对主线增量、维护成本 |
| Review 履约 | 有效 Review、对应提交、修改是否满足要求、剩余动作 |
| 逻辑正确性 | 正常、失败、边界、状态一致性和错误处理 |
| 代码质量与可维护性 | 架构一致性、职责、复杂度、重复、长期维护成本 |
| 测试覆盖 | 单元、集成、回归、失败和跨平台场景 |
| 安全与性能 | 权限、注入、路径、凭据、依赖、资源和性能退化 |
## 2. CLI 契约
| 维度 | 最低判断内容 |
|---|---|
| 参数与帮助 | 命令层级、flag、别名、默认值、帮助与示例 |
| JSON 与文本输出 | 字段、类型、可选性、人读与机读隔离 |
| 错误与退出码 | 参数、认证、权限、远端和解析失败语义 |
| 编码与颜色 | UTF-8、乱码、ANSI、`NO_COLOR` 和跨平台 |
| 兼容与文档 | 旧调用、迁移成本、README 和帮助一致性 |
## 3. 仓库关系
| 维度 | 最低判断内容 |
|---|---|
| 对当前主线 | 已实现、补缺、扩展、重复或回归 |
| 对 open 队列 | 依赖、竞争、重叠、互补、冲突和联审对象 |
| 对 merged 历史 | 继承来源、已合入等价实现和演进关系 |
| 完整性比较 | 重叠实现的范围、测试、架构适配和维护成本 |
| 依赖与处理顺序 | 硬依赖、顺序依赖、合入次序和解除条件 |
## 4. 集成门禁
| 维度 | 最低判断内容 |
|---|---|
| 合并态与冲突 | 最新主线合并、冲突文件和基线 SHA |
| 构建 | 仓库规定环境、构建命令、结果和限制 |
| 测试与功能一致性 | PR 声明、实际行为、全量/专项测试和失败路径 |
| CI 与证据关联 | head SHA、分支回退、未关联构建和时效 |
| 安全与发布影响 | 安全门禁、依赖、迁移、发布和回滚 |
| 集成结论 | 是否进入 merge queue、阻塞项和复验条件 |
## 5. 维护治理
| 维度 | 最低判断内容 |
|---|---|
| 响应 SLA | 首响、Review、复看和合并等待时间 |
| 当前等待方 | 作者、reviewer、maintainer、外部依赖或未知 |
| Reviewer 负载 | 分配数量、瓶颈和可释放容量 |
| 责任停滞 | 已分配但无进展、requested changes 后未更新 |
| 安全与 Issue 优先级 | 安全运营信号、关联 Issue 和队列优先级 |
## 6. 编排器独有判断
| 维度 | 最低判断内容 |
|---|---|
| 证据完整性与新鲜度 | 五阶段齐全、run/head/as_of 一致、缺失和过期证据 |
| 跨专项结论一致性 | 结论是否冲突,冲突如何按证据和门禁解释 |
| 风险传播与门禁对齐 | CR/CG/TP 风险如何影响 IN 和 MR |
| 动作去重与责任归属 | 重复动作合并、责任方、先后顺序和解除条件 |
| 决策置信度 | 覆盖率、限制、未验证项和结论可信程度 |
## 聊天直接输出
聊天摘要不能把总报告压缩成五个阶段状态。每个 PR 至少输出以下 15 项简明判断,每项一至两句,包含结论、最关键依据和影响:
1. 最终建议
2. 贡献价值与功能增量
3. Review 履约
4. 逻辑、质量与可维护性
5. 测试、安全与性能
6. 参数、帮助与兼容
7. JSON、错误、编码与文档
8. 主线与 merged 历史关系
9. open PR 重叠、依赖与处理顺序
10. 合并态、构建与冲突
11. 测试、CI、安全与发布门禁
12. SLA、等待方与 Reviewer 负载
13. 责任停滞、Issue 和安全运营优先级
14. 证据完整性与跨专项一致性
15. 优先动作、责任方与复验条件
多个 PR 分别输出,不能共用结论。聊天不复制 Markdown 卡片、HTML 标签、阶段索引、分页过程或完整证据台账;完整内容通过报告链接交付。

View File

@ -46,6 +46,17 @@
"fact": "核心成功路径可用,但远端失败没有回归证据",
"basis": "当前 head 的 Diff、专项测试和失败路径测试清单"
},
"dimensions": [
{
"aspect": "逻辑正确性",
"conclusion": "失败路径仍不可靠",
"decision": "action_required",
"fact": "远端失败没有稳定错误映射",
"basis": "当前 Diff 与失败路径测试",
"evidence": ["E-CR-001"],
"impact": "修复并补测后复验"
}
],
"findings": [],
"top_actions": [],
"evidence": [],
@ -59,6 +70,11 @@
`action_required`、`reorder`、`observe`、`blocked`。阶段之间不得篡改其他 Skill 的
finding只通过 `related_ids` 关联。
`dimensions` 必须覆盖该阶段在
[`comprehensive-dimensions.md`](comprehensive-dimensions.md) 中列出的全部维度。每个维度必须有 `aspect`、`conclusion`、`decision`、`fact`、`basis`、`evidence` 和 `impact`。没有证据时仍保留维度,并明确写 `not_run`、`partial` 或 `observe`;禁止用阶段级 `assessment` 代替全部维度。
编排器在五阶段之后额外生成“证据完整性与新鲜度、跨专项结论一致性、风险传播与门禁对齐、动作去重与责任归属、决策置信度”五项判断。这些判断属于编排器,不得伪装成某个专项 finding。
## 最终决策规则
按以下顺序计算最终决策:
@ -75,6 +91,7 @@ finding只通过 `related_ids` 关联。
## 首屏压缩规则
- 每个 PR 先展示五个专项的完整维度矩阵,再展示编排器独有判断和最终结论。
- 首屏最多五项待办,按 `blocking > high > medium > low`、再按责任等待方和证据置信度排序。
- 同一对象、同一动作、同一责任方的重复项合并;保留全部 `source_ids` 供追溯。
- 首屏每项只展示一个主证据,完整证据放到 JSON 或附录。

View File

@ -141,6 +141,68 @@ function Resolve-EvidenceText {
return ($items -join '')
}
function Get-RequiredDimensions {
param([string]$Producer)
switch ($Producer) {
'gitlink-code-review' {
return @('贡献价值', 'Review 履约', '逻辑正确性', '代码质量与可维护性', '测试覆盖', '安全与性能')
}
'gitlink-cli-contract-guard' {
return @('参数与帮助', 'JSON 与文本输出', '错误与退出码', '编码与颜色', '兼容与文档')
}
'gitlink-pr-topology' {
return @('对当前主线', '对 open 队列', '对 merged 历史', '完整性比较', '依赖与处理顺序')
}
'gitlink-pr-integrator' {
return @('合并态与冲突', '构建', '测试与功能一致性', 'CI 与证据关联', '安全与发布影响', '集成结论')
}
'gitlink-maintainer-radar' {
return @('响应 SLA', '当前等待方', 'Reviewer 负载', '责任停滞', '安全与 Issue 优先级')
}
default { return @('专项判断') }
}
}
function Get-StageDimensions {
param([string]$Producer, [object]$Artifact)
$provided = @()
$assessment = $null
$stageEvidence = @()
if ($null -ne $Artifact) {
$provided = @(Get-Value $Artifact 'dimensions' @())
$assessment = Get-Value $Artifact 'assessment' $null
$stageEvidence = @(Get-Value $Artifact 'evidence' @())
}
$result = New-Object Collections.Generic.List[object]
foreach ($aspect in @(Get-RequiredDimensions $Producer)) {
$item = $provided | Where-Object { [string](Get-Value $_ 'aspect' '') -eq $aspect } | Select-Object -First 1
if ($null -eq $item) {
$stageConclusion = if ($null -ne $assessment) { [string](Get-Value $assessment 'conclusion' '') } else { '' }
$stageBasis = if ($null -ne $assessment) { [string](Get-Value $assessment 'basis' '') } else { '' }
$result.Add([pscustomobject][ordered]@{
aspect = $aspect
conclusion = '该维度证据不足'
decision = 'observe'
fact = "当前 ${Producer} 结果未提供维度 ${aspect} 的独立判断,不能从阶段总评推断为通过"
basis = if ($stageBasis) { "阶段级依据仅为:$stageBasis" } else { '缺少该维度的结构化依据' }
evidence = @($stageEvidence | Select-Object -First 1)
impact = if ($stageConclusion) { "保留阶段总评 ${stageConclusion},补齐本维度后再提高置信度" } else { '补齐本维度证据后重新评估' }
})
continue
}
$result.Add([pscustomobject][ordered]@{
aspect = $aspect
conclusion = [string](Get-Value $item 'conclusion' '该维度尚无明确结论')
decision = [string](Get-Value $item 'decision' 'observe')
fact = [string](Get-Value $item 'fact' '未提供事实摘要')
basis = [string](Get-Value $item 'basis' '未提供判断依据')
evidence = @(Get-Value $item 'evidence' @())
impact = [string](Get-Value $item 'impact' '根据该维度证据决定下一步')
})
}
return $result.ToArray()
}
function Get-StageSummary {
param([string]$Producer, [object]$Artifact)
$findings = @()
@ -192,6 +254,7 @@ function Get-StageSummary {
focus = $focus
basis = $basis
evidence = @($evidence | Select-Object -First 2)
dimensions = @(Get-StageDimensions -Producer $Producer -Artifact $Artifact)
}
}
@ -284,6 +347,73 @@ function Get-FinalDecision {
return 'observe'
}
function New-OrchestratorDimensions {
param(
[bool]$AllComplete,
[object[]]$Summaries,
[object[]]$Actions,
[object]$Counts,
[string]$SecurityGate,
[string]$Verification,
[string]$Decision,
[object[]]$Limitations,
[object[]]$Evidence
)
$stageStates = @($Summaries | ForEach-Object { "$($_.producer)=$($_.status)/$($_.decision)" }) -join ''
$evidenceSample = @($Evidence | Select-Object -First 2)
$missingOwners = @($Actions | Where-Object { [string]::IsNullOrWhiteSpace([string]$_.owner) -or $_.owner -eq '维护者' }).Count
$coverageConclusion = if ($AllComplete -and $Verification -eq 'complete' -and @($Limitations).Count -eq 0) { '五阶段证据完整且时间上下文一致' } else { '证据覆盖或新鲜度仍有限制' }
$coverageDecision = if ($AllComplete -and $Verification -eq 'complete' -and @($Limitations).Count -eq 0) { 'merge' } else { 'observe' }
$confidence = if ($coverageDecision -eq 'merge' -and $SecurityGate -eq 'passed') { '高' } elseif ($AllComplete) { '中' } else { '低' }
return @(
[pscustomobject][ordered]@{
aspect = '证据完整性与新鲜度'
conclusion = $coverageConclusion
decision = $coverageDecision
fact = "五个阶段完成状态为:$stageStates;验证汇总为 $Verification,限制 $(@($Limitations).Count)"
basis = '核对 run_id、as_of、目标 head、阶段状态、验证状态和限制列表'
evidence = $evidenceSample
impact = if ($coverageDecision -eq 'merge') { '可以使用同一快照支撑综合判断' } else { '缺失维度不得推断通过,需要补证或重跑' }
},
[pscustomobject][ordered]@{
aspect = '跨专项结论一致性'
conclusion = '五个专项结论已按职责和门禁优先级收敛'
decision = if ($AllComplete) { 'merge' } else { 'observe' }
fact = "阶段决策为:$stageStates;不同专项的 merge、reorder 或 action_required 代表不同决策对象,不自动视为冲突"
basis = '比较五阶段 decision、finding 关联和集成门禁优先级'
evidence = $evidenceSample
impact = '无法解释的结论冲突必须阻止最终合并建议'
},
[pscustomobject][ordered]@{
aspect = '风险传播与门禁对齐'
conclusion = if ($Decision -eq 'merge') { '专项风险未形成合并阻断' } else { '专项风险已传递到最终门禁' }
decision = $Decision
fact = "当前阻断 $($Counts.blocking) 项、高风险 $($Counts.high) 项,安全门禁 $SecurityGate,最终决策 $Decision"
basis = '按 blocking、安全失败、集成器、拓扑顺序和维护 HOT 动作计算'
evidence = $evidenceSample
impact = '高优先级技术风险不能被维护排序或正向价值结论覆盖'
},
[pscustomobject][ordered]@{
aspect = '动作去重与责任归属'
conclusion = if (@($Actions).Count -eq 0) { '当前没有需要立即执行的跨专项动作' } else { "已收敛为 $(@($Actions).Count) 项跨专项动作" }
decision = if (@($Actions).Count -eq 0) { 'merge' } else { 'action_required' }
fact = "动作按对象、内容和责任方去重;责任不明确 $missingOwners"
basis = '汇总五阶段 top_actions并按严重性、责任方和动作内容排序'
evidence = $evidenceSample
impact = '维护者可以按统一责任链处理,不必在五份报告间重复查找'
},
[pscustomobject][ordered]@{
aspect = '决策置信度'
conclusion = "当前综合决策置信度为$confidence"
decision = if ($confidence -eq '高') { 'merge' } else { 'observe' }
fact = "置信度由阶段覆盖、验证完整性、安全门禁和限制数量共同决定;最终决策为 $Decision"
basis = '不以单一测试通过或单个专项结论替代完整证据覆盖'
evidence = $evidenceSample
impact = if ($confidence -eq '高') { '可将综合结论作为维护者决策依据' } else { '维护者应先处理限制和未验证项' }
}
)
}
function New-FinalReport {
param([string]$Path)
$run = Read-JsonFile (Join-Path $Path 'run.json')
@ -298,7 +428,7 @@ function New-FinalReport {
$artifact = $artifacts[$entry.Key]
if ($null -eq $artifact) {
$allComplete = $false
$summaries.Add([ordered]@{ producer = $entry.Key; status = 'not_run'; decision = 'blocked'; security_gate = 'not_run'; verification = 'not_run'; finding_count = 0; blocking_count = 0; high_count = 0; top_action_count = 0 })
$summaries.Add([ordered]@{ producer = $entry.Key; status = 'not_run'; decision = 'blocked'; security_gate = 'not_run'; verification = 'not_run'; finding_count = 0; blocking_count = 0; high_count = 0; top_action_count = 0; conclusion = '阶段未运行'; focus = '缺少专项结果'; basis = '运行目录中没有对应阶段 JSON'; evidence = @(); dimensions = @(Get-StageDimensions -Producer $entry.Key -Artifact $null) })
$limitations.Add("缺少阶段结果:$($entry.Key)")
continue
}
@ -343,6 +473,7 @@ function New-FinalReport {
$findingArray = [object[]]$findings
$evidenceArray = [object[]]$evidence
$limitationArray = Get-UniqueStrings $limitations
$orchestratorDimensions = New-OrchestratorDimensions -AllComplete $allComplete -Summaries $stageArray -Actions ([object[]]$actions) -Counts $counts -SecurityGate $securityGate -Verification $verification -Decision $decision -Limitations $limitationArray -Evidence $evidenceArray
$report = [ordered]@{
schema_version = '1.0'
producer = 'gitlink-maintenance-orchestrator'
@ -355,6 +486,7 @@ function New-FinalReport {
scope = $scope
run = $run
stages = $stageArray
orchestrator_dimensions = @($orchestratorDimensions)
top_actions = @($topActions)
findings = $findingArray
evidence = $evidenceArray
@ -454,7 +586,19 @@ function Write-MarkdownReport {
$aspect = Get-StageAspect ([string]$stage.producer)
$impact = Get-DecisionImpact ([string]$stage.decision)
$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")
$lines.Add('')
$lines.Add("### $aspect")
$lines.Add("**专项总评:** $(Get-ConclusionLabel ([string]$stage.conclusion) ([string]$stage.decision))$($stage.focus);依据:$($stage.basis);证据摘录:$evidenceText;影响:$impact")
foreach ($dimension in @($stage.dimensions)) {
$dimensionEvidence = Resolve-EvidenceText -EvidenceValues @($dimension.evidence) -EvidenceIndex $evidenceIndex -FindingIndex $findingIndex
$lines.Add("**$($dimension.aspect)** $(Get-ConclusionLabel ([string]$dimension.conclusion) ([string]$dimension.decision))$($dimension.fact);依据:$($dimension.basis);证据摘录:$dimensionEvidence;影响:$($dimension.impact)")
}
}
$lines.Add('')
$lines.Add('### 编排器综合判断')
foreach ($dimension in @($Report.orchestrator_dimensions)) {
$dimensionEvidence = Resolve-EvidenceText -EvidenceValues @($dimension.evidence) -EvidenceIndex $evidenceIndex -FindingIndex $findingIndex
$lines.Add("**$($dimension.aspect)** $(Get-ConclusionLabel ([string]$dimension.conclusion) ([string]$dimension.decision))$($dimension.fact);依据:$($dimension.basis);证据摘录:$dimensionEvidence;影响:$($dimension.impact)")
}
$finalReason = if (@($Report.top_actions).Count -gt 0) {
[string]$Report.top_actions[0].action

View File

@ -28,7 +28,8 @@ try {
$decisionPattern = '<strong>[^<]+</strong></span> \*\*\[(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' }
$preActionDecisions = @($decisions | Where-Object { $_.Index -lt $actionsIndex })
if ($actionsIndex -lt 0 -or $preActionDecisions.Count -lt 33) { throw 'comprehensive dimensions and final conclusion must appear before actions' }
& python -X utf8 $orchestratorPromptTests | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'orchestrator prompt contract tests failed' }

View File

@ -15,7 +15,8 @@ def test_chat_summary_requires_agent_synthesis() -> None:
"重新提炼",
"不能复制报告首屏",
"不能机械删除 Markdown/HTML 格式",
"每个方面一至两句完整自然语言",
"15 项直接判断",
"每项一至两句",
"可点击链接或当前 Agent 平台的文件附件",
]
missing = [marker for marker in required_markers if marker not in content]
@ -25,6 +26,6 @@ def test_chat_summary_requires_agent_synthesis() -> None:
def test_default_agent_prompt_requires_a_plain_summary() -> None:
content = read_utf8(ROOT / "agents" / "openai.yaml")
required_markers = ["重新提炼", "不复制报告卡片原文", "不输出 HTML/Markdown 展示标签"]
required_markers = ["重新提炼", "15 项", "不复制报告卡片原文", "不输出 HTML/Markdown 展示标签"]
missing = [marker for marker in required_markers if marker not in content]
assert not missing, f"missing agent prompt markers: {missing}"

View File

@ -2,21 +2,24 @@
import unittest
from validate_chinese_report import validate_report
from validate_chinese_report import REQUIRED_ASPECTS, REQUIRED_GROUPS, validate_report
class ChineseReportValidatorTests(unittest.TestCase):
def test_accepts_complete_chinese_report(self) -> None:
report = """# PR 维护全流程摘要
groups = "\n".join(REQUIRED_GROUPS)
cards = "\n".join(
f"**{aspect}** <span><strong>证据支持当前判断</strong></span> "
f"**[observe]**:已完成 {aspect} 的专项分析;依据:当前 Diff、测试和只读平台快照"
"证据摘录:目标提交和验证命令均已记录;影响:维护者可据此决定下一步。"
for aspect in REQUIRED_ASPECTS
)
report = f"""# PR 维护全流程摘要
**范围** Gitlink/gitlink-cli
**风险** 阻断 1 高风险 2 以下内容用于帮助维护者快速确认处理顺序和责任人
## PR #123
**代码审查** <span><strong>需要修改</strong></span> **[action_required]**失败路径缺少覆盖依据当前 Diff 与专项测试证据摘录无权限测试未覆盖来源`shortcuts/example/example_test.go:42`下一步补回归测试
**CLI 契约** <span><strong>兼容性部分成立</strong></span> **[partial]**旧调用正常但 JSON 缺边界依据帮助和输出对照证据摘录`--help` 可用但 JSON golden 缺少非法参数样例下一步 golden
**仓库关系** <span><strong>需要调整顺序</strong></span> **[reorder]**目标消费上游字段依据主线open merged 索引证据摘录open PR #124 修改同一字段;影响:先稳定上游。
**集成门禁** <span><strong>当前被阻断</strong></span> **[blocked]**关键测试未完成依据合并态和验证账本证据摘录`go test ./...` 尚未在合并工作树执行下一步补测试
**维护状态** <span><strong>需要维护者接单</strong></span> **[action_required]**责任方尚未确认依据时间和分配快照证据摘录队列快照中的 reviewer assignee 均为空下一步安排 reviewer
**最终结论** <span><strong>修复阻断问题后重新审查</strong></span> **[blocked]**存在未解决高风险项依据CR-001 IN-001证据摘录失败路径缺测试且全量验证未运行下一步修复并重跑
{groups}
{cards}
## 先处理这几项
先修复真实响应错误再补充测试然后重新执行完整验证并由维护者复看
## 五个专项状态索引

View File

@ -20,12 +20,47 @@ REQUIRED_MARKERS = (
"## 完整证据与限制",
"## 证据台账",
)
REQUIRED_GROUPS = (
"### 代码审查",
"### CLI 契约",
"### 仓库关系",
"### 集成门禁",
"### 维护状态",
"### 编排器综合判断",
)
REQUIRED_ASPECTS = (
"代码审查",
"CLI 契约",
"仓库关系",
"集成门禁",
"维护状态",
"贡献价值",
"Review 履约",
"逻辑正确性",
"代码质量与可维护性",
"测试覆盖",
"安全与性能",
"参数与帮助",
"JSON 与文本输出",
"错误与退出码",
"编码与颜色",
"兼容与文档",
"对当前主线",
"对 open 队列",
"对 merged 历史",
"完整性比较",
"依赖与处理顺序",
"合并态与冲突",
"构建",
"测试与功能一致性",
"CI 与证据关联",
"安全与发布影响",
"集成结论",
"响应 SLA",
"当前等待方",
"Reviewer 负载",
"责任停滞",
"安全与 Issue 优先级",
"证据完整性与新鲜度",
"跨专项结论一致性",
"风险传播与门禁对齐",
"动作去重与责任归属",
"决策置信度",
"最终结论",
)
FORBIDDEN_ENGLISH_TEMPLATES = (
@ -74,10 +109,16 @@ def validate_report(text: str, required_prs: list[int] | None = None) -> list[st
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])
section = text[start:end]
for group in REQUIRED_GROUPS:
if group not in section:
errors.append(f"PR #{number} is missing assessment group: {group}")
cards = CARD_PATTERN.findall(section)
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")
errors.append(
f"PR #{number} has {len(cards)} judgment cards, need {len(REQUIRED_ASPECTS)}"
)
for aspect in REQUIRED_ASPECTS:
if aspect not in aspects:
errors.append(f"PR #{number} is missing aspect card: {aspect}")