@ifrankwang/openspec-agents 0.128.0 → 0.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mcp-server/cli.mjs +583 -155
- package/README.md +1 -1
- package/assets/agents/openspec-developer.md +1 -1
- package/assets/agents/openspec-reviewer.md +2 -1
- package/assets/workflows/task-simple.yaml +11 -10
- package/assets/workflows/task.yaml +1 -1
- package/dsh/cordis.patch.yml +3 -2
- package/package.json +1 -1
- package/src/core/git.ts +90 -1
- package/src/core/tools/gate.ts +5 -0
- package/src/core/tools/lifecycle.ts +63 -22
- package/src/core/tools/schemas.ts +5 -1
- package/src/core/tools/submit.ts +26 -8
- package/src/core/views.ts +21 -0
- package/src/core/workflow/status.ts +41 -13
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- **多工具支持**:Claude Code、Codex、ZCode、DeepSeek Harness(DSH)、OpenCode 均可原生接入。
|
|
13
13
|
- **规范驱动**:直接消费 OpenSpec 变更规范(proposal / design / tasks / specs),流程与规范一一对应。
|
|
14
14
|
- **智能体团队**:不再是单个 AI 从头写到尾,而是「实现者 + 审查者 + 质量把关者」分工协作,结果更稳。
|
|
15
|
-
- **质量门禁**:实现之后经过工具检查、任务验证与多维度质量审查;发现的问题自动回退修复,也可按规则豁免并留痕。
|
|
15
|
+
- **质量门禁**:实现之后经过工具检查、任务验证与多维度质量审查;发现的问题自动回退修复,也可按规则豁免并留痕。simple 模式下验证按性质分流——开发者执行确定性检查并申报,审查者分级复验(低成本项实跑、高成本项核验申报加抽样重放)并专注对抗性判断。
|
|
16
16
|
- **隔离执行**:每个变更在独立的环境中实施,互不干扰;变更完成后合并并清理。
|
|
17
17
|
- **过程可审计**:进度、结论、豁免与恢复点全程记录,随时可以接着上次的进度继续。
|
|
18
18
|
|
|
@@ -11,7 +11,7 @@ permission:
|
|
|
11
11
|
|
|
12
12
|
## 角色(开发者身份)
|
|
13
13
|
|
|
14
|
-
你是后端开发工程师,专注于实现 OpenSpec
|
|
14
|
+
你是后端开发工程师,专注于实现 OpenSpec 任务组中的代码开发任务,同时是质量门与接口测试的执行责任人:确定性检查与接口测试由你执行并经自检申报留痕,自检申报是 reviewer 分级复验的事实输入。
|
|
15
15
|
|
|
16
16
|
提交前按已加载 skill(含质量维度规范类)完成自检,审查兜底与终局裁定仍由对应 reviewer 承担(自检不替代 reviewer 裁定)。
|
|
17
17
|
|
|
@@ -17,7 +17,7 @@ permission:
|
|
|
17
17
|
- **工具维度审查者**(tool review,full 模式三层门禁第一层):执行确定性工具检查,将工具输出映射为统一 issue 结构;简单(simple)模式亦承担该职责
|
|
18
18
|
- **任务维度审查者**(task review,full 模式三层门禁第二层):验证 task 产出完整性、启动服务并检查健康、独立执行 API 测试并审查质量、审查测试代码质量;验证完成后清理本变更启动的环境(停止残留服务、清理隔离环境);简单(simple)模式亦承担该职责
|
|
19
19
|
- **质量维度审查者**(quality review,full 模式三层门禁第三层):按分派维度(style / architecture / performance / security / maintainability)审查
|
|
20
|
-
- **simple
|
|
20
|
+
- **simple 合并审查者**:以分级复验与对抗审查承担 simple 模式合并审查——低成本确定性工具实跑,高成本项核验 dev 自检申报并以核心链路抽样重放验证,对抗性判断(测试代码质量、业务行为真实性、关键数据产出核验)保持独立视角不重走 dev 路径;验证完成后清理本变更启动的环境(停止残留服务、清理隔离环境)
|
|
21
21
|
|
|
22
22
|
审查范围、非本轮问题处置、文档读取范围、既有 issue 去重与工具调用边界以 opx_status 操作指引与约束区块为准,此处不重复描述。
|
|
23
23
|
|
|
@@ -195,6 +195,7 @@ Info 级别 issue 的 description/suggestion 中禁止出现阶段/时机相关
|
|
|
195
195
|
|
|
196
196
|
- **确定性工具检查**:工具维度审查者与 simple 合并审查者被允许运行确定性工具检查(代码格式/架构约束/静态分析/单元测试编译/深度扫描),按已加载质量门 skill 的必做清单执行并将输出映射为统一 issue 结构;检查结果纳入审查结论
|
|
197
197
|
- **任务验证**:任务维度审查者与 simple 合并审查者承担任务验证职责,验证 task 产出完整性、服务健康与 API 测试质量;未通过的 task 经 failed_tasks 上报;验证完成后按清理规范做幂等清理,不留环境残留;数据产出核验——对照 spec 数据产出要求核验关键数据产出落位/完整性/搬迁承接/基线对比,识别『流程正常但数据缺失/落错位置』类缺陷
|
|
198
|
+
- **维度审查锚点**:以 spec 验收标准、行为组与数据产出要求为主锚做对照审查,skill 检查点清单与 dev 自检申报对照兜底(抽查申报遗漏项)
|
|
198
199
|
- **工具改进**:可工具化的质量 pattern 双报业务 issue 与工具改进 issue(规则草案由审查者提供),实施工作由开发者完成,审查者不直接改动规则/配置文件以外的实现
|
|
199
200
|
|
|
200
201
|
## 压制判定
|
|
@@ -32,7 +32,7 @@ phases:
|
|
|
32
32
|
- "实施前识别 spec 描述的一并操作行为组(如状态变更与数据入队这类需同时发生的多个行为);实施后逐项自证每个行为真实生效,禁止以单一状态字段的实现替代其他行为"
|
|
33
33
|
- "涉及数据模型或流程产出变更(含字段/实体承载位置搬迁、AI 生成等流程产物)时,自证须包含关键数据产出断言:产出字段已持久化到 spec 指定承载位置且值完整非空、搬迁承接与同一输入产出基线一致(按已加载接口测试规范执行);禁止仅以流程跑通/接口成功替代"
|
|
34
34
|
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock 服务、消息队列/缓存等中间件、扫描分析平台等一切环境资源均须按隔离标识隔离,禁止复用既有实例或共享端口;启动前发现隔离环境有残留时,先按已加载的清理规范幂等清空再启动"
|
|
35
|
-
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http`
|
|
35
|
+
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http` 脚本)执行接口测试,确保变更内容真实有效;执行结果(附执行顺序与覆盖接口清单)经 test_results 参数提交,作为审查者分级复验的事实输入;修复被驳回 issue 后再次提交前同样须执行"
|
|
36
36
|
- "自证完成后停止本次启动的服务/基础设施实例,不留残留"
|
|
37
37
|
- "环境/基础设施问题(schema 缺失、DDL 未执行、依赖未安装)应通过 migration/初始化脚本等代码层面解决;仅生产级凭据、真实第三方资源、人工运维才走 blocker/豁免"
|
|
38
38
|
- "issue 指向文件的目录已并入执行边界,修复这些文件(含回归引入问题)不算越界;issue 的 file 字段指向 openspec/changes/ 下文档时,按该 issue 要求修复对应文档同样不算越界(仅限该 issue 指定文件,不得扩大范围);可按 issue 的 suggestion 直接执行修复;reviewer 的 boundary_expansion 已并入边界"
|
|
@@ -40,7 +40,7 @@ phases:
|
|
|
40
40
|
- "对修复未过次数≥2 的 issue 须先完成 5-Why 根因分析再动手,不得跳过分析直接改代码"
|
|
41
41
|
- "全部实现完成后先 git commit 再提交"
|
|
42
42
|
- "提交前按已加载的质量门类 skill 完成本地构建验证并核对覆盖率门禁达标后再提交"
|
|
43
|
-
- "提交前完成自检并经 self_check_results
|
|
43
|
+
- "提交前完成自检并经 self_check_results 汇总:每项检查须含执行命令与结果摘要(证据可核验,供审查者分级复验对照与核验申报),禁止仅报结论不附证据"
|
|
44
44
|
- "提交前按已加载的维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次变更文件逐项自检(聚焦本轮变更文件,不扩散全量代码),自检结论与工具检查结果一并汇总进 self_check_results;修复被驳回 issue 后再次提交前同样须按维度规范类 skill 对变更文件自检后再提交"
|
|
45
45
|
- "自检须覆盖已加载的维度规范类 skill 的全部检查点,self_check_results 按维度逐项列结论(通过/遗留及理由),禁止仅汇总工具检查结果"
|
|
46
46
|
- "修复完成的 issue 经 fixed_issue_ids 上报后进入待复核(review)状态,终态由报源 reviewer 复核裁定;不可修 issue 经 exempt_issue_ids 申请豁免(申请后进入待裁定(review)状态,由报源 reviewer 在后续审查轮次裁定,被驳回的 issue 回待修复);Info 级 issue 不阻塞提交,无需申请豁免;全部 blocking issue 均已上报修复或申请豁免后才可 passed 提交推进"
|
|
@@ -66,13 +66,14 @@ phases:
|
|
|
66
66
|
on_pass: done
|
|
67
67
|
on_fail: implement
|
|
68
68
|
instructions:
|
|
69
|
-
- "依据 opx_status 提供的变更检测结果分流:无代码/配置变更且本层无待复核/待裁定项 → 直接提交 passed,不运行全量工具检查,并以一条 step 名首段为 no_change 的 validation_steps 声明本轮整体豁免必做清单(completed=false + 结构化 skip_reason 注明判定依据);无变更但有本层待复核/待裁定项 → 仅复核/裁定待处理项后提交,不跑全量工具扫描;有变更 → 加载质量门类 skill
|
|
70
|
-
- "「有变更」分支先审查上方「本次变更证据」区块的变更文件清单与区间 diff 内容后自行判断:若确认本次变更仅为注释/文档性且无逻辑影响(非注释掉代码、非结构性/格式化变更、非文件重命名),可自行决定跳过全量工具检查直接提交 passed(validation_steps 以 step=no_change
|
|
71
|
-
- "
|
|
72
|
-
- "
|
|
73
|
-
- "
|
|
74
|
-
- "
|
|
75
|
-
- "
|
|
69
|
+
- "依据 opx_status 提供的变更检测结果分流:无代码/配置变更且本层无待复核/待裁定项 → 直接提交 passed,不运行全量工具检查,并以一条 step 名首段为 no_change 的 validation_steps 声明本轮整体豁免必做清单(completed=false + 结构化 skip_reason 注明判定依据);无变更但有本层待复核/待裁定项 → 仅复核/裁定待处理项后提交,不跑全量工具扫描;有变更 → 加载质量门类 skill 获取工具清单与映射规则,按分级复验执行确定性检查:低成本必做项(env/compile/format/architecture/static_analysis/unit_test/config_check 等分钟级项)全量实跑并逐项申报,将工具输出映射为统一 issue 结构;deep_scan 按核验申报口径处理(见下条)"
|
|
70
|
+
- "「有变更」分支先审查上方「本次变更证据」区块的变更文件清单与区间 diff 内容后自行判断:若确认本次变更仅为注释/文档性且无逻辑影响(非注释掉代码、非结构性/格式化变更、非文件重命名),可自行决定跳过全量工具检查直接提交 passed(validation_steps 以 step=no_change 申报整体豁免必做清单);无法确认或含逻辑/结构变更,必须按分级复验口径执行确定性检查;裁量仅免除全量工具扫描,不免除复核/裁定——本层 blocking issue 仍须处理至终态后提交"
|
|
71
|
+
- "分级复验:低成本必做项(分钟级)全量实跑 + 逐项申报(completed=true 附执行结果);deep_scan 核验上方「开发者自检申报」区块的 dev 申报(证据完整性、命中项与 diff 的合理性)并抽验命中项(如本地复跑单规则静态分析验证命中真实存在),不重扫全量,申报形态为 step 名首段 deep_scan、completed=true、描述注明核验方式与抽验样本;白名单口径:仅高成本必做项(deep_scan)允许核验申报形态,低成本必做项必须实跑后申报;升级回退全量重跑触发条件(统一四处):dev 申报缺证据或不完整 / 抽验失败 / 发现断言放水类 issue / 本轮为修复轮且修复波及接口契约或数据口径——命中任一即本轮高成本项改为实跑全量"
|
|
72
|
+
- "对抗性判断(独立视角,不重走 dev 路径):①task 产出完整性 ②③按 spec 验收标准选样的核心链路抽样重放(1-2 条端到端,覆盖创建-检索-变更-删除闭环中 spec 声明的关键行为;服务健康并入抽样重放——能重放即健康)并审查 `.http` 脚本质量(对照 spec 核断言覆盖/放水/Mock 过度——防作弊主力;含 API 测试自闭环链路:创建后可检索、状态变更后可见、删除后不可见;测试数据贴近业务语义,禁止无意义占位值)④审查测试代码质量(断言放水/Mock 过度/覆盖不足)⑤业务行为真实性:对照 spec 逐项核验每个业务行为的实现与断言证据(含一并操作的行为组、连带效果、下游消费),识别『仅打状态标记未实际生效』类缺陷 ⑥关键数据产出核验:对照 spec 数据产出要求,核验实现与测试是否覆盖产出数据落位(spec 指定承载位置)、完整非空、搬迁承接与同一输入基线一致,识别『流程正常但数据缺失/落错位置』类缺陷;轮次口径:首轮按上述分级复验与对抗判断全项执行;复核轮(二轮起)聚焦被修复 issue 相关面 + 本轮修复 diff 波及面 + dev 再自检申报(「开发者自检申报」区块),首轮已通过且无波及的维度与低成本工具结论不重审,但低成本工具实跑保留(防修复引入回归)"
|
|
73
|
+
- "验证完成后(含 verdict=failed 报 issue 回退 implement 的轮次)按已加载的清理规范执行幂等清理:停止残留的服务实例(含抽样重放启动的服务)、清理本 change 的隔离环境、删除残留的隔离扫描项目;无残留即通过,不因重复执行报错;清理结果纳入 validation_steps 申报(未执行须以 completed=false + 结构化 skip_reason 说明)"
|
|
74
|
+
- "本次变更仅含测试代码(判定:视图「变更范围」文件清单全部命中已加载技术栈 skill 定义的测试代码路径,且无生产代码、接口契约、构建与测试配置等非测试文件)时,豁免②③核心链路抽样重放,仅验证①④;豁免时 validation_steps 中②③置 completed=false,并以结构化 skip_reason 申报(格式:{\"item\":\"<对应验证项>\",\"category\":\"<降级类别>\",\"adjudication\":\"user_response|unattended_auto|env_unavailable\",\"note\":\"<说明>\"}),note 注明「本次变更仅含测试代码,豁免抽样重放」及判定依据;⑤⑥的实现核验在纯测试代码变更场景无对象,其断言证据核验并入④测试代码质量"
|
|
75
|
+
- "读取 design.md 的 API 定义(请求/响应结构、数据模型)与 specs/ 下相关 spec.md(需求细节、验收标准与数据产出要求),用于准备测试数据与对照 API 合约、选样核心链路"
|
|
76
|
+
- "加载匹配维度 skill 做维度审查:以 spec 验收标准、行为组与数据产出要求为锚对照审查实现,skill 检查点清单与 dev 自检申报对照兜底(抽查申报遗漏项——检查点清单与 dev 自检同源锚定同一 skill,仅执行模式从重走清单变为 dev 申报走清单 + 本审查兜底抽查),结合领域知识做拓展审查(skill 仅为最低基准,主力在清单之外);顺带发现的非本轮缺陷按对应维度严重级别标准报 issue,禁止静默丢弃"
|
|
76
77
|
- "审查新 issue 前先查看视图「待复核」区块中的既有 issue(仅本审查者报源),避免语义重复"
|
|
77
78
|
- "报 issue 时必须显式声明归因维度 dimension(style/architecture/performance/security/maintainability):本 reviewer 不归属任一质量维度,dimension 即该 issue 归属的目标质量维度"
|
|
78
79
|
- "当发现修复需要调整 openspec/changes/ 下文档时,所报 issue 的 file 字段必须指向目标文档"
|
|
@@ -93,4 +94,4 @@ phases:
|
|
|
93
94
|
- "Info 级 issue 的 description/suggestion 禁止阶段/时机表述(如'可后续处理')"
|
|
94
95
|
- "禁止以 SonarQube 平台状态修改(SAFE/WONTFIX/False Positive/Confirmed 等)作为豁免手段:平台状态仅作用于当前 SonarQube 项目实例,每次 change 使用独立项目 key,不跨 change 生效;存量问题跨 change 豁免必须经 exempt_issue_ids 申请 → 报源 reviewer 裁定 dismissed 的流程"
|
|
95
96
|
- "压制类 issue 严重级别至少 Low"
|
|
96
|
-
- "
|
|
97
|
+
- "有实质变更时,低成本实跑项的工具检查及首轮对抗审查覆盖全量代码;非本轮变更文件的工具违规同样必须映射为 issue(命中项目级豁免清单的按 Info,不重新豁免;其余非本轮引入问题按原严重级别上报),不得以不在变更清单或未越界修改为由丢弃;高成本项按核验申报口径执行(复核轮按轮次口径收敛,不重走首轮全量)"
|
|
@@ -63,7 +63,7 @@ phases:
|
|
|
63
63
|
- "实施前识别 spec 描述的一并操作行为组(如状态变更与数据入队这类需同时发生的多个行为);实施后逐项自证每个行为真实生效,禁止以单一状态字段的实现替代其他行为"
|
|
64
64
|
- "涉及数据模型或流程产出变更(含字段/实体承载位置搬迁、AI 生成等流程产物)时,自证须包含关键数据产出断言:产出字段已持久化到 spec 指定承载位置且值完整非空、搬迁承接与同一输入产出基线一致(按已加载接口测试规范执行);禁止仅以流程跑通/接口成功替代"
|
|
65
65
|
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock 服务、消息队列/缓存等中间件、扫描分析平台等一切环境资源均须按隔离标识隔离,禁止复用既有实例或共享端口;启动前发现隔离环境有残留时,先按已加载的清理规范幂等清空再启动"
|
|
66
|
-
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http`
|
|
66
|
+
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http` 脚本)执行接口测试,确保变更内容真实有效;执行结果(附执行顺序与覆盖接口清单)经 test_results 参数提交;修复被驳回 issue 后再次提交前同样须执行"
|
|
67
67
|
- "自证完成后停止本次启动的服务/基础设施实例,不留残留"
|
|
68
68
|
- "环境/基础设施问题(schema 缺失、DDL 未执行、依赖未安装)应通过 migration/初始化脚本等代码层面解决;仅生产级凭据、真实第三方资源、人工运维才走 blocker/豁免"
|
|
69
69
|
- "issue 指向文件的目录已并入执行边界,修复这些文件(含回归引入问题)不算越界;issue 的 file 字段指向 openspec/changes/ 下文档时,按该 issue 要求修复对应文档同样不算越界(仅限该 issue 指定文件,不得扩大范围);可按 issue 的 suggestion 直接执行修复;reviewer 的 boundary_expansion 已并入边界"
|
package/dsh/cordis.patch.yml
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
## 角色(开发者身份)
|
|
51
51
|
|
|
52
|
-
你是后端开发工程师,专注于实现 OpenSpec
|
|
52
|
+
你是后端开发工程师,专注于实现 OpenSpec 任务组中的代码开发任务,同时是质量门与接口测试的执行责任人:确定性检查与接口测试由你执行并经自检申报留痕,自检申报是 reviewer 分级复验的事实输入。
|
|
53
53
|
|
|
54
54
|
提交前按已加载 skill(含质量维度规范类)完成自检,审查兜底与终局裁定仍由对应 reviewer 承担(自检不替代 reviewer 裁定)。
|
|
55
55
|
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
- **工具维度审查者**(tool review,full 模式三层门禁第一层):执行确定性工具检查,将工具输出映射为统一 issue 结构;简单(simple)模式亦承担该职责
|
|
97
97
|
- **任务维度审查者**(task review,full 模式三层门禁第二层):验证 task 产出完整性、启动服务并检查健康、独立执行 API 测试并审查质量、审查测试代码质量;验证完成后清理本变更启动的环境(停止残留服务、清理隔离环境);简单(simple)模式亦承担该职责
|
|
98
98
|
- **质量维度审查者**(quality review,full 模式三层门禁第三层):按分派维度(style / architecture / performance / security / maintainability)审查
|
|
99
|
-
- **simple
|
|
99
|
+
- **simple 合并审查者**:以分级复验与对抗审查承担 simple 模式合并审查——低成本确定性工具实跑,高成本项核验 dev 自检申报并以核心链路抽样重放验证,对抗性判断(测试代码质量、业务行为真实性、关键数据产出核验)保持独立视角不重走 dev 路径;验证完成后清理本变更启动的环境(停止残留服务、清理隔离环境)
|
|
100
100
|
|
|
101
101
|
审查范围、非本轮问题处置、文档读取范围、既有 issue 去重与工具调用边界以 opx_status 操作指引与约束区块为准,此处不重复描述。
|
|
102
102
|
|
|
@@ -274,6 +274,7 @@
|
|
|
274
274
|
|
|
275
275
|
- **确定性工具检查**:工具维度审查者与 simple 合并审查者被允许运行确定性工具检查(代码格式/架构约束/静态分析/单元测试编译/深度扫描),按已加载质量门 skill 的必做清单执行并将输出映射为统一 issue 结构;检查结果纳入审查结论
|
|
276
276
|
- **任务验证**:任务维度审查者与 simple 合并审查者承担任务验证职责,验证 task 产出完整性、服务健康与 API 测试质量;未通过的 task 经 failed_tasks 上报;验证完成后按清理规范做幂等清理,不留环境残留;数据产出核验——对照 spec 数据产出要求核验关键数据产出落位/完整性/搬迁承接/基线对比,识别『流程正常但数据缺失/落错位置』类缺陷
|
|
277
|
+
- **维度审查锚点**:以 spec 验收标准、行为组与数据产出要求为主锚做对照审查,skill 检查点清单与 dev 自检申报对照兜底(抽查申报遗漏项)
|
|
277
278
|
- **工具改进**:可工具化的质量 pattern 双报业务 issue 与工具改进 issue(规则草案由审查者提供),实施工作由开发者完成,审查者不直接改动规则/配置文件以外的实现
|
|
278
279
|
|
|
279
280
|
## 压制判定
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifrankwang/openspec-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.131.0",
|
|
4
4
|
"description": "OpenSpec change orchestration plugin for AI coding agents (Claude Code, Codex, ZCode, DeepSeek Harness, OpenCode)",
|
|
5
5
|
"author": "ifrankwang",
|
|
6
6
|
"license": "MIT",
|
package/src/core/git.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import path from "path"
|
|
2
2
|
import { execFile } from "node:child_process"
|
|
3
|
-
import { readFile, writeFile, stat } from "node:fs/promises"
|
|
3
|
+
import { readFile, writeFile, stat, rm } from "node:fs/promises"
|
|
4
|
+
|
|
5
|
+
/** 判断路径是否存在于磁盘(fs 层判断,git 命令结果不作为存在性依据)。 */
|
|
6
|
+
async function pathExists(p: string): Promise<boolean> {
|
|
7
|
+
try {
|
|
8
|
+
await stat(p)
|
|
9
|
+
return true
|
|
10
|
+
} catch {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
export interface GitRunner {
|
|
6
16
|
run(worktree: string, args: string[]): Promise<string>
|
|
@@ -54,6 +64,85 @@ export async function runGitChecked(
|
|
|
54
64
|
return gitRunner.runChecked(worktree, args)
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
/** worktree 清理结果:成功判定以目录从磁盘真实消失为准,不信任单条 git 命令的退出码。 */
|
|
68
|
+
export interface WorktreeRemovalResult {
|
|
69
|
+
/** 目录最终是否已从磁盘消失。 */
|
|
70
|
+
dirResolved: boolean
|
|
71
|
+
/** 目录是否经文件系统兜底删除后才消失(true 表示主路径未直接达成,发生了补救)。 */
|
|
72
|
+
dirResolvedByFallback: boolean
|
|
73
|
+
/** worktree prune 是否执行成功(幽灵管理记录清理)。 */
|
|
74
|
+
pruned: boolean
|
|
75
|
+
/** 分支删除结果:null=未指定分支或因目录残留被跳过;true/false=删除断言结果。 */
|
|
76
|
+
branchDeleted: boolean | null
|
|
77
|
+
/** 各环节失败原因聚合(人类可读),全部成功时为空数组。 */
|
|
78
|
+
errors: string[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 删除任务组 worktree 并清理关联管理记录(收尾清理与 set_worktree 复用失败重建共用)。
|
|
83
|
+
*
|
|
84
|
+
* 序列:
|
|
85
|
+
* 1. 目录不存在 → 直接 prune(清理只残留管理记录的幽灵 worktree);
|
|
86
|
+
* 2. 目录存在 → `git worktree remove --force`;命令失败或目录仍在磁盘时走补救链:
|
|
87
|
+
* fs 递归删除 + prune(prune 失败仅记入 errors,不阻断);
|
|
88
|
+
* 3. 目录确认消失后才 `git branch -D`;仍残留则跳过删分支并记入 errors,
|
|
89
|
+
* 防止孤儿分支掩盖问题、以及向不完整状态发出被 git 拒绝的命令。
|
|
90
|
+
*
|
|
91
|
+
* @param repoRoot 主仓库根路径(worktree 管理命令的 -C 目标)
|
|
92
|
+
* @param wtPath 待删除的 worktree 路径
|
|
93
|
+
* @param opts.branchName 关联分支名;缺省/null 时只做 worktree 侧清理
|
|
94
|
+
*/
|
|
95
|
+
export async function removeTaskGroupWorktree(
|
|
96
|
+
repoRoot: string,
|
|
97
|
+
wtPath: string,
|
|
98
|
+
opts: { branchName?: string | null } = {},
|
|
99
|
+
): Promise<WorktreeRemovalResult> {
|
|
100
|
+
const result: WorktreeRemovalResult = { dirResolved: false, dirResolvedByFallback: false, pruned: false, branchDeleted: null, errors: [] }
|
|
101
|
+
|
|
102
|
+
if (!(await pathExists(wtPath))) {
|
|
103
|
+
const pruneRes = await runGitChecked(repoRoot, ["worktree", "prune"])
|
|
104
|
+
if (pruneRes.success) result.pruned = true
|
|
105
|
+
else result.errors.push(`worktree prune 失败:${pruneRes.stderr}`)
|
|
106
|
+
result.dirResolved = true
|
|
107
|
+
} else {
|
|
108
|
+
const rmRes = await runGitChecked(repoRoot, ["worktree", "remove", wtPath, "--force"])
|
|
109
|
+
if (!rmRes.success) result.errors.push(`git worktree remove 失败:${rmRes.stderr}`)
|
|
110
|
+
if (await pathExists(wtPath)) {
|
|
111
|
+
// 补救链:fs 物理删除目录 + prune 清理残留管理记录
|
|
112
|
+
try {
|
|
113
|
+
await rm(wtPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
114
|
+
} catch (e) {
|
|
115
|
+
result.errors.push(`文件系统兜底删除 "${wtPath}" 失败:${e instanceof Error ? e.message : String(e)}`)
|
|
116
|
+
}
|
|
117
|
+
const pruneRes = await runGitChecked(repoRoot, ["worktree", "prune"])
|
|
118
|
+
if (pruneRes.success) result.pruned = true
|
|
119
|
+
else result.errors.push(`worktree prune 失败:${pruneRes.stderr}`)
|
|
120
|
+
if (!(await pathExists(wtPath))) {
|
|
121
|
+
result.dirResolved = true
|
|
122
|
+
result.dirResolvedByFallback = true
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
result.dirResolved = true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!result.dirResolved) {
|
|
130
|
+
if (opts.branchName) {
|
|
131
|
+
result.errors.push(`目录 "${wtPath}" 未能删除,跳过分支 "${opts.branchName}" 的删除以保留排查线索`)
|
|
132
|
+
}
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
if (opts.branchName) {
|
|
136
|
+
const branchRes = await runGitChecked(repoRoot, ["branch", "-D", opts.branchName])
|
|
137
|
+
if (branchRes.success) result.branchDeleted = true
|
|
138
|
+
else {
|
|
139
|
+
result.branchDeleted = false
|
|
140
|
+
result.errors.push(`删除分支 "${opts.branchName}" 失败:${branchRes.stderr}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result
|
|
144
|
+
}
|
|
145
|
+
|
|
57
146
|
export async function getCurrentHead(worktree: string): Promise<string> {
|
|
58
147
|
return runGit(worktree, ["rev-parse", "HEAD"])
|
|
59
148
|
}
|
package/src/core/tools/gate.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* {"item":"<对应必做项>","category":"<降级类别>","adjudication":"user_response|unattended_auto|env_unavailable","note":"<说明>"}
|
|
12
12
|
* adjudication 裁定方式枚举:user_response=用户答复、unattended_auto=无人值守自动降级、
|
|
13
13
|
* env_unavailable=环境不可用(note 须记录尝试)。
|
|
14
|
+
*
|
|
15
|
+
* 必做覆盖口径:核验申报(completed=true + 描述注明核验方式与抽验样本,step 名首段 token 命中必做项)
|
|
16
|
+
* 是高成本必做项的合法覆盖形态——仅限 workflow 指令白名单限定的高成本必做项(deep_scan),低成本
|
|
17
|
+
* 必做项必须实跑后申报。覆盖判定只认 step 名 token 命中,不区分实跑与核验形态(机制层不拦截低成本项
|
|
18
|
+
* 误用核验申报形态,白名单约束由 workflow 指令与门禁错误提示承载)。
|
|
14
19
|
*/
|
|
15
20
|
import {
|
|
16
21
|
scanSkillTags,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from "path"
|
|
2
|
+
import { rmdir } from "node:fs/promises"
|
|
2
3
|
import type { OrchestrateState, TaskItem, TaskStatus, WorkflowMode } from "../types.ts"
|
|
3
4
|
import { BUILD_PHASE_TARGETS, REVIEW_LAYERS, REVIEW_VERIFY_STEPS } from "../types.ts"
|
|
4
5
|
import { agentToReviewLayer } from "../constants.ts"
|
|
5
|
-
import { runGit, runGitChecked, getCurrentBranch, getMergeBase, isWorktreeClean, markTaskGroupCheckboxesComplete, mergeBranchToTarget, discoverDiskWorktrees, detectMainRepoPollution, detectChanges, type DetectChangesResult } from "../git.ts"
|
|
6
|
+
import { runGit, runGitChecked, getCurrentBranch, getMergeBase, isWorktreeClean, markTaskGroupCheckboxesComplete, mergeBranchToTarget, discoverDiskWorktrees, detectMainRepoPollution, detectChanges, removeTaskGroupWorktree, type DetectChangesResult } from "../git.ts"
|
|
6
7
|
import { readStateByWorktree, readStateByChangeId, writeState, writeContextToWorktree } from "../state.ts"
|
|
7
8
|
import { generateIsolationNamespace } from "../namespace.ts"
|
|
8
9
|
import { readExemptions } from "../exemptions.ts"
|
|
@@ -576,13 +577,13 @@ export async function setWorktreeExecute(params: SetWorktreeParams, ctx: ToolCon
|
|
|
576
577
|
await bindWorktreeRefs(item, existingPath, branch, state.baseBranch)
|
|
577
578
|
reused = true
|
|
578
579
|
} else {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
throw new Error(
|
|
580
|
+
// 复用失败的清理走共享清理函数:目录侧(git remove + fs 兜底 + prune)与分支删除统一语义
|
|
581
|
+
const cleanup = await removeTaskGroupWorktree(repoRoot, existingPath, { branchName: branch })
|
|
582
|
+
if (!cleanup.dirResolved || cleanup.branchDeleted === false) {
|
|
583
|
+
const problems: string[] = []
|
|
584
|
+
if (!cleanup.dirResolved) problems.push(`无法清理已有 worktree "${existingPath}"`)
|
|
585
|
+
if (cleanup.branchDeleted === false) problems.push(`无法清理已有分支 "${branch}"`)
|
|
586
|
+
throw new Error(`${problems.join(";")}:${cleanup.errors.join(";") || "原因未知"}\n请手动处理后重试。`)
|
|
586
587
|
}
|
|
587
588
|
}
|
|
588
589
|
}
|
|
@@ -641,16 +642,17 @@ export async function statusExecute(params: StatusParams, ctx: ToolContext): Pro
|
|
|
641
642
|
const tg = findTaskGroup(state, state.taskGroupId)
|
|
642
643
|
// 主仓库 openspec 污染诊断(56ddfe9 意图):编排者分派视图展示主仓库污染,供编排者人工核对
|
|
643
644
|
const mainPollution = ctx.orchestrator ? await detectMainRepoPollution(ctx.worktree) : null
|
|
644
|
-
// tool review 检查点增量检测(A4):
|
|
645
|
-
//
|
|
646
|
-
//
|
|
645
|
+
// tool review 检查点增量检测(A4):full 的 verify_tool(reviewer-tool)与 simple 的 quality_review
|
|
646
|
+
// (合并审查者 openspec-reviewer)工作视图均按「检查点 → 当前 HEAD」区间变更分流。渲染层为同步函数,
|
|
647
|
+
// 此处预计算后经 WorkflowStatusViewOptions 传入。仅推荐分派对应 agent 时计算(verify_tool → reviewer-tool、
|
|
648
|
+
// quality_review → openspec-reviewer),其余角色/step 不产生额外 git 调用。
|
|
647
649
|
let toolChanges: DetectChangesResult | undefined
|
|
648
|
-
|
|
649
|
-
agentToReviewLayer(agent) === "tool" &&
|
|
650
|
+
const changesEvidenceStep =
|
|
650
651
|
rec.status === "recommend" &&
|
|
651
|
-
rec.
|
|
652
|
-
|
|
653
|
-
|
|
652
|
+
rec.agents.includes(agent) &&
|
|
653
|
+
((agentToReviewLayer(agent) === "tool" && rec.stepId === "verify_tool") ||
|
|
654
|
+
(agent === "openspec-reviewer" && rec.stepId === "quality_review"))
|
|
655
|
+
if (changesEvidenceStep) {
|
|
654
656
|
const wtPath = typeof item.metadata["worktree_path"] === "string" ? item.metadata["worktree_path"] : undefined
|
|
655
657
|
if (wtPath) {
|
|
656
658
|
const checkpoint =
|
|
@@ -666,6 +668,22 @@ export async function statusExecute(params: StatusParams, ctx: ToolContext): Pro
|
|
|
666
668
|
return renderWorkflowStatusView(item, workflow, rec, { agent, orchestrator: ctx.orchestrator, identityDeclared: ctx.identityDeclared }, { state, tg, mainPollution, toolChanges, exemptedHits, exemptionItems })
|
|
667
669
|
}
|
|
668
670
|
|
|
671
|
+
/**
|
|
672
|
+
* 收尾后清扫默认布局的空父目录(`.worktree/<changeId>` 与 `.worktree`):
|
|
673
|
+
* 仅对这两级字面路径做非递归 rmdir(空目录才会成功),任一失败静默忽略。
|
|
674
|
+
* 自定义 worktree_path 时以「被删目录的实际 dirname === `.worktree/<changeId>`」判定是否可扫第一级,
|
|
675
|
+
* 第二级固定只试 `.worktree` 本身;其余任意父路径一律不做处理。
|
|
676
|
+
*/
|
|
677
|
+
async function sweepEmptyWorktreeParents(repoRoot: string, removedDir: string, changeId: string): Promise<void> {
|
|
678
|
+
const repoRootAbs = path.resolve(repoRoot)
|
|
679
|
+
const groupLevelDir = path.join(repoRootAbs, ".worktree", changeId)
|
|
680
|
+
const worktreeLevelDir = path.join(repoRootAbs, ".worktree")
|
|
681
|
+
if (path.dirname(path.resolve(removedDir)) === groupLevelDir) {
|
|
682
|
+
try { await rmdir(groupLevelDir) } catch {}
|
|
683
|
+
}
|
|
684
|
+
try { await rmdir(worktreeLevelDir) } catch {}
|
|
685
|
+
}
|
|
686
|
+
|
|
669
687
|
export async function completeTaskGroupExecute(params: { change_id: string }, ctx: ToolContext): Promise<string> {
|
|
670
688
|
assertOrchestrator(ctx, "opx_orch_complete_task_group")
|
|
671
689
|
const state = await readStateByWorktree(ctx.worktree, params.change_id)
|
|
@@ -730,17 +748,40 @@ export async function completeTaskGroupExecute(params: { change_id: string }, ct
|
|
|
730
748
|
].join("\n")
|
|
731
749
|
}
|
|
732
750
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
751
|
+
// 收尾清理:只要记录了 worktree_path 就执行;branch_name 缺省时仅做 worktree 侧清理。
|
|
752
|
+
// 残留不阻断收尾:completed_at 照写,残留信息写入 metadata.cleanup_residual 并在返回体给出人工处理命令。
|
|
753
|
+
let cleanupNote = ""
|
|
754
|
+
let cleanupResidualWarning = ""
|
|
755
|
+
if (worktreePath) {
|
|
756
|
+
const cleanup = await removeTaskGroupWorktree(ctx.worktree, worktreePath, { branchName })
|
|
757
|
+
if (cleanup.dirResolved) {
|
|
758
|
+
await sweepEmptyWorktreeParents(ctx.worktree, worktreePath, params.change_id)
|
|
759
|
+
const successNotes: string[] = []
|
|
760
|
+
if (cleanup.dirResolvedByFallback) {
|
|
761
|
+
successNotes.push("- **cleanup**: git worktree remove 未直接移除目录,已按文件系统兜底删除并 prune 管理记录。")
|
|
762
|
+
}
|
|
763
|
+
if (cleanup.errors.length > 0) {
|
|
764
|
+
successNotes.push(`- **cleanup 部分告警**: ${cleanup.errors.join(";")}`)
|
|
765
|
+
}
|
|
766
|
+
cleanupNote = successNotes.join("\n")
|
|
767
|
+
} else {
|
|
768
|
+
item.metadata["cleanup_residual"] = { worktree_path: worktreePath, errors: cleanup.errors }
|
|
769
|
+
cleanupResidualWarning = [
|
|
770
|
+
"",
|
|
771
|
+
"## ⚠️ worktree 清理残留(不影响收尾)",
|
|
772
|
+
`- **残留路径**: \`${worktreePath}\``,
|
|
773
|
+
"- **原因**:",
|
|
774
|
+
...cleanup.errors.map((e) => ` - ${e}`),
|
|
775
|
+
`- **处理**: 请人工执行 \`rm -rf '${worktreePath}' && git worktree prune\` 清理残留目录与 git 管理记录。`,
|
|
776
|
+
branchName ? `- **分支**: 分支 "${branchName}" 未删除。` : "",
|
|
777
|
+
].filter(Boolean).join("\n")
|
|
738
778
|
}
|
|
739
779
|
}
|
|
740
780
|
item.metadata["completed_at"] = new Date().toISOString()
|
|
741
781
|
await writeState(ctx.worktree, state)
|
|
742
782
|
const doneMessage = `任务组已完成并合并到 "${mergeTarget}"。`
|
|
743
|
-
|
|
783
|
+
const notes = [cleanupNote, checkboxWarning, cleanupResidualWarning].filter(Boolean)
|
|
784
|
+
return notes.length > 0 ? `${doneMessage}\n${notes.join("\n")}` : doneMessage
|
|
744
785
|
}
|
|
745
786
|
|
|
746
787
|
export async function setUnattendedExecute(params: UnattendedParams, ctx: ToolContext): Promise<string> {
|
|
@@ -333,7 +333,11 @@ export const agentSubmitSchema: JSONSchema = {
|
|
|
333
333
|
description: "implement step:已完成的 task id(覆盖门禁:全部 open/rejected task 必须被覆盖)",
|
|
334
334
|
items: { type: "string" },
|
|
335
335
|
},
|
|
336
|
-
test_results: {
|
|
336
|
+
test_results: {
|
|
337
|
+
type: "string",
|
|
338
|
+
description:
|
|
339
|
+
"implement / verify_tool step:测试运行结果摘要(implement 为接口测试执行结果——附执行顺序与覆盖接口清单;verify_tool 为 UT 运行结果摘要);quality_review(simple 合并审查)下由开发者在 implement 申报,审查者提交时不生效",
|
|
340
|
+
},
|
|
337
341
|
validation_steps: {
|
|
338
342
|
type: "array",
|
|
339
343
|
description: "review step:验证步骤执行摘要",
|
package/src/core/tools/submit.ts
CHANGED
|
@@ -264,7 +264,8 @@ function handleAnalyzeParams(item: WorkItem, params: AgentSubmitParams): void {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
/** implement step 参数处理:blocker(on_fail)、completed_task_ids + 覆盖门禁、self_check_results
|
|
267
|
+
/** implement step 参数处理:blocker(on_fail)、completed_task_ids + 覆盖门禁、self_check_results /
|
|
268
|
+
* test_results 申报存档。 */
|
|
268
269
|
function handleImplementParams(item: WorkItem, params: AgentSubmitParams): void {
|
|
269
270
|
if (params.blocker) {
|
|
270
271
|
if (params.verdict !== "failed") {
|
|
@@ -311,6 +312,11 @@ function handleImplementParams(item: WorkItem, params: AgentSubmitParams): void
|
|
|
311
312
|
if (params.self_check_results) {
|
|
312
313
|
item.metadata["self_check_results"] = params.self_check_results
|
|
313
314
|
}
|
|
315
|
+
// test_results 为 dev 申报的接口测试执行结果存档(simple 模式 quality_review 视图「开发者自检申报」
|
|
316
|
+
// 区块渲染源),与 self_check_results 同为跨角色可见的正式提交参数存档。
|
|
317
|
+
if (params.test_results) {
|
|
318
|
+
item.metadata["test_results"] = params.test_results
|
|
319
|
+
}
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
/** 把 boundary_expansion 的目录/包合并进执行边界(metadata 为事实源,缺失则忽略)。 */
|
|
@@ -395,7 +401,7 @@ function assertFailedHasReason(
|
|
|
395
401
|
}
|
|
396
402
|
}
|
|
397
403
|
|
|
398
|
-
/** review step(verify_tool/verify_task/verify_quality)参数处理。 */
|
|
404
|
+
/** review step(verify_tool/verify_task/verify_quality/quality_review)参数处理。 */
|
|
399
405
|
function handleReviewParams(
|
|
400
406
|
item: WorkItem,
|
|
401
407
|
params: AgentSubmitParams,
|
|
@@ -417,7 +423,10 @@ function handleReviewParams(
|
|
|
417
423
|
mergeBoundaryInto(item, params.boundary_expansion)
|
|
418
424
|
}
|
|
419
425
|
|
|
420
|
-
|
|
426
|
+
// test_results 透传:full 模式 verify_tool 的 UT 摘要写入不受影响;simple 模式 quality_review 下
|
|
427
|
+
// test_results 是 dev 申报的接口测试结果存档(quality_review 视图「开发者自检申报」区块渲染源),
|
|
428
|
+
// 合并审查者提交不得覆盖 dev 申报(spec 契约:申报区块语义为 dev 产出)。
|
|
429
|
+
if (params.test_results && stepId !== "quality_review") item.metadata["test_results"] = params.test_results
|
|
421
430
|
|
|
422
431
|
if (params.validation_steps) {
|
|
423
432
|
// 结构化 skip_reason 校验(升级既有「非空即可」):未完成项必须带合法结构化降级声明
|
|
@@ -428,6 +437,9 @@ function handleReviewParams(
|
|
|
428
437
|
// 必做清单覆盖度门禁:verdict=passed 且非补交豁免(仅裁定参数)时,validation_steps 须逐项覆盖
|
|
429
438
|
// 当前 agent 能力集解析出的质量门 skill 必做清单(must_do)。遗漏必做项且无有效降级证据 → 拒绝提交。
|
|
430
439
|
// 未声明 must_do 的 skill / 解析不到质量门 skill 的 step 自动跳过(不误伤 verify_task/verify_quality)。
|
|
440
|
+
// 覆盖口径:核验申报(completed=true + 描述注明核验方式与抽验样本,step 名首段 token 命中必做项)是
|
|
441
|
+
// 高成本必做项的合法覆盖形态,仅限 workflow 指令白名单限定的高成本必做项(deep_scan);低成本必做项
|
|
442
|
+
// 必须实跑后申报(机制层只认 token 命中不区分形态,白名单口径在错误提示中明示)。
|
|
431
443
|
if (params.verdict === "passed" && !opts.skipMustDoGate) {
|
|
432
444
|
const uncovered = uncoveredMustDo(opts.capabilityTags, params.validation_steps)
|
|
433
445
|
if (uncovered.length > 0) {
|
|
@@ -435,7 +447,8 @@ function handleReviewParams(
|
|
|
435
447
|
`提交的验证步骤未覆盖质量门 skill 必做清单,缺少以下必做项:${uncovered.join("、")}。\n` +
|
|
436
448
|
`请按已加载质量门类 skill 的必做清单逐项执行并逐项申报结果(completed=true);` +
|
|
437
449
|
`确实无法执行的必做项须以结构化 skip_reason 申报降级理由` +
|
|
438
|
-
`(格式:${SKIP_REASON_FORMAT})。`
|
|
450
|
+
`(格式:${SKIP_REASON_FORMAT})。` +
|
|
451
|
+
`低成本必做项必须实跑后申报;核验申报仅限 workflow 指令白名单限定的高成本必做项。`
|
|
439
452
|
)
|
|
440
453
|
}
|
|
441
454
|
}
|
|
@@ -777,10 +790,15 @@ export async function agentSubmitExecute(params: AgentSubmitParams, ctx: ToolCon
|
|
|
777
790
|
})
|
|
778
791
|
}
|
|
779
792
|
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
|
|
793
|
+
// 审查检查点增量检测(A3):full 的 verify_tool(reviewer-tool)与 simple 的 quality_review
|
|
794
|
+
// (合并审查者 openspec-reviewer)提交成功后记录当前 HEAD,供下次重进该 step 时仅对比「检查点 →
|
|
795
|
+
// 当前 HEAD」区间的变更(无代码/配置变更时直提 passed,省去全量工具扫描)。两者写同一 metadata key
|
|
796
|
+
// `_tool_review_checkpoint`,但 simple / full 两 workflow 无对方 step,读写互不交叉。写入位于
|
|
797
|
+
// submitForStep 成功与 writeState 之间,任何异常路径均不落盘(零状态变更)。
|
|
798
|
+
if (
|
|
799
|
+
(params.step_id === "verify_tool" && agentToReviewLayer(ctx.agent) === "tool") ||
|
|
800
|
+
(params.step_id === "quality_review" && ctx.agent === "openspec-reviewer")
|
|
801
|
+
) {
|
|
784
802
|
const head = await getCurrentHead(wtPath)
|
|
785
803
|
if (head) item.metadata["_tool_review_checkpoint"] = head
|
|
786
804
|
}
|
package/src/core/views.ts
CHANGED
|
@@ -165,6 +165,27 @@ export function renderAgentSummaries(agentSummaries: Record<string, string> | un
|
|
|
165
165
|
return lines
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/** 开发者自检申报渲染。数据源为 workItem metadata 的 self_check_results / test_results(提交参数的存档),
|
|
169
|
+
* 分「自检申报(self_check_results)」与「接口测试结果(test_results)」两段渲染,两者皆缺失/为空时
|
|
170
|
+
* 返回空数组(不渲染空区块)。
|
|
171
|
+
* 与 renderAgentSummaries 的语义区分:会话摘要按角色隔离(辅助记忆,不跨 agent 传递);自检申报是
|
|
172
|
+
* 正式提交参数的存档,跨角色可见是设计意图——reviewer 分级复验以 dev 申报为事实输入(低成本实跑对照、
|
|
173
|
+
* 高成本项核验申报+抽样重放),不违反「子代理上下文不得转述」:该条约束的是编排者在分派 prompt 中
|
|
174
|
+
* 转述动态上下文,视图渲染动态上下文本就是 opx_status 工具本职。 */
|
|
175
|
+
export function renderDevSelfCheckDeclaration(metadata: Record<string, unknown>): string[] {
|
|
176
|
+
const selfCheck = typeof metadata["self_check_results"] === "string" ? metadata["self_check_results"].trim() : ""
|
|
177
|
+
const testResults = typeof metadata["test_results"] === "string" ? metadata["test_results"].trim() : ""
|
|
178
|
+
if (selfCheck === "" && testResults === "") return []
|
|
179
|
+
const lines: string[] = ["## 开发者自检申报", ""]
|
|
180
|
+
if (selfCheck !== "") {
|
|
181
|
+
lines.push("**自检申报(self_check_results)**:", "", selfCheck, "")
|
|
182
|
+
}
|
|
183
|
+
if (testResults !== "") {
|
|
184
|
+
lines.push("**接口测试结果(test_results)**:", "", testResults, "")
|
|
185
|
+
}
|
|
186
|
+
return lines
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
// ─── 占位符插值层 ───
|
|
169
190
|
|
|
170
191
|
/** 插值白名单:仅这些 key 可被 {{key}} 占位符引用,其余一律保留原文(防配置注入任意动态值)。 */
|