@ifrankwang/openspec-agents 0.136.0 → 0.138.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.
@@ -1,11 +1,12 @@
1
1
  # simple task workflow:simple 模式的 OpenSpec change 任务流转
2
- # 对应编排阶段:实现(in_progress)→ 合并审查(review)→ 完成(done);无 analyze / verify_* / verify_cleanup 环节
2
+ # 对应编排阶段:实现(in_progress)→ 合并审查(review)→ 完成(done);无 analyze / verify_* 环节。
3
+ # verify_cleanup 仅作收口漂移/冲突回退落点(quality_review on_pass 直达 done,正常流转不经过)
3
4
  # 语义归属:step 级 instructions/constraints 定义各 step 专属语义;
4
5
  # 跨所有 step 共享内容收敛到顶层 common(step 自动继承);非 step 归属的引擎状态文案不进配置。
5
6
  # 占位符 {{key}} 经 interpolateText 注入动态值,缺值保留原文。
6
7
  # 注意:simple 无 analyze 环节,execution_boundary 永不写入,step constraints 不得含
7
8
  # {{allowed_directories}} / {{allowed_packages}} / {{notes}} 边界占位符(缺值会原样渲染),执行边界默认整个 worktree。
8
- # implement/quality_review 的压制与隔离/清理约束与 task.yaml 同步维护
9
+ # implement/quality_review/verify_cleanup 的压制与隔离/清理约束与 task.yaml 同步维护
9
10
  id: task-simple
10
11
  name: Simple Task Workflow
11
12
  max_retries: 10
@@ -43,7 +44,7 @@ phases:
43
44
  - "提交前完成自检并经 self_check_results 汇总:每项检查须含执行命令与结果摘要(证据可核验,供审查者分级复验对照与核验申报),禁止仅报结论不附证据"
44
45
  - "提交前按已加载的维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次变更文件逐项自检(聚焦本轮变更文件,不扩散全量代码),自检结论与工具检查结果一并汇总进 self_check_results;修复被驳回 issue 后再次提交前同样须按维度规范类 skill 对变更文件自检后再提交"
45
46
  - "自检须覆盖已加载的维度规范类 skill 的全部检查点,self_check_results 按维度逐项列结论(通过/遗留及理由),禁止仅汇总工具检查结果"
46
- - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化)凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
47
+ - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化、依赖清单/构建配置变更(合并后需在主仓库重装依赖、重新构建))凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
47
48
  - "修复完成的 issue 经 fixed_issue_ids 上报后进入待复核(review)状态,终态由报源 reviewer 复核裁定;不可修 issue 经 exempt_issue_ids 申请豁免(申请后进入待裁定(review)状态,由报源 reviewer 在后续审查轮次裁定,被驳回的 issue 回待修复);Info 级 issue 不阻塞提交,无需申请豁免;全部 blocking issue 均已上报修复或申请豁免后才可 passed 提交推进"
48
49
  - "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由(commit message 可补充),并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复;被驳回后须移除压制并真实修复"
49
50
  - "遇外部依赖、凭证、真实输入或必须 stub/降级/跳过验收才能继续时,以 verdict=failed 提交 blocker(含 source_role、task_id、category、description、evidence、attempted_actions、options)"
@@ -98,3 +99,22 @@ phases:
98
99
  - "禁止以 SonarQube 平台状态修改(SAFE/WONTFIX/False Positive/Confirmed 等)作为豁免手段:平台状态仅作用于当前 SonarQube 项目实例,每次 change 使用独立项目 key,不跨 change 生效;存量问题跨 change 豁免必须经 exempt_issue_ids 申请 → 报源 reviewer 裁定 dismissed 的流程"
99
100
  - "压制类 issue 严重级别至少 Low"
100
101
  - "有实质变更时,低成本实跑项的工具检查及首轮对抗审查覆盖全量代码;非本轮变更文件的工具违规同样必须映射为 issue(命中项目级豁免清单的按 Info,不重新豁免;其余非本轮引入问题按原严重级别上报),不得以不在变更清单或未越界修改为由丢弃;高成本项按核验申报口径执行(复核轮按轮次口径收敛,不重走首轮全量)"
102
+ - id: verify_cleanup
103
+ agents:
104
+ - id: openspec-developer
105
+ capability_tags: [cleanup, api-testing, dev-practices, efficiency]
106
+ transitions:
107
+ on_pass: done
108
+ on_fail: verify_cleanup
109
+ instructions:
110
+ - "将基准分支({{base_branch}})最新代码合并进当前工作分支(git merge 基准分支),解决合并冲突;修复范围仅限合并冲突涉及文件与回归失败定位文件(改动仅用于解决合并引入的问题,禁止借机扩大范围)"
111
+ - "合并后回归复验:按已加载的开发实践规范执行构建验证;按已加载的接口测试规范启动服务并执行接口测试(含关键数据产出/搬迁回归断言);全部通过才算通过,不分失败来源(含基准分支新代码引入的失败)"
112
+ - "按已加载的清理规范执行幂等清理:停止残留的服务实例、清理本 change 的隔离环境、删除残留的扫描分析项目;无对应 skill 规范时跳过并在申报中注明"
113
+ - "清理后确认工作分支 git status 干净(含未跟踪文件)"
114
+ - "逐项申报 validation_steps(completed + 证据;未执行项以 completed=false + 结构化 skip_reason 申报:{\"item\":\"<必做项>\",\"category\":\"<降级类别>\",\"adjudication\":\"user_response|unattended_auto|env_unavailable\",\"note\":\"<说明>\"})"
115
+ - "自报 issue 必须在本 step 内收敛至终态(修复后自行复核通过,或申请豁免后自行裁定)后再提交;报 issue 时显式声明归因维度 dimension(该维度仅作归因标签,不代表对应维度 reviewer 会复核此 issue)"
116
+ - "存在无法解决的残留或回归失败 → verdict=failed 并报 Low+ issue(显式声明归因维度 dimension)"
117
+ - "完成后调用 opx_agent_submit 提交裁决"
118
+ constraints:
119
+ - "可 bash 执行构建/测试/清理命令;禁止 edit/write 任何 {{worktree_path}} 之外的文件"
120
+ - "工具调用边界:仅可调用 opx_status、opx_agent_submit、question"
@@ -74,7 +74,7 @@ phases:
74
74
  - "提交前完成自检并经 self_check_results 汇总"
75
75
  - "提交前按已加载的维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次变更文件逐项自检(聚焦本轮变更文件,不扩散全量代码),自检结论与工具检查结果一并汇总进 self_check_results;修复被驳回 issue 后再次提交前同样须按维度规范类 skill 对变更文件自检后再提交"
76
76
  - "自检须覆盖已加载的维度规范类 skill 的全部检查点,self_check_results 按维度逐项列结论(通过/遗留及理由),禁止仅汇总工具检查结果"
77
- - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化)凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
77
+ - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化、依赖清单/构建配置变更(合并后需在主仓库重装依赖、重新构建))凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
78
78
  - "修复完成的 issue 经 fixed_issue_ids 上报后进入待复核(review)状态,终态由报源 reviewer 复核裁定;不可修 issue 经 exempt_issue_ids 申请豁免(申请后进入待裁定(review)状态,由报源 reviewer 在后续审查轮次裁定,被驳回的 issue 回待修复);Info 级 issue 不阻塞提交,无需申请豁免;全部 blocking issue 均已上报修复或申请豁免后才可 passed 提交推进"
79
79
  - "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由(commit message 可补充),并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复;被驳回后须移除压制并真实修复"
80
80
  - "遇外部依赖、凭证、真实输入或必须 stub/降级/跳过验收才能继续时,以 verdict=failed 提交 blocker(含 source_role、task_id、category、description、evidence、attempted_actions、options)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifrankwang/openspec-agents",
3
- "version": "0.136.0",
3
+ "version": "0.138.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",
@@ -43,7 +43,7 @@ const TOOL_SPECS: Record<string, ToolSpec> = {
43
43
  },
44
44
  opx_orch_set_worktree: {
45
45
  description:
46
- "确保目标组的 git worktree 就绪。若已存在则复用,否则按规范自动创建(分支 task-group/{changeId}/{taskGroupId},路径 .worktree/{changeId}/task-group-{taskGroupId})。只补齐资源,不改变阶段。",
46
+ "确保目标组的 git worktree 就绪。change 会话按 change 模型 create-or-reuse:分支 change/{changeId} 不存在时从基准分支 tip 创建,worktree 常驻于 .worktree/{changeId}/ws,全部任务组串行复用(复用校验:目录缺失自愈重建、openspec 文档脏自动提交、代码文件脏拒绝)。只补齐资源,不改变阶段。",
47
47
  schema: setWorktreeSchema,
48
48
  execute: (args, ctx) => setWorktreeExecute(args as any, ctx),
49
49
  },
@@ -55,7 +55,7 @@ const TOOL_SPECS: Record<string, ToolSpec> = {
55
55
  },
56
56
  opx_orch_complete_task_group: {
57
57
  description:
58
- "完成任务组收尾:合并 task-group 分支到 baseBranch → 清理 worktree 与分支。须在收尾验证(verify_cleanup)通过后调用。合并冲突、主仓库本地改动文件与合并写入文件重合、或存在部分暂存文件时中止并返回 blocked(保留 worktree/分支);主仓库无关脏文件不阻塞合并。",
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
@@ -3,7 +3,7 @@ import { execFile } from "node:child_process"
3
3
  import { readFile, writeFile, stat, rm, realpath } from "node:fs/promises"
4
4
 
5
5
  /** 判断路径是否存在于磁盘(fs 层判断,git 命令结果不作为存在性依据)。 */
6
- async function pathExists(p: string): Promise<boolean> {
6
+ export async function pathExists(p: string): Promise<boolean> {
7
7
  try {
8
8
  await stat(p)
9
9
  return true
@@ -157,6 +157,28 @@ export async function getMergeBase(worktree: string, baseBranch: string): Promis
157
157
  return runGit(worktree, ["merge-base", "HEAD", baseBranch])
158
158
  }
159
159
 
160
+ /**
161
+ * 判断 <a> 是否为 <b> 的祖先(`merge-base --is-ancestor`)。
162
+ * 退出码 0=是祖先、1=不是祖先;其余退出码(分支/oid 缺失等真实错误)抛错,不静默降级。
163
+ * 消费点:change 收口的基准分支漂移检查(基准 tip 须为 change 分支祖先)与 scope 端点历史校验。
164
+ */
165
+ export async function isAncestor(worktree: string, a: string, b: string): Promise<boolean> {
166
+ const r = await runGitChecked(worktree, ["merge-base", "--is-ancestor", a, b])
167
+ if (r.success) return true
168
+ if (r.exitCode === 1) return false
169
+ throw new Error(`无法判断 "${a}" 是否为 "${b}" 的祖先:${r.stderr}`)
170
+ }
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
+
160
182
  /** 校验本地分支存在(refs/heads/<name> 严格本地命名空间,不含远端跟踪 ref)。 */
161
183
  export async function isLocalBranch(worktree: string, branch: string): Promise<boolean> {
162
184
  const r = await runGitChecked(worktree, ["rev-parse", "--verify", `refs/heads/${branch}`])
@@ -355,7 +377,7 @@ export async function mergeBranchToTarget(
355
377
  success: false,
356
378
  conflict: false,
357
379
  blockedMessage: [
358
- `- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且以下本地改动文件与任务组合并将写入的文件重合;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品):`,
380
+ `- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且以下本地改动文件与变更分支合并将写入的文件重合;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品):`,
359
381
  ...overlap.map((f) => ` - \`${f}\``),
360
382
  `- **处理**: 请先 commit/stash/移除上述文件的本地改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`,
361
383
  ].join("\n"),
@@ -440,6 +462,10 @@ export async function mergeBranchToTarget(
440
462
  return { success: true, conflict: false }
441
463
  }
442
464
 
465
+ /** 可恢复磁盘 worktree 的分支前缀集合:change/(change 常驻 worktree)、review/(独立审查会话)、
466
+ * task-group/(旧每任务组模型残留,过渡期保留识别以便恢复/人工清理)。 */
467
+ export const RESTORABLE_WORKTREE_BRANCH_PREFIXES = ["change/", "review/", "task-group/"] as const
468
+
443
469
  export async function discoverDiskWorktrees(worktree: string): Promise<{ branch: string; path: string }[]> {
444
470
  const result: { branch: string; path: string }[] = []
445
471
  const wtList = await runGit(worktree, ["worktree", "list"])
@@ -447,8 +473,7 @@ export async function discoverDiskWorktrees(worktree: string): Promise<{ branch:
447
473
  const m = line.match(/^(\S+)\s+[0-9a-f]+\s+\[(.+?)\]/)
448
474
  if (m) {
449
475
  const branch = m[2].trim()
450
- // 独立审查会话 worktree 分支(review/<sessionId>)与任务组分支(task-group/)同为可恢复磁盘痕迹
451
- if (branch.startsWith("task-group/") || branch.startsWith("review/")) {
476
+ if (RESTORABLE_WORKTREE_BRANCH_PREFIXES.some((p) => branch.startsWith(p))) {
452
477
  result.push({ branch, path: m[1].trim() })
453
478
  }
454
479
  }