Files
Claude-Code-Workflow/.claude/skills/ccw/phases/orchestrator.md
catlog22 266f6f11ec feat: Enhance documentation diagnosis and category mapping
- Introduced action to diagnose documentation structure, identifying redundancies and conflicts.
- Added centralized category mappings in JSON format for improved detection and strategy application.
- Updated existing functions to utilize new mappings for taxonomy and strategy matching.
- Implemented new detection patterns for documentation redundancy and conflict.
- Expanded state schema to include documentation diagnosis results.
- Enhanced severity criteria and strategy selection guide to accommodate new documentation issues.
2026-01-14 21:07:52 +08:00

27 KiB

CCW Orchestrator

无状态编排器:分析输入 → 选择工作流链 → TODO 跟踪执行

Architecture

┌────────────────────────────────────────────────────────────────┐
│  CCW Orchestrator (CLI-Enhanced + Requirement Analysis)         │
├────────────────────────────────────────────────────────────────┤
│  Phase 1   │ Input Analysis (rule-based, fast path)            │
│  Phase 1.5 │ CLI Classification (semantic, smart path)         │
│  Phase 1.75│ Requirement Clarification (clarity < 2)           │
│  Phase 2   │ Chain Selection (intent → workflow)               │
│  Phase 2.5 │ CLI Action Planning (high complexity)             │
│  Phase 3   │ User Confirmation (optional)                      │
│  Phase 4   │ TODO Tracking Setup                                │
│  Phase 5   │ Execution Loop                                     │
└────────────────────────────────────────────────────────────────┘

References:

CLI Enhancement Config

Feature Trigger Default Tool
Classification matchCount < 2 OR complexity = high OR input > 100 chars gemini
Action Planning complexity = high OR steps >= 3 OR input > 200 chars gemini

Settings in index/intent-rules.json. Fallback: gemini → qwen → rule-based.


Core Helpers

// === Pattern Matching ===
const matchesAny = (text, patterns) => 
  Array.isArray(patterns) && patterns.some(p => text.toLowerCase().includes(p.toLowerCase()))

// === CLI Execution Helper ===
async function executeCli(prompt, config, purpose) {
  const tool = config.default_tool || 'gemini'
  const timeout = config.timeout_ms || 60000
  
  try {
    const escaped = prompt.replace(/"/g, '\\"').replace(/\n/g, '\\n')
    const result = Bash({
      command: `ccw cli -p "${escaped}" --tool ${tool} --mode analysis`,
      run_in_background: false,
      timeout
    })
    
    const jsonMatch = result.match(/\{[\s\S]*\}/)
    if (!jsonMatch) throw new Error('No JSON in CLI response')
    return JSON.parse(jsonMatch[0])
  } catch (error) {
    console.log(`> CLI ${purpose} failed: ${error.message}, falling back`)
    return null
  }
}

// === TODO Tracking Helpers ===
function updateTodos(todos, updates) {
  TodoWrite({ todos: todos.map((t, i) => updates[i] ? { ...t, ...updates[i] } : t) })
}

function formatDuration(ms) { return (ms / 1000).toFixed(1) }
function timestamp() { return new Date().toLocaleTimeString() }

Phase 1: Input Analysis

const intentRules = JSON.parse(Read('.claude/skills/ccw/index/intent-rules.json'))
const chains = JSON.parse(Read('.claude/skills/ccw/index/workflow-chains.json'))

function analyzeInput(userInput) {
  const input = userInput.trim()
  
  // Explicit command passthrough
  if (input.match(/^\/(?:workflow|issue|memory|task):/)) {
    return { type: 'explicit', command: input, passthrough: true }
  }
  
  return {
    type: 'natural',
    text: input,
    intent: classifyIntent(input, intentRules.intent_patterns),
    complexity: assessComplexity(input, intentRules.complexity_indicators),
    toolPreference: detectToolPreference(input, intentRules.cli_tool_triggers),
    passthrough: false
  }
}

function classifyIntent(text, patterns) {
  const sorted = Object.entries(patterns).sort((a, b) => a[1].priority - b[1].priority)
  
  for (const [intentType, config] of sorted) {
    // Handle variants (bugfix, ui, docs)
    if (config.variants) {
      for (const [variant, vc] of Object.entries(config.variants)) {
        const vPatterns = vc.patterns || vc.triggers || []
        if (matchesAny(text, vPatterns)) {
          if (intentType === 'bugfix') {
            if (matchesAny(text, config.variants.standard?.patterns || []))
              return { type: intentType, variant, workflow: vc.workflow }
          } else {
            return { type: intentType, variant, workflow: vc.workflow }
          }
        }
      }
      if (config.variants.standard && matchesAny(text, config.variants.standard.patterns))
        return { type: intentType, variant: 'standard', workflow: config.variants.standard.workflow }
    }
    
    // Simple patterns
    if (config.patterns && !config.require_both && matchesAny(text, config.patterns))
      return { type: intentType, workflow: config.workflow }
    
    // Dual-pattern (issue_batch)
    if (config.require_both && config.patterns) {
      if (matchesAny(text, config.patterns.batch_keywords) && matchesAny(text, config.patterns.action_keywords))
        return { type: intentType, workflow: config.workflow }
    }
  }
  return { type: 'feature' }
}

function assessComplexity(text, indicators) {
  let score = 0
  for (const [level, config] of Object.entries(indicators)) {
    if (config.patterns) {
      for (const [, pc] of Object.entries(config.patterns)) {
        if (matchesAny(text, pc.keywords)) score += pc.weight || 1
      }
    }
  }
  if (score >= indicators.high.score_threshold) return 'high'
  if (score >= indicators.medium.score_threshold) return 'medium'
  return 'low'
}

function detectToolPreference(text, triggers) {
  for (const [tool, config] of Object.entries(triggers)) {
    if (matchesAny(text, config.explicit) || matchesAny(text, config.semantic)) return tool
  }
  return null
}

function calculateMatchCount(text, patterns) {
  let count = 0
  for (const [, config] of Object.entries(patterns)) {
    if (config.variants) {
      for (const [, vc] of Object.entries(config.variants)) {
        if (matchesAny(text, vc.patterns || vc.triggers || [])) count++
      }
    }
    if (config.patterns && !config.require_both && matchesAny(text, config.patterns)) count++
  }
  return count
}

Dimension Extraction (WHAT/WHERE/WHY/HOW)

const PATTERNS = {
  actions: {
    create: /创建|新增|添加|实现|生成|create|add|implement|generate/i,
    fix: /修复|修正|解决|fix|repair|resolve|debug/i,
    refactor: /重构|优化结构|重写|refactor|restructure|rewrite/i,
    optimize: /优化|提升|改进|性能|optimize|improve|enhance|performance/i,
    analyze: /分析|理解|探索|研究|analyze|understand|explore|research/i,
    review: /审查|检查|评估|review|check|assess|audit/i
  },
  files: /(\S+\.(ts|js|py|md|json|yaml|yml|tsx|jsx|vue|css|scss))/g,
  modules: /(src\/\S+|lib\/\S+|packages\/\S+|components\/\S+)/g,
  dirs: /(\/[\w\-\.\/]+)/g,
  systemScope: /全局|全部|整个|all|entire|whole|系统/i,
  goal: /(?:为了|因为|目的是|to|for|because)\s+([^,,。]+)/i,
  motivation: /(?:需要|想要|希望|want|need|should)\s+([^,,。]+)/i,
  must: /(?:必须|一定要|需要|must|required)\s+([^,,。]+)/i,
  mustNot: /(?:不要|禁止|不能|避免|must not|don't|avoid)\s+([^,,。]+)/i,
  prefer: /(?:应该|最好|建议|should|prefer)\s+([^,,。]+)/i,
  unclear: /不知道|不确定|maybe|可能|how to|怎么/i,
  helpRequest: /帮我|help me|请问/i
}

function extractDimensions(input) {
  // WHAT
  let action = null
  for (const [key, pattern] of Object.entries(PATTERNS.actions)) {
    if (pattern.test(input)) { action = key; break }
  }
  const targetMatch = input.match(/(?:对|in|for|the)\s+([^\s,,。]+)/i)
  
  // WHERE
  const files = [...input.matchAll(PATTERNS.files)].map(m => m[1])
  const modules = [...input.matchAll(PATTERNS.modules)].map(m => m[1])
  const dirs = [...input.matchAll(PATTERNS.dirs)].map(m => m[1])
  const paths = [...new Set([...files, ...modules, ...dirs])]
  const scope = files.length > 0 ? 'file' : modules.length > 0 ? 'module' : PATTERNS.systemScope.test(input) ? 'system' : 'unknown'
  
  // WHY
  const goalMatch = input.match(PATTERNS.goal)
  const motivationMatch = input.match(PATTERNS.motivation)
  
  // HOW
  const constraints = []
  const mustMatch = input.match(PATTERNS.must)
  const mustNotMatch = input.match(PATTERNS.mustNot)
  if (mustMatch) constraints.push(mustMatch[1].trim())
  if (mustNotMatch) constraints.push('NOT: ' + mustNotMatch[1].trim())
  const prefMatch = input.match(PATTERNS.prefer)
  const preferences = prefMatch ? [prefMatch[1].trim()] : null

  return {
    what: { action, target: targetMatch?.[1] || null, description: input.substring(0, 100) },
    where: { scope, paths: paths.length > 0 ? paths : null, patterns: null },
    why: { goal: goalMatch?.[1]?.trim() || null, motivation: motivationMatch?.[1]?.trim() || null, success_criteria: null },
    how: { constraints: constraints.length > 0 ? constraints : null, preferences, approach: null },
    clarity_score: 0,
    missing_dimensions: []
  }
}

function calculateClarityScore(input, d) {
  let score = 0
  if (d.what.action) score += 0.5
  if (d.what.target) score += 0.5
  if (d.where.paths?.length > 0) score += 0.5
  if (d.where.scope !== 'unknown') score += 0.5
  if (d.why.goal) score += 0.5
  if (d.how.constraints?.length > 0) score += 0.5
  if (PATTERNS.unclear.test(input)) score -= 0.5
  if (PATTERNS.helpRequest.test(input)) score -= 0.5
  return Math.max(0, Math.min(3, Math.round(score)))
}

function identifyMissing(d) {
  const missing = []
  if (!d.what.target) missing.push('what.target')
  if (d.where.scope === 'unknown') missing.push('where.scope')
  return missing
}

Phase 1.5: CLI-Assisted Classification

async function cliAssistedClassification(input, ruleBasedResult, intentRules) {
  const cfg = intentRules.cli_classification
  if (!cfg?.enabled) return { ...ruleBasedResult, source: 'rules', matchCount: 0 }
  
  const matchCount = calculateMatchCount(input, intentRules.intent_patterns)
  const triggers = cfg.trigger_conditions
  const shouldUseCli = matchCount < triggers.low_match_count ||
    ruleBasedResult.complexity === triggers.complexity_trigger ||
    input.length > triggers.min_input_length ||
    matchesAny(input, triggers.ambiguous_patterns)
  
  if (!shouldUseCli) return { ...ruleBasedResult, source: 'rules', matchCount }
  
  console.log('### CLI-Assisted Intent Classification\n')
  
  const prompt = `
PURPOSE: Classify user request intent and recommend optimal workflow
TASK: Analyze semantic meaning, classify intent, assess complexity, recommend workflow
MODE: analysis
USER REQUEST: ${input}
EXPECTED: JSON { intent: { type, variant?, confidence, reasoning }, complexity: { level, factors, confidence }, recommended_workflow: { chain_id, reasoning }, tool_preference?: { suggested, reasoning } }
RULES: Output ONLY valid JSON. Types: bugfix|feature|exploration|ui|issue|tdd|review|docs. Chains: rapid|full|coupled|bugfix|issue|tdd|ui|review-fix|docs`

  const parsed = await executeCli(prompt, cfg, 'classification')
  if (!parsed) return { ...ruleBasedResult, source: 'rules', matchCount }
  
  console.log(`**CLI Result**: ${parsed.intent.type}${parsed.intent.variant ? ` (${parsed.intent.variant})` : ''} | ${parsed.complexity.level} | ${(parsed.intent.confidence * 100).toFixed(0)}% confidence`)
  
  return {
    type: 'natural', text: input,
    intent: { type: parsed.intent.type, variant: parsed.intent.variant, workflow: parsed.recommended_workflow.chain_id },
    complexity: parsed.complexity.level,
    toolPreference: parsed.tool_preference?.suggested || ruleBasedResult.toolPreference,
    confidence: parsed.intent.confidence,
    source: 'cli', cliReasoning: parsed.intent.reasoning, passthrough: false
  }
}

Phase 1.75: Requirement Clarification

async function clarifyRequirement(analysis, dimensions) {
  if (dimensions.clarity_score >= 2 || analysis.passthrough)
    return { needsClarification: false, dimensions }
  
  console.log('### Requirement Clarification\n')
  console.log(`**Clarity**: ${dimensions.clarity_score}/3 | WHAT: ${dimensions.what.action || '?'} | WHERE: ${dimensions.where.scope} | WHY: ${dimensions.why.goal || '?'}`)
  
  const questions = generateClarificationQuestions(dimensions)
  if (questions.length === 0) return { needsClarification: false, dimensions }
  
  const responses = AskUserQuestion({ questions: questions.slice(0, 4) })
  const refined = refineDimensions(dimensions, responses)
  refined.clarity_score = Math.min(3, dimensions.clarity_score + 1)
  
  return { needsClarification: false, dimensions: refined }
}

function generateClarificationQuestions(d) {
  const Q = []
  
  if (!d.what.target) Q.push({
    question: "你想要对什么进行操作?", header: "目标", multiSelect: false,
    options: [
      { label: "特定文件", description: "修改特定的文件或代码" },
      { label: "功能模块", description: "处理整个功能模块" },
      { label: "系统级", description: "架构或系统级变更" },
      { label: "让我指定", description: "我会提供具体说明" }
    ]
  })
  
  if (d.where.scope === 'unknown' && !d.where.paths?.length) Q.push({
    question: "操作的范围是什么?", header: "范围", multiSelect: false,
    options: [
      { label: "自动发现", description: "分析代码库后推荐相关位置" },
      { label: "当前目录", description: "只在当前工作目录" },
      { label: "全项目", description: "整个项目范围" },
      { label: "让我指定", description: "我会提供具体路径" }
    ]
  })
  
  if (!d.why.goal && d.what.action !== 'analyze') Q.push({
    question: "这个操作的主要目标是什么?", header: "目标类型", multiSelect: false,
    options: [
      { label: "修复问题", description: "解决已知的Bug或错误" },
      { label: "新增功能", description: "添加新的能力或特性" },
      { label: "改进质量", description: "提升性能、可维护性" },
      { label: "代码审查", description: "检查和评估代码" }
    ]
  })
  
  if (d.what.action === 'refactor' || d.what.action === 'create') Q.push({
    question: "有什么特殊要求或限制?", header: "约束", multiSelect: true,
    options: [
      { label: "保持兼容", description: "不破坏现有功能" },
      { label: "最小改动", description: "尽量少修改文件" },
      { label: "包含测试", description: "需要添加测试" },
      { label: "无特殊要求", description: "按最佳实践处理" }
    ]
  })
  
  return Q
}

function refineDimensions(d, responses) {
  const r = { ...d, what: { ...d.what }, where: { ...d.where }, why: { ...d.why }, how: { ...d.how } }
  
  const scopeMap = { '特定文件': 'file', '功能模块': 'module', '系统级': 'system', '全项目': 'system', '当前目录': 'module' }
  const goalMap = { '修复问题': 'fix bug', '新增功能': 'add feature', '改进质量': 'improve quality', '代码审查': 'code review' }
  
  if (responses['目标'] && scopeMap[responses['目标']]) r.where.scope = scopeMap[responses['目标']]
  if (responses['范围'] && scopeMap[responses['范围']]) r.where.scope = scopeMap[responses['范围']]
  if (responses['目标类型'] && goalMap[responses['目标类型']]) r.why.goal = goalMap[responses['目标类型']]
  if (responses['约束']) {
    const c = Array.isArray(responses['约束']) ? responses['约束'] : [responses['约束']]
    r.how.constraints = c.filter(x => x !== '无特殊要求')
  }
  
  r.missing_dimensions = identifyMissing(r)
  return r
}

Phase 2: Chain Selection

function selectChain(analysis) {
  const { intent, complexity } = analysis
  const chainMap = {
    bugfix: 'bugfix', issue_batch: 'issue', exploration: 'full', ui_design: 'ui',
    tdd: 'tdd', review: 'review-fix', documentation: 'docs', feature: null
  }
  
  let chainId = chainMap[intent.type] || chains.chain_selection_rules.complexity_fallback[complexity]
  const chain = chains.chains[chainId]
  let steps = chain.steps
  
  if (chain.variants) {
    const variant = intent.variant || Object.keys(chain.variants)[0]
    steps = chain.variants[variant].steps
  }
  
  return { id: chainId, name: chain.name, description: chain.description, steps, complexity: chain.complexity, estimated_time: chain.estimated_time }
}

Phase 2.5: CLI-Assisted Action Planning

async function cliAssistedPlanning(analysis, selectedChain, intentRules) {
  const cfg = intentRules.cli_action_planning
  if (!cfg?.enabled) return { useDefaultChain: true, chain: selectedChain }
  
  const triggers = cfg.trigger_conditions
  const shouldUseCli = analysis.complexity === triggers.complexity_threshold ||
    selectedChain.steps.length >= triggers.step_count_threshold ||
    (analysis.text?.length > 200)
  
  if (!shouldUseCli) return { useDefaultChain: true, chain: selectedChain }
  
  console.log('### CLI-Assisted Action Planning\n')
  
  const prompt = `
PURPOSE: Plan optimal workflow execution strategy
CONTEXT: Intent=${analysis.intent.type}, Complexity=${analysis.complexity}, Chain=${selectedChain.name}, Steps=${selectedChain.steps.map(s => s.command).join(',')}
USER REQUEST: ${analysis.text?.substring(0, 200) || 'N/A'}
EXPECTED: JSON { recommendation: "use_default|modify|upgrade", modified_steps?: [], cli_injections?: [], reasoning, risks?: [], suggestions?: [] }
RULES: Output ONLY valid JSON. If no modifications needed, use "use_default".`

  const parsed = await executeCli(prompt, cfg, 'planning')
  if (!parsed) return { useDefaultChain: true, chain: selectedChain, source: 'default' }
  
  console.log(`**Planning**: ${parsed.recommendation}${parsed.reasoning ? ` - ${parsed.reasoning}` : ''}`)
  
  if (parsed.recommendation === 'modify' && parsed.modified_steps?.length > 0 && cfg.allow_step_modification) {
    return {
      useDefaultChain: false,
      chain: { ...selectedChain, steps: parsed.modified_steps },
      reasoning: parsed.reasoning, risks: parsed.risks, cliInjections: parsed.cli_injections, source: 'cli-planned'
    }
  }
  
  return { useDefaultChain: true, chain: selectedChain, suggestions: parsed.suggestions, risks: parsed.risks, source: 'cli-reviewed' }
}

Phase 3: User Confirmation

function confirmChain(selectedChain, analysis) {
  if (selectedChain.steps.length <= 2 && analysis.complexity === 'low') return selectedChain
  
  console.log(`\n## CCW Workflow: ${selectedChain.name}\n**Intent**: ${analysis.intent.type} | **Complexity**: ${analysis.complexity}\n**Steps**: ${selectedChain.steps.map((s, i) => `${i + 1}. ${s.command}`).join(' → ')}`)
  
  const response = AskUserQuestion({
    questions: [{
      question: `Proceed with ${selectedChain.name}?`, header: "Confirm", multiSelect: false,
      options: [
        { label: "Proceed", description: `Execute ${selectedChain.steps.length} steps` },
        { label: "Rapid", description: "Use lite-plan → lite-execute" },
        { label: "Full", description: "Use brainstorm → plan → execute" },
        { label: "Manual", description: "Specify commands manually" }
      ]
    }]
  })
  
  if (response.Confirm === 'Rapid') return selectChain({ intent: { type: 'feature' }, complexity: 'low' })
  if (response.Confirm === 'Full') return chains.chains['full']
  if (response.Confirm === 'Manual') return null
  return selectedChain
}

Phase 4: TODO Tracking Setup

function setupTodoTracking(chain, analysis) {
  const todos = [
    { content: `CCW: ${chain.name} (${chain.steps.length} steps)`, status: 'in_progress', activeForm: `Running ${chain.name} workflow` },
    ...chain.steps.map((step, i) => ({
      content: `[${i + 1}/${chain.steps.length}] ${step.command}`,
      status: i === 0 ? 'in_progress' : 'pending',
      activeForm: `Executing ${step.command}`
    }))
  ]
  TodoWrite({ todos })
  return { chain, currentStep: 0, todos }
}

function trackCommandDispatch(command) {
  const name = command.split(' ')[0]
  const todos = [
    { content: `CCW: Direct Command Dispatch`, status: 'in_progress', activeForm: `Dispatching ${name}` },
    { content: `[${timestamp()}] ${command}`, status: 'in_progress', activeForm: `Executing ${name}` }
  ]
  TodoWrite({ todos })
  return { command, startTime: Date.now(), todos }
}

function markCommandResult(tracking, success, error) {
  const duration = formatDuration(Date.now() - tracking.startTime)
  const name = tracking.command.split(' ')[0]
  const icon = success ? '✓' : '✗'
  const suffix = success ? `(${duration}s)` : `(failed: ${error})`
  
  TodoWrite({ todos: [
    { content: `CCW: Direct Command Dispatch`, status: 'completed', activeForm: success ? `Completed ${name}` : 'Failed' },
    { content: `${icon} ${tracking.command} ${suffix}`, status: 'completed', activeForm: success ? `Completed ${name}` : 'Failed' }
  ]})
}

Phase 5: Execution Loop

async function executeChain(execution, analysis) {
  const { chain, todos } = execution
  let step = 0
  const timings = []
  
  while (step < chain.steps.length) {
    const s = chain.steps[step]
    const start = Date.now()
    
    // Update TODO: current step in_progress
    const updated = todos.map((t, i) => {
      if (i === 0) return { ...t, status: 'in_progress' }
      if (i === step + 1) return { ...t, status: 'in_progress', content: `[${step + 1}/${chain.steps.length}] ${s.command} (started ${timestamp()})` }
      if (i <= step) {
        const tm = timings[i - 1]
        return { ...t, status: 'completed', content: tm === 'skipped' ? `⊘ ${t.content}` : `✓ [${i}/${chain.steps.length}] ${chain.steps[i-1].command} (${tm}s)` }
      }
      return { ...t, status: 'pending' }
    })
    TodoWrite({ todos: updated })
    
    console.log(`\n### Step ${step + 1}/${chain.steps.length}: ${s.command}`)
    
    // Confirmation if required
    if (s.confirm_before) {
      const r = AskUserQuestion({
        questions: [{ question: `Execute ${s.command}?`, header: "Step", multiSelect: false,
          options: [{ label: "Execute", description: "Run" }, { label: "Skip", description: "Skip" }, { label: "Abort", description: "Stop" }] }]
      })
      if (r.Step === 'Skip') { timings.push('skipped'); step++; continue }
      if (r.Step === 'Abort') break
    }
    
    // Execute
    try {
      SlashCommand(s.command, { args: analysis.text })
      const duration = formatDuration(Date.now() - start)
      timings.push(duration)
      updated[step + 1] = { ...updated[step + 1], status: 'completed', content: `✓ [${step + 1}/${chain.steps.length}] ${s.command} (${duration}s)` }
      TodoWrite({ todos: updated })
      console.log(`> Completed (${duration}s)`)
    } catch (error) {
      updated[step + 1] = { ...updated[step + 1], status: 'completed', content: `✗ [${step + 1}/${chain.steps.length}] ${s.command} (failed)` }
      TodoWrite({ todos: updated })
      console.log(`> Failed: ${error.message}`)
      
      const r = AskUserQuestion({
        questions: [{ question: `Step failed. Proceed?`, header: "Error", multiSelect: false,
          options: [{ label: "Retry", description: "Retry" }, { label: "Skip", description: "Skip" }, { label: "Abort", description: "Stop" }] }]
      })
      if (r.Error === 'Retry') continue
      if (r.Error === 'Abort') break
    }
    
    step++
    
    // Check auto_continue
    if (!s.auto_continue && step < chain.steps.length) {
      console.log(`\nPaused. Next: ${chain.steps[step].command}. Type "continue" to proceed.`)
      break
    }
  }
  
  // Final status
  if (step >= chain.steps.length) {
    const total = timings.filter(t => t !== 'skipped').reduce((sum, t) => sum + parseFloat(t), 0).toFixed(1)
    const final = todos.map((t, i) => {
      if (i === 0) return { ...t, status: 'completed', content: `✓ CCW: ${chain.name} completed (${total}s)`, activeForm: 'Complete' }
      const tm = timings[i - 1]
      return { ...t, status: 'completed', content: tm === 'skipped' ? `⊘ [${i}/${chain.steps.length}] ${chain.steps[i-1].command}` : `✓ [${i}/${chain.steps.length}] ${chain.steps[i-1].command} (${tm}s)` }
    })
    TodoWrite({ todos: final })
    console.log(`\n✓ ${chain.name} completed (${chain.steps.length} steps, ${total}s)`)
  }
  
  return { completed: step, total: chain.steps.length, timings }
}

Main Orchestration Entry

async function ccwOrchestrate(userInput) {
  console.log('## CCW Orchestrator\n')
  
  // Phase 1: Input Analysis
  const ruleBasedAnalysis = analyzeInput(userInput)
  
  // Handle passthrough commands
  if (ruleBasedAnalysis.passthrough) {
    console.log(`Direct: ${ruleBasedAnalysis.command}`)
    const tracking = trackCommandDispatch(ruleBasedAnalysis.command)
    try {
      const result = SlashCommand(ruleBasedAnalysis.command)
      markCommandResult(tracking, true)
      return result
    } catch (error) {
      markCommandResult(tracking, false, error.message)
      throw error
    }
  }
  
  // Phase 1.5: CLI Classification
  const analysis = await cliAssistedClassification(userInput, ruleBasedAnalysis, intentRules)
  
  // Extract dimensions
  const dimensions = extractDimensions(userInput)
  dimensions.clarity_score = calculateClarityScore(userInput, dimensions)
  dimensions.missing_dimensions = identifyMissing(dimensions)
  
  console.log(`### Classification\n**Source**: ${analysis.source} | **Intent**: ${analysis.intent.type}${analysis.intent.variant ? ` (${analysis.intent.variant})` : ''} | **Complexity**: ${analysis.complexity} | **Clarity**: ${dimensions.clarity_score}/3`)
  
  // Phase 1.75: Clarification
  const { dimensions: refined } = await clarifyRequirement(analysis, dimensions)
  analysis.dimensions = refined
  
  // Phase 2: Chain Selection
  const selectedChain = selectChain(analysis)
  
  // Phase 2.5: CLI Planning
  const { chain: optimizedChain, source: planSource, reasoning, risks } = await cliAssistedPlanning(analysis, selectedChain, intentRules)
  if (planSource?.includes('cli')) console.log(`### Planning\n${reasoning || ''}${risks?.length ? ` | Risks: ${risks.join(', ')}` : ''}`)
  
  // Phase 3: Confirm
  const confirmedChain = confirmChain(optimizedChain, analysis)
  if (!confirmedChain) { console.log('Manual mode. Specify commands directly.'); return }
  
  // Phase 4: Setup TODO
  const execution = setupTodoTracking(confirmedChain, analysis)
  
  // Phase 5: Execute
  return await executeChain(execution, analysis)
}

Decision Matrix

Intent Complexity Chain Steps
bugfix * bugfix lite-fix
issue * issue plan → queue → execute
exploration * full brainstorm → plan → execute
ui * ui ui-design → sync → plan → execute
tdd * tdd tdd-plan → execute → tdd-verify
review * review-fix review-session-cycle → review-fix
docs low/medium+ docs update-related / docs → execute
feature low/medium/high rapid/coupled/full lite-plan / plan → verify / brainstorm

Continuation Commands

Input Action
continue Next step
skip Skip current
abort Stop workflow
/workflow:* Specific command
Natural language Re-analyze