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:
catlog22
2026-02-15 13:51:50 +08:00
parent a897858c6a
commit 80b7dfc817
45 changed files with 6369 additions and 505 deletions

View File

@@ -159,6 +159,45 @@ Full-lifecycle:
[Spec pipeline] → PLAN-001(blockedBy: DISCUSS-006) → IMPL-001 → TEST-001 + REVIEW-001
```
## Unified Session Directory
All session artifacts are stored under a single session folder:
```
.workflow/.team/TLS-{slug}-{YYYY-MM-DD}/
├── team-session.json # Session state (status, progress, completed_tasks)
├── spec/ # Spec artifacts (analyst, writer, reviewer output)
│ ├── 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 (discussant output)
│ └── discuss-001..006.md
└── plan/ # Plan artifacts (planner output)
├── exploration-{angle}.json
├── explorations-manifest.json
├── plan.json
└── .task/
└── TASK-*.json
```
Messages remain at `.workflow/.team-msg/{team-name}/` (unchanged).
## Session Resume
Coordinator supports `--resume` / `--continue` flags to resume interrupted sessions:
1. Scans `.workflow/.team/TLS-*/team-session.json` for `status: "active"` or `"paused"`
2. Multiple matches → `AskUserQuestion` for user selection
3. Loads session state: `teamName`, `mode`, `sessionFolder`, `completed_tasks`
4. Rebuilds team (`TeamCreate` + worker spawns)
5. Creates only uncompleted tasks in the task chain
6. Jumps to Phase 4 coordination loop
## Coordinator Spawn Template
When coordinator creates teammates:
@@ -237,6 +276,7 @@ Task({
当你收到 PLAN-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=planner") 执行。
当前需求: ${taskDescription}
约束: ${constraints}
Session: ${sessionFolder}
## 消息总线(必须)
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。

View File

@@ -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}`

View File

@@ -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.jsonEPIC → 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

View File

@@ -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]

View File

@@ -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) {

View File

@@ -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 |

View File

@@ -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' ? '质量基本达标但有改进空间,建议在讨论中审查。' :

View File

@@ -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'
}
```

View File

@@ -71,8 +71,10 @@
"collaboration_patterns": ["CP-1", "CP-2", "CP-4", "CP-5", "CP-6", "CP-10"],
"session_dirs": {
"spec": ".workflow/.spec-team/{topic-slug}-{YYYY-MM-DD}/",
"impl": ".workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/",
"base": ".workflow/.team/TLS-{slug}-{YYYY-MM-DD}/",
"spec": "spec/",
"discussions": "discussions/",
"plan": "plan/",
"messages": ".workflow/.team-msg/{team-name}/"
}
}