@ifrankwang/openspec-agents 0.136.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 +277 -79
- package/README.md +3 -2
- 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
|
@@ -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
|
}
|