@ifrankwang/openspec-agents 0.132.0 → 0.132.1
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 +40 -11
- package/package.json +1 -1
- package/src/core/git.ts +45 -10
- package/src/core/tools/lifecycle.ts +2 -2
package/.mcp-server/cli.mjs
CHANGED
|
@@ -29775,7 +29775,7 @@ var defaultRunner = {
|
|
|
29775
29775
|
},
|
|
29776
29776
|
async runChecked(worktree, args) {
|
|
29777
29777
|
const { stdout, stderr, exitCode } = await execGit(["-C", worktree, ...args]);
|
|
29778
|
-
return { success: exitCode === 0, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
29778
|
+
return { success: exitCode === 0, stdout: stdout.trim(), stderr: stderr.trim(), exitCode };
|
|
29779
29779
|
}
|
|
29780
29780
|
};
|
|
29781
29781
|
var gitRunner = defaultRunner;
|
|
@@ -29890,16 +29890,45 @@ async function markTaskGroupCheckboxesComplete(worktree, changeId, taskGroupId)
|
|
|
29890
29890
|
throw new Error(`git commit tasks.md 失败:${commitResult.stderr}`);
|
|
29891
29891
|
}
|
|
29892
29892
|
}
|
|
29893
|
-
async function mergeBranchToTarget(
|
|
29894
|
-
const
|
|
29895
|
-
if (
|
|
29896
|
-
|
|
29893
|
+
async function mergeBranchToTarget(repoDir, sourceBranch, targetBranch) {
|
|
29894
|
+
const ancestor = await runGitChecked(repoDir, ["merge-base", "--is-ancestor", sourceBranch, targetBranch]);
|
|
29895
|
+
if (ancestor.success)
|
|
29896
|
+
return { success: true, conflict: false };
|
|
29897
|
+
if (ancestor.exitCode !== 1) {
|
|
29898
|
+
throw new Error(`无法判断 "${sourceBranch}" 是否已并入 "${targetBranch}":${ancestor.stderr}`);
|
|
29897
29899
|
}
|
|
29898
|
-
const
|
|
29899
|
-
if (!
|
|
29900
|
-
|
|
29900
|
+
const mergeTree = await runGitChecked(repoDir, ["merge-tree", "--write-tree", "--messages", targetBranch, sourceBranch]);
|
|
29901
|
+
if (!mergeTree.success) {
|
|
29902
|
+
if (mergeTree.exitCode !== 1) {
|
|
29903
|
+
throw new Error(`无法试算 "${sourceBranch}" → "${targetBranch}" 的合并:${mergeTree.stderr}`);
|
|
29904
|
+
}
|
|
29901
29905
|
return { success: false, conflict: true };
|
|
29902
29906
|
}
|
|
29907
|
+
const treeOid = mergeTree.stdout.split(`
|
|
29908
|
+
`)[0].trim();
|
|
29909
|
+
if (!treeOid)
|
|
29910
|
+
throw new Error("merge-tree 未返回树对象,无法生成合并提交。");
|
|
29911
|
+
const targetOid = (await runGit(repoDir, ["rev-parse", targetBranch])).trim();
|
|
29912
|
+
const sourceOid = (await runGit(repoDir, ["rev-parse", sourceBranch])).trim();
|
|
29913
|
+
if (!targetOid || !sourceOid) {
|
|
29914
|
+
throw new Error(`无法解析分支 OID:target="${targetOid}" source="${sourceOid}"`);
|
|
29915
|
+
}
|
|
29916
|
+
const commitOid = (await runGit(repoDir, [
|
|
29917
|
+
"commit-tree",
|
|
29918
|
+
treeOid,
|
|
29919
|
+
"-p",
|
|
29920
|
+
targetOid,
|
|
29921
|
+
"-p",
|
|
29922
|
+
sourceOid,
|
|
29923
|
+
"-m",
|
|
29924
|
+
`Merge branch '${sourceBranch}'`
|
|
29925
|
+
])).trim();
|
|
29926
|
+
if (!commitOid)
|
|
29927
|
+
throw new Error("git commit-tree 失败:无法创建合并提交。");
|
|
29928
|
+
const updateRef = await runGitChecked(repoDir, ["update-ref", `refs/heads/${targetBranch}`, commitOid, targetOid]);
|
|
29929
|
+
if (!updateRef.success) {
|
|
29930
|
+
throw new Error(`基础分支 "${targetBranch}" 已被并发推进,合并提交未写入(update-ref 旧值校验失败):${updateRef.stderr}`);
|
|
29931
|
+
}
|
|
29903
29932
|
return { success: true, conflict: false };
|
|
29904
29933
|
}
|
|
29905
29934
|
async function discoverDiskWorktrees(worktree) {
|
|
@@ -36569,8 +36598,8 @@ async function completeTaskGroupExecute(params, ctx) {
|
|
|
36569
36598
|
return [
|
|
36570
36599
|
`- **status**: blocked`,
|
|
36571
36600
|
`- **merge_conflict**: true`,
|
|
36572
|
-
`- **说明**: 合并到 "${mergeTarget}"
|
|
36573
|
-
`- **处理**:
|
|
36601
|
+
`- **说明**: 合并到 "${mergeTarget}" 时发生冲突,未产生任何变更(分支引用未动,无半成品合并)。`,
|
|
36602
|
+
`- **处理**: 请自行执行 \`git checkout ${mergeTarget} && git merge ${branchName}\` 解决冲突并提交,完成后重新调用 opx_orch_complete_task_group 完成收尾(重调时工具会自动识别已合并并继续)。worktree 与分支已保留。`
|
|
36574
36603
|
].join(`
|
|
36575
36604
|
`);
|
|
36576
36605
|
}
|
|
@@ -37684,7 +37713,7 @@ async function ensureDefaultUnattended(args, ctx) {
|
|
|
37684
37713
|
}
|
|
37685
37714
|
} catch {}
|
|
37686
37715
|
}
|
|
37687
|
-
var PKG_VERSION = "0.132.
|
|
37716
|
+
var PKG_VERSION = "0.132.1";
|
|
37688
37717
|
function buildMcpServer(worktree, opts = {}) {
|
|
37689
37718
|
const mcp = new McpServer({ name: "openspec-agents", version: PKG_VERSION });
|
|
37690
37719
|
for (const [name, spec] of Object.entries(TOOL_SPECS)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifrankwang/openspec-agents",
|
|
3
|
-
"version": "0.132.
|
|
3
|
+
"version": "0.132.1",
|
|
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
|
@@ -17,7 +17,7 @@ export interface GitRunner {
|
|
|
17
17
|
runChecked(
|
|
18
18
|
worktree: string,
|
|
19
19
|
args: string[]
|
|
20
|
-
): Promise<{ success: boolean; stdout: string; stderr: string }>
|
|
20
|
+
): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number }>
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function execGit(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
@@ -43,7 +43,7 @@ const defaultRunner: GitRunner = {
|
|
|
43
43
|
},
|
|
44
44
|
async runChecked(worktree, args) {
|
|
45
45
|
const { stdout, stderr, exitCode } = await execGit(["-C", worktree, ...args])
|
|
46
|
-
return { success: exitCode === 0, stdout: stdout.trim(), stderr: stderr.trim() }
|
|
46
|
+
return { success: exitCode === 0, stdout: stdout.trim(), stderr: stderr.trim(), exitCode }
|
|
47
47
|
},
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -60,7 +60,7 @@ export async function runGit(worktree: string, args: string[]): Promise<string>
|
|
|
60
60
|
export async function runGitChecked(
|
|
61
61
|
worktree: string,
|
|
62
62
|
args: string[]
|
|
63
|
-
): Promise<{ success: boolean; stdout: string; stderr: string }> {
|
|
63
|
+
): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number }> {
|
|
64
64
|
return gitRunner.runChecked(worktree, args)
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -204,20 +204,55 @@ export async function markTaskGroupCheckboxesComplete(
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* 收尾裸合并:将任务组分支合并进基础分支,全程不触碰任何工作目录(worktreeless 底层命令)。
|
|
209
|
+
*
|
|
210
|
+
* 主仓库工作区状态(当前分支、未提交改动、index)不受影响;冲突时目标分支引用未动、磁盘无半成品。
|
|
211
|
+
* 序列:
|
|
212
|
+
* 1. `merge-base --is-ancestor` 幂等检查:源分支已是目标分支祖先(如人工解决冲突后重调)直接成功,
|
|
213
|
+
* 不产生空合并提交;
|
|
214
|
+
* 2. `merge-tree --write-tree`(需 git ≥ 2.38)内存试算合并树:退出码 1 = 冲突,直接返回;
|
|
215
|
+
* 3. `commit-tree` 生成双父合并提交,`update-ref` 以试算前目标分支 oid 作旧值校验推进(CAS,防并发覆盖)。
|
|
216
|
+
*
|
|
217
|
+
* @param repoDir 仓库内任意目录(底层命令不读取工作区,不影响 HEAD/index)
|
|
218
|
+
*/
|
|
207
219
|
export async function mergeBranchToTarget(
|
|
208
|
-
|
|
220
|
+
repoDir: string,
|
|
209
221
|
sourceBranch: string,
|
|
210
222
|
targetBranch: string
|
|
211
223
|
): Promise<{ success: boolean; conflict: boolean }> {
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
|
|
224
|
+
const ancestor = await runGitChecked(repoDir, ["merge-base", "--is-ancestor", sourceBranch, targetBranch])
|
|
225
|
+
if (ancestor.success) return { success: true, conflict: false }
|
|
226
|
+
// is-ancestor 退出码 1 = 尚未并入(继续合并);其余为分支缺失等真实错误
|
|
227
|
+
if (ancestor.exitCode !== 1) {
|
|
228
|
+
throw new Error(`无法判断 "${sourceBranch}" 是否已并入 "${targetBranch}":${ancestor.stderr}`)
|
|
215
229
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
|
|
231
|
+
const mergeTree = await runGitChecked(repoDir, ["merge-tree", "--write-tree", "--messages", targetBranch, sourceBranch])
|
|
232
|
+
if (!mergeTree.success) {
|
|
233
|
+
// merge-tree 退出码 1 = 冲突;其余为分支缺失等真实错误
|
|
234
|
+
if (mergeTree.exitCode !== 1) {
|
|
235
|
+
throw new Error(`无法试算 "${sourceBranch}" → "${targetBranch}" 的合并:${mergeTree.stderr}`)
|
|
236
|
+
}
|
|
219
237
|
return { success: false, conflict: true }
|
|
220
238
|
}
|
|
239
|
+
const treeOid = mergeTree.stdout.split("\n")[0].trim()
|
|
240
|
+
if (!treeOid) throw new Error("merge-tree 未返回树对象,无法生成合并提交。")
|
|
241
|
+
|
|
242
|
+
const targetOid = (await runGit(repoDir, ["rev-parse", targetBranch])).trim()
|
|
243
|
+
const sourceOid = (await runGit(repoDir, ["rev-parse", sourceBranch])).trim()
|
|
244
|
+
if (!targetOid || !sourceOid) {
|
|
245
|
+
throw new Error(`无法解析分支 OID:target="${targetOid}" source="${sourceOid}"`)
|
|
246
|
+
}
|
|
247
|
+
const commitOid = (await runGit(repoDir, [
|
|
248
|
+
"commit-tree", treeOid, "-p", targetOid, "-p", sourceOid, "-m", `Merge branch '${sourceBranch}'`,
|
|
249
|
+
])).trim()
|
|
250
|
+
if (!commitOid) throw new Error("git commit-tree 失败:无法创建合并提交。")
|
|
251
|
+
|
|
252
|
+
const updateRef = await runGitChecked(repoDir, ["update-ref", `refs/heads/${targetBranch}`, commitOid, targetOid])
|
|
253
|
+
if (!updateRef.success) {
|
|
254
|
+
throw new Error(`基础分支 "${targetBranch}" 已被并发推进,合并提交未写入(update-ref 旧值校验失败):${updateRef.stderr}`)
|
|
255
|
+
}
|
|
221
256
|
return { success: true, conflict: false }
|
|
222
257
|
}
|
|
223
258
|
|
|
@@ -774,8 +774,8 @@ export async function completeTaskGroupExecute(params: { change_id: string }, ct
|
|
|
774
774
|
return [
|
|
775
775
|
`- **status**: blocked`,
|
|
776
776
|
`- **merge_conflict**: true`,
|
|
777
|
-
`- **说明**: 合并到 "${mergeTarget}"
|
|
778
|
-
`- **处理**:
|
|
777
|
+
`- **说明**: 合并到 "${mergeTarget}" 时发生冲突,未产生任何变更(分支引用未动,无半成品合并)。`,
|
|
778
|
+
`- **处理**: 请自行执行 \`git checkout ${mergeTarget} && git merge ${branchName}\` 解决冲突并提交,完成后重新调用 opx_orch_complete_task_group 完成收尾(重调时工具会自动识别已合并并继续)。worktree 与分支已保留。`,
|
|
779
779
|
].join("\n")
|
|
780
780
|
}
|
|
781
781
|
}
|