Files
Claude-Code-Workflow/.claude/commands/team/spec-discuss.md
catlog22 6054a01b8f feat: add CLI fallback for MCP calls in team commands
- Implemented CLI fallback using `ccw team` for various team command operations in `execute.md`, `plan.md`, `review.md`, `spec-analyst.md`, `spec-coordinate.md`, `spec-discuss.md`, `spec-reviewer.md`, `spec-writer.md`, and `test.md`.
- Updated command generation templates to include CLI fallback examples.
- Enhanced validation checks to ensure CLI fallback sections are present.
- Added quality standards for CLI fallback in team command design.
- Introduced a new `GlobalGraphExpander` class for expanding search results with cross-directory relationships.
- Added tests for `GlobalGraphExpander` to verify functionality and score decay factors.
2026-02-13 12:05:48 +08:00

20 KiB
Raw Blame History

name, description, argument-hint, allowed-tools, group
name description argument-hint allowed-tools group
spec-discuss Team spec discuss - 结构化团队讨论、多视角批判、共识构建、分歧调解 SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Bash(*), Glob(*), Grep(*), Task(*) team

Team Spec Discuss Command (/team:spec-discuss)

Overview

Team spec-discuss role command. Operates as a teammate within a Spec Team, responsible for facilitating structured team discussions between specification phases. This is the key differentiator of the spec team workflow — ensuring multi-perspective critique, consensus building, and quality feedback before each phase transition.

Core capabilities:

  • Task discovery from shared team task list (DISCUSS-* tasks)
  • Multi-perspective analysis: Product, Technical, Quality, Risk viewpoints
  • Structured discussion facilitation with critique + suggestion format
  • Consensus synthesis with action items and decision records
  • Conflict identification and escalation when consensus cannot be reached
  • CLI-assisted deep critique (parallel multi-model analysis)

Role Definition

Name: spec-discuss Responsibility: Load Artifact → Multi-Perspective Critique → Synthesize Consensus → Report Communication: SendMessage to coordinator only

消息总线

每次 SendMessage ,必须调用 mcp__ccw-tools__team_msg 记录消息:

mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })

支持的 Message Types

Type 方向 触发时机 说明
discussion_ready spec-discuss → coordinator 讨论完成,共识达成 附带讨论记录路径和决策摘要
discussion_blocked spec-discuss → coordinator 讨论无法达成共识 附带分歧点和可选方案,需 coordinator 介入
impl_progress spec-discuss → coordinator 长讨论进展更新 多视角分析进度
error spec-discuss → coordinator 讨论无法进行 输入制品缺失等

调用示例

// 讨论共识达成
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "discussion_ready", summary: "DISCUSS-002: 共识达成, 3个改进建议 + 2个开放问题", ref: ".workflow/.spec-team/session/discussions/discuss-002-brief.md" })

// 讨论阻塞
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "discussion_blocked", summary: "DISCUSS-004: 技术选型分歧 - 微服务 vs 单体, 需用户决策", data: { reason: "技术架构风格无法达成共识", options: [{ label: "微服务", description: "更好扩展性但增加复杂度" }, { label: "单体", description: "简单但限制扩展" }] } })

// 错误上报
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: "DISCUSS-001: 找不到 discovery-context.json" })

CLI 回退

mcp__ccw-tools__team_msg MCP 不可用时,使用 ccw team CLI 作为等效回退:

// 回退: 将 MCP 调用替换为 Bash CLI参数一一对应
Bash(`ccw team log --team "${teamName}" --from "spec-discuss" --to "coordinator" --type "discussion_ready" --summary "DISCUSS-002: 共识达成, 3个改进建议" --ref "${sessionFolder}/discussions/discuss-002-brief.md" --json`)

// 带 data 参数(讨论阻塞时)
Bash(`ccw team log --team "${teamName}" --from "spec-discuss" --to "coordinator" --type "discussion_blocked" --summary "技术选型分歧" --data '{"reason":"微服务 vs 单体","options":[{"label":"微服务"},{"label":"单体"}]}' --json`)

参数映射: team_msg(params)ccw team log --team <team> --from spec-discuss --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]

讨论维度模型

每个讨论轮次从4个视角进行结构化分析

视角 关注点 代表角色
产品视角 市场适配、用户价值、商业可行性、竞品差异 Product Manager
技术视角 可行性、技术债务、性能、安全、可维护性 Tech Lead
质量视角 完整性、可测试性、一致性、标准合规 QA Lead
风险视角 风险识别、依赖分析、假设验证、失败模式 Risk Analyst

讨论轮次配置

轮次 制品 重点维度 讨论深度
DISCUSS-001 discovery-context 产品+风险 范围确认、方向调整
DISCUSS-002 product-brief 产品+技术+质量 定位审视、可行性
DISCUSS-003 requirements 质量+产品 完整性、优先级
DISCUSS-004 architecture 技术+风险 技术选型、安全性
DISCUSS-005 epics 产品+技术+质量 MVP范围、估算
DISCUSS-006 readiness-report 全维度 最终签收

Execution Process

Phase 1: Task Discovery
   ├─ TaskList to find unblocked DISCUSS-* tasks
   ├─ TaskGet to read full task details
   └─ TaskUpdate to mark in_progress

Phase 2: Artifact Loading
   ├─ Identify discussion round from task subject (001-006)
   ├─ Load target artifact for discussion
   ├─ Load prior discussion records for continuity
   └─ Determine discussion dimensions from round config

Phase 3: Multi-Perspective Critique
   ├─ Product perspective analysis
   ├─ Technical perspective analysis
   ├─ Quality perspective analysis
   ├─ Risk perspective analysis
   └─ (Parallel CLI execution for depth)

Phase 4: Consensus Synthesis
   ├─ Identify convergent themes (areas of agreement)
   ├─ Identify divergent views (conflicts)
   ├─ Generate action items and recommendations
   ├─ Formulate consensus or escalate divergence
   └─ Write discussion record

Phase 5: Report to Coordinator
   ├─ team_msg log + SendMessage discussion results
   ├─ TaskUpdate completed (if consensus)
   └─ Flag discussion_blocked (if unresolvable conflict)

Implementation

Phase 1: Task Discovery

// Find assigned DISCUSS-* tasks
const tasks = TaskList()
const myTasks = tasks.filter(t =>
  t.subject.startsWith('DISCUSS-') &&
  t.owner === 'spec-discuss' &&
  t.status === 'pending' &&
  t.blockedBy.length === 0
)

if (myTasks.length === 0) return // idle

const task = TaskGet({ taskId: myTasks[0].id })
TaskUpdate({ taskId: task.id, status: 'in_progress' })

Phase 2: Artifact Loading

// Extract session folder and discussion round
const sessionMatch = task.description.match(/Session:\s*(.+)/)
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
const roundMatch = task.subject.match(/DISCUSS-(\d+)/)
const roundNumber = roundMatch ? parseInt(roundMatch[1]) : 0

// Discussion round configuration
const roundConfig = {
  1: { artifact: 'discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk'], label: '范围讨论' },
  2: { artifact: 'product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality'], label: 'Brief评审' },
  3: { artifact: 'requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product'], 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'], label: 'Epics讨论' },
  6: { artifact: 'readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk'], label: '最终签收' }
}

const config = roundConfig[roundNumber]
if (!config) {
  mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: `未知讨论轮次: DISCUSS-${roundNumber}` })
  return
}

// Load target artifact
let artifact = null
try {
  const raw = Read(`${sessionFolder}/${config.artifact}`)
  artifact = config.type === 'json' ? JSON.parse(raw) : raw
} catch (e) {
  mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: `无法加载制品: ${config.artifact}` })
  return
}

// Load prior discussion records for context continuity
const priorDiscussions = []
for (let i = 1; i < roundNumber; i++) {
  const priorConfig = roundConfig[i]
  try { priorDiscussions.push(Read(`${sessionFolder}/discussions/${priorConfig.outputFile}`)) } catch {}
}

// Ensure discussions directory exists
Bash(`mkdir -p ${sessionFolder}/discussions`)

Phase 3: Multi-Perspective Critique

const perspectives = {}
const artifactContent = typeof artifact === 'string' ? artifact : JSON.stringify(artifact, null, 2)

// Progress notification
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "impl_progress", summary: `开始 ${config.label}: ${config.perspectives.length} 个视角分析` })

// --- Product Perspective ---
if (config.perspectives.includes('product')) {
  Bash({
    command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a PRODUCT MANAGER perspective.
TASK:
• Evaluate market fit and user value proposition
• Assess target user alignment and persona coverage
• Check business viability and competitive differentiation
• Identify gaps in user journey coverage
• Rate on scale 1-5 with specific improvement suggestions

ARTIFACT TYPE: ${config.label}
CONTENT: ${artifactContent.substring(0, 8000)}

${priorDiscussions.length > 0 ? `PRIOR DISCUSSION CONTEXT: ${priorDiscussions[priorDiscussions.length - 1]?.substring(0, 2000)}` : ''}

EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], open_questions[], rating (1-5)
CONSTRAINTS: Be constructive but rigorous" --tool gemini --mode analysis`,
    run_in_background: true
  })
  // perspectives.product = parseCLIResult(...)
}

// --- Technical Perspective ---
if (config.perspectives.includes('technical')) {
  Bash({
    command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a TECH LEAD perspective.
TASK:
• Evaluate technical feasibility and implementation complexity
• Assess architecture decisions and technology choices
• Check for technical debt risks and scalability concerns
• Identify missing technical requirements or constraints
• Rate on scale 1-5 with specific improvement suggestions

ARTIFACT TYPE: ${config.label}
CONTENT: ${artifactContent.substring(0, 8000)}

EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], risks[], rating (1-5)
CONSTRAINTS: Focus on engineering concerns" --tool codex --mode analysis`,
    run_in_background: true
  })
  // perspectives.technical = parseCLIResult(...)
}

// --- Quality Perspective ---
if (config.perspectives.includes('quality')) {
  Bash({
    command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a QA LEAD perspective.
TASK:
• Evaluate completeness of acceptance criteria and testability
• Check consistency of terminology and formatting
• Assess traceability between documents
• Identify ambiguous or untestable requirements
• Rate on scale 1-5 with specific improvement suggestions

ARTIFACT TYPE: ${config.label}
CONTENT: ${artifactContent.substring(0, 8000)}

EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], testability_issues[], rating (1-5)
CONSTRAINTS: Focus on quality and verifiability" --tool claude --mode analysis`,
    run_in_background: true
  })
  // perspectives.quality = parseCLIResult(...)
}

// --- Risk Perspective ---
if (config.perspectives.includes('risk')) {
  Bash({
    command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a RISK ANALYST perspective.
TASK:
• Identify project risks and failure modes
• Assess dependency risks and external factors
• Validate assumptions made in the specification
• Check for missing contingency plans
• Rate risk level (Low/Medium/High/Critical) with mitigation suggestions

ARTIFACT TYPE: ${config.label}
CONTENT: ${artifactContent.substring(0, 8000)}

EXPECTED: Structured critique with: risks[{description, likelihood, impact, mitigation}], assumptions_to_validate[], dependencies[], overall_risk_level
CONSTRAINTS: Focus on risk identification" --tool gemini --mode analysis`,
    run_in_background: true
  })
  // perspectives.risk = parseCLIResult(...)
}

// Wait for all parallel CLI analyses to complete

Phase 4: Consensus Synthesis

// Analyze all perspectives for convergence and divergence
const synthesis = {
  convergent_themes: [],   // Areas where all perspectives agree
  divergent_views: [],     // Areas of conflict
  action_items: [],        // Concrete improvements to make
  open_questions: [],      // Unresolved questions for user/team
  decisions: [],           // Decisions made during discussion
  risk_flags: [],          // Risks identified
  overall_sentiment: '',   // positive/neutral/concerns/critical
  consensus_reached: true  // false if major unresolvable conflicts
}

// Extract convergent themes (items mentioned positively by 2+ perspectives)
// Extract divergent views (items where perspectives conflict)
// Generate action items from suggestions

// Check for unresolvable conflicts
const criticalDivergences = synthesis.divergent_views.filter(d => d.severity === 'high')
if (criticalDivergences.length > 0) {
  synthesis.consensus_reached = false
}

// Determine overall sentiment
const avgRating = Object.values(perspectives)
  .map(p => p?.rating || 3)
  .reduce((a, b) => a + b, 0) / config.perspectives.length

synthesis.overall_sentiment = avgRating >= 4 ? 'positive'
  : avgRating >= 3 ? 'neutral'
  : avgRating >= 2 ? 'concerns'
  : 'critical'

// Generate discussion record
const discussionRecord = `# 讨论记录: ${config.label} (DISCUSS-${String(roundNumber).padStart(3, '0')})

**讨论对象**: ${config.artifact}
**参与视角**: ${config.perspectives.join(', ')}
**讨论时间**: ${new Date().toISOString()}
**共识状态**: ${synthesis.consensus_reached ? '已达成共识' : '存在分歧需coordinator介入'}
**总体评价**: ${synthesis.overall_sentiment}

## 多视角评审结果

${config.perspectives.map(p => {
  const pData = perspectives[p]
  return `### ${p === 'product' ? '产品视角 (Product Manager)' :
    p === 'technical' ? '技术视角 (Tech Lead)' :
    p === 'quality' ? '质量视角 (QA Lead)' :
    '风险视角 (Risk Analyst)'}

**评分**: ${pData?.rating || 'N/A'}/5

**优点**:
${(pData?.strengths || []).map(s => '- ' + s).join('\n') || '- (待分析)'}

**不足**:
${(pData?.weaknesses || []).map(w => '- ' + w).join('\n') || '- (待分析)'}

**建议**:
${(pData?.suggestions || []).map(s => '- ' + s).join('\n') || '- (待分析)'}
`}).join('\n')}

## 共识分析

### 一致认同的优点
${synthesis.convergent_themes.map(t => '- ' + t).join('\n') || '- (待合成)'}

### 存在的分歧
${synthesis.divergent_views.map(d => `- **${d.topic}**: ${d.description}`).join('\n') || '- 无重大分歧'}

### 风险标记
${synthesis.risk_flags.map(r => `- [${r.level}] ${r.description}`).join('\n') || '- 无重大风险'}

## 行动项

${synthesis.action_items.map((item, i) => `${i+1}. ${item}`).join('\n') || '无需修改'}

## 开放问题

${synthesis.open_questions.map((q, i) => `${i+1}. ${q}`).join('\n') || '无开放问题'}

## 决策记录

${synthesis.decisions.map((d, i) => `${i+1}. **${d.topic}**: ${d.decision} (理由: ${d.rationale})`).join('\n') || '无新决策'}

## 对下一阶段的建议

${roundNumber < 6 ? `下一阶段应关注: ${synthesis.action_items.slice(0, 3).join('; ') || '按原计划推进'}` : '所有阶段已完成,建议进入执行。'}
`

Write(`${sessionFolder}/discussions/${config.outputFile}`, discussionRecord)

Phase 5: Report to Coordinator

if (synthesis.consensus_reached) {
  // Consensus reached
  mcp__ccw-tools__team_msg({
    operation: "log", team: teamName,
    from: "spec-discuss", to: "coordinator",
    type: "discussion_ready",
    summary: `${config.label}讨论完成: ${synthesis.action_items.length}个行动项, ${synthesis.open_questions.length}个开放问题, 总体${synthesis.overall_sentiment}`,
    ref: `${sessionFolder}/discussions/${config.outputFile}`
  })

  SendMessage({
    type: "message",
    recipient: "coordinator",
    content: `## 讨论结果: ${config.label}

**Task**: ${task.subject}
**共识**: 已达成
**总体评价**: ${synthesis.overall_sentiment}
**参与视角**: ${config.perspectives.join(', ')}

### 关键发现
**一致优点**: ${synthesis.convergent_themes.length}**分歧点**: ${synthesis.divergent_views.length}**风险标记**: ${synthesis.risk_flags.length}
### 行动项 (${synthesis.action_items.length})
${synthesis.action_items.map((item, i) => `${i+1}. ${item}`).join('\n') || '无'}

### 开放问题 (${synthesis.open_questions.length})
${synthesis.open_questions.map((q, i) => `${i+1}. ${q}`).join('\n') || '无'}

### 讨论记录
${sessionFolder}/discussions/${config.outputFile}

共识已达成,可推进至下一阶段。`,
    summary: `${config.label}共识达成: ${synthesis.action_items.length}行动项`
  })

  TaskUpdate({ taskId: task.id, status: 'completed' })
} else {
  // Consensus blocked - escalate to coordinator
  mcp__ccw-tools__team_msg({
    operation: "log", team: teamName,
    from: "spec-discuss", to: "coordinator",
    type: "discussion_blocked",
    summary: `${config.label}讨论阻塞: ${criticalDivergences.length}个关键分歧需决策`,
    data: {
      reason: criticalDivergences.map(d => d.description).join('; '),
      options: criticalDivergences.map(d => ({
        label: d.topic,
        description: d.options?.join(' vs ') || d.description
      }))
    }
  })

  SendMessage({
    type: "message",
    recipient: "coordinator",
    content: `## 讨论阻塞: ${config.label}

**Task**: ${task.subject}
**状态**: 无法达成共识,需要 coordinator 介入

### 关键分歧
${criticalDivergences.map((d, i) => `${i+1}. **${d.topic}**: ${d.description}
   - 选项A: ${d.optionA || ''}
   - 选项B: ${d.optionB || ''}`).join('\n\n')}

### 已达成共识的部分
${synthesis.convergent_themes.map(t => `- ${t}`).join('\n') || '- 无'}

### 建议
请通过 AskUserQuestion 收集用户对分歧点的决策,然后将决策写入讨论记录以继续推进。

### 讨论记录(部分)
${sessionFolder}/discussions/${config.outputFile}`,
    summary: `${config.label}阻塞: ${criticalDivergences.length}分歧`
  })

  // Keep task in_progress, wait for coordinator resolution
}

// Check for next DISCUSS task
const nextTasks = TaskList().filter(t =>
  t.subject.startsWith('DISCUSS-') &&
  t.owner === 'spec-discuss' &&
  t.status === 'pending' &&
  t.blockedBy.length === 0
)

if (nextTasks.length > 0) {
  // Continue with next task -> back to Phase 1
}

Error Handling

Scenario Resolution
No DISCUSS-* tasks available Idle, wait for coordinator assignment
Target artifact not found Notify coordinator, request prerequisite completion
CLI perspective analysis failure Fallback to direct Claude analysis for that perspective
All CLI analyses fail Generate basic discussion from direct reading
Consensus timeout (all perspectives diverge) Escalate as discussion_blocked
Prior discussion records missing Continue without continuity context
Session folder not found Notify coordinator, request session path
Unexpected error Log error via team_msg, report to coordinator