@ifrankwang/openspec-agents 0.133.2 → 0.134.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/README.md CHANGED
@@ -77,7 +77,7 @@ npm install -D @ifrankwang/openspec-agents
77
77
  1. **输入**:项目中的 OpenSpec 变更规范(变更说明、设计、任务清单、验收规格)。
78
78
  2. **分派**:主智能体把任务交给专职智能体——实现者负责写代码,审查者负责检查,质量把关者负责多维验证。
79
79
  3. **门禁**:每一步的结论通过检查后才进入下一步;不通过的结论自动回到实现环节修复。
80
- 4. **收尾**:变更完成后自动合并分支并清理执行环境,全程留痕可审计。
80
+ 4. **收尾**:变更完成后自动合并分支并清理执行环境;change 的全部任务组完成后,主智能体按状态视图指引汇总部署注意事项(规划文档提取 + 开发者实施补充点)向用户汇报。全程留痕可审计。
81
81
 
82
82
  ## 独立审查会话
83
83
 
@@ -6,6 +6,7 @@ permission:
6
6
  read:
7
7
  "*": deny
8
8
  "openspec/states/*": allow
9
+ "openspec/changes/*": allow
9
10
  edit: deny
10
11
  write: deny
11
12
  grep: allow
@@ -29,7 +29,7 @@ capabilities: ["orchestrator"]
29
29
  2. 若未初始化,调用初始化工具(如 `mcp__opx__orch_init`)传入 `change_id` / `task_group_id`;
30
30
  3. 若 worktree 未就绪,调用 `mcp__opx__orch_set_worktree`(或列表中对应工具);
31
31
  4. 再次调用状态查询工具获取权威「下一步」并严格按返回执行。
32
- - 用户仅给出 change 名(未指定任务组)时的约定语义:表示以简单模式串行实施该 change 的全部任务组。先读取 `openspec/changes/<change 名>/tasks.md` 获取任务组清单与顺序(该文件是编排规划清单,非被编排项目的业务源码,也是初始化前唯一允许读取的项目文件):未初始化时从清单第一个任务组开始,以显式 `mode="simple"` 调用初始化工具;当前任务组收尾完成后初始化顺序中的下一个任务组,直至全部任务组完成后编排结束。用户明确指定任务组或要求 full 模式时按用户指定执行,不适用本约定。
32
+ - 用户仅给出 change 名(未指定任务组)时的约定语义:表示以简单模式串行实施该 change 的全部任务组。先读取 `openspec/changes/<change 名>/tasks.md` 获取任务组清单与顺序(该文件是编排规划清单,非被编排项目的业务源码,也是初始化前唯一允许读取的项目文件):未初始化时从清单第一个任务组开始,以显式 `mode="simple"` 调用初始化工具;当前任务组收尾完成后初始化顺序中的下一个任务组,直至全部任务组完成后按状态视图指引完成部署注意事项汇报,编排结束。用户明确指定任务组或要求 full 模式时按用户指定执行,不适用本约定。
33
33
  - 子代理工具命名随 harness 不同:
34
34
  - DSH 形态:`openspec_developer` / `openspec_reviewer` 两个物理子代理(`openspec_<role>`,物理 agent 已收敛,9 种逻辑身份经 `_agent` 参数承载),分派时直接调用这些专用工具,不要使用通用 `subagent` 代替;
35
35
  - 其他 harness 按各自原生子代理机制分派(Claude Code / Codex / ZCode 插件已注入对应子代理)。
@@ -52,12 +52,13 @@ capabilities: ["orchestrator"]
52
52
  - 断点续传:子代理因步骤限制中断后重新分派即可继续,无需保存已完成子任务列表
53
53
  - 工具列出多个子代理时并排分派(单条消息中同时发送),不串行等待
54
54
  - 分派前校验分派 prompt:按「禁止事项」中禁止转述的动态内容清单逐项检查,校验通过后再分派
55
- - 先解锁、后 status、再探索;禁止在首次 status 前手动探查项目文件/状态文件(唯一例外:用户未指定任务组时,按启动编排约定语义读取 tasks.md 确定任务组清单与顺序)
55
+ - 先解锁、后 status、再探索;禁止在首次 status 前手动探查项目文件/状态文件(例外一:用户未指定任务组时,按启动编排约定语义读取 tasks.md 确定任务组清单与顺序;例外二:change 全部任务组完成后的部署要点读取,以状态视图指引为准)
56
+ - change 全部任务组完成后,按状态视图「部署注意事项」区块指引向用户做一次收尾汇报:读主仓库 `openspec/changes/<changeId>/` 规划文档提取部署要点,并原样转述视图列出的实施补充点;不读业务源码、不自行解读或扩写实施补充点,无规划文档时跳过文档提取
56
57
 
57
58
  ## 禁止事项
58
59
 
59
60
  - 禁止修改代码/文档(edit/write 权限已强制禁止)
60
- - **禁止自行阅读/探查被编排项目的业务源码**——收到子代理的 issue 反馈时不得用 read 或其他工具理解 issue 技术内容。issue 的理解与修复是子代理职责,编排者只负责按状态流转分派,不解读 issue 内容;read 仅限状态文件做状态交叉验证
61
+ - **禁止自行阅读/探查被编排项目的业务源码**——收到子代理的 issue 反馈时不得用 read 或其他工具理解 issue 技术内容。issue 的理解与修复是子代理职责,编排者只负责按状态流转分派,不解读 issue 内容;read 仅限状态文件做状态交叉验证,以及 change 全部任务组完成后按状态视图指引读取 `openspec/changes/<changeId>/` 下的规划文档(仅提取部署要点,不读业务源码、不解读实现)
61
62
  - 禁止代子代理提交 step 裁决——step 提交必须由对应子代理独立调用提交工具完成;仅检查点决策可由编排者执行推进
62
63
  - **禁止居中代问/代记 blocker 的用户答复**——需用户拍板的问题由对应子代理直接向用户确认(有人值守)或自行裁决(无人值守),编排者不转述答复、不代记
63
64
  - 禁止使用通用子代理代替专用审查子代理——各子代理角色按工具返回的分派指令确定
@@ -43,6 +43,7 @@ phases:
43
43
  - "提交前完成自检并经 self_check_results 汇总:每项检查须含执行命令与结果摘要(证据可核验,供审查者分级复验对照与核验申报),禁止仅报结论不附证据"
44
44
  - "提交前按已加载的维度规范类 skill(style/architecture/performance/security/maintainability 能力类别)对本次变更文件逐项自检(聚焦本轮变更文件,不扩散全量代码),自检结论与工具检查结果一并汇总进 self_check_results;修复被驳回 issue 后再次提交前同样须按维度规范类 skill 对变更文件自检后再提交"
45
45
  - "自检须覆盖已加载的维度规范类 skill 的全部检查点,self_check_results 按维度逐项列结论(通过/遗留及理由),禁止仅汇总工具检查结果"
46
+ - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化)凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
46
47
  - "修复完成的 issue 经 fixed_issue_ids 上报后进入待复核(review)状态,终态由报源 reviewer 复核裁定;不可修 issue 经 exempt_issue_ids 申请豁免(申请后进入待裁定(review)状态,由报源 reviewer 在后续审查轮次裁定,被驳回的 issue 回待修复);Info 级 issue 不阻塞提交,无需申请豁免;全部 blocking issue 均已上报修复或申请豁免后才可 passed 提交推进"
47
48
  - "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由(commit message 可补充),并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复;被驳回后须移除压制并真实修复"
48
49
  - "遇外部依赖、凭证、真实输入或必须 stub/降级/跳过验收才能继续时,以 verdict=failed 提交 blocker(含 source_role、task_id、category、description、evidence、attempted_actions、options)"
@@ -74,6 +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
+ - "实施产出的部署注意点(数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化)凡属规划文档(design.md/tasks.md/proposal.md/specs/)未记载的,随提交在 self_check_results 中以 `部署注意点:` 开头的独立行逐条申报(每个注意点独占一行);规划文档已记载的不重复申报;修复轮重新提交时保留既有部署注意点行"
77
78
  - "修复完成的 issue 经 fixed_issue_ids 上报后进入待复核(review)状态,终态由报源 reviewer 复核裁定;不可修 issue 经 exempt_issue_ids 申请豁免(申请后进入待裁定(review)状态,由报源 reviewer 在后续审查轮次裁定,被驳回的 issue 回待修复);Info 级 issue 不阻塞提交,无需申请豁免;全部 blocking issue 均已上报修复或申请豁免后才可 passed 提交推进"
78
79
  - "确定性质量工具检查出的问题不得以压制手段(压制注解/注释抑制/规则排除配置/平台状态标注/构建跳过开关等)静默规避:确需压制时,必须在压制处代码注释说明清楚理由(commit message 可补充),并经 exempt_issue_ids 申请豁免由报源 reviewer 裁定;不得以 fixed_issue_ids 冒充修复;被驳回后须移除压制并真实修复"
79
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.133.2",
3
+ "version": "0.134.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",
@@ -12,7 +12,7 @@ import type { ParsedTask } from "../tasks-md.ts"
12
12
  import { assertOrchestrator, findTaskGroup } from "../derive.ts"
13
13
  import { assertPathWithin } from "../paths.ts"
14
14
  import { loadWorkflowFile, resolveWorkflowPath, type LoadedWorkflow } from "../workflow/loader.ts"
15
- import { createInitialWorkItem, isBlockingSeverity, isTerminalPhase, recommendForItem, resetInternalRetryCount, adjudicateStep, clearStepTags, REVIEW_FIX_POLICY_KEY } from "../workflow/engine.ts"
15
+ import { createInitialWorkItem, isBlockingSeverity, isTaskGroupSettled, isTerminalPhase, recommendForItem, resetInternalRetryCount, adjudicateStep, clearStepTags, REVIEW_FIX_POLICY_KEY } from "../workflow/engine.ts"
16
16
  import { renderWorkflowStatusView } from "../workflow/status.ts"
17
17
  import { taskChildrenOf } from "../task-children.ts"
18
18
  import type { WorkItem, WorkItemPhase } from "../workflow/types.ts"
@@ -357,21 +357,12 @@ function assertValidResetStepValues(
357
357
 
358
358
  /**
359
359
  * 判断目标组以外的全部任务组(workItems 中 `task:` 前缀、非目标组)是否"终态或从未激活",
360
- * 即 mode 切换窗口(W1 切组 / W2 重制当前组)的共同前置条件:
361
- * - 终态:item.phase === "done" 或 metadata.completed_at 已设置;
362
- * - 从未激活:无执行痕迹——tags 为空且所有 task children 的 phase 均为 todo 或终态(isTerminalPhase)。
360
+ * 即 mode 切换窗口(W1 切组 / W2 重制当前组)的共同前置条件(判定单一事实源:isTaskGroupSettled)。
363
361
  */
364
362
  function otherTaskGroupsSettled(state: OrchestrateState, targetGroupId: string): boolean {
365
363
  return state.workItems
366
364
  .filter((w) => w.id.startsWith("task:") && w.id !== `task:${targetGroupId}`)
367
- .every((item) => {
368
- if (item.phase === "done" || item.metadata["completed_at"] !== undefined) return true
369
- const noTags = Object.keys(item.tags).length === 0
370
- const childrenSettled = item.children
371
- .filter((c) => c.type === "task")
372
- .every((c) => c.phase === "todo" || isTerminalPhase(c.phase))
373
- return noTags && childrenSettled
374
- })
365
+ .every(isTaskGroupSettled)
375
366
  }
376
367
 
377
368
  // ─── 独立审查会话(kind=review,不绑定 OpenSpec change)───
package/src/core/views.ts CHANGED
@@ -199,6 +199,25 @@ export function renderDevSelfCheckDeclaration(metadata: Record<string, unknown>)
199
199
  return lines
200
200
  }
201
201
 
202
+ // ─── 部署注意点提取 ───
203
+
204
+ /** self_check_results 申报中实施补充点的行前缀(与 workflow 配置 implement instructions 的申报格式约定一致)。 */
205
+ const DEPLOYMENT_NOTE_PREFIX = "部署注意点:"
206
+
207
+ /** 从任务组 WorkItem 的 self_check_results 申报中提取「部署注意点:」前缀行(实施补充点,
208
+ * 规划文档未记载、开发者实施时申报);无命中返回空数组。命中行去除行首尾空白后原样保留
209
+ * (含前缀本身,便于转述端直接输出)。 */
210
+ export function extractDeploymentNoteLines(metadata: Record<string, unknown>): string[] {
211
+ const raw = metadata["self_check_results"]
212
+ if (typeof raw !== "string" || raw.trim() === "") return []
213
+ const hits: string[] = []
214
+ for (const line of raw.split("\n")) {
215
+ const trimmed = line.trim()
216
+ if (trimmed.startsWith(DEPLOYMENT_NOTE_PREFIX)) hits.push(trimmed)
217
+ }
218
+ return hits
219
+ }
220
+
202
221
  // ─── 占位符插值层 ───
203
222
 
204
223
  /** 插值白名单:仅这些 key 可被 {{key}} 占位符引用,其余一律保留原文(防配置注入任意动态值)。 */
@@ -32,6 +32,18 @@ export function isTerminalPhase(phase: WorkItemPhase): boolean {
32
32
  return phase === "done" || phase === "cancelled"
33
33
  }
34
34
 
35
+ /** 任务组「终态或从未激活」(mode 切换窗口与终态视图共享的单一判定):
36
+ * done 或 completed_at 已设置即终态;否则须无执行痕迹(tags 为空)且全部 task children
37
+ * 处于 todo 或终态。 */
38
+ export function isTaskGroupSettled(item: WorkItem): boolean {
39
+ if (item.phase === "done" || item.metadata["completed_at"] !== undefined) return true
40
+ const noTags = Object.keys(item.tags).length === 0
41
+ const childrenSettled = item.children
42
+ .filter((c) => c.type === "task")
43
+ .every((c) => c.phase === "todo" || isTerminalPhase(c.phase))
44
+ return noTags && childrenSettled
45
+ }
46
+
35
47
  export function isBlockingSeverity(severity: string | undefined): boolean {
36
48
  return severity !== undefined && (BLOCKING_SEVERITIES as readonly string[]).includes(severity)
37
49
  }
@@ -6,7 +6,7 @@ import type { ExemptionRecord } from "../exemptions.ts"
6
6
  import type { LoadedWorkflow } from "./loader.ts"
7
7
  import type { EngineRecommendation } from "./engine.ts"
8
8
  import type { DetectChangesResult } from "../git.ts"
9
- import { getStepVerdict, isTerminalPhase, isBlockingSeverity, phaseStepMismatch, REVIEW_STEP_TO_LAYER, blockingStepChildren } from "./engine.ts"
9
+ import { getStepVerdict, isTerminalPhase, isBlockingSeverity, isTaskGroupSettled, phaseStepMismatch, REVIEW_STEP_TO_LAYER, blockingStepChildren } from "./engine.ts"
10
10
  import { agentToReviewDimension, agentToReviewLayer, readIssueSource } from "../constants.ts"
11
11
  import { resolveChildIssueFields } from "./reset.ts"
12
12
  import { taskListOf, issueChildrenOf } from "../task-children.ts"
@@ -14,7 +14,9 @@ import {
14
14
  renderSkillSuggestions, renderEfficiencySteps, renderWorktreeSection,
15
15
  renderAgentSummaries, renderDevSelfCheckDeclaration, renderTaskItem, formatFilePath, formatSeverity,
16
16
  isWorktreeReady, renderWorktreeNotReady, interpolateText, renderStateMismatchDiagnostic,
17
+ extractDeploymentNoteLines,
17
18
  } from "../views.ts"
19
+ import { taskGroupFromWorkItem } from "../derive.ts"
18
20
  import { resolveMustDoForCaps, SKIP_REASON_FORMAT } from "../tools/gate.ts"
19
21
 
20
22
  export interface WorkflowStatusViewOptions {
@@ -71,7 +73,7 @@ export function renderWorkflowStatusView(
71
73
  return renderSuspended(item)
72
74
  }
73
75
  if (item.phase === "done" || item.phase === "cancelled") {
74
- return renderTerminalPhase(item, options.state)
76
+ return renderTerminalPhase(item, options.state, caller)
75
77
  }
76
78
  if (rec.status === "blocked") {
77
79
  if (caller.orchestrator) {
@@ -158,8 +160,10 @@ function renderSuspended(item: WorkItem): string {
158
160
  /** 终态渲染:done 区分已完成(completed_at 已写)与待收尾;cancelled 呈现已取消。
159
161
  * 独立审查会话(kind=review)待收口时渲染审查结果报告视图(issue 按严重级别/维度/文件位置归并,
160
162
  * 只审模式的交付物)——fix=none 下 issue 停留 todo 态、step tag=failed、item=done 为合法组合,
161
- * 报告视图不渲染豁免/复核操作提示(该通道在只审模式下是死路,无修复者消费裁定结论)。 */
162
- function renderTerminalPhase(item: WorkItem, state?: OrchestrateState): string {
163
+ * 报告视图不渲染豁免/复核操作提示(该通道在只审模式下是死路,无修复者消费裁定结论)。
164
+ * change 会话两形态在编排视角下追加「部署注意事项」区块(见 renderDeploymentNotesSection),
165
+ * 子代理视角维持既有文案。 */
166
+ function renderTerminalPhase(item: WorkItem, state?: OrchestrateState, caller?: StatusViewCaller): string {
163
167
  if (item.phase === "cancelled") {
164
168
  return [
165
169
  "# 🚫 任务组已取消",
@@ -197,6 +201,7 @@ function renderTerminalPhase(item: WorkItem, state?: OrchestrateState): string {
197
201
  "",
198
202
  "编排已完成并收尾。",
199
203
  "",
204
+ ...renderDeploymentNotesSection(state, caller),
200
205
  ].join("\n")
201
206
  }
202
207
  return [
@@ -204,9 +209,41 @@ function renderTerminalPhase(item: WorkItem, state?: OrchestrateState): string {
204
209
  "",
205
210
  "全部审核层已通过。调用 `opx_orch_complete_task_group` 合并分支并完成收尾。",
206
211
  "",
212
+ ...renderDeploymentNotesSection(state, caller),
207
213
  ].join("\n")
208
214
  }
209
215
 
216
+ /** 终态视图的「部署注意事项」区块(仅编排视角、change 会话渲染):指引编排者读主仓库规划文档提取
217
+ * 部署要点,并聚合各任务组 self_check_results 申报的「部署注意点:」前缀行(实施补充点,编排者纯转述)。
218
+ * 渲染前提:change 全部任务组均终态或从未激活(isTaskGroupSettled,含当前组——渲染到终态时当前组必已
219
+ * 终态),存在未收口组时不渲染(两形态维持既有文案)。全部组无实施补充点时省略补充点段(不渲染空段)。 */
220
+ function renderDeploymentNotesSection(state: OrchestrateState | undefined, caller: StatusViewCaller | undefined): string[] {
221
+ if (!caller?.orchestrator || !state || state.kind === "review") return []
222
+ const taskGroups = state.workItems.filter((w) => w.id.startsWith("task:"))
223
+ if (!taskGroups.every(isTaskGroupSettled)) return []
224
+ const lines = [
225
+ "## 部署注意事项汇报",
226
+ "",
227
+ "向用户汇报本次变更的部署注意事项:",
228
+ "",
229
+ `- 阅读主仓库 \`openspec/changes/${state.changeId}/\` 下规划文档(design.md、tasks.md、proposal.md、specs/)提取部署要点:数据/结构变更脚本执行、消息队列/缓存等中间件配置调整、新增配置项或功能开关、外部接口契约变化`,
230
+ "- 无规划文档时跳过文档提取,仅转述下方实施补充点",
231
+ "",
232
+ ]
233
+ const supplementary = taskGroups.flatMap((g) => {
234
+ const notes = extractDeploymentNoteLines(g.metadata)
235
+ if (notes.length === 0) return []
236
+ const tg = taskGroupFromWorkItem(g)
237
+ return [`- ${tg.name}(${tg.id}):`, ...notes.map((n) => ` - ${n}`)]
238
+ })
239
+ if (supplementary.length > 0) {
240
+ lines.push("实施补充点(开发者实施时申报,原样转述):", "")
241
+ lines.push(...supplementary)
242
+ lines.push("")
243
+ }
244
+ return lines
245
+ }
246
+
210
247
  /** 独立审查报告视图:issue 按严重级别/维度/文件位置归并的最终报告形态(views 同构渲染共享)。 */
211
248
  function renderReviewReport(item: WorkItem): string {
212
249
  const issues = issueChildrenOf(item)