@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.
@@ -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
- return args.recovery ? `编排会话已初始化。已恢复到 ${args.recovery.phase} 阶段。` : "编排会话已初始化。";
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,缺省)。仅新建编排状态(首次 opx_orch_init)时生效;已开始的变更沿用固化模式,不支持中途切换。"
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.126.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
- OpenSpec 流程的 Agent Team:面向实施阶段的多 Agent 工作流编排。
3
+ ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)
4
+ ![Built for OpenSpec](https://img.shields.io/badge/built%20for-OpenSpec-6b46c1.svg)
4
5
 
5
- > 当前定位:把已确定的 OpenSpec change 落地为高质量代码。
6
- > 未来方向:向前扩展「探索 + 方案制定」,覆盖 OpenSpec 更早期环节。
6
+ > 让多个 AI 智能体按规范流程协作,把 OpenSpec 变更规范落地为经过审查的高质量代码。
7
7
 
8
- ## 它解决什么问题
8
+ `openspec-agents` 是一组面向 AI 编码工具的插件,把 OpenSpec 的「需求 → 设计 → 任务拆分」成果,自动推进到「实现 → 验证 → 质量审查 → 收尾合并」的完整实施流程。主智能体负责调度,专职智能体分别承担实现、检查与质量把关,每一步都有门禁确认,全程可审计、可恢复。
9
9
 
10
- OpenSpec 把需求、设计、任务拆解成规范文档后,实施阶段仍然需要多步开发、验证、质量审查和收尾。本项目用一组 Agent 角色 + workflow 引擎,把这一过程编排成可重复、可审计的流水线:
10
+ ## 特性
11
11
 
12
- `analyze → implement → verify_tool → verify_task → verify_quality → verify_cleanup → done`(full 模式;simple 模式见「编排流程」)
12
+ - **多工具支持**:Claude Code、Codex、ZCode、DeepSeek Harness(DSH)、OpenCode 均可原生接入。
13
+ - **规范驱动**:直接消费 OpenSpec 变更规范(proposal / design / tasks / specs),流程与规范一一对应。
14
+ - **智能体团队**:不再是单个 AI 从头写到尾,而是「实现者 + 审查者 + 质量把关者」分工协作,结果更稳。
15
+ - **质量门禁**:实现之后经过工具检查、任务验证与多维度质量审查;发现的问题自动回退修复,也可按规则豁免并留痕。
16
+ - **隔离执行**:每个变更在独立的环境中实施,互不干扰;变更完成后合并并清理。
17
+ - **过程可审计**:进度、结论、豁免与恢复点全程记录,随时可以接着上次的进度继续。
13
18
 
14
- 主代理负责调度,子代理负责实施、审查、质量验证和清理;所有裁决通过统一 `opx_*` 工具提交,状态持久化到仓库内。
19
+ ## 快速开始
15
20
 
16
- ## 编排流程
21
+ 1. 准备环境:**Node.js ≥ 23.6**(或 Bun)与 **git**。
22
+ 2. 按下方「安装」选择你正在使用的 AI 工具完成安装。
23
+ 3. 重启该工具后,把下面「一句话接入」的提示词发给你的 AI 即可开始。
17
24
 
18
- 实施流程按变更开始时固化的模式执行:模式经 `opx_orch_init` 的 `mode` 参数在变更开始时固化(`full` 完整流程 / `simple` 精简流程,不传缺省 simple)。已开始的变更不受后续参数影响;恢复(recovery)沿用固化模式;旧变更(状态无模式存档)一律按 full 处理,不支持中途切换。
25
+ ## 安装
19
26
 
20
- ### full 模式
27
+ 按你使用的 AI 工具选择一种方式:
21
28
 
22
- `analyze → implement → verify_tool → verify_task → verify_quality → verify_cleanup → done`
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
- ### simple 模式
53
+ 安装后重启 `dsh web` 即可。
27
54
 
28
- `implement → quality_review → done`
55
+ ### OpenCode
29
56
 
30
- 面向轻量变更的精简流程:无 analyze 环节(执行边界默认整个 worktree),无 verify_tool / verify_task / verify_cleanup 环节。implement 失败自循环重试;quality_review 由单一审查者合并承担工具检查、任务验证与质量审查(工具改进建议双报机制保留,由开发者实施),失败回 implement 整步重审;开发者实施时按已加载接口测试规范即时更新并执行 `.http` 接口测试自证变更有效(含关键数据产出断言);开发者提交 implement 成果时强检查工作区干净(不干净拒绝并提示先 commit),收尾为直接合并分支并清理(裸合并,无回归;环境清理由审查者验证完成后自行完成——停止残留服务、清理隔离环境,收尾不单独承担环境清理;合并冲突由开发者解决后直接收尾)。tasks.md 复选框在任务组收尾(opx_orch_complete_task_group)时统一勾选当前任务组,勾选提交落在 worktree 分支、随合并带回主分支;full 与 simple 行为一致,任务完成状态以状态文件为准。
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
- 物理 agent 定义收敛为两个子代理(`assets/agents/` 下的 `openspec-developer.md`、`openspec-reviewer.md`,主代理模板 `openspec-main.md` 保留),9 种逻辑身份(architect、reviewer-tool、reviewer-task、style / architecture / performance / security / maintainability 五维审查者、simple 审查者)不再有独立定义文件,统一经 `_agent` 参数承载:子代理调用 `opx_*` 工具时传自身角色名,系统按该逻辑身份路由状态视图、issue 来源与筛选、「谁提谁裁定」与状态标识。
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
- - 开发者物理 agent 承载 developer 与 architect 两个逻辑身份;审查者物理 agent 承载 tool / task / 五维 / simple 审查者逻辑身份。full 模式的质量审查以 5 个逻辑身份并行执行——同一物理审查者被多次分派,每次以不同 `_agent` 参数承载对应逻辑身份。
37
- - 物理权限取各逻辑身份权限并集(维度审查者因此具备编辑与写入能力);审查行为以「只报不改」指令约束兜底——文档/注释等不影响代码运行的问题可直接修改,逻辑类问题只上报,full 模式同样适用。
38
- - `_agent` 为纯自述、无硬校验:任何调用者可自报任意身份,裁定权与视图路由均信任自述值;冒认只能以自报身份行事,无法越出该身份自身的既有权限面。
73
+ 如果已经安装,只需说一句「请运行 openspec-agents 编排流程」即可开工。AI 会负责完成安装、初始化、任务分派与质量把关的全部步骤。
39
74
 
40
- ## 特色
75
+ ## 工作原理(简述)
41
76
 
42
- - **Agent 无关**:内核不绑定具体 Agent,opencode / claude code / codex / zcode / deepseek harness 通过各自原生插件/适配器接入同一套状态机。
43
- - **Workflow 驱动**:步骤、角色、门禁在 workflow 中声明,`opx_status` 是下一步调度的唯一事实源。
44
- - **多 Agent 团队**:主代理编排,2 个物理子代理承载 9 种逻辑身份分工协作,避免单一大模型从头写到尾失控。
45
- - **强 Review 门禁**:full 模式 tool / task / quality 三层审查 + 收尾验证;simple 模式单一审查者合并审查。问题可回退、可豁免、可检查点决策。确定性质量工具问题以压制手段解决时视为豁免申请,须说明理由并经审查判定合理性。
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
- | 缺点 | 慢(多轮 Agent 调用 + 多级 review);贵(token/API 消耗高于单 Agent 直接改) |
86
+ | 适合 | 对代码质量、过程可控性要求高的团队与项目 |
87
+ | 代价 | 比单个 AI 直接修改更慢,API 消耗更高 |
55
88
 
56
- 适合对质量、可控性要求高,且愿意用成本换稳定产出的实施场景。
89
+ 如果你愿意用一点成本换取稳定、可复现、可审计的实施过程,这个项目就是为你准备的。
57
90
 
58
- ## 接入方式
91
+ ## 开发
59
92
 
60
- 项目已为各 Agent 工具提供官方插件/适配器,普通使用时直接按对应工具的原生机制接入即可,无需手动拼装或启动 MCP Server。
93
+ ```bash
94
+ bun install # 安装依赖
95
+ bun test # 运行测试
96
+ bun run typecheck # 类型检查
97
+ bun run build:plugins # 构建各平台的插件包(发布用)
98
+ ```
61
99
 
62
- 依赖:Node.js ≥ 23.6(或 Bun)、git。
100
+ ## 贡献
63
101
 
64
- ```bash
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
- 各 Agent 接入:
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
- 详细实现见 `AGENTS.md` 与 `src/core/`、`assets/workflows/`。
110
+ [MIT](LICENSE) © 2025 ifrankwang
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@ifrankwang/openspec-agents",
3
- "version": "0.126.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 编排:opx_* 编排工具(MCP)+ 编排子代理 + orchestrator skill,非 OpenCode agent 默认无人值守"
22
- /** plugin.json author(package.json 无 author 字段,取固定值)。 */
23
- export const PLUGIN_AUTHOR = "openspec-agents maintainers"
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
- // 透传固化模式:state 已存在(重复初始化/切换任务组/recovery)时沿用既有 mode,
275
- // 缺 mode 的旧变更保持缺省(消费端读时兜底 full,不在此写回)
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
- // 沿用 state 既有 mode;旧 state 缺 mode 由消费端读时兜底 full,不写回。
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
- return args.recovery
447
- ? `编排会话已初始化。已恢复到 ${args.recovery.phase} 阶段。`
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,缺省)。仅新建编排状态(首次 opx_orch_init)时生效;已开始的变更沿用固化模式,不支持中途切换。",
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"],
@@ -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
  }