@@ -67,25 +67,247 @@ if (isResume) {
6767 const mode = resumedSession .mode
6868 const sessionFolder = ` .workflow/.team/${ resumedSession .session_id } `
6969 const taskDescription = resumedSession .topic
70+ const executionMethod = resumedSession .user_preferences ? .execution_method || ' Auto'
71+ const codeReviewTool = resumedSession .user_preferences ? .code_review || ' Skip'
72+
73+ // ============================================================
74+ // Pipeline Constants
75+ // ============================================================
76+ const SPEC_CHAIN = [
77+ ' RESEARCH-001' , ' DISCUSS-001' , ' DRAFT-001' , ' DISCUSS-002' ,
78+ ' DRAFT-002' , ' DISCUSS-003' , ' DRAFT-003' , ' DISCUSS-004' ,
79+ ' DRAFT-004' , ' DISCUSS-005' , ' QUALITY-001' , ' DISCUSS-006'
80+ ]
81+ const IMPL_CHAIN = [' PLAN-001' , ' IMPL-001' , ' TEST-001' , ' REVIEW-001' ]
82+
83+ // Task metadata: prefix → { subject, owner, description template, activeForm }
84+ const TASK_METADATA = {
85+ ' RESEARCH-001' : { owner: ' analyst' , subject: ' RESEARCH-001: 主题发现与上下文研究' , activeForm: ' 研究中' ,
86+ desc : () => ` ${ taskDescription} \n\n Session: ${ sessionFolder} \n 输出: ${ sessionFolder} /spec/spec-config.json + spec/discovery-context.json` },
87+ ' DISCUSS-001' : { owner: ' discussant' , subject: ' DISCUSS-001: 研究结果讨论 - 范围确认与方向调整' , activeForm: ' 讨论范围中' ,
88+ desc : () => ` 讨论 RESEARCH-001 的发现结果\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/discovery-context.json\n 输出: ${ sessionFolder} /discussions/discuss-001-scope.md` },
89+ ' DRAFT-001' : { owner: ' writer' , subject: ' DRAFT-001: 撰写 Product Brief' , activeForm: ' 撰写 Brief 中' ,
90+ desc : () => ` 基于研究和讨论共识撰写产品简报\n\n Session: ${ sessionFolder} \n 输入: discovery-context.json + discuss-001-scope.md\n 输出: ${ sessionFolder} /spec/product-brief.md` },
91+ ' DISCUSS-002' : { owner: ' discussant' , subject: ' DISCUSS-002: Product Brief 多视角评审' , activeForm: ' 评审 Brief 中' ,
92+ desc : () => ` 评审 Product Brief 文档\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/product-brief.md\n 输出: ${ sessionFolder} /discussions/discuss-002-brief.md` },
93+ ' DRAFT-002' : { owner: ' writer' , subject: ' DRAFT-002: 撰写 Requirements/PRD' , activeForm: ' 撰写 PRD 中' ,
94+ desc : () => ` 基于 Brief 和讨论反馈撰写需求文档\n\n Session: ${ sessionFolder} \n 输入: product-brief.md + discuss-002-brief.md\n 输出: ${ sessionFolder} /spec/requirements/` },
95+ ' DISCUSS-003' : { owner: ' discussant' , subject: ' DISCUSS-003: 需求完整性与优先级讨论' , activeForm: ' 讨论需求中' ,
96+ desc : () => ` 讨论 PRD 需求完整性\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/requirements/_index.md\n 输出: ${ sessionFolder} /discussions/discuss-003-requirements.md` },
97+ ' DRAFT-003' : { owner: ' writer' , subject: ' DRAFT-003: 撰写 Architecture Document' , activeForm: ' 撰写架构中' ,
98+ desc : () => ` 基于需求和讨论反馈撰写架构文档\n\n Session: ${ sessionFolder} \n 输入: requirements/ + discuss-003-requirements.md\n 输出: ${ sessionFolder} /spec/architecture/` },
99+ ' DISCUSS-004' : { owner: ' discussant' , subject: ' DISCUSS-004: 架构决策与技术可行性讨论' , activeForm: ' 讨论架构中' ,
100+ desc : () => ` 讨论架构设计合理性\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/architecture/_index.md\n 输出: ${ sessionFolder} /discussions/discuss-004-architecture.md` },
101+ ' DRAFT-004' : { owner: ' writer' , subject: ' DRAFT-004: 撰写 Epics & Stories' , activeForm: ' 撰写 Epics 中' ,
102+ desc : () => ` 基于架构和讨论反馈撰写史诗和用户故事\n\n Session: ${ sessionFolder} \n 输入: architecture/ + discuss-004-architecture.md\n 输出: ${ sessionFolder} /spec/epics/` },
103+ ' DISCUSS-005' : { owner: ' discussant' , subject: ' DISCUSS-005: 执行计划与MVP范围讨论' , activeForm: ' 讨论执行计划中' ,
104+ desc : () => ` 讨论执行计划就绪性\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/epics/_index.md\n 输出: ${ sessionFolder} /discussions/discuss-005-epics.md` },
105+ ' QUALITY-001' : { owner: ' reviewer' , subject: ' QUALITY-001: 规格就绪度检查' , activeForm: ' 质量检查中' ,
106+ desc : () => ` 全文档交叉验证和质量评分\n\n Session: ${ sessionFolder} \n 输入: 全部文档\n 输出: ${ sessionFolder} /spec/readiness-report.md + spec/spec-summary.md` },
107+ ' DISCUSS-006' : { owner: ' discussant' , subject: ' DISCUSS-006: 最终签收与交付确认' , activeForm: ' 最终签收讨论中' ,
108+ desc : () => ` 最终讨论和签收\n\n Session: ${ sessionFolder} \n 输入: ${ sessionFolder} /spec/readiness-report.md\n 输出: ${ sessionFolder} /discussions/discuss-006-final.md` },
109+ ' PLAN-001' : { owner: ' planner' , subject: ' PLAN-001: 探索和规划实现' , activeForm: ' 规划中' ,
110+ desc : () => ` ${ taskDescription} \n\n Session: ${ sessionFolder} \n 写入: ${ sessionFolder} /plan/` },
111+ ' IMPL-001' : { owner: ' executor' , subject: ' IMPL-001: 实现已批准的计划' , activeForm: ' 实现中' ,
112+ desc : () => ` ${ taskDescription} \n\n Session: ${ sessionFolder} \n Plan: ${ sessionFolder} /plan/plan.json\n execution_method: ${ executionMethod} \n code_review: ${ codeReviewTool} ` },
113+ ' TEST-001' : { owner: ' tester' , subject: ' TEST-001: 测试修复循环' , activeForm: ' 测试中' ,
114+ desc : () => taskDescription },
115+ ' REVIEW-001' : { owner: ' reviewer' , subject: ' REVIEW-001: 代码审查与需求验证' , activeForm: ' 审查中' ,
116+ desc : () => ` ${ taskDescription} \n\n Session: ${ sessionFolder} \n Plan: ${ sessionFolder} /plan/plan.json` }
117+ }
118+
119+ // Pipeline dependency: prefix → predecessor prefix (special: TEST-001 & REVIEW-001 both depend on IMPL-001)
120+ function getPredecessor (prefix , pipeline ) {
121+ if (prefix === ' TEST-001' || prefix === ' REVIEW-001' ) return ' IMPL-001'
122+ const idx = pipeline .indexOf (prefix)
123+ return idx > 0 ? pipeline[idx - 1 ] : null
124+ }
125+
126+ // ============================================================
127+ // Step 1: Audit TaskList — 审计当前任务清单状态
128+ // ============================================================
129+ const allTasks = TaskList ()
130+ const pipeline = mode === ' spec-only' ? SPEC_CHAIN
131+ : mode === ' impl-only' ? IMPL_CHAIN
132+ : [... SPEC_CHAIN, ... IMPL_CHAIN]
133+ const sessionCompleted = new Set (resumedSession .completed_tasks || [])
134+
135+ // Build prefix → task mapping from existing TaskList
136+ const existingByPrefix = {}
137+ allTasks .forEach (t => {
138+ const prefixMatch = t .subject .match (/ ^ ([A-Z ] + -\d + )/ )
139+ if (prefixMatch) existingByPrefix[prefixMatch[1 ]] = t
140+ })
141+
142+ // ============================================================
143+ // Step 2: Reconcile — 同步 session 与 TaskList 状态
144+ // ============================================================
145+ const reconciledCompleted = new Set (sessionCompleted)
146+ const statusFixes = []
147+
148+ for (const prefix of pipeline) {
149+ const existing = existingByPrefix[prefix]
150+ if (! existing) continue
151+
152+ // Case A: session 记录已完成,但 TaskList 状态不是 completed → 修正 TaskList
153+ if (sessionCompleted .has (prefix) && existing .status !== ' completed' ) {
154+ TaskUpdate ({ taskId: existing .id , status: ' completed' })
155+ statusFixes .push (` ${ prefix} : ${ existing .status } → completed (sync from session)` )
156+ }
157+
158+ // Case B: TaskList 已 completed,但 session 未记录 → 补录 session
159+ if (existing .status === ' completed' && ! sessionCompleted .has (prefix)) {
160+ reconciledCompleted .add (prefix)
161+ statusFixes .push (` ${ prefix} : completed (sync to session)` )
162+ }
163+
164+ // Case C: TaskList 是 in_progress(暂停时可能中断)→ 重置为 pending
165+ if (existing .status === ' in_progress' && ! sessionCompleted .has (prefix)) {
166+ TaskUpdate ({ taskId: existing .id , status: ' pending' })
167+ statusFixes .push (` ${ prefix} : in_progress → pending (reset for retry)` )
168+ }
169+ }
170+
171+ // Update session with reconciled completed_tasks
172+ resumedSession .completed_tasks = [... reconciledCompleted]
70173
71- // Rebuild team
174+ // ============================================================
175+ // Step 3: Determine remaining pipeline — 确定剩余任务顺序
176+ // ============================================================
177+ const remainingPipeline = pipeline .filter (p => ! reconciledCompleted .has (p))
178+
179+ // ============================================================
180+ // Step 4: Rebuild team + Spawn workers — 重建团队
181+ // ============================================================
72182 TeamCreate ({ team_name: teamName })
73- // Spawn workers based on mode (see Phase 2)
74183
75- // Update session status
184+ // Determine which worker roles are needed based on remaining tasks
185+ const neededRoles = new Set ()
186+ remainingPipeline .forEach (prefix => {
187+ const meta = TASK_METADATA [prefix]
188+ if (meta) neededRoles .add (meta .owner )
189+ })
190+
191+ // Spawn only needed workers using Phase 2 spawn template (see SKILL.md Coordinator Spawn Template)
192+ // Each worker is spawned with prompt that:
193+ // 1. Identifies their role
194+ // 2. Instructs to call Skill(skill="team-lifecycle", args="--role=<name>")
195+ // 3. Includes session context: taskDescription, sessionFolder, constraints
196+ // 4. Instructs immediate TaskList polling on startup
197+ neededRoles .forEach (role => {
198+ // → Use SKILL.md Coordinator Spawn Template for each role
199+ // → Worker prompt includes: "Session: ${sessionFolder}", "需求: ${taskDescription}"
200+ })
201+
202+ // ============================================================
203+ // Step 5: Create missing tasks with correct dependencies
204+ // ============================================================
205+ // In a new conversation, TaskList is EMPTY — all remaining tasks must be created.
206+ // In a same-conversation resume, some tasks may already exist.
207+ const missingPrefixes = remainingPipeline .filter (p => ! existingByPrefix[p])
208+
209+ for (const prefix of missingPrefixes) {
210+ const meta = TASK_METADATA [prefix]
211+ if (! meta) continue
212+
213+ // Create task
214+ const newTask = TaskCreate ({
215+ subject: meta .subject ,
216+ description: meta .desc (),
217+ activeForm: meta .activeForm
218+ })
219+ TaskUpdate ({ taskId: newTask .id , owner: meta .owner })
220+
221+ // Register in existingByPrefix for dependency wiring
222+ existingByPrefix[prefix] = { id: newTask .id , status: ' pending' , blockedBy: [] }
223+
224+ // Wire dependency: find predecessor
225+ const predPrefix = getPredecessor (prefix, pipeline)
226+ if (predPrefix && ! reconciledCompleted .has (predPrefix)) {
227+ const predTask = existingByPrefix[predPrefix]
228+ if (predTask) {
229+ TaskUpdate ({ taskId: newTask .id , addBlockedBy: [predTask .id ] })
230+ }
231+ }
232+
233+ statusFixes .push (` ${ prefix} : created (missing in TaskList)` )
234+ }
235+
236+ // ============================================================
237+ // Step 6: Verify dependency chain integrity for existing tasks
238+ // ============================================================
239+ for (const prefix of remainingPipeline) {
240+ // Skip tasks we just created (already wired)
241+ if (missingPrefixes .includes (prefix)) continue
242+ const task = existingByPrefix[prefix]
243+ if (! task || task .status === ' completed' ) continue
244+
245+ const predPrefix = getPredecessor (prefix, pipeline)
246+ if (! predPrefix || reconciledCompleted .has (predPrefix)) continue
247+
248+ const predTask = existingByPrefix[predPrefix]
249+ if (predTask && task .blockedBy && ! task .blockedBy .includes (predTask .id )) {
250+ TaskUpdate ({ taskId: task .id , addBlockedBy: [predTask .id ] })
251+ statusFixes .push (` ${ prefix} : added missing blockedBy → ${ predPrefix} ` )
252+ }
253+ }
254+
255+ // ============================================================
256+ // Step 7: Update session file — 写入恢复状态
257+ // ============================================================
76258 resumedSession .status = ' active'
77259 resumedSession .resumed_at = new Date ().toISOString ()
78260 resumedSession .updated_at = new Date ().toISOString ()
261+ if (remainingPipeline .length > 0 ) {
262+ const firstRemaining = remainingPipeline[0 ]
263+ if (/ ^ (RESEARCH| DISCUSS| DRAFT| QUALITY)/ .test (firstRemaining)) {
264+ resumedSession .current_phase = ' spec'
265+ } else if (firstRemaining .startsWith (' PLAN' )) {
266+ resumedSession .current_phase = ' plan'
267+ } else {
268+ resumedSession .current_phase = ' impl'
269+ }
270+ }
79271 Write (` ${ sessionFolder} /team-session.json` , JSON .stringify (resumedSession, null , 2 ))
80272
81- // Create only uncompleted tasks from pipeline
82- const completedTasks = new Set (resumedSession .completed_tasks || [])
83- const pipeline = resumedSession .mode === ' spec-only' ? SPEC_CHAIN
84- : resumedSession .mode === ' impl-only' ? IMPL_CHAIN
85- : [... SPEC_CHAIN, ... IMPL_CHAIN]
86- const remainingTasks = pipeline .filter (t => ! completedTasks .has (t))
273+ // ============================================================
274+ // Step 8: Report reconciliation — 输出恢复摘要
275+ // ============================================================
276+ // Output to user:
277+ // - Session: {session_id} resumed
278+ // - Completed: {reconciledCompleted.size}/{pipeline.length} tasks
279+ // - Remaining: {remainingPipeline.join(' → ')}
280+ // - Status fixes: {statusFixes.length} corrections applied
281+ // - Next task: {remainingPipeline[0]}
282+ // - Workers spawned: {[...neededRoles].join(', ')}
283+
284+ // ============================================================
285+ // Step 9: Kick — 通知首个可执行任务的 worker 启动
286+ // ============================================================
287+ // 解决 resume 后的死锁:coordinator 等 worker 消息 ↔ worker 等任务
288+ // 找到第一个 pending + blockedBy 为空的任务,向其 owner 发送 task_unblocked
289+ const firstActionable = remainingPipeline .find (prefix => {
290+ const task = existingByPrefix[prefix]
291+ return task && task .status === ' pending' && (! task .blockedBy || task .blockedBy .length === 0 )
292+ })
293+
294+ if (firstActionable) {
295+ const meta = TASK_METADATA [firstActionable]
296+ mcp__ccw- tools__team_msg ({
297+ operation: " log" , team: teamName,
298+ from: " coordinator" , to: meta .owner ,
299+ type: " task_unblocked" ,
300+ summary: ` Resume: ${ firstActionable} is ready for execution`
301+ })
302+ SendMessage ({
303+ type: " message" ,
304+ recipient: meta .owner ,
305+ content: ` Session 已恢复。你的任务 ${ firstActionable} 已就绪,请立即执行 TaskList 检查并开始工作。` ,
306+ summary: ` Resume kick: ${ firstActionable} `
307+ })
308+ }
87309
88- // → Skip to Phase 3 with remainingTasks, then Phase 4 coordination loop
310+ // → Skip to Phase 4 coordination loop
89311 }
90312}
91313` ` `
0 commit comments