@ifrankwang/openspec-agents 0.137.0 → 0.139.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 +1843 -3504
- package/README.md +1 -1
- package/assets/skills/api-test/SKILL.md +3 -3
- package/assets/skills/environment-cleanup/SKILL.md +11 -4
- package/assets/workflows/review-simple.yaml +1 -1
- package/assets/workflows/review.yaml +1 -1
- package/assets/workflows/task-simple.yaml +1 -1
- package/assets/workflows/task.yaml +1 -1
- package/package.json +1 -1
- package/src/adapters/mcp-common/server.ts +1 -1
- package/src/core/git.ts +10 -0
- package/src/core/tools/lifecycle.ts +37 -15
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ npm install -D @ifrankwang/openspec-agents
|
|
|
92
92
|
1. **输入**:项目中的 OpenSpec 变更规范(变更说明、设计、任务清单、验收规格)。
|
|
93
93
|
2. **分派**:主智能体把任务交给专职智能体——实现者负责写代码,审查者负责检查,质量把关者负责多维验证。
|
|
94
94
|
3. **门禁**:每一步的结论通过检查后才进入下一步;不通过的结论自动回到实现环节修复。
|
|
95
|
-
4. **收尾合并**:任务组完成只做门禁校验并标记变更范围,不合并、不销毁 worktree;最后一个任务组收口时把 change 分支一次性合并回基准分支,随后销毁 worktree 并删除 change
|
|
95
|
+
4. **收尾合并**:任务组完成只做门禁校验并标记变更范围,不合并、不销毁 worktree;最后一个任务组收口时把 change 分支一次性合并回基准分支,随后销毁 worktree 并删除 change 分支。合并前若基准分支已推进,先做基线漂移检查并比对基准侧净变化内容:纯文档变化(md / mdx / markdown / rst / adoc 五类后缀)不影响代码语义,直接合并收口;含任一非文档文件或文本冲突才把该任务组回退到收尾验证环节(合并基准分支最新代码并回归复验)后再收口——验收仅在实质漂移时动态发生,正常路径零额外环节。
|
|
96
96
|
5. **部署注意事项**:change 的全部任务组完成后,主智能体按状态视图指引汇总部署注意事项(规划文档提取 + 开发者实施补充点,含依赖清单/构建配置变更等合并后需在主仓库重装依赖、重新构建的事项)向用户汇报。全程留痕可审计。
|
|
97
97
|
|
|
98
98
|
## 独立审查会话
|
|
@@ -100,15 +100,15 @@ API 测试脚本运行前需获取有效认证凭证。常见模式:
|
|
|
100
100
|
```
|
|
101
101
|
1. **MUST** 启动基础设施(Docker compose 项目名带隔离标识:`docker compose -p <namespace> up -d`,避免与并发 change 的容器冲突;隔离对象不限于数据库——mock 服务、消息队列/缓存等中间件、扫描分析平台等一切为本变更启动的资源均须经隔离标识启动,禁止复用宿主既有实例或共享端口)
|
|
102
102
|
2. **MUST** 准备 SQL 前置数据(写入本 change 的隔离容器内 DB)
|
|
103
|
-
3. **MUST** 启动服务(端口用编排会话建议端口:`--server.port=<建议端口>`,避免并发 change
|
|
103
|
+
3. **MUST** 启动服务(端口用编排会话建议端口:`--server.port=<建议端口>`,避免并发 change 端口冲突),启动后记录服务进程 PID,并确认实际监听端口与会话建议端口一致;不一致视为启动失败,修正端口参数重新启动,禁止让服务落回项目默认端口
|
|
104
104
|
4. API 测试脚本(BASE_URL 使用上述端口)
|
|
105
|
-
5. **MUST** 停止服务并清理隔离环境(`docker compose -p <namespace> down`),随后幂等自检:本 change 隔离标识下无残留容器(`docker compose -p <namespace> ps`
|
|
105
|
+
5. **MUST** 停止服务并清理隔离环境(`docker compose -p <namespace> down`),随后幂等自检:本 change 隔离标识下无残留容器(`docker compose -p <namespace> ps` 为空)、本变更记录的服务进程已退出且隔离端口(建议端口)无进程监听;自检中发现无法确认归属本变更的进程(工作目录不在本变更工作目录内)禁止清理,在结果中标注说明
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
以上第 1、2、3、5 条为 MUST:隔离(独立 compose 项目名、隔离数据库、隔离 mock 服务/中间件/扫描分析平台、建议端口、down 清理)是并发安全硬约束,不适用本文件开头「项目规范优先」的推荐豁免。
|
|
109
109
|
|
|
110
110
|
- SQL 在前:先准备数据,再启动应用确保应用启动时读取到完整数据
|
|
111
|
-
- 隔离标识 `<namespace
|
|
111
|
+
- 隔离标识 `<namespace>`、建议端口与本变更工作目录同来自编排会话上下文
|
|
112
112
|
- 隔离对象不限于数据库:除数据库外,mock 服务、消息队列/缓存等中间件、扫描分析平台等其余为本变更启动的环境资源均须按隔离标识隔离;仅隔离数据库不满足并发安全要求
|
|
113
113
|
- 启动服务:按项目构建文件确定的启动方式(mvn / gradle / npm 等),端口用编排会话建议端口
|
|
114
114
|
- API 脚本:BASE_URL 使用上述建议端口,依赖运行中的服务
|
|
@@ -9,12 +9,19 @@ capabilities: ["cleanup"]
|
|
|
9
9
|
|
|
10
10
|
## 停止残留的服务实例
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
停止本次变更启动的服务实例,禁止误杀其他变更或他人启动的进程:
|
|
13
13
|
|
|
14
|
-
-
|
|
14
|
+
- 经进程启动的服务:确认归属本变更后停止,按以下顺序判别
|
|
15
15
|
- 经容器编排启动的服务:按本变更的隔离标识停止对应 compose 项目
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
经进程启动的服务按以下顺序确认归属:
|
|
18
|
+
|
|
19
|
+
1. 优先按启动时记录的进程 PID 停止;停止前核对该 PID 对应进程的启动命令或工作目录仍属于本变更(操作系统可能复用已退出进程的 PID),确认后方可停止
|
|
20
|
+
2. 无 PID 记录时,以会话分配的隔离端口(专属本变更)为唯一定位入口:隔离端口无进程占用即视为无本变更服务残留;有占用时查询该进程的工作目录,位于本变更工作目录内(来源:会话分配的隔离上下文,含隔离标识、建议端口、本变更工作目录)方可停止,不在即禁止停止
|
|
21
|
+
3. 禁止按启动命令关键字全局搜索进程作为定位手段(本变更工作区与主仓库的启动命令相同,无法区分归属)
|
|
22
|
+
4. 无法确认归属本变更的进程(含监听在项目默认端口上的进程)一律不停止、不干扰;目标端口被其他进程占用属于本变更启动失败,应修正端口参数重新以隔离方式启动,禁止为腾出端口而停止任何进程
|
|
23
|
+
|
|
24
|
+
停止确认以被停止的进程已退出为准;端口随后被其他进程占用不构成本变更未停止(如用户或他人进程占用同一端口),不得据此二次清理。
|
|
18
25
|
|
|
19
26
|
## 清理隔离环境
|
|
20
27
|
|
|
@@ -40,7 +47,7 @@ capabilities: ["cleanup"]
|
|
|
40
47
|
|
|
41
48
|
清理完成后自检以下各项,全部无残留即通过:
|
|
42
49
|
|
|
43
|
-
-
|
|
50
|
+
- 无残留的服务进程(以本变更启动的进程已退出为准,端口被其他进程占用不构成本变更未停止)
|
|
44
51
|
- 无残留的隔离容器(隔离标识下 compose ps 为空)
|
|
45
52
|
- 无残留的隔离 mock 服务/中间件(按隔离标识查询不存在或容器已移除)
|
|
46
53
|
- 无残留的隔离扫描分析项目(按隔离标识查询不存在)
|
|
@@ -43,7 +43,7 @@ phases:
|
|
|
43
43
|
- "执行边界缺省整个 worktree(独立审查无分析环节,不生成边界白名单);issue 的 file 字段指向的文件即修复边界,修复这些文件(含回归引入问题)不算越界;可按 issue 的 suggestion 直接执行修复;reviewer 的 boundary_expansion 已并入边界"
|
|
44
44
|
- "对修复未过次数≥2 的 issue 须先完成 5-Why 根因分析再动手,不得跳过分析直接改代码"
|
|
45
45
|
- "修复完成后先 git commit 再提交;提交前按已加载的质量门类 skill 完成本地构建验证,并按维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次修复文件逐项自检,自检结论与工具检查结果一并汇总进 self_check_results"
|
|
46
|
-
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree
|
|
46
|
+
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;验证完成后停止本次启动的实例,不留残留,残留判定遵循已加载清理规范的归属判别,无法确认归属本变更的进程一律不清;禁止为腾出端口或资源而停止、复用或干扰非本会话启动的进程"
|
|
47
47
|
- "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由,并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复"
|
|
48
48
|
- "提交 opx_agent_submit:必传 verdict;修复完成的 issue 经 fixed_issue_ids 上报、不可修 issue 经 exempt_issue_ids 申请豁免"
|
|
49
49
|
constraints:
|
|
@@ -42,7 +42,7 @@ phases:
|
|
|
42
42
|
- "执行边界缺省整个 worktree(独立审查无分析环节,不生成边界白名单);issue 的 file 字段指向的文件即修复边界,修复这些文件(含回归引入问题)不算越界;可按 issue 的 suggestion 直接执行修复;reviewer 的 boundary_expansion 已并入边界"
|
|
43
43
|
- "对修复未过次数≥2 的 issue 须先完成 5-Why 根因分析再动手,不得跳过分析直接改代码"
|
|
44
44
|
- "修复完成后先 git commit 再提交;提交前按已加载的质量门类 skill 完成本地构建验证,并按维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次修复文件逐项自检,自检结论与工具检查结果一并汇总进 self_check_results"
|
|
45
|
-
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree
|
|
45
|
+
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;验证完成后停止本次启动的实例,不留残留,残留判定遵循已加载清理规范的归属判别,无法确认归属本变更的进程一律不清;禁止为腾出端口或资源而停止、复用或干扰非本会话启动的进程"
|
|
46
46
|
- "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由,并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复"
|
|
47
47
|
- "提交 opx_agent_submit:必传 verdict;修复完成的 issue 经 fixed_issue_ids 上报、不可修 issue 经 exempt_issue_ids 申请豁免"
|
|
48
48
|
constraints:
|
|
@@ -32,7 +32,7 @@ phases:
|
|
|
32
32
|
- "按 Task 项顺序逐个实现,聚焦当前子任务,不超出执行边界"
|
|
33
33
|
- "实施前识别 spec 描述的一并操作行为组(如状态变更与数据入队这类需同时发生的多个行为);实施后逐项自证每个行为真实生效,禁止以单一状态字段的实现替代其他行为"
|
|
34
34
|
- "涉及数据模型或流程产出变更(含字段/实体承载位置搬迁、AI 生成等流程产物)时,自证须包含关键数据产出断言:产出字段已持久化到 spec 指定承载位置且值完整非空、搬迁承接与同一输入产出基线一致(按已加载接口测试规范执行);禁止仅以流程跑通/接口成功替代"
|
|
35
|
-
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock
|
|
35
|
+
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock 服务、消息队列/缓存等中间件、扫描分析平台等一切环境资源均须按隔离标识隔离,禁止复用既有实例或共享端口;启动前发现隔离环境有残留时,先按已加载的清理规范幂等清空再启动;残留判定遵循已加载清理规范的归属判别,无法确认归属本变更的进程一律不清"
|
|
36
36
|
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http` 脚本)执行接口测试,确保变更内容真实有效;执行结果(附执行顺序与覆盖接口清单)经 test_results 参数提交,作为审查者分级复验的事实输入;修复被驳回 issue 后再次提交前同样须执行"
|
|
37
37
|
- "自证完成后停止本次启动的服务/基础设施实例,不留残留"
|
|
38
38
|
- "环境/基础设施问题(schema 缺失、DDL 未执行、依赖未安装)应通过 migration/初始化脚本等代码层面解决;仅生产级凭据、真实第三方资源、人工运维才走 blocker/豁免"
|
|
@@ -62,7 +62,7 @@ phases:
|
|
|
62
62
|
- "按 Task 项顺序逐个实现,聚焦当前子任务,不超出执行边界"
|
|
63
63
|
- "实施前识别 spec 描述的一并操作行为组(如状态变更与数据入队这类需同时发生的多个行为);实施后逐项自证每个行为真实生效,禁止以单一状态字段的实现替代其他行为"
|
|
64
64
|
- "涉及数据模型或流程产出变更(含字段/实体承载位置搬迁、AI 生成等流程产物)时,自证须包含关键数据产出断言:产出字段已持久化到 spec 指定承载位置且值完整非空、搬迁承接与同一输入产出基线一致(按已加载接口测试规范执行);禁止仅以流程跑通/接口成功替代"
|
|
65
|
-
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock
|
|
65
|
+
- "自证/验证需启动服务或基础设施时,一律按已加载技术栈 skill 的隔离启动规范以隔离方式启动,隔离标识与建议端口以视图 Worktree 区块为准;隔离对象不限于数据库——为本变更启动的 mock 服务、消息队列/缓存等中间件、扫描分析平台等一切环境资源均须按隔离标识隔离,禁止复用既有实例或共享端口;启动前发现隔离环境有残留时,先按已加载的清理规范幂等清空再启动;残留判定遵循已加载清理规范的归属判别,无法确认归属本变更的进程一律不清"
|
|
66
66
|
- "涉及接口契约或数据口径变更(按已加载的接口测试规范判定)时,实施过程中即时更新/新建与变更接口对应的 `.http` 测试脚本(已有脚本更新为匹配变更后契约,尚无脚本则新建,并同步 SQL 前置数据脚本),按已加载接口测试规范的完整执行顺序(含 SQL 前置数据准备、隔离启动基础设施与服务、执行 `.http` 脚本)执行接口测试,确保变更内容真实有效;执行结果(附执行顺序与覆盖接口清单)经 test_results 参数提交;修复被驳回 issue 后再次提交前同样须执行"
|
|
67
67
|
- "自证完成后停止本次启动的服务/基础设施实例,不留残留"
|
|
68
68
|
- "环境/基础设施问题(schema 缺失、DDL 未执行、依赖未安装)应通过 migration/初始化脚本等代码层面解决;仅生产级凭据、真实第三方资源、人工运维才走 blocker/豁免"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifrankwang/openspec-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.139.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",
|
|
@@ -55,7 +55,7 @@ const TOOL_SPECS: Record<string, ToolSpec> = {
|
|
|
55
55
|
},
|
|
56
56
|
opx_orch_complete_task_group: {
|
|
57
57
|
description:
|
|
58
|
-
"完成任务组收尾。非最后任务组仅做门禁与范围标记(不合并、不销毁);最后一个任务组收口时把 change 分支(change/{changeId})一次性合并回 baseBranch
|
|
58
|
+
"完成任务组收尾。非最后任务组仅做门禁与范围标记(不合并、不销毁);最后一个任务组收口时把 change 分支(change/{changeId})一次性合并回 baseBranch(漂移含任一非文档文件或文本冲突时回退到收尾验证 verify_cleanup 并返回 blocked,纯文档漂移直接合并收口),成功后销毁 worktree 并删分支。须在收尾验证(verify_cleanup)通过后调用。主仓库本地改动文件与合并写入文件重合、或存在部分暂存文件时中止并返回 blocked(保留 worktree/分支);主仓库无关脏文件不阻塞合并。",
|
|
59
59
|
schema: completeTaskGroupSchema,
|
|
60
60
|
execute: (args, ctx) => completeTaskGroupExecute(args as any, ctx),
|
|
61
61
|
},
|
package/src/core/git.ts
CHANGED
|
@@ -169,6 +169,16 @@ export async function isAncestor(worktree: string, a: string, b: string): Promis
|
|
|
169
169
|
throw new Error(`无法判断 "${a}" 是否为 "${b}" 的祖先:${r.stderr}`)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/** 查询基准侧漂移文件清单:`git diff --name-only --no-renames <sourceBranch>...<targetBranch>`
|
|
173
|
+
* 三点区间 = merge-base(source, target) 到 target 的内容差异(基准侧自分支切出点以来的净变化,非 commit 口径)。
|
|
174
|
+
* 参数方向固定:source 传变更分支,target 传基准分支(切出点在 source 一侧)。
|
|
175
|
+
* 返回 null 表示 git 查询失败,调用方须按非文档保守回退。 */
|
|
176
|
+
export async function listBranchDriftFiles(worktree: string, sourceBranch: string, targetBranch: string): Promise<string[] | null> {
|
|
177
|
+
const r = await runGitChecked(worktree, ["diff", "--name-only", "--no-renames", `${sourceBranch}...${targetBranch}`])
|
|
178
|
+
if (!r.success) return null
|
|
179
|
+
return parseDiffNameOnly(r.stdout)
|
|
180
|
+
}
|
|
181
|
+
|
|
172
182
|
/** 校验本地分支存在(refs/heads/<name> 严格本地命名空间,不含远端跟踪 ref)。 */
|
|
173
183
|
export async function isLocalBranch(worktree: string, branch: string): Promise<boolean> {
|
|
174
184
|
const r = await runGitChecked(worktree, ["rev-parse", "--verify", `refs/heads/${branch}`])
|
|
@@ -3,7 +3,7 @@ import { rmdir } from "node:fs/promises"
|
|
|
3
3
|
import type { OrchestrateState, TaskItem, TaskStatus, WorkflowMode, ReviewScope } from "../types.ts"
|
|
4
4
|
import { BUILD_PHASE_TARGETS, REVIEW_LAYERS, REVIEW_VERIFY_STEPS, SIMPLE_REVIEW_STEPS, REVIEW_TASK_GROUP_ID } from "../types.ts"
|
|
5
5
|
import { agentToReviewLayer } from "../constants.ts"
|
|
6
|
-
import { runGit, runGitChecked, getCurrentBranch, getMergeBase, isAncestor, isWorktreeClean, markTaskGroupCheckboxesComplete, mergeBranchToTarget, discoverDiskWorktrees, detectMainRepoPollution, detectChanges, removeTaskGroupWorktree, isLocalBranch, listLocalBranches, pathExists, type DetectChangesResult } from "../git.ts"
|
|
6
|
+
import { runGit, runGitChecked, getCurrentBranch, getMergeBase, isAncestor, isWorktreeClean, listBranchDriftFiles, markTaskGroupCheckboxesComplete, mergeBranchToTarget, discoverDiskWorktrees, detectMainRepoPollution, detectChanges, removeTaskGroupWorktree, isLocalBranch, listLocalBranches, pathExists, type DetectChangesResult } from "../git.ts"
|
|
7
7
|
import { readStateByWorktree, readStateByChangeId, writeState, writeContextToWorktree, getLockPath, acquireLock, releaseLock } from "../state.ts"
|
|
8
8
|
import { generateIsolationNamespace } from "../namespace.ts"
|
|
9
9
|
import { readExemptions } from "../exemptions.ts"
|
|
@@ -1193,6 +1193,14 @@ function renderReviewIssueSummary(item: WorkItem): string {
|
|
|
1193
1193
|
return lines.join("\n")
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
/** 漂移内容纯文档判定:null(查询失败)按非文档保守回退;空清单 = 内容无净变化(如基准上改了又回滚)视为纯文档;
|
|
1197
|
+
* 全部文件后缀属文档格式才放行。.txt 刻意排除(requirements.txt 等依赖清单具代码语义)。 */
|
|
1198
|
+
const DOCUMENTATION_EXTENSIONS = [".md", ".mdx", ".markdown", ".rst", ".adoc"]
|
|
1199
|
+
function isDocumentationOnly(files: string[] | null): boolean {
|
|
1200
|
+
if (files === null) return false
|
|
1201
|
+
return files.every((f) => DOCUMENTATION_EXTENSIONS.some((ext) => f.endsWith(ext)))
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1196
1204
|
/**
|
|
1197
1205
|
* 最后任务组收口被基准分支漂移/文本冲突拦截时的回退:
|
|
1198
1206
|
* 该任务组回退到收尾验证(verify_cleanup)等待重新收口——phase 回退 + clearStepTags + 按 recovery
|
|
@@ -1321,24 +1329,38 @@ async function completeTaskGroupLocked(params: { change_id: string }, ctx: ToolC
|
|
|
1321
1329
|
|
|
1322
1330
|
// 最后任务组 → change 级收口:合并 change 分支回基准分支(一次性),成功后销毁 worktree 并删分支
|
|
1323
1331
|
const mergeTarget = state.baseBranch
|
|
1332
|
+
// 纯文档漂移直通时的事实说明:仅在本次收口实际发生直通时非空;合并 blocked/conflict 分支不带该说明
|
|
1333
|
+
let docDriftNote = ""
|
|
1324
1334
|
if (branchName) {
|
|
1325
1335
|
// create-or-reuse(合并目标解析前):在途旧模型会话升级时从当前基准 tip 创建,天然含已合入代码
|
|
1326
1336
|
await ensureChangeBranch(ctx.worktree, branchName, mergeTarget)
|
|
1327
1337
|
|
|
1328
|
-
// 前置漂移检查(独立封装):基准 tip 须为 change
|
|
1338
|
+
// 前置漂移检查(独立封装):基准 tip 须为 change 分支祖先;不满足时先比对基准侧净变化内容——
|
|
1339
|
+
// 纯文档漂移(含空清单 = 无净变化)不回退、直接合并收口;含任一非文档文件或清单查询失败 → 回退收尾验证并 blocked
|
|
1329
1340
|
if (!(await isAncestor(ctx.worktree, mergeTarget, branchName))) {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1341
|
+
const driftFiles = await listBranchDriftFiles(ctx.worktree, branchName, mergeTarget)
|
|
1342
|
+
if (!isDocumentationOnly(driftFiles)) {
|
|
1343
|
+
const workflow = loadWorkflowFile(resolveWorkflowPath(state))
|
|
1344
|
+
const rolledBack = rollbackToCleanupStep(item, workflow)
|
|
1345
|
+
if (rolledBack) await writeState(ctx.worktree, state)
|
|
1346
|
+
return renderFinalizeBlocked(
|
|
1347
|
+
[
|
|
1348
|
+
`基准分支 \`${mergeTarget}\` 已推进(与变更分支 \`${branchName}\` 存在漂移):基准分支最新提交未包含在变更分支历史中,直接合并会遗漏基准分支新内容。`,
|
|
1349
|
+
],
|
|
1350
|
+
[
|
|
1351
|
+
`在 worktree 内执行 \`git merge ${mergeTarget}\` 合入基准分支最新代码并解决冲突,完成回归验证后重新提交收尾验证(opx_agent_submit,step_id="verify_cleanup"),通过后再调用 opx_orch_complete_task_group 重新收口。`,
|
|
1352
|
+
],
|
|
1353
|
+
{ branchName, mergeTarget, rolledBack, checkboxWarning },
|
|
1354
|
+
)
|
|
1355
|
+
}
|
|
1356
|
+
const files = driftFiles ?? []
|
|
1357
|
+
const driftList = files.length > 10
|
|
1358
|
+
? `${files.slice(0, 10).join(", ")} 等 ${files.length} 个`
|
|
1359
|
+
: files.join(", ")
|
|
1360
|
+
docDriftNote =
|
|
1361
|
+
`- **基准漂移(纯文档)**: 基准分支 \`${mergeTarget}\` 相对变更分支切出点净变化 ${files.length} 个文件` +
|
|
1362
|
+
(files.length > 0 ? `(${driftList})` : "") +
|
|
1363
|
+
",均为文档,不影响代码语义,未回退收尾验证直接合并。"
|
|
1342
1364
|
}
|
|
1343
1365
|
|
|
1344
1366
|
const mergeResult = await mergeBranchToTarget(ctx.worktree, branchName, mergeTarget)
|
|
@@ -1394,7 +1416,7 @@ async function completeTaskGroupLocked(params: { change_id: string }, ctx: ToolC
|
|
|
1394
1416
|
}
|
|
1395
1417
|
item.metadata["completed_at"] = new Date().toISOString()
|
|
1396
1418
|
await writeState(ctx.worktree, state)
|
|
1397
|
-
const notes = [cleanupNote, checkboxWarning, cleanupResidualWarning].filter(Boolean)
|
|
1419
|
+
const notes = [docDriftNote, cleanupNote, checkboxWarning, cleanupResidualWarning].filter(Boolean)
|
|
1398
1420
|
const doneMessage = branchName
|
|
1399
1421
|
? `任务组已完成并合并到 "${mergeTarget}"。`
|
|
1400
1422
|
: "任务组已完成(无变更分支引用,跳过合并)。"
|