@ifrankwang/openspec-agents 0.126.0 → 0.127.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 +31 -4
- package/LICENSE +21 -0
- package/README.md +77 -96
- package/package.json +8 -1
- package/src/adapters/deepseek-harness/index.ts +7 -0
- package/src/adapters/mcp-common/server.ts +1 -1
- package/src/adapters/plugin-common/index.ts +12 -3
- package/src/core/state.ts +2 -2
- package/src/core/tools/lifecycle.ts +51 -6
- package/src/core/tools/schemas.ts +1 -1
- package/src/core/tools/types.ts +3 -1
- package/src/core/types.ts +3 -1
package/.mcp-server/cli.mjs
CHANGED
|
@@ -35738,6 +35738,15 @@ function assertValidRecovery(recovery) {
|
|
|
35738
35738
|
}
|
|
35739
35739
|
}
|
|
35740
35740
|
}
|
|
35741
|
+
function otherTaskGroupsSettled(state, targetGroupId) {
|
|
35742
|
+
return state.workItems.filter((w) => w.id.startsWith("task:") && w.id !== `task:${targetGroupId}`).every((item) => {
|
|
35743
|
+
if (item.phase === "done" || item.metadata["completed_at"] !== undefined)
|
|
35744
|
+
return true;
|
|
35745
|
+
const noTags = Object.keys(item.tags).length === 0;
|
|
35746
|
+
const childrenSettled = item.children.filter((c) => c.type === "task").every((c) => c.phase === "todo" || isTerminalPhase(c.phase));
|
|
35747
|
+
return noTags && childrenSettled;
|
|
35748
|
+
});
|
|
35749
|
+
}
|
|
35741
35750
|
async function initExecute(params, ctx) {
|
|
35742
35751
|
assertOrchestrator(ctx, "opx_orch_init");
|
|
35743
35752
|
const args = { ...params };
|
|
@@ -35780,6 +35789,19 @@ async function initExecute(params, ctx) {
|
|
|
35780
35789
|
const baseBranch = args.base_branch || await getCurrentBranch(ctx.worktree);
|
|
35781
35790
|
let state = await readStateByChangeId(ctx.worktree, args.change_id);
|
|
35782
35791
|
const wasCurrentGroup = state?.taskGroupId === args.task_group_id;
|
|
35792
|
+
let modeSwitchNote = null;
|
|
35793
|
+
if (state && args.mode !== undefined && args.mode !== (state.mode ?? "full")) {
|
|
35794
|
+
const othersSettled = otherTaskGroupsSettled(state, args.task_group_id);
|
|
35795
|
+
const withinWindow = (args.task_group_id !== state.taskGroupId || args.recovery?.phase === "task_analysis") && othersSettled;
|
|
35796
|
+
if (!withinWindow) {
|
|
35797
|
+
throw new Error(`mode 参数与已固化的流程模式不一致:已固化 mode="${state.mode ?? "full"}"${state.mode === undefined ? "(旧变更未固化,读取时兜底 full)" : ""},传入值:"${args.mode}"。
|
|
35798
|
+
` + `当前场景不允许切换流程模式:切换任务组须其他任务组均已完成或从未激活;重制当前组仅支持 recovery.phase="task_analysis"(其他任务组同样须已完成或从未激活)。
|
|
35799
|
+
` + `请去掉 mode 参数继续沿用固化模式,或满足上述窗口条件后再切换。`);
|
|
35800
|
+
}
|
|
35801
|
+
const prevMode = state.mode;
|
|
35802
|
+
state.mode = args.mode;
|
|
35803
|
+
modeSwitchNote = `流程模式已从 ${prevMode ?? "full"} 切换为 ${args.mode}。`;
|
|
35804
|
+
}
|
|
35783
35805
|
if (!state) {
|
|
35784
35806
|
state = {
|
|
35785
35807
|
changeId: args.change_id,
|
|
@@ -35880,7 +35902,12 @@ async function initExecute(params, ctx) {
|
|
|
35880
35902
|
}
|
|
35881
35903
|
state.taskGroupId = args.task_group_id;
|
|
35882
35904
|
await writeState(ctx.worktree, state);
|
|
35883
|
-
|
|
35905
|
+
const parts = ["编排会话已初始化。"];
|
|
35906
|
+
if (args.recovery)
|
|
35907
|
+
parts.push(`已恢复到 ${args.recovery.phase} 阶段。`);
|
|
35908
|
+
if (modeSwitchNote)
|
|
35909
|
+
parts.push(modeSwitchNote);
|
|
35910
|
+
return parts.join("");
|
|
35884
35911
|
}
|
|
35885
35912
|
async function bindWorktreeRefs(item, worktreePath, branch, baseBranch, opts = {}) {
|
|
35886
35913
|
item.metadata["worktree_path"] = worktreePath;
|
|
@@ -36848,7 +36875,7 @@ var orchInitSchema = {
|
|
|
36848
36875
|
mode: {
|
|
36849
36876
|
type: "string",
|
|
36850
36877
|
enum: ["full", "simple"],
|
|
36851
|
-
description: "流程模式选择:full=完整流程(analyze→implement→三重审查+收尾验证);simple=精简流程(implement→quality_review→done
|
|
36878
|
+
description: "流程模式选择:full=完整流程(analyze→implement→三重审查+收尾验证);simple=精简流程(implement→quality_review→done,缺省)。首次新建编排状态时固化;已开始的变更仅在允许窗口内可更新:切换任务组(其他任务组均已完成或从未激活)或 recovery.phase=task_analysis 重制当前组(其他任务组同样须已完成或从未激活);其余场景传不同 mode 将报错。"
|
|
36852
36879
|
}
|
|
36853
36880
|
},
|
|
36854
36881
|
required: ["change_id", "task_group_id"],
|
|
@@ -37040,7 +37067,7 @@ function jsonSchemaToZod(schema2) {
|
|
|
37040
37067
|
var AGENT_ARG = "_agent";
|
|
37041
37068
|
var TOOL_SPECS = {
|
|
37042
37069
|
opx_orch_init: {
|
|
37043
|
-
description: "初始化编排会话。传入变更 ID 和任务组 ID,工具自动解析 tasks.md 提取全部任务组并解析目标组子任务。可通过 recovery 参数恢复到指定阶段。无 recovery 重复初始化当前任务组时保留其阶段和进度;切换到其它任务组时初始化该组。可选 mode 参数(full/simple,缺省 simple
|
|
37070
|
+
description: "初始化编排会话。传入变更 ID 和任务组 ID,工具自动解析 tasks.md 提取全部任务组并解析目标组子任务。可通过 recovery 参数恢复到指定阶段。无 recovery 重复初始化当前任务组时保留其阶段和进度;切换到其它任务组时初始化该组。可选 mode 参数(full/simple,缺省 simple)选择流程模式:首次新建状态时固化;已开始的变更仅切组(其他任务组均已完成或从未激活)或 recovery.phase=task_analysis 重制当前组(其他任务组同样须已完成或从未激活)时可更新,其余场景传不同 mode 报错。",
|
|
37044
37071
|
schema: orchInitSchema,
|
|
37045
37072
|
execute: (args, ctx) => initExecute(args, ctx)
|
|
37046
37073
|
},
|
|
@@ -37100,7 +37127,7 @@ async function ensureDefaultUnattended(args, ctx) {
|
|
|
37100
37127
|
}
|
|
37101
37128
|
} catch {}
|
|
37102
37129
|
}
|
|
37103
|
-
var PKG_VERSION = "0.
|
|
37130
|
+
var PKG_VERSION = "0.127.0";
|
|
37104
37131
|
function buildMcpServer(worktree, opts = {}) {
|
|
37105
37132
|
const mcp = new McpServer({ name: "openspec-agents", version: PKG_VERSION });
|
|
37106
37133
|
for (const [name, spec] of Object.entries(TOOL_SPECS)) {
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ifrankwang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,129 +1,110 @@
|
|
|
1
1
|
# openspec-agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
4
5
|
|
|
5
|
-
>
|
|
6
|
-
> 未来方向:向前扩展「探索 + 方案制定」,覆盖 OpenSpec 更早期环节。
|
|
6
|
+
> 让多个 AI 智能体按规范流程协作,把 OpenSpec 变更规范落地为经过审查的高质量代码。
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`openspec-agents` 是一组面向 AI 编码工具的插件,把 OpenSpec 的「需求 → 设计 → 任务拆分」成果,自动推进到「实现 → 验证 → 质量审查 → 收尾合并」的完整实施流程。主智能体负责调度,专职智能体分别承担实现、检查与质量把关,每一步都有门禁确认,全程可审计、可恢复。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## 特性
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- **多工具支持**:Claude Code、Codex、ZCode、DeepSeek Harness(DSH)、OpenCode 均可原生接入。
|
|
13
|
+
- **规范驱动**:直接消费 OpenSpec 变更规范(proposal / design / tasks / specs),流程与规范一一对应。
|
|
14
|
+
- **智能体团队**:不再是单个 AI 从头写到尾,而是「实现者 + 审查者 + 质量把关者」分工协作,结果更稳。
|
|
15
|
+
- **质量门禁**:实现之后经过工具检查、任务验证与多维度质量审查;发现的问题自动回退修复,也可按规则豁免并留痕。
|
|
16
|
+
- **隔离执行**:每个变更在独立的环境中实施,互不干扰;变更完成后合并并清理。
|
|
17
|
+
- **过程可审计**:进度、结论、豁免与恢复点全程记录,随时可以接着上次的进度继续。
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
## 快速开始
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
1. 准备环境:**Node.js ≥ 23.6**(或 Bun)与 **git**。
|
|
22
|
+
2. 按下方「安装」选择你正在使用的 AI 工具完成安装。
|
|
23
|
+
3. 重启该工具后,把下面「一句话接入」的提示词发给你的 AI 即可开始。
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
## 安装
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
按你使用的 AI 工具选择一种方式:
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
### Claude Code
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
```bash
|
|
32
|
+
claude plugin marketplace add https://github.com/ifrankwang/claude-code-plugins
|
|
33
|
+
claude plugin install openspec-agents@ifrankwang
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Codex
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
codex plugin marketplace add https://github.com/ifrankwang/codex-plugins
|
|
40
|
+
codex plugin add openspec-agents@ifrankwang
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ZCode
|
|
44
|
+
|
|
45
|
+
打开 ZCode 插件页,添加市场 `ifrankwang/zcode-plugins`,然后安装 `openspec-agents`。
|
|
46
|
+
|
|
47
|
+
### DeepSeek Harness(DSH)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
dsh plugin --profile web add @ifrankwang/openspec-agents
|
|
51
|
+
```
|
|
25
52
|
|
|
26
|
-
|
|
53
|
+
安装后重启 `dsh web` 即可。
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
### OpenCode
|
|
29
56
|
|
|
30
|
-
|
|
57
|
+
```bash
|
|
58
|
+
npm install -D @ifrankwang/openspec-agents
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
然后在 OpenCode 配置中加载 `@ifrankwang/openspec-agents` 插件,重启 OpenCode。
|
|
62
|
+
|
|
63
|
+
## 让 AI 帮你接入(一句话)
|
|
64
|
+
|
|
65
|
+
安装后(甚至还没安装时),直接把这句提示词发给你的 AI 编码工具:
|
|
66
|
+
|
|
67
|
+
> 请帮我安装并启用 openspec-agents 插件,然后基于当前项目的 OpenSpec 变更规范,执行完整的变更实施编排流程。
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
英文版:
|
|
33
70
|
|
|
34
|
-
|
|
71
|
+
> Please install and enable the openspec-agents plugin for my current AI coding tool, then run the OpenSpec change orchestration workflow on this repository.
|
|
35
72
|
|
|
36
|
-
-
|
|
37
|
-
- 物理权限取各逻辑身份权限并集(维度审查者因此具备编辑与写入能力);审查行为以「只报不改」指令约束兜底——文档/注释等不影响代码运行的问题可直接修改,逻辑类问题只上报,full 模式同样适用。
|
|
38
|
-
- `_agent` 为纯自述、无硬校验:任何调用者可自报任意身份,裁定权与视图路由均信任自述值;冒认只能以自报身份行事,无法越出该身份自身的既有权限面。
|
|
73
|
+
如果已经安装,只需说一句「请运行 openspec-agents 编排流程」即可开工。AI 会负责完成安装、初始化、任务分派与质量把关的全部步骤。
|
|
39
74
|
|
|
40
|
-
##
|
|
75
|
+
## 工作原理(简述)
|
|
41
76
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **隔离与安全**:每个 change 使用独立 git worktree、资源隔离命名空间(数据库、mock 服务、消息队列/缓存等中间件、扫描分析平台均按隔离标识隔离),减少相互干扰。
|
|
47
|
-
- **可观测**:内置进度看板,状态/豁免/并发锁持久化到 `openspec/states/`。
|
|
77
|
+
1. **输入**:项目中的 OpenSpec 变更规范(变更说明、设计、任务清单、验收规格)。
|
|
78
|
+
2. **分派**:主智能体把任务交给专职智能体——实现者负责写代码,审查者负责检查,质量把关者负责多维验证。
|
|
79
|
+
3. **门禁**:每一步的结论通过检查后才进入下一步;不通过的结论自动回到实现环节修复。
|
|
80
|
+
4. **收尾**:变更完成后自动合并分支并清理执行环境,全程留痕可审计。
|
|
48
81
|
|
|
49
|
-
##
|
|
82
|
+
## 适用场景与代价
|
|
50
83
|
|
|
51
84
|
| | |
|
|
52
85
|
|---|---|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
86
|
+
| 适合 | 对代码质量、过程可控性要求高的团队与项目 |
|
|
87
|
+
| 代价 | 比单个 AI 直接修改更慢,API 消耗更高 |
|
|
55
88
|
|
|
56
|
-
|
|
89
|
+
如果你愿意用一点成本换取稳定、可复现、可审计的实施过程,这个项目就是为你准备的。
|
|
57
90
|
|
|
58
|
-
##
|
|
91
|
+
## 开发
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
```bash
|
|
94
|
+
bun install # 安装依赖
|
|
95
|
+
bun test # 运行测试
|
|
96
|
+
bun run typecheck # 类型检查
|
|
97
|
+
bun run build:plugins # 构建各平台的插件包(发布用)
|
|
98
|
+
```
|
|
61
99
|
|
|
62
|
-
|
|
100
|
+
## 贡献
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
# 安装依赖
|
|
66
|
-
bun install
|
|
102
|
+
欢迎提交 [Issue](https://github.com/ifrankwang/openspec-agents/issues) 与 Pull Request,也欢迎 Star 支持。
|
|
67
103
|
|
|
68
|
-
|
|
69
|
-
bun test
|
|
70
|
-
bun run typecheck
|
|
71
|
-
```
|
|
104
|
+
## 致谢
|
|
72
105
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- **OpenCode**:以 npm 插件形式加载,插件注入 agent/skill 与 MCP server 配置(`config.mcp` 的 stdio server,自包含 bundle,`--worktree` 指向当前项目根)。
|
|
76
|
-
```bash
|
|
77
|
-
npm install -D @ifrankwang/openspec-agents
|
|
78
|
-
```
|
|
79
|
-
然后在 OpenCode 配置中加载 `@ifrankwang/openspec-agents` 插件并重启 OpenCode(MCP server 配置在启动时加载):`opx_*` 工具以 `mcp__opx__*` 形态出现(如 `mcp__opx__status`、`mcp__opx__agent_submit`,serverName=opx),身份经 `_agent` 参数承载(缺省视为编排视角)。
|
|
80
|
-
|
|
81
|
-
- **Claude Code**:通过官方插件市场安装。
|
|
82
|
-
```bash
|
|
83
|
-
claude plugin marketplace add https://github.com/ifrankwang/claude-code-plugins
|
|
84
|
-
claude plugin install openspec-agents@ifrankwang
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
- **Codex**:通过官方插件市场安装。
|
|
88
|
-
```bash
|
|
89
|
-
codex plugin marketplace add https://github.com/ifrankwang/codex-plugins
|
|
90
|
-
codex plugin add openspec-agents@ifrankwang
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
- **ZCode**:在 ZCode 插件页添加市场 `ifrankwang/zcode-plugins`,然后安装 `openspec-agents`。
|
|
94
|
-
|
|
95
|
-
- **DeepSeek Harness(DSH)**:通过 DSH 的 bundle 插件机制接入。发布到 npm 后可直接安装:
|
|
96
|
-
```bash
|
|
97
|
-
dsh plugin --profile web add @ifrankwang/openspec-agents
|
|
98
|
-
```
|
|
99
|
-
本地开发时也可使用构建产物:
|
|
100
|
-
```bash
|
|
101
|
-
bun run build:plugins
|
|
102
|
-
dsh plugin --profile web add ./dist/deepseek-harness-plugin
|
|
103
|
-
```
|
|
104
|
-
安装后重启 `dsh web`:
|
|
105
|
-
- `opx_*` 会以 `mcp__opx__*`(如 `mcp__opx__status`、`mcp__opx__agent_submit`) 原生工具出现;
|
|
106
|
-
- `assets/skills` 会作为额外 skill 根被扫描;
|
|
107
|
-
- 每个子代理会注册为 DSH 原生 subagent 工具:`openspec_developer` 与 `openspec_reviewer` 两个物理子代理,编排主代理可直接分派(9 种逻辑身份经 `_agent` 参数承载,同一物理子代理可被多次分派)。
|
|
108
|
-
|
|
109
|
-
## 常用命令
|
|
110
|
-
|
|
111
|
-
| 命令 | 说明 |
|
|
112
|
-
|------|------|
|
|
113
|
-
| `bun test` | 运行所有测试 |
|
|
114
|
-
| `bun run typecheck` | TypeScript 类型检查 |
|
|
115
|
-
| `bun run build:plugins` | 构建 Claude Code / Codex / ZCode / DeepSeek Harness 插件包(CI/内部使用) |
|
|
116
|
-
| `bun run sync` | 同步本地最新开发版本到各 harness 插件缓存,并安装/刷新依赖 |
|
|
117
|
-
|
|
118
|
-
## 项目结构
|
|
106
|
+
本项目基于 [OpenSpec](https://github.com/Fission-AI/OpenSpec)([openspec.sh](https://openspec.sh))构建——感谢 OpenSpec 团队带来的规范驱动开发方法:先写规范、再写代码,让 AI 开发有据可依。本插件专注于补全 OpenSpec 变更的**实施与质量把关**环节,是对 OpenSpec 工作流的编排增强。
|
|
119
107
|
|
|
120
|
-
|
|
121
|
-
src/core/ — Agent 无关内核(状态机、工具执行器、状态持久化)
|
|
122
|
-
src/adapters/ — opencode / claude-code / codex / zcode / deepseek-harness / mcp 适配层
|
|
123
|
-
assets/agents/ — Agent 定义
|
|
124
|
-
assets/skills/ — 内置 skill(含 orchestrator)
|
|
125
|
-
assets/workflows/ — workflow 定义
|
|
126
|
-
tests/ — 测试
|
|
127
|
-
```
|
|
108
|
+
## 许可证
|
|
128
109
|
|
|
129
|
-
|
|
110
|
+
[MIT](LICENSE) © 2025 ifrankwang
|
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifrankwang/openspec-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.127.0",
|
|
4
|
+
"description": "OpenSpec change orchestration plugin for AI coding agents (Claude Code, Codex, ZCode, DeepSeek Harness, OpenCode)",
|
|
5
|
+
"author": "ifrankwang",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/ifrankwang/openspec-agents#readme",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/ifrankwang/openspec-agents/issues"
|
|
10
|
+
},
|
|
4
11
|
"type": "module",
|
|
5
12
|
"main": "./src/core/index.ts",
|
|
6
13
|
"exports": {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
buildAgents,
|
|
26
26
|
buildSkills,
|
|
27
27
|
copyWorkflows,
|
|
28
|
+
copyLicense,
|
|
28
29
|
readPkgVersion,
|
|
29
30
|
type PluginPackageResult,
|
|
30
31
|
} from "../plugin-common/index.ts"
|
|
@@ -32,6 +33,9 @@ import {
|
|
|
32
33
|
/** DSH bundle 的 npm 包名(与项目发布包一致,便于 `dsh plugin add` 与 sync 识别)。 */
|
|
33
34
|
export const DSH_PLUGIN_NAME = "@ifrankwang/openspec-agents"
|
|
34
35
|
|
|
36
|
+
/** DSH bundle 包的作者署名(与各插件包 plugin.json 的 author 保持一致)。 */
|
|
37
|
+
export const PLUGIN_AUTHOR_STRING = "ifrankwang"
|
|
38
|
+
|
|
35
39
|
/** 默认插件包输出目录(dist/ 已 gitignore,生成物不入库)。 */
|
|
36
40
|
export const DEEP_SEEK_HARNESS_PLUGIN_DIR = resolve("dist", "deepseek-harness-plugin")
|
|
37
41
|
/** 简写别名,便于与 `dsh` 缩写保持一致。 */
|
|
@@ -174,6 +178,7 @@ export function buildDeepSeekHarnessPlugin(outDir: string = DEEP_SEEK_HARNESS_PL
|
|
|
174
178
|
const skills = buildSkills(join(outDir, "skills"))
|
|
175
179
|
bundleMcpServer(outDir)
|
|
176
180
|
copyWorkflows(outDir)
|
|
181
|
+
copyLicense(outDir)
|
|
177
182
|
|
|
178
183
|
writeFileSync(
|
|
179
184
|
join(outDir, "package.json"),
|
|
@@ -184,6 +189,8 @@ export function buildDeepSeekHarnessPlugin(outDir: string = DEEP_SEEK_HARNESS_PL
|
|
|
184
189
|
description: PLUGIN_DESCRIPTION,
|
|
185
190
|
type: "module",
|
|
186
191
|
private: true,
|
|
192
|
+
author: PLUGIN_AUTHOR_STRING,
|
|
193
|
+
license: "MIT",
|
|
187
194
|
dsh: {
|
|
188
195
|
bundle: {
|
|
189
196
|
patch: `./${MANIFEST_PATCH_FILENAME}`,
|
|
@@ -37,7 +37,7 @@ interface ToolSpec {
|
|
|
37
37
|
const TOOL_SPECS: Record<string, ToolSpec> = {
|
|
38
38
|
opx_orch_init: {
|
|
39
39
|
description:
|
|
40
|
-
"初始化编排会话。传入变更 ID 和任务组 ID,工具自动解析 tasks.md 提取全部任务组并解析目标组子任务。可通过 recovery 参数恢复到指定阶段。无 recovery 重复初始化当前任务组时保留其阶段和进度;切换到其它任务组时初始化该组。可选 mode 参数(full/simple,缺省 simple
|
|
40
|
+
"初始化编排会话。传入变更 ID 和任务组 ID,工具自动解析 tasks.md 提取全部任务组并解析目标组子任务。可通过 recovery 参数恢复到指定阶段。无 recovery 重复初始化当前任务组时保留其阶段和进度;切换到其它任务组时初始化该组。可选 mode 参数(full/simple,缺省 simple)选择流程模式:首次新建状态时固化;已开始的变更仅切组(其他任务组均已完成或从未激活)或 recovery.phase=task_analysis 重制当前组(其他任务组同样须已完成或从未激活)时可更新,其余场景传不同 mode 报错。",
|
|
41
41
|
schema: orchInitSchema,
|
|
42
42
|
execute: (args, ctx) => initExecute(args as any, ctx),
|
|
43
43
|
},
|
|
@@ -18,9 +18,9 @@ import { parseAgentMd, resolve, EXCLUDED_AGENTS } from "../agent-md.ts"
|
|
|
18
18
|
|
|
19
19
|
export const PLUGIN_NAME = "openspec-agents"
|
|
20
20
|
export const PLUGIN_DESCRIPTION =
|
|
21
|
-
"OpenSpec change
|
|
22
|
-
/** plugin.json author
|
|
23
|
-
export const PLUGIN_AUTHOR = "
|
|
21
|
+
"OpenSpec change orchestration for AI coding agents: turn OpenSpec change specs into reviewed, high-quality code."
|
|
22
|
+
/** plugin.json author(各插件包统一署名项目作者)。 */
|
|
23
|
+
export const PLUGIN_AUTHOR = "ifrankwang"
|
|
24
24
|
|
|
25
25
|
export function readPkgVersion(): string {
|
|
26
26
|
return (JSON.parse(readFileSync(resolve("package.json"), "utf-8")) as { version: string }).version
|
|
@@ -98,6 +98,14 @@ export function copyWorkflows(targetDir: string): void {
|
|
|
98
98
|
cpSync(resolve("assets", "workflows"), join(targetDir, "assets", "workflows"), { recursive: true })
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/** 复制 MIT 协议文件到插件包根目录(随插件与 marketplace 一起分发)。 */
|
|
102
|
+
export function copyLicense(targetDir: string): void {
|
|
103
|
+
const license = resolve("LICENSE")
|
|
104
|
+
if (existsSync(license)) {
|
|
105
|
+
cpSync(license, join(targetDir, "LICENSE"))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
export interface PluginPackageParams {
|
|
102
110
|
/** 输出目录(如 dist/zcode-plugin/) */
|
|
103
111
|
outDir: string
|
|
@@ -129,6 +137,7 @@ export function buildPluginPackage({ outDir, manifestDirName }: PluginPackagePar
|
|
|
129
137
|
// workflow 定义随包分发:bundle 内 TASK_WORKFLOW_PATH 按部署深度逐级上溯探测
|
|
130
138
|
// assets/workflows/task.yaml,插件根(dist/cache 形态)上溯 1 级即命中
|
|
131
139
|
copyWorkflows(outDir)
|
|
140
|
+
copyLicense(outDir)
|
|
132
141
|
|
|
133
142
|
writeFileSync(
|
|
134
143
|
join(outDir, manifestDirName, "plugin.json"),
|
package/src/core/state.ts
CHANGED
|
@@ -271,8 +271,8 @@ export async function readStateByChangeId(worktree: string, changeId: string): P
|
|
|
271
271
|
createdAt: legacy.createdAt,
|
|
272
272
|
updatedAt: legacy.updatedAt,
|
|
273
273
|
unattended: legacy.unattended,
|
|
274
|
-
//
|
|
275
|
-
//
|
|
274
|
+
// 透传固化模式:读取不改动 mode(缺 mode 的旧变更保持缺省,消费端读时兜底 full,不在此写回);
|
|
275
|
+
// init 在允许窗口(切组/重制当前组,其他任务组均终态或从未激活)内可显式更新 state.mode
|
|
276
276
|
mode: legacy.mode,
|
|
277
277
|
}
|
|
278
278
|
if (needsUpgrade) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "path"
|
|
2
|
-
import type { TaskItem, TaskStatus, WorkflowMode } from "../types.ts"
|
|
2
|
+
import type { OrchestrateState, TaskItem, TaskStatus, WorkflowMode } from "../types.ts"
|
|
3
3
|
import { BUILD_PHASE_TARGETS, REVIEW_LAYERS, REVIEW_VERIFY_STEPS } from "../types.ts"
|
|
4
4
|
import { agentToReviewLayer } from "../constants.ts"
|
|
5
5
|
import { runGit, runGitChecked, getCurrentBranch, getMergeBase, isWorktreeClean, markTaskGroupCheckboxesComplete, mergeBranchToTarget, discoverDiskWorktrees, detectMainRepoPollution, detectChanges, type DetectChangesResult } from "../git.ts"
|
|
@@ -278,6 +278,25 @@ function assertValidRecovery(recovery: InitParams["recovery"]): void {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* 判断目标组以外的全部任务组(workItems 中 `task:` 前缀、非目标组)是否"终态或从未激活",
|
|
283
|
+
* 即 mode 切换窗口(W1 切组 / W2 重制当前组)的共同前置条件:
|
|
284
|
+
* - 终态:item.phase === "done" 或 metadata.completed_at 已设置;
|
|
285
|
+
* - 从未激活:无执行痕迹——tags 为空且所有 task children 的 phase 均为 todo 或终态(isTerminalPhase)。
|
|
286
|
+
*/
|
|
287
|
+
function otherTaskGroupsSettled(state: OrchestrateState, targetGroupId: string): boolean {
|
|
288
|
+
return state.workItems
|
|
289
|
+
.filter((w) => w.id.startsWith("task:") && w.id !== `task:${targetGroupId}`)
|
|
290
|
+
.every((item) => {
|
|
291
|
+
if (item.phase === "done" || item.metadata["completed_at"] !== undefined) return true
|
|
292
|
+
const noTags = Object.keys(item.tags).length === 0
|
|
293
|
+
const childrenSettled = item.children
|
|
294
|
+
.filter((c) => c.type === "task")
|
|
295
|
+
.every((c) => c.phase === "todo" || isTerminalPhase(c.phase))
|
|
296
|
+
return noTags && childrenSettled
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
281
300
|
export async function initExecute(params: InitParams, ctx: ToolContext): Promise<string> {
|
|
282
301
|
assertOrchestrator(ctx, "opx_orch_init")
|
|
283
302
|
|
|
@@ -326,6 +345,31 @@ export async function initExecute(params: InitParams, ctx: ToolContext): Promise
|
|
|
326
345
|
let state = await readStateByChangeId(ctx.worktree, args.change_id)
|
|
327
346
|
const wasCurrentGroup = state?.taskGroupId === args.task_group_id
|
|
328
347
|
|
|
348
|
+
// state 已存在时的 mode 变更窗口校验/更新(位于 for 循环前——循环内非活跃组新建 item 与
|
|
349
|
+
// applyRecoveryState 均读 state.mode;错误早于任何状态变更抛出,不落盘):
|
|
350
|
+
// - 无变更意图(不传 mode 或与生效值一致——旧 state 缺 mode 时生效值兜底 full)→ 放行,保持既有行为;
|
|
351
|
+
// - W1 切组:task_group_id ≠ state.taskGroupId 且其他任务组均终态或从未激活;
|
|
352
|
+
// - W2 重制当前组:recovery.phase=task_analysis 且其他任务组均终态或从未激活
|
|
353
|
+
// (reopenIssues 仅支持 dev_impl,与 W2 天然互斥,不构成冲突路径);
|
|
354
|
+
// - 窗口外传不同 mode → 报错。
|
|
355
|
+
let modeSwitchNote: string | null = null
|
|
356
|
+
if (state && args.mode !== undefined && args.mode !== (state.mode ?? "full")) {
|
|
357
|
+
const othersSettled = otherTaskGroupsSettled(state, args.task_group_id)
|
|
358
|
+
const withinWindow =
|
|
359
|
+
((args.task_group_id !== state.taskGroupId || args.recovery?.phase === "task_analysis") && othersSettled)
|
|
360
|
+
if (!withinWindow) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`mode 参数与已固化的流程模式不一致:已固化 mode="${state.mode ?? "full"}"${state.mode === undefined ? "(旧变更未固化,读取时兜底 full)" : ""},传入值:"${args.mode}"。\n` +
|
|
363
|
+
`当前场景不允许切换流程模式:切换任务组须其他任务组均已完成或从未激活;重制当前组仅支持 recovery.phase="task_analysis"(其他任务组同样须已完成或从未激活)。\n` +
|
|
364
|
+
`请去掉 mode 参数继续沿用固化模式,或满足上述窗口条件后再切换。`
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
const prevMode = state.mode
|
|
368
|
+
state.mode = args.mode
|
|
369
|
+
// 旧变更未固化 mode 时读取兜底 full,切换说明按 full 表述
|
|
370
|
+
modeSwitchNote = `流程模式已从 ${prevMode ?? "full"} 切换为 ${args.mode}。`
|
|
371
|
+
}
|
|
372
|
+
|
|
329
373
|
if (!state) {
|
|
330
374
|
state = {
|
|
331
375
|
changeId: args.change_id,
|
|
@@ -335,8 +379,8 @@ export async function initExecute(params: InitParams, ctx: ToolContext): Promise
|
|
|
335
379
|
workItems: [],
|
|
336
380
|
createdAt: new Date().toISOString(),
|
|
337
381
|
updatedAt: new Date().toISOString(),
|
|
338
|
-
// 新建 state 时固化模式:取 init 参数 mode(缺省 simple);state
|
|
339
|
-
//
|
|
382
|
+
// 新建 state 时固化模式:取 init 参数 mode(缺省 simple);state 已存在时的 mode 变更
|
|
383
|
+
// 由上方窗口校验统一处理(W1 切组 / W2 重制当前组),旧 state 缺 mode 由消费端读时兜底 full,不写回。
|
|
340
384
|
mode: args.mode ?? "simple",
|
|
341
385
|
}
|
|
342
386
|
} else {
|
|
@@ -443,9 +487,10 @@ export async function initExecute(params: InitParams, ctx: ToolContext): Promise
|
|
|
443
487
|
state.taskGroupId = args.task_group_id
|
|
444
488
|
await writeState(ctx.worktree, state)
|
|
445
489
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
490
|
+
const parts = ["编排会话已初始化。"]
|
|
491
|
+
if (args.recovery) parts.push(`已恢复到 ${args.recovery.phase} 阶段。`)
|
|
492
|
+
if (modeSwitchNote) parts.push(modeSwitchNote)
|
|
493
|
+
return parts.join("")
|
|
449
494
|
}
|
|
450
495
|
|
|
451
496
|
async function bindWorktreeRefs(
|
|
@@ -217,7 +217,7 @@ export const orchInitSchema: JSONSchema = {
|
|
|
217
217
|
type: "string",
|
|
218
218
|
enum: ["full", "simple"],
|
|
219
219
|
description:
|
|
220
|
-
"流程模式选择:full=完整流程(analyze→implement→三重审查+收尾验证);simple=精简流程(implement→quality_review→done
|
|
220
|
+
"流程模式选择:full=完整流程(analyze→implement→三重审查+收尾验证);simple=精简流程(implement→quality_review→done,缺省)。首次新建编排状态时固化;已开始的变更仅在允许窗口内可更新:切换任务组(其他任务组均已完成或从未激活)或 recovery.phase=task_analysis 重制当前组(其他任务组同样须已完成或从未激活);其余场景传不同 mode 将报错。",
|
|
221
221
|
},
|
|
222
222
|
},
|
|
223
223
|
required: ["change_id", "task_group_id"],
|
package/src/core/tools/types.ts
CHANGED
|
@@ -28,7 +28,9 @@ export interface InitParams {
|
|
|
28
28
|
* 恢复后 currentStep 落在第一个未全部通过的 verify step,可能早于被重置的 step。 */
|
|
29
29
|
reset_steps?: ReviewVerifyStep[]
|
|
30
30
|
}
|
|
31
|
-
/** 流程模式(full/simple
|
|
31
|
+
/** 流程模式(full/simple):首次新建编排状态时固化;已开始的变更仅在切组(其他任务组均已完成
|
|
32
|
+
* 或从未激活)或 recovery.phase=task_analysis 重制当前组(其他任务组同样须已完成或从未激活)
|
|
33
|
+
* 时允许更新,窗口外传不同 mode 报错。 */
|
|
32
34
|
mode?: WorkflowMode
|
|
33
35
|
}
|
|
34
36
|
|
package/src/core/types.ts
CHANGED
|
@@ -136,6 +136,8 @@ export interface OrchestrateState {
|
|
|
136
136
|
updatedAt: string
|
|
137
137
|
unattended?: boolean
|
|
138
138
|
/** 变更开始时固化的流程模式(经 opx_orch_init 的 mode 参数写入,缺省 simple)。
|
|
139
|
-
* 缺失即旧变更,一律按 full 处理(读时兜底,不写回);已存在的 state
|
|
139
|
+
* 缺失即旧变更,一律按 full 处理(读时兜底,不写回);已存在的 state 仅在 init 允许窗口
|
|
140
|
+
* (切组且其他任务组均已完成或从未激活,或 recovery.phase=task_analysis 重制当前组)内
|
|
141
|
+
* 可被 mode 参数更新。 */
|
|
140
142
|
mode?: WorkflowMode
|
|
141
143
|
}
|