@ifrankwang/openspec-agents 0.135.0 → 0.137.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 +284 -85
- package/README.md +3 -2
- package/assets/skills/openspec-standard/templates/openspec/config.yaml +1 -1
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/README.md +3 -2
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/schema.yaml +4 -1
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/clarify.md +4 -3
- package/assets/workflows/task-simple.yaml +23 -3
- package/assets/workflows/task.yaml +1 -1
- package/package.json +1 -1
- package/src/adapters/mcp-common/server.ts +2 -2
- package/src/core/git.ts +19 -4
- package/src/core/tools/lifecycle.ts +355 -80
- package/src/core/tools/schemas.ts +4 -4
- package/src/core/types.ts +2 -1
- package/src/core/views.ts +1 -1
- package/src/core/workflow/engine.ts +8 -0
- package/src/core/workflow/status.ts +17 -9
|
@@ -140,7 +140,10 @@ artifacts:
|
|
|
140
140
|
the user and stop for confirmation. The summary must state: what the
|
|
141
141
|
change does in one sentence; whether it touches contracts (API
|
|
142
142
|
parameters or DDL); the chosen approach and its key trade-offs
|
|
143
|
-
versus the alternatives; the capabilities affected.
|
|
143
|
+
versus the alternatives; the capabilities affected. Present it as a
|
|
144
|
+
plain message — do NOT use the question tool for this confirmation
|
|
145
|
+
(the question tool belongs to the interview rounds, not to this
|
|
146
|
+
confirmation) — and stop for the user's free-text reply. Interview
|
|
144
147
|
answers do NOT count as confirmation — the confirmation must be a
|
|
145
148
|
direct user response to this summary. If the user requests changes,
|
|
146
149
|
revise the summary and re-present; loop until the user explicitly
|
package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/clarify.md
CHANGED
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
|
|
33
33
|
<!-- The crystallized approach: chosen direction, key decisions, 2-3 alternatives
|
|
34
34
|
considered with tradeoffs, and the plan summary the user explicitly confirmed
|
|
35
|
-
(interview answers alone do not count as confirmation
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
(interview answers alone do not count as confirmation; the summary must be a
|
|
36
|
+
plain message listing the key points — never a question-tool prompt). This is
|
|
37
|
+
the HARD-GATE output — proposal, specs, design, and tasks must stay consistent
|
|
38
|
+
with what is recorded here. -->
|
|
38
39
|
|
|
39
40
|
## Authoring Tier
|
|
40
41
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# simple task workflow:simple 模式的 OpenSpec change 任务流转
|
|
2
|
-
# 对应编排阶段:实现(in_progress)→ 合并审查(review)→ 完成(done);无 analyze / verify_*
|
|
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
|
-
- "
|
|
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
|
-
- "
|
|
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.
|
|
3
|
+
"version": "0.137.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
|
|
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
|
-
"
|
|
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,18 @@ 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
|
+
|
|
160
172
|
/** 校验本地分支存在(refs/heads/<name> 严格本地命名空间,不含远端跟踪 ref)。 */
|
|
161
173
|
export async function isLocalBranch(worktree: string, branch: string): Promise<boolean> {
|
|
162
174
|
const r = await runGitChecked(worktree, ["rev-parse", "--verify", `refs/heads/${branch}`])
|
|
@@ -355,7 +367,7 @@ export async function mergeBranchToTarget(
|
|
|
355
367
|
success: false,
|
|
356
368
|
conflict: false,
|
|
357
369
|
blockedMessage: [
|
|
358
|
-
`- **原因**: 目标分支 \`${targetBranch}\`
|
|
370
|
+
`- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且以下本地改动文件与变更分支合并将写入的文件重合;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品):`,
|
|
359
371
|
...overlap.map((f) => ` - \`${f}\``),
|
|
360
372
|
`- **处理**: 请先 commit/stash/移除上述文件的本地改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`,
|
|
361
373
|
].join("\n"),
|
|
@@ -440,6 +452,10 @@ export async function mergeBranchToTarget(
|
|
|
440
452
|
return { success: true, conflict: false }
|
|
441
453
|
}
|
|
442
454
|
|
|
455
|
+
/** 可恢复磁盘 worktree 的分支前缀集合:change/(change 常驻 worktree)、review/(独立审查会话)、
|
|
456
|
+
* task-group/(旧每任务组模型残留,过渡期保留识别以便恢复/人工清理)。 */
|
|
457
|
+
export const RESTORABLE_WORKTREE_BRANCH_PREFIXES = ["change/", "review/", "task-group/"] as const
|
|
458
|
+
|
|
443
459
|
export async function discoverDiskWorktrees(worktree: string): Promise<{ branch: string; path: string }[]> {
|
|
444
460
|
const result: { branch: string; path: string }[] = []
|
|
445
461
|
const wtList = await runGit(worktree, ["worktree", "list"])
|
|
@@ -447,8 +463,7 @@ export async function discoverDiskWorktrees(worktree: string): Promise<{ branch:
|
|
|
447
463
|
const m = line.match(/^(\S+)\s+[0-9a-f]+\s+\[(.+?)\]/)
|
|
448
464
|
if (m) {
|
|
449
465
|
const branch = m[2].trim()
|
|
450
|
-
|
|
451
|
-
if (branch.startsWith("task-group/") || branch.startsWith("review/")) {
|
|
466
|
+
if (RESTORABLE_WORKTREE_BRANCH_PREFIXES.some((p) => branch.startsWith(p))) {
|
|
452
467
|
result.push({ branch, path: m[1].trim() })
|
|
453
468
|
}
|
|
454
469
|
}
|