mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: Add roles for issue resolution pipeline including planner, reviewer, integrator, and implementer
- Implemented `planner` role for solution design and task decomposition using issue-plan-agent. - Introduced `reviewer` role for solution review, technical feasibility validation, and risk assessment. - Created `integrator` role for queue formation and conflict detection using issue-queue-agent. - Added `implementer` role for code implementation and test verification via code-developer. - Defined message types and role boundaries for each role to ensure clear responsibilities. - Established a team configuration file to manage roles, pipelines, and collaboration patterns for the issue processing pipeline.
This commit is contained in:
@@ -23,7 +23,7 @@ Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Research complete
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "research_ready", summary: "Research done: 5 exploration dimensions", ref: `${sessionFolder}/discovery-context.json` })
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "research_ready", summary: "Research done: 5 exploration dimensions", ref: `${sessionFolder}/spec/discovery-context.json` })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "error", summary: "Codebase access failed" })
|
||||
@@ -61,7 +61,7 @@ TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
```javascript
|
||||
// Extract session folder from task description
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : '.workflow/.spec-team/default'
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : '.workflow/.team/default'
|
||||
|
||||
// Parse topic from task description
|
||||
const topicLines = task.description.split('\n').filter(l => !l.startsWith('Session:') && !l.startsWith('输出:') && l.trim())
|
||||
@@ -135,7 +135,7 @@ const specConfig = {
|
||||
session_folder: sessionFolder,
|
||||
discussion_depth: task.description.match(/讨论深度:\s*(.+)/)?.[1] || "standard"
|
||||
}
|
||||
Write(`${sessionFolder}/spec-config.json`, JSON.stringify(specConfig, null, 2))
|
||||
Write(`${sessionFolder}/spec/spec-config.json`, JSON.stringify(specConfig, null, 2))
|
||||
|
||||
// Generate discovery-context.json
|
||||
const discoveryContext = {
|
||||
@@ -155,7 +155,7 @@ const discoveryContext = {
|
||||
codebase_context: codebaseContext,
|
||||
recommendations: { focus_areas: [], risks: [], open_questions: [] }
|
||||
}
|
||||
Write(`${sessionFolder}/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
Write(`${sessionFolder}/spec/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
@@ -191,8 +191,8 @@ ${(discoveryContext.seed_analysis.target_users || []).map(u => '- ' + u).join('\
|
||||
${(discoveryContext.seed_analysis.exploration_dimensions || []).map((d, i) => (i+1) + '. ' + d).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- Config: ${sessionFolder}/spec-config.json
|
||||
- Context: ${sessionFolder}/discovery-context.json
|
||||
- Config: ${sessionFolder}/spec/spec-config.json
|
||||
- Context: ${sessionFolder}/spec/discovery-context.json
|
||||
|
||||
研究已就绪,可进入讨论轮次 DISCUSS-001。`,
|
||||
summary: `研究就绪: ${dimensionCount}维度, ${specConfig.complexity}`
|
||||
|
||||
@@ -22,6 +22,74 @@ Team lifecycle coordinator. Orchestrates the full pipeline across three modes: s
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 0: Session Resume Check
|
||||
|
||||
Before any new session setup, check if resuming an existing session:
|
||||
|
||||
```javascript
|
||||
const args = "$ARGUMENTS"
|
||||
const isResume = /--resume|--continue/.test(args)
|
||||
|
||||
if (isResume) {
|
||||
// Scan for active/paused sessions
|
||||
const sessionDirs = Glob({ pattern: '.workflow/.team/TLS-*/team-session.json' })
|
||||
const resumable = sessionDirs.map(f => {
|
||||
try {
|
||||
const session = JSON.parse(Read(f))
|
||||
if (session.status === 'active' || session.status === 'paused') return session
|
||||
} catch {}
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
|
||||
if (resumable.length === 0) {
|
||||
// No resumable sessions → fall through to Phase 1
|
||||
} else if (resumable.length === 1) {
|
||||
var resumedSession = resumable[0]
|
||||
} else {
|
||||
// Multiple matches → user selects
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "检测到多个可恢复的会话,请选择:",
|
||||
header: "Resume",
|
||||
multiSelect: false,
|
||||
options: resumable.slice(0, 4).map(s => ({
|
||||
label: s.session_id,
|
||||
description: `${s.topic} (${s.current_phase}, ${s.status})`
|
||||
}))
|
||||
}]
|
||||
})
|
||||
var resumedSession = resumable.find(s => s.session_id === userChoice)
|
||||
}
|
||||
|
||||
if (resumedSession) {
|
||||
// Restore session state
|
||||
const teamName = resumedSession.team_name
|
||||
const mode = resumedSession.mode
|
||||
const sessionFolder = `.workflow/.team/${resumedSession.session_id}`
|
||||
const taskDescription = resumedSession.topic
|
||||
|
||||
// Rebuild team
|
||||
TeamCreate({ team_name: teamName })
|
||||
// Spawn workers based on mode (see Phase 2)
|
||||
|
||||
// Update session status
|
||||
resumedSession.status = 'active'
|
||||
resumedSession.resumed_at = new Date().toISOString()
|
||||
resumedSession.updated_at = new Date().toISOString()
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(resumedSession, null, 2))
|
||||
|
||||
// Create only uncompleted tasks from pipeline
|
||||
const completedTasks = new Set(resumedSession.completed_tasks || [])
|
||||
const pipeline = resumedSession.mode === 'spec-only' ? SPEC_CHAIN
|
||||
: resumedSession.mode === 'impl-only' ? IMPL_CHAIN
|
||||
: [...SPEC_CHAIN, ...IMPL_CHAIN]
|
||||
const remainingTasks = pipeline.filter(t => !completedTasks.has(t))
|
||||
|
||||
// → Skip to Phase 3 with remainingTasks, then Phase 4 coordination loop
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 1: Requirement Clarification
|
||||
|
||||
Parse `$ARGUMENTS` to extract `--team-name` and task description.
|
||||
@@ -30,7 +98,7 @@ Parse `$ARGUMENTS` to extract `--team-name` and task description.
|
||||
const args = "$ARGUMENTS"
|
||||
const teamNameMatch = args.match(/--team-name[=\s]+([\w-]+)/)
|
||||
const teamName = teamNameMatch ? teamNameMatch[1] : `lifecycle-${Date.now().toString(36)}`
|
||||
const taskDescription = args.replace(/--team-name[=\s]+[\w-]+/, '').replace(/--role[=\s]+\w+/, '').trim()
|
||||
const taskDescription = args.replace(/--team-name[=\s]+[\w-]+/, '').replace(/--role[=\s]+\w+/, '').replace(/--resume|--continue/, '').trim()
|
||||
```
|
||||
|
||||
Use AskUserQuestion to collect mode and constraints:
|
||||
@@ -97,18 +165,42 @@ Simple tasks can skip clarification.
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Session setup
|
||||
// Unified session setup
|
||||
const topicSlug = taskDescription.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const specSessionFolder = `.workflow/.spec-team/${topicSlug}-${dateStr}`
|
||||
const implSessionFolder = `.workflow/.team-plan/${topicSlug}-${dateStr}`
|
||||
const sessionId = `TLS-${topicSlug}-${dateStr}`
|
||||
const sessionFolder = `.workflow/.team/${sessionId}`
|
||||
|
||||
// Create unified directory structure
|
||||
if (mode === 'spec-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p ${specSessionFolder}/discussions`)
|
||||
Bash(`mkdir -p "${sessionFolder}/spec" "${sessionFolder}/discussions"`)
|
||||
}
|
||||
if (mode === 'impl-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p ${implSessionFolder}`)
|
||||
Bash(`mkdir -p "${sessionFolder}/plan"`)
|
||||
}
|
||||
|
||||
// Create team-session.json
|
||||
const teamSession = {
|
||||
session_id: sessionId,
|
||||
team_name: teamName,
|
||||
topic: taskDescription,
|
||||
mode: mode,
|
||||
status: "active",
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
paused_at: null,
|
||||
resumed_at: null,
|
||||
completed_at: null,
|
||||
current_phase: mode === 'impl-only' ? 'plan' : 'spec',
|
||||
completed_tasks: [],
|
||||
pipeline_progress: {
|
||||
spec: mode !== 'impl-only' ? { total: 12, completed: 0 } : null,
|
||||
impl: mode !== 'spec-only' ? { total: 4, completed: 0 } : null
|
||||
},
|
||||
user_preferences: { scope: scope || '', focus: focus || '', discussion_depth: discussionDepth || '' },
|
||||
messages_team: teamName
|
||||
}
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(teamSession, null, 2))
|
||||
```
|
||||
|
||||
**Conditional spawn based on mode** (see SKILL.md Coordinator Spawn Template for full prompts):
|
||||
@@ -129,51 +221,51 @@ Task chain creation depends on the selected mode.
|
||||
|
||||
```javascript
|
||||
// RESEARCH Phase
|
||||
TaskCreate({ subject: "RESEARCH-001: 主题发现与上下文研究", description: `${taskDescription}\n\nSession: ${specSessionFolder}\n输出: ${specSessionFolder}/spec-config.json + discovery-context.json`, activeForm: "研究中" })
|
||||
TaskCreate({ subject: "RESEARCH-001: 主题发现与上下文研究", description: `${taskDescription}\n\nSession: ${sessionFolder}\n输出: ${sessionFolder}/spec/spec-config.json + spec/discovery-context.json`, activeForm: "研究中" })
|
||||
TaskUpdate({ taskId: researchId, owner: "analyst" })
|
||||
|
||||
// DISCUSS-001: 范围讨论 (blockedBy RESEARCH-001)
|
||||
TaskCreate({ subject: "DISCUSS-001: 研究结果讨论 - 范围确认与方向调整", description: `讨论 RESEARCH-001 的发现结果\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/discovery-context.json\n输出: ${specSessionFolder}/discussions/discuss-001-scope.md\n\n讨论维度: 范围确认、方向调整、风险预判、探索缺口`, activeForm: "讨论范围中" })
|
||||
TaskCreate({ subject: "DISCUSS-001: 研究结果讨论 - 范围确认与方向调整", description: `讨论 RESEARCH-001 的发现结果\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/discovery-context.json\n输出: ${sessionFolder}/discussions/discuss-001-scope.md\n\n讨论维度: 范围确认、方向调整、风险预判、探索缺口`, activeForm: "讨论范围中" })
|
||||
TaskUpdate({ taskId: discuss1Id, owner: "discussant", addBlockedBy: [researchId] })
|
||||
|
||||
// DRAFT-001: Product Brief (blockedBy DISCUSS-001)
|
||||
TaskCreate({ subject: "DRAFT-001: 撰写 Product Brief", description: `基于研究和讨论共识撰写产品简报\n\nSession: ${specSessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${specSessionFolder}/product-brief.md\n\n使用多视角分析: 产品/技术/用户`, activeForm: "撰写 Brief 中" })
|
||||
TaskCreate({ subject: "DRAFT-001: 撰写 Product Brief", description: `基于研究和讨论共识撰写产品简报\n\nSession: ${sessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${sessionFolder}/product-brief.md\n\n使用多视角分析: 产品/技术/用户`, activeForm: "撰写 Brief 中" })
|
||||
TaskUpdate({ taskId: draft1Id, owner: "writer", addBlockedBy: [discuss1Id] })
|
||||
|
||||
// DISCUSS-002: Brief 评审 (blockedBy DRAFT-001)
|
||||
TaskCreate({ subject: "DISCUSS-002: Product Brief 多视角评审", description: `评审 Product Brief 文档\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/product-brief.md\n输出: ${specSessionFolder}/discussions/discuss-002-brief.md\n\n讨论维度: 产品定位、目标用户、成功指标、竞品差异`, activeForm: "评审 Brief 中" })
|
||||
TaskCreate({ subject: "DISCUSS-002: Product Brief 多视角评审", description: `评审 Product Brief 文档\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/product-brief.md\n输出: ${sessionFolder}/discussions/discuss-002-brief.md\n\n讨论维度: 产品定位、目标用户、成功指标、竞品差异`, activeForm: "评审 Brief 中" })
|
||||
TaskUpdate({ taskId: discuss2Id, owner: "discussant", addBlockedBy: [draft1Id] })
|
||||
|
||||
// DRAFT-002: Requirements/PRD (blockedBy DISCUSS-002)
|
||||
TaskCreate({ subject: "DRAFT-002: 撰写 Requirements/PRD", description: `基于 Brief 和讨论反馈撰写需求文档\n\nSession: ${specSessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${specSessionFolder}/requirements/\n\n包含: 功能需求(REQ-*) + 非功能需求(NFR-*) + MoSCoW 优先级`, activeForm: "撰写 PRD 中" })
|
||||
TaskCreate({ subject: "DRAFT-002: 撰写 Requirements/PRD", description: `基于 Brief 和讨论反馈撰写需求文档\n\nSession: ${sessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${sessionFolder}/requirements/\n\n包含: 功能需求(REQ-*) + 非功能需求(NFR-*) + MoSCoW 优先级`, activeForm: "撰写 PRD 中" })
|
||||
TaskUpdate({ taskId: draft2Id, owner: "writer", addBlockedBy: [discuss2Id] })
|
||||
|
||||
// DISCUSS-003: 需求完整性 (blockedBy DRAFT-002)
|
||||
TaskCreate({ subject: "DISCUSS-003: 需求完整性与优先级讨论", description: `讨论 PRD 需求完整性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/requirements/_index.md\n输出: ${specSessionFolder}/discussions/discuss-003-requirements.md\n\n讨论维度: 需求遗漏、MoSCoW合理性、验收标准可测性、非功能需求充分性`, activeForm: "讨论需求中" })
|
||||
TaskCreate({ subject: "DISCUSS-003: 需求完整性与优先级讨论", description: `讨论 PRD 需求完整性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/requirements/_index.md\n输出: ${sessionFolder}/discussions/discuss-003-requirements.md\n\n讨论维度: 需求遗漏、MoSCoW合理性、验收标准可测性、非功能需求充分性`, activeForm: "讨论需求中" })
|
||||
TaskUpdate({ taskId: discuss3Id, owner: "discussant", addBlockedBy: [draft2Id] })
|
||||
|
||||
// DRAFT-003: Architecture (blockedBy DISCUSS-003)
|
||||
TaskCreate({ subject: "DRAFT-003: 撰写 Architecture Document", description: `基于需求和讨论反馈撰写架构文档\n\nSession: ${specSessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${specSessionFolder}/architecture/\n\n包含: 架构风格 + 组件图 + 技术选型 + ADR-* + 数据模型`, activeForm: "撰写架构中" })
|
||||
TaskCreate({ subject: "DRAFT-003: 撰写 Architecture Document", description: `基于需求和讨论反馈撰写架构文档\n\nSession: ${sessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${sessionFolder}/architecture/\n\n包含: 架构风格 + 组件图 + 技术选型 + ADR-* + 数据模型`, activeForm: "撰写架构中" })
|
||||
TaskUpdate({ taskId: draft3Id, owner: "writer", addBlockedBy: [discuss3Id] })
|
||||
|
||||
// DISCUSS-004: 技术可行性 (blockedBy DRAFT-003)
|
||||
TaskCreate({ subject: "DISCUSS-004: 架构决策与技术可行性讨论", description: `讨论架构设计合理性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/architecture/_index.md\n输出: ${specSessionFolder}/discussions/discuss-004-architecture.md\n\n讨论维度: 技术选型风险、可扩展性、安全架构、ADR替代方案`, activeForm: "讨论架构中" })
|
||||
TaskCreate({ subject: "DISCUSS-004: 架构决策与技术可行性讨论", description: `讨论架构设计合理性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/architecture/_index.md\n输出: ${sessionFolder}/discussions/discuss-004-architecture.md\n\n讨论维度: 技术选型风险、可扩展性、安全架构、ADR替代方案`, activeForm: "讨论架构中" })
|
||||
TaskUpdate({ taskId: discuss4Id, owner: "discussant", addBlockedBy: [draft3Id] })
|
||||
|
||||
// DRAFT-004: Epics & Stories (blockedBy DISCUSS-004)
|
||||
TaskCreate({ subject: "DRAFT-004: 撰写 Epics & Stories", description: `基于架构和讨论反馈撰写史诗和用户故事\n\nSession: ${specSessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${specSessionFolder}/epics/\n\n包含: EPIC-* + STORY-* + 依赖图 + MVP定义 + 执行顺序`, activeForm: "撰写 Epics 中" })
|
||||
TaskCreate({ subject: "DRAFT-004: 撰写 Epics & Stories", description: `基于架构和讨论反馈撰写史诗和用户故事\n\nSession: ${sessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${sessionFolder}/epics/\n\n包含: EPIC-* + STORY-* + 依赖图 + MVP定义 + 执行顺序`, activeForm: "撰写 Epics 中" })
|
||||
TaskUpdate({ taskId: draft4Id, owner: "writer", addBlockedBy: [discuss4Id] })
|
||||
|
||||
// DISCUSS-005: 执行就绪 (blockedBy DRAFT-004)
|
||||
TaskCreate({ subject: "DISCUSS-005: 执行计划与MVP范围讨论", description: `讨论执行计划就绪性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/epics/_index.md\n输出: ${specSessionFolder}/discussions/discuss-005-epics.md\n\n讨论维度: Epic粒度、故事估算、MVP范围、执行顺序、依赖风险`, activeForm: "讨论执行计划中" })
|
||||
TaskCreate({ subject: "DISCUSS-005: 执行计划与MVP范围讨论", description: `讨论执行计划就绪性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/epics/_index.md\n输出: ${sessionFolder}/discussions/discuss-005-epics.md\n\n讨论维度: Epic粒度、故事估算、MVP范围、执行顺序、依赖风险`, activeForm: "讨论执行计划中" })
|
||||
TaskUpdate({ taskId: discuss5Id, owner: "discussant", addBlockedBy: [draft4Id] })
|
||||
|
||||
// QUALITY-001: Readiness Check (blockedBy DISCUSS-005)
|
||||
TaskCreate({ subject: "QUALITY-001: 规格就绪度检查", description: `全文档交叉验证和质量评分\n\nSession: ${specSessionFolder}\n输入: 全部文档\n输出: ${specSessionFolder}/readiness-report.md + spec-summary.md\n\n评分维度: 完整性(20%) + 一致性(20%) + 可追溯性(20%) + 深度(20%) + 需求覆盖率(20%)`, activeForm: "质量检查中" })
|
||||
TaskCreate({ subject: "QUALITY-001: 规格就绪度检查", description: `全文档交叉验证和质量评分\n\nSession: ${sessionFolder}\n输入: 全部文档\n输出: ${sessionFolder}/spec/readiness-report.md + spec/spec-summary.md\n\n评分维度: 完整性(20%) + 一致性(20%) + 可追溯性(20%) + 深度(20%) + 需求覆盖率(20%)`, activeForm: "质量检查中" })
|
||||
TaskUpdate({ taskId: qualityId, owner: "reviewer", addBlockedBy: [discuss5Id] })
|
||||
|
||||
// DISCUSS-006: 最终签收 (blockedBy QUALITY-001)
|
||||
TaskCreate({ subject: "DISCUSS-006: 最终签收与交付确认", description: `最终讨论和签收\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/readiness-report.md\n输出: ${specSessionFolder}/discussions/discuss-006-final.md\n\n讨论维度: 质量报告审查、遗留问题处理、交付确认、下一步建议`, activeForm: "最终签收讨论中" })
|
||||
TaskCreate({ subject: "DISCUSS-006: 最终签收与交付确认", description: `最终讨论和签收\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/readiness-report.md\n输出: ${sessionFolder}/discussions/discuss-006-final.md\n\n讨论维度: 质量报告审查、遗留问题处理、交付确认、下一步建议`, activeForm: "最终签收讨论中" })
|
||||
TaskUpdate({ taskId: discuss6Id, owner: "discussant", addBlockedBy: [qualityId] })
|
||||
```
|
||||
|
||||
@@ -181,11 +273,11 @@ TaskUpdate({ taskId: discuss6Id, owner: "discussant", addBlockedBy: [qualityId]
|
||||
|
||||
```javascript
|
||||
// PLAN-001
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\n写入: ${implSessionFolder}/`, activeForm: "规划中" })
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\nSession: ${sessionFolder}\n写入: ${sessionFolder}/plan/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner" })
|
||||
|
||||
// IMPL-001 (blockedBy PLAN-001)
|
||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nPlan: ${implSessionFolder}/plan.json`, activeForm: "实现中" })
|
||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json`, activeForm: "实现中" })
|
||||
TaskUpdate({ taskId: implId, owner: "executor", addBlockedBy: [planId] })
|
||||
|
||||
// TEST-001 (blockedBy IMPL-001)
|
||||
@@ -193,7 +285,7 @@ TaskCreate({ subject: "TEST-001: 测试修复循环", description: `${taskDescri
|
||||
TaskUpdate({ taskId: testId, owner: "tester", addBlockedBy: [implId] })
|
||||
|
||||
// REVIEW-001 (blockedBy IMPL-001, parallel with TEST-001)
|
||||
TaskCreate({ subject: "REVIEW-001: 代码审查与需求验证", description: `${taskDescription}\n\nPlan: ${implSessionFolder}/plan.json`, activeForm: "审查中" })
|
||||
TaskCreate({ subject: "REVIEW-001: 代码审查与需求验证", description: `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json`, activeForm: "审查中" })
|
||||
TaskUpdate({ taskId: reviewId, owner: "reviewer", addBlockedBy: [implId] })
|
||||
```
|
||||
|
||||
@@ -204,7 +296,7 @@ Create both spec and impl chains, with PLAN-001 blockedBy DISCUSS-006:
|
||||
```javascript
|
||||
// [All spec-only tasks as above]
|
||||
// Then:
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\nSpec: ${specSessionFolder}\n写入: ${implSessionFolder}/`, activeForm: "规划中" })
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\nSession: ${sessionFolder}\n写入: ${sessionFolder}/plan/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner", addBlockedBy: [discuss6Id] })
|
||||
// [Rest of impl-only tasks as above]
|
||||
```
|
||||
@@ -248,7 +340,7 @@ When receiving `research_ready` from analyst, confirm extracted requirements wit
|
||||
|
||||
```javascript
|
||||
if (msgType === 'research_ready') {
|
||||
const discoveryContext = JSON.parse(Read(`${specSessionFolder}/discovery-context.json`))
|
||||
const discoveryContext = JSON.parse(Read(`${sessionFolder}/spec/discovery-context.json`))
|
||||
const dimensions = discoveryContext.seed_analysis?.exploration_dimensions || []
|
||||
const constraints = discoveryContext.seed_analysis?.constraints || []
|
||||
const problemStatement = discoveryContext.seed_analysis?.problem_statement || ''
|
||||
@@ -271,7 +363,7 @@ if (msgType === 'research_ready') {
|
||||
// User provides additional requirements via free text
|
||||
// Merge into discovery-context.json, then unblock DISCUSS-001
|
||||
discoveryContext.seed_analysis.user_supplements = userInput
|
||||
Write(`${specSessionFolder}/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
Write(`${sessionFolder}/spec/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
} else if (userChoice === '需要重新研究') {
|
||||
// Reset RESEARCH-001 to pending, notify analyst
|
||||
TaskUpdate({ taskId: researchId, status: 'pending' })
|
||||
@@ -341,13 +433,13 @@ if (userChoice === '交付执行') {
|
||||
})
|
||||
|
||||
// 读取 spec 文档
|
||||
const specConfig = JSON.parse(Read(`${specSessionFolder}/spec-config.json`))
|
||||
const specSummary = Read(`${specSessionFolder}/spec-summary.md`)
|
||||
const productBrief = Read(`${specSessionFolder}/product-brief.md`)
|
||||
const requirementsIndex = Read(`${specSessionFolder}/requirements/_index.md`)
|
||||
const architectureIndex = Read(`${specSessionFolder}/architecture/_index.md`)
|
||||
const epicsIndex = Read(`${specSessionFolder}/epics/_index.md`)
|
||||
const epicFiles = Glob(`${specSessionFolder}/epics/EPIC-*.md`)
|
||||
const specConfig = JSON.parse(Read(`${sessionFolder}/spec/spec-config.json`))
|
||||
const specSummary = Read(`${sessionFolder}/spec/spec-summary.md`)
|
||||
const productBrief = Read(`${sessionFolder}/spec/product-brief.md`)
|
||||
const requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`)
|
||||
const architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`)
|
||||
const epicsIndex = Read(`${sessionFolder}/spec/epics/_index.md`)
|
||||
const epicFiles = Glob(`${sessionFolder}/spec/epics/EPIC-*.md`)
|
||||
|
||||
if (handoffChoice === 'lite-plan') {
|
||||
// 读取首个 MVP Epic → 调用 lite-plan
|
||||
@@ -368,7 +460,7 @@ if (userChoice === '交付执行') {
|
||||
// Step A: 构建结构化描述
|
||||
const structuredDesc = `GOAL: ${specConfig.seed_analysis?.problem_statement || specConfig.topic}
|
||||
SCOPE: ${specConfig.complexity} complexity
|
||||
CONTEXT: Generated from spec team session ${specConfig.session_id}. Source: ${specSessionFolder}/`
|
||||
CONTEXT: Generated from spec team session ${specConfig.session_id}. Source: ${sessionFolder}/`
|
||||
|
||||
// Step B: 创建 WFS session
|
||||
Skill({ skill: "workflow:session:start", args: `--auto "${structuredDesc}"` })
|
||||
@@ -384,7 +476,7 @@ CONTEXT: Generated from spec team session ${specConfig.session_id}. Source: ${sp
|
||||
|
||||
**Source**: spec-team session ${specConfig.session_id}
|
||||
**Generated**: ${new Date().toISOString()}
|
||||
**Spec Directory**: ${specSessionFolder}
|
||||
**Spec Directory**: ${sessionFolder}
|
||||
|
||||
## 1. Project Positioning & Goals
|
||||
${extractSection(productBrief, "Vision")}
|
||||
@@ -407,11 +499,11 @@ ${extractSection(epicsIndex, "Traceability Matrix")}
|
||||
## Appendix: Source Documents
|
||||
| Document | Path | Description |
|
||||
|----------|------|-------------|
|
||||
| Product Brief | ${specSessionFolder}/product-brief.md | Vision, goals, scope |
|
||||
| Requirements | ${specSessionFolder}/requirements/ | _index.md + REQ-*.md + NFR-*.md |
|
||||
| Architecture | ${specSessionFolder}/architecture/ | _index.md + ADR-*.md |
|
||||
| Epics | ${specSessionFolder}/epics/ | _index.md + EPIC-*.md |
|
||||
| Readiness Report | ${specSessionFolder}/readiness-report.md | Quality validation |
|
||||
| Product Brief | ${sessionFolder}/spec/product-brief.md | Vision, goals, scope |
|
||||
| Requirements | ${sessionFolder}/spec/requirements/ | _index.md + REQ-*.md + NFR-*.md |
|
||||
| Architecture | ${sessionFolder}/spec/architecture/ | _index.md + ADR-*.md |
|
||||
| Epics | ${sessionFolder}/spec/epics/ | _index.md + EPIC-*.md |
|
||||
| Readiness Report | ${sessionFolder}/spec/readiness-report.md | Quality validation |
|
||||
`)
|
||||
|
||||
// C.2: feature-index.json(EPIC → Feature 映射)
|
||||
@@ -501,30 +593,58 @@ function parseYAML(yamlStr) {
|
||||
}
|
||||
```
|
||||
|
||||
## Session State Tracking
|
||||
|
||||
At each key transition, update `team-session.json`:
|
||||
|
||||
```javascript
|
||||
// Helper: update session state
|
||||
function updateSession(sessionFolder, updates) {
|
||||
const session = JSON.parse(Read(`${sessionFolder}/team-session.json`))
|
||||
Object.assign(session, updates, { updated_at: new Date().toISOString() })
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(session, null, 2))
|
||||
}
|
||||
|
||||
// On task completion:
|
||||
updateSession(sessionFolder, {
|
||||
completed_tasks: [...session.completed_tasks, taskPrefix],
|
||||
pipeline_progress: { ...session.pipeline_progress,
|
||||
[phase]: { ...session.pipeline_progress[phase], completed: session.pipeline_progress[phase].completed + 1 }
|
||||
}
|
||||
})
|
||||
|
||||
// On phase transition (spec → plan):
|
||||
updateSession(sessionFolder, { current_phase: 'plan' })
|
||||
|
||||
// On completion:
|
||||
updateSession(sessionFolder, { status: 'completed', completed_at: new Date().toISOString() })
|
||||
|
||||
// On user closes team:
|
||||
updateSession(sessionFolder, { status: 'completed', completed_at: new Date().toISOString() })
|
||||
```
|
||||
|
||||
## Session File Structure
|
||||
|
||||
```
|
||||
# Spec session
|
||||
.workflow/.spec-team/{topic-slug}-{YYYY-MM-DD}/
|
||||
├── spec-config.json
|
||||
├── discovery-context.json
|
||||
├── product-brief.md
|
||||
├── requirements/
|
||||
├── architecture/
|
||||
├── epics/
|
||||
├── readiness-report.md
|
||||
├── spec-summary.md
|
||||
└── discussions/
|
||||
└── discuss-001..006.md
|
||||
|
||||
# Impl session
|
||||
.workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-*.json
|
||||
├── explorations-manifest.json
|
||||
├── planning-context.md
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
.workflow/.team/TLS-{slug}-{YYYY-MM-DD}/
|
||||
├── team-session.json # Session state (resume support)
|
||||
├── spec/ # Spec artifacts
|
||||
│ ├── spec-config.json
|
||||
│ ├── discovery-context.json
|
||||
│ ├── product-brief.md
|
||||
│ ├── requirements/ # _index.md + REQ-*.md + NFR-*.md
|
||||
│ ├── architecture/ # _index.md + ADR-*.md
|
||||
│ ├── epics/ # _index.md + EPIC-*.md
|
||||
│ ├── readiness-report.md
|
||||
│ └── spec-summary.md
|
||||
├── discussions/ # Discussion records
|
||||
│ └── discuss-001..006.md
|
||||
└── plan/ # Plan artifacts
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
@@ -92,12 +92,12 @@ const roundMatch = task.subject.match(/DISCUSS-(\d+)/)
|
||||
const roundNumber = roundMatch ? parseInt(roundMatch[1]) : 0
|
||||
|
||||
const roundConfig = {
|
||||
1: { artifact: 'discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk', 'coverage'], label: '范围讨论' },
|
||||
2: { artifact: 'product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Brief评审' },
|
||||
3: { artifact: 'requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product', 'coverage'], label: '需求讨论' },
|
||||
4: { artifact: 'architecture/_index.md', type: 'md', outputFile: 'discuss-004-architecture.md', perspectives: ['technical', 'risk'], label: '架构讨论' },
|
||||
5: { artifact: 'epics/_index.md', type: 'md', outputFile: 'discuss-005-epics.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Epics讨论' },
|
||||
6: { artifact: 'readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk', 'coverage'], label: '最终签收' }
|
||||
1: { artifact: 'spec/discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk', 'coverage'], label: '范围讨论' },
|
||||
2: { artifact: 'spec/product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Brief评审' },
|
||||
3: { artifact: 'spec/requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product', 'coverage'], label: '需求讨论' },
|
||||
4: { artifact: 'spec/architecture/_index.md', type: 'md', outputFile: 'discuss-004-architecture.md', perspectives: ['technical', 'risk'], label: '架构讨论' },
|
||||
5: { artifact: 'spec/epics/_index.md', type: 'md', outputFile: 'discuss-005-epics.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Epics讨论' },
|
||||
6: { artifact: 'spec/readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk', 'coverage'], label: '最终签收' }
|
||||
}
|
||||
|
||||
const config = roundConfig[roundNumber]
|
||||
|
||||
@@ -59,7 +59,7 @@ const task = TaskGet({ taskId: myTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
|
||||
// Extract plan path from task description
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team\/[^\s]+\/plan\/plan\.json/)
|
||||
const planPath = planPathMatch ? planPathMatch[0] : null
|
||||
|
||||
if (!planPath) {
|
||||
|
||||
@@ -24,7 +24,7 @@ Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Plan ready
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_ready", summary: "Plan ready: 3 tasks, Medium complexity", ref: `${sessionFolder}/plan.json` })
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_ready", summary: "Plan ready: 3 tasks, Medium complexity", ref: `${sessionFolder}/plan/plan.json` })
|
||||
|
||||
// Plan revision
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_revision", summary: "Split task-2 into two subtasks per feedback" })
|
||||
@@ -38,7 +38,7 @@ mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "planner" --to "coordinator" --type "plan_ready" --summary "Plan ready: 3 tasks" --ref "${sessionFolder}/plan.json" --json`)
|
||||
Bash(`ccw team log --team "${teamName}" --from "planner" --to "coordinator" --type "plan_ready" --summary "Plan ready: 3 tasks" --ref "${sessionFolder}/plan/plan.json" --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
@@ -60,14 +60,30 @@ const task = TaskGet({ taskId: myTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
```
|
||||
|
||||
### Phase 1.5: Load Spec Context (Full-Lifecycle Mode)
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description (set by coordinator)
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : `.workflow/.team/default`
|
||||
const planDir = `${sessionFolder}/plan`
|
||||
Bash(`mkdir -p ${planDir}`)
|
||||
|
||||
// Check if spec directory exists (full-lifecycle mode)
|
||||
const specDir = `${sessionFolder}/spec`
|
||||
let specContext = null
|
||||
try {
|
||||
const reqIndex = Read(`${specDir}/requirements/_index.md`)
|
||||
const archIndex = Read(`${specDir}/architecture/_index.md`)
|
||||
const epicsIndex = Read(`${specDir}/epics/_index.md`)
|
||||
const specConfig = JSON.parse(Read(`${specDir}/spec-config.json`))
|
||||
specContext = { reqIndex, archIndex, epicsIndex, specConfig }
|
||||
} catch { /* impl-only mode has no spec */ }
|
||||
```
|
||||
|
||||
### Phase 2: Multi-Angle Exploration
|
||||
|
||||
```javascript
|
||||
// Session setup
|
||||
const taskSlug = task.subject.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const sessionFolder = `.workflow/.team-plan/${taskSlug}-${dateStr}`
|
||||
Bash(`mkdir -p ${sessionFolder}`)
|
||||
|
||||
// Complexity assessment
|
||||
function assessComplexity(desc) {
|
||||
@@ -110,7 +126,7 @@ if (complexity === 'Low') {
|
||||
project_root_path: projectRoot,
|
||||
query: task.description
|
||||
})
|
||||
Write(`${sessionFolder}/exploration-${selectedAngles[0]}.json`, JSON.stringify({
|
||||
Write(`${planDir}/exploration-${selectedAngles[0]}.json`, JSON.stringify({
|
||||
project_structure: "...",
|
||||
relevant_files: [],
|
||||
patterns: [],
|
||||
@@ -133,11 +149,12 @@ Execute **${angle}** exploration for task planning context.
|
||||
|
||||
## Output Location
|
||||
**Session Folder**: ${sessionFolder}
|
||||
**Output File**: ${sessionFolder}/exploration-${angle}.json
|
||||
**Output File**: ${planDir}/exploration-${angle}.json
|
||||
|
||||
## Assigned Context
|
||||
- **Exploration Angle**: ${angle}
|
||||
- **Task Description**: ${task.description}
|
||||
- **Spec Context**: ${specContext ? 'Available — use spec/requirements, spec/architecture, spec/epics for informed exploration' : 'Not available (impl-only mode)'}
|
||||
- **Exploration Index**: ${index + 1} of ${selectedAngles.length}
|
||||
|
||||
## MANDATORY FIRST STEPS
|
||||
@@ -146,7 +163,7 @@ Execute **${angle}** exploration for task planning context.
|
||||
3. Read: .workflow/project-tech.json (if exists - technology stack)
|
||||
|
||||
## Expected Output
|
||||
Write JSON to: ${sessionFolder}/exploration-${angle}.json
|
||||
Write JSON to: ${planDir}/exploration-${angle}.json
|
||||
Follow explore-json-schema.json structure with ${angle}-focused findings.
|
||||
|
||||
**MANDATORY**: Every file in relevant_files MUST have:
|
||||
@@ -168,10 +185,10 @@ const explorationManifest = {
|
||||
explorations: selectedAngles.map(angle => ({
|
||||
angle: angle,
|
||||
file: `exploration-${angle}.json`,
|
||||
path: `${sessionFolder}/exploration-${angle}.json`
|
||||
path: `${planDir}/exploration-${angle}.json`
|
||||
}))
|
||||
}
|
||||
Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2))
|
||||
Write(`${planDir}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2))
|
||||
```
|
||||
|
||||
### Phase 3: Plan Generation
|
||||
@@ -182,7 +199,7 @@ const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-ba
|
||||
|
||||
if (complexity === 'Low') {
|
||||
// Direct Claude planning
|
||||
Bash(`mkdir -p ${sessionFolder}/.task`)
|
||||
Bash(`mkdir -p ${planDir}/.task`)
|
||||
// Generate plan.json + .task/TASK-*.json following schemas
|
||||
} else {
|
||||
// Use cli-lite-planning-agent for Medium/High
|
||||
@@ -191,11 +208,16 @@ if (complexity === 'Low') {
|
||||
run_in_background: false,
|
||||
description: "Generate detailed implementation plan",
|
||||
prompt: `Generate implementation plan.
|
||||
Output: ${sessionFolder}/plan.json + ${sessionFolder}/.task/TASK-*.json
|
||||
Output: ${planDir}/plan.json + ${planDir}/.task/TASK-*.json
|
||||
Schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json
|
||||
Task Description: ${task.description}
|
||||
Explorations: ${explorationManifest}
|
||||
Complexity: ${complexity}
|
||||
${specContext ? `Spec Context:
|
||||
- Requirements: ${specContext.reqIndex.substring(0, 500)}
|
||||
- Architecture: ${specContext.archIndex.substring(0, 500)}
|
||||
- Epics: ${specContext.epicsIndex.substring(0, 500)}
|
||||
Reference REQ-* IDs, follow ADR decisions, reuse Epic/Story decomposition.` : ''}
|
||||
Requirements: 2-7 tasks, each with id, title, files[].change, convergence.criteria, depends_on`
|
||||
})
|
||||
}
|
||||
@@ -204,8 +226,8 @@ Requirements: 2-7 tasks, each with id, title, files[].change, convergence.criter
|
||||
### Phase 4: Submit for Approval
|
||||
|
||||
```javascript
|
||||
const plan = JSON.parse(Read(`${sessionFolder}/plan.json`))
|
||||
const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${sessionFolder}/.task/${id}.json`)))
|
||||
const plan = JSON.parse(Read(`${planDir}/plan.json`))
|
||||
const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${planDir}/.task/${id}.json`)))
|
||||
const taskCount = plan.task_count || plan.task_ids.length
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
@@ -213,7 +235,7 @@ mcp__ccw-tools__team_msg({
|
||||
from: "planner", to: "coordinator",
|
||||
type: "plan_ready",
|
||||
summary: `Plan就绪: ${taskCount}个task, ${complexity}复杂度`,
|
||||
ref: `${sessionFolder}/plan.json`
|
||||
ref: `${planDir}/plan.json`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
@@ -232,8 +254,8 @@ ${planTasks.map((t, i) => (i+1) + '. ' + t.title).join('\n')}
|
||||
${plan.approach}
|
||||
|
||||
### Plan Location
|
||||
${sessionFolder}/plan.json
|
||||
Task Files: ${sessionFolder}/.task/
|
||||
${planDir}/plan.json
|
||||
Task Files: ${planDir}/.task/
|
||||
|
||||
Please review and approve or request revisions.`,
|
||||
summary: `Plan ready: ${taskCount} tasks`
|
||||
@@ -253,7 +275,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
## Session Files
|
||||
|
||||
```
|
||||
.workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
{sessionFolder}/plan/
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── planning-context.md
|
||||
@@ -262,6 +284,8 @@ TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
> **Note**: `sessionFolder` is extracted from task description (`Session: .workflow/.team/TLS-xxx`). Plan outputs go to `plan/` subdirectory. In full-lifecycle mode, spec products are available at `../spec/`.
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|
||||
@@ -73,7 +73,7 @@ const reviewMode = task.subject.startsWith('REVIEW-') ? 'code' : 'spec'
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
// Load plan for acceptance criteria
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team\/[^\s]+\/plan\/plan\.json/)
|
||||
let plan = null
|
||||
if (planPathMatch) {
|
||||
try { plan = JSON.parse(Read(planPathMatch[0])) } catch {}
|
||||
@@ -112,18 +112,18 @@ if (reviewMode === 'spec') {
|
||||
adrs: [], epicsIndex: null, epics: [], discussions: []
|
||||
}
|
||||
|
||||
try { documents.config = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/epics/_index.md`) } catch {}
|
||||
try { documents.config = JSON.parse(Read(`${sessionFolder}/spec/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/spec/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/spec/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/spec/epics/_index.md`) } catch {}
|
||||
|
||||
// Load individual documents
|
||||
Glob({ pattern: `${sessionFolder}/requirements/REQ-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/requirements/NFR-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/architecture/ADR-*.md` }).forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/epics/EPIC-*.md` }).forEach(f => { try { documents.epics.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/requirements/REQ-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/requirements/NFR-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/architecture/ADR-*.md` }).forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/epics/EPIC-*.md` }).forEach(f => { try { documents.epics.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/discussions/discuss-*.md` }).forEach(f => { try { documents.discussions.push(Read(f)) } catch {} })
|
||||
|
||||
const docInventory = {
|
||||
@@ -473,7 +473,7 @@ ${allSpecIssues.map(i => '- ' + i).join('\n') || 'None'}
|
||||
## Document Inventory
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (v === true ? '✓' : v === false ? '✗' : v)).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/readiness-report.md`, readinessReport)
|
||||
Write(`${sessionFolder}/spec/readiness-report.md`, readinessReport)
|
||||
|
||||
// Generate spec-summary.md
|
||||
const specSummary = `---
|
||||
@@ -503,7 +503,7 @@ ${qualityGate === 'PASS' ? '- Ready for handoff to execution workflows' :
|
||||
qualityGate === 'REVIEW' ? '- Address review items, then proceed to execution' :
|
||||
'- Fix critical issues before proceeding'}
|
||||
`
|
||||
Write(`${sessionFolder}/spec-summary.md`, specSummary)
|
||||
Write(`${sessionFolder}/spec/spec-summary.md`, specSummary)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -590,8 +590,8 @@ ${allSpecIssues.map(i => '- ' + i).join('\n') || '无问题'}
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (typeof v === 'boolean' ? (v ? '✓' : '✗') : v)).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- 就绪报告: ${sessionFolder}/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec-summary.md
|
||||
- 就绪报告: ${sessionFolder}/spec/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec/spec-summary.md
|
||||
|
||||
${qualityGate === 'PASS' ? '质量达标,可进入最终讨论轮次 DISCUSS-006。' :
|
||||
qualityGate === 'REVIEW' ? '质量基本达标但有改进空间,建议在讨论中审查。' :
|
||||
|
||||
@@ -69,7 +69,7 @@ const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load session config
|
||||
let specConfig = null
|
||||
try { specConfig = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
try { specConfig = JSON.parse(Read(`${sessionFolder}/spec/spec-config.json`)) } catch {}
|
||||
|
||||
// Determine document type from task subject
|
||||
const docType = task.subject.includes('Product Brief') ? 'product-brief'
|
||||
@@ -91,16 +91,16 @@ try { discussionFeedback = Read(`${sessionFolder}/${discussionFiles[docType]}`)
|
||||
// Load prior documents progressively
|
||||
const priorDocs = {}
|
||||
if (docType !== 'product-brief') {
|
||||
try { priorDocs.discoveryContext = Read(`${sessionFolder}/discovery-context.json`) } catch {}
|
||||
try { priorDocs.discoveryContext = Read(`${sessionFolder}/spec/discovery-context.json`) } catch {}
|
||||
}
|
||||
if (['requirements', 'architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
try { priorDocs.productBrief = Read(`${sessionFolder}/spec/product-brief.md`) } catch {}
|
||||
}
|
||||
if (['architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
try { priorDocs.requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`) } catch {}
|
||||
}
|
||||
if (docType === 'epics') {
|
||||
try { priorDocs.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
try { priorDocs.architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`) } catch {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -246,8 +246,8 @@ dependencies:
|
||||
// 填充 template 中所有 section: Vision, Problem Statement, Target Users, Goals, Scope
|
||||
// 应用 document-standards.md 格式规范
|
||||
|
||||
Write(`${sessionFolder}/product-brief.md`, `${frontmatter}\n\n${filledContent}`)
|
||||
outputPath = 'product-brief.md'
|
||||
Write(`${sessionFolder}/spec/product-brief.md`, `${frontmatter}\n\n${filledContent}`)
|
||||
outputPath = 'spec/product-brief.md'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -299,7 +299,7 @@ CONSTRAINTS: Every requirement must be specific enough to estimate and test. No
|
||||
}
|
||||
|
||||
// === 生成 requirements/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/requirements"`)
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/requirements"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
@@ -330,7 +330,7 @@ ${req.user_story}
|
||||
## Acceptance Criteria
|
||||
${req.acceptance_criteria.map((ac, i) => `${i+1}. ${ac}`).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/requirements/REQ-${req.id}-${req.slug}.md`, reqContent)
|
||||
Write(`${sessionFolder}/spec/requirements/REQ-${req.id}-${req.slug}.md`, reqContent)
|
||||
})
|
||||
|
||||
// 写入独立 NFR-*.md 文件
|
||||
@@ -353,7 +353,7 @@ ${nfr.requirement}
|
||||
## Metric & Target
|
||||
${nfr.metric} — Target: ${nfr.target}
|
||||
`
|
||||
Write(`${sessionFolder}/requirements/NFR-${nfr.type}-${nfr.id}-${nfr.slug}.md`, nfrContent)
|
||||
Write(`${sessionFolder}/spec/requirements/NFR-${nfr.type}-${nfr.id}-${nfr.slug}.md`, nfrContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(汇总 + 链接)
|
||||
@@ -390,8 +390,8 @@ ${nfReqs.map(n => `| [NFR-${n.type}-${n.id}](NFR-${n.type}-${n.id}-${n.slug}.md)
|
||||
- **Could**: ${funcReqs.filter(r => r.priority === 'Could').length}
|
||||
- **Won't**: ${funcReqs.filter(r => r.priority === "Won't").length}
|
||||
`
|
||||
Write(`${sessionFolder}/requirements/_index.md`, indexContent)
|
||||
outputPath = 'requirements/_index.md'
|
||||
Write(`${sessionFolder}/spec/requirements/_index.md`, indexContent)
|
||||
outputPath = 'spec/requirements/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -485,7 +485,7 @@ CONSTRAINTS: Be genuinely critical, not just validating. Focus on actionable imp
|
||||
}
|
||||
|
||||
// === 生成 architecture/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/architecture"`)
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/architecture"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
const adrs = parseADRs(geminiArchitectureOutput, codexReviewOutput)
|
||||
@@ -518,7 +518,7 @@ ${adr.consequences}
|
||||
## Review Feedback
|
||||
${adr.reviewFeedback || 'N/A'}
|
||||
`
|
||||
Write(`${sessionFolder}/architecture/ADR-${adr.id}-${adr.slug}.md`, adrContent)
|
||||
Write(`${sessionFolder}/spec/architecture/ADR-${adr.id}-${adr.slug}.md`, adrContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(含 Mermaid 组件图 + ER图 + 链接)
|
||||
@@ -535,8 +535,8 @@ dependencies:
|
||||
---`
|
||||
// 包含: system overview, component diagram (Mermaid), tech stack table,
|
||||
// ADR links table, data model (Mermaid erDiagram), API design, security controls
|
||||
Write(`${sessionFolder}/architecture/_index.md`, archIndexContent)
|
||||
outputPath = 'architecture/_index.md'
|
||||
Write(`${sessionFolder}/spec/architecture/_index.md`, archIndexContent)
|
||||
outputPath = 'spec/architecture/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -596,7 +596,7 @@ CONSTRAINTS: Every Must-have requirement must appear in at least one Story. Stor
|
||||
}
|
||||
|
||||
// === 生成 epics/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/epics"`)
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/epics"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
const epicsList = parseEpics(cliOutput)
|
||||
@@ -643,7 +643,7 @@ ${epic.reqs.map(r => `- [${r}](../requirements/${r}.md)`).join('\n')}
|
||||
## Architecture
|
||||
${epic.adrs.map(a => `- [${a}](../architecture/${a}.md)`).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/epics/EPIC-${epic.id}-${epic.slug}.md`, epicContent)
|
||||
Write(`${sessionFolder}/spec/epics/EPIC-${epic.id}-${epic.slug}.md`, epicContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(含 Mermaid 依赖图 + MVP + 链接)
|
||||
@@ -660,8 +660,8 @@ dependencies:
|
||||
---`
|
||||
// 包含: Epic overview table (with links), dependency Mermaid diagram,
|
||||
// execution order, MVP scope, traceability matrix
|
||||
Write(`${sessionFolder}/epics/_index.md`, epicsIndexContent)
|
||||
outputPath = 'epics/_index.md'
|
||||
Write(`${sessionFolder}/spec/epics/_index.md`, epicsIndexContent)
|
||||
outputPath = 'spec/epics/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user