@ifrankwang/openspec-agents 0.139.0 → 0.140.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifrankwang/openspec-agents",
3
- "version": "0.139.0",
3
+ "version": "0.140.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",
@@ -1322,8 +1322,9 @@ async function completeTaskGroupLocked(params: { change_id: string }, ctx: ToolC
1322
1322
  // 非最后任务组:门禁 → 勾选 → scope_end → completed_at;无合并、无销毁
1323
1323
  item.metadata["completed_at"] = new Date().toISOString()
1324
1324
  await writeState(ctx.worktree, state)
1325
+ // 取消限制事实说明与终态视图非最后组文案同措辞(cancelTaskGroupNote)
1325
1326
  const doneMessage =
1326
- "任务组已完成。本任务组变更保留在 change 分支上,待全部任务组完成后统一收口合并(本次不合并、不销毁 worktree)。"
1327
+ `任务组已完成。本任务组变更保留在 change 分支上,待全部任务组完成后统一收口合并(本次不合并、不销毁 worktree)。若某任务组已确认不再执行,当前版本不支持取消任务组,须人工编辑编排 state 文件(openspec/states/${state.changeId}.json)将该任务组的 phase 改为 "cancelled",收口合并才会触发。`
1327
1328
  return checkboxWarning ? `${doneMessage}\n${checkboxWarning}` : doneMessage
1328
1329
  }
1329
1330
 
@@ -32,9 +32,10 @@ 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 或终态。 */
35
+ /** 任务组「终态或从未激活」(mode 切换窗口专用判定):done 或 completed_at 已设置即终态;
36
+ * 否则须无执行痕迹(tags 为空)且全部 task children 处于 todo 或终态。
37
+ * 「从未激活」计入收口仅对窗口场景正确(未开工组无进度,切走不丢东西);收口合并判定
38
+ * 不得复用此口径(见 isFinalTaskGroup)。 */
38
39
  export function isTaskGroupSettled(item: WorkItem): boolean {
39
40
  if (item.phase === "done" || item.metadata["completed_at"] !== undefined) return true
40
41
  const noTags = Object.keys(item.tags).length === 0
@@ -44,12 +45,14 @@ export function isTaskGroupSettled(item: WorkItem): boolean {
44
45
  return noTags && childrenSettled
45
46
  }
46
47
 
47
- /** 判断 item 是否为其 change 的最后一个收口任务组:其余全部任务组均「终态或从未激活」。
48
- * complete 的 change 级收口判定与终态视图文案分流共用此判定(单一事实源)。 */
48
+ /** 判断 item 是否为其 change 的最后一个收口任务组:其余全部任务组均终态(done 或
49
+ * completed_at 已设置)。「从未激活」不计入收口——串行编排下未开工组无任何进度痕迹,
50
+ * 若计入会令首个完成组被误判为最后组而提前合并销毁。complete 的 change 级收口判定与
51
+ * 终态视图文案分流/部署注意区块门禁共用此判定(单一事实源)。 */
49
52
  export function isFinalTaskGroup(state: { workItems: WorkItem[] }, item: WorkItem): boolean {
50
53
  return state.workItems
51
54
  .filter((w) => w.id.startsWith("task:") && w.id !== item.id)
52
- .every(isTaskGroupSettled)
55
+ .every((w) => isTerminalPhase(w.phase) || w.metadata["completed_at"] !== undefined)
53
56
  }
54
57
 
55
58
  export function isBlockingSeverity(severity: string | undefined): boolean {
@@ -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, isTaskGroupSettled, isFinalTaskGroup, phaseStepMismatch, REVIEW_STEP_TO_LAYER, blockingStepChildren } from "./engine.ts"
9
+ import { getStepVerdict, isTerminalPhase, isBlockingSeverity, isFinalTaskGroup, 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"
@@ -204,9 +204,9 @@ function renderTerminalPhase(item: WorkItem, state?: OrchestrateState, caller?:
204
204
  "",
205
205
  finalGroup
206
206
  ? "本任务组为最后一个收口任务组,change 收口已完成:变更分支已合并回基准分支,编排已完成并收尾。"
207
- : "本任务组已完成,变更保留在 change 分支上,待全部任务组完成后统一收口合并。",
207
+ : `本任务组已完成,变更保留在 change 分支上,待全部任务组完成后统一收口合并。${cancelTaskGroupNote(state)}`,
208
208
  "",
209
- ...renderDeploymentNotesSection(state, caller),
209
+ ...renderDeploymentNotesSection(state, item, caller),
210
210
  ].join("\n")
211
211
  }
212
212
  return [
@@ -217,18 +217,25 @@ function renderTerminalPhase(item: WorkItem, state?: OrchestrateState, caller?:
217
217
  ? "调用 `opx_orch_complete_task_group` 完成 change 收口(把变更分支合并回基准分支并清理)。"
218
218
  : "调用 `opx_orch_complete_task_group` 完成任务组收尾(变更保留在 change 分支,统一收口时合并)。",
219
219
  "",
220
- ...renderDeploymentNotesSection(state, caller),
220
+ ...renderDeploymentNotesSection(state, item, caller),
221
221
  ].join("\n")
222
222
  }
223
223
 
224
+ /** 非最后组收口文案的取消限制事实说明(与 complete 非最后组返回体同措辞):当前版本无取消任务组
225
+ * 的工具入口,弃做组须人工置 cancelled 后收口合并才会触发。无 state(理论不可达)返回空串。 */
226
+ function cancelTaskGroupNote(state: OrchestrateState | undefined): string {
227
+ if (!state) return ""
228
+ return `若某任务组已确认不再执行,当前版本不支持取消任务组,须人工编辑编排 state 文件(openspec/states/${state.changeId}.json)将该任务组的 phase 改为 "cancelled",收口合并才会触发。`
229
+ }
230
+
224
231
  /** 终态视图的「部署注意事项」区块(仅编排视角、change 会话渲染):指引编排者读主仓库规划文档提取
225
232
  * 部署要点,并聚合各任务组 self_check_results 申报的「部署注意点:」前缀行(实施补充点,编排者纯转述)。
226
- * 渲染前提:change 全部任务组均终态或从未激活(isTaskGroupSettled,含当前组——渲染到终态时当前组必已
227
- * 终态),存在未收口组时不渲染(两形态维持既有文案)。全部组无实施补充点时省略补充点段(不渲染空段)。 */
228
- function renderDeploymentNotesSection(state: OrchestrateState | undefined, caller: StatusViewCaller | undefined): string[] {
233
+ * 渲染前提:当前组为最后一个收口任务组(isFinalTaskGroup,其余组均终态且渲染到终态时当前组必已
234
+ * 终态),尚有未收口组时不渲染(两形态维持既有文案)。全部组无实施补充点时省略补充点段(不渲染空段)。 */
235
+ function renderDeploymentNotesSection(state: OrchestrateState | undefined, item: WorkItem, caller: StatusViewCaller | undefined): string[] {
229
236
  if (!caller?.orchestrator || !state || state.kind === "review") return []
237
+ if (!isFinalTaskGroup(state, item)) return []
230
238
  const taskGroups = state.workItems.filter((w) => w.id.startsWith("task:"))
231
- if (!taskGroups.every(isTaskGroupSettled)) return []
232
239
  const lines = [
233
240
  "## 部署注意事项汇报",
234
241
  "",