Files
Claude-Code-Workflow/.claude/commands/workflow-tune.md
catlog22 8953795c49 feat: Implement workflow tuning command and remove synthesis prompt template
- Added a new command for workflow tuning that extracts commands from reference documents or natural language, executes them via the ccw CLI, and analyzes the artifacts using Gemini.
- The command includes detailed phases for setup, execution, analysis, synthesis, and reporting, ensuring a structured approach to workflow optimization.
- Removed the synthesis prompt template as it is no longer needed with the new command implementation.
2026-03-20 20:25:41 +08:00

36 KiB
Raw Blame History

name, description, argument-hint, allowed-tools
name description argument-hint allowed-tools
workflow-tune Workflow tuning - extract commands from reference docs or natural language, execute each via ccw cli --tool claude --mode write, then analyze artifacts via gemini. For testing how commands execute in Claude. <file-path> <intent> | "step1 | step2 | step3" | "skill-a,skill-b" | --file workflow.json [--depth quick|standard|deep] [-y|--yes] [--auto-fix] Agent(*), AskUserQuestion(*), TaskCreate(*), TaskUpdate(*), TaskList(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*)

Workflow Tune

测试 Claude command/skill 的执行效果并优化。提取可执行命令,逐步通过 ccw cli --tool claude 执行,分析产物质量,生成优化建议。

Tool Assignment

Phase Tool Mode Rule
Execute claude write universal-rigorous-style
Analyze gemini analysis analysis-review-code-quality
Synthesize gemini analysis analysis-review-architecture

Architecture

Input → Parse → GenTestTask → Confirm → Setup → [resolveCmd → readMeta → assemblePrompt → Execute → STOP → Analyze → STOP]×N → Synthesize → STOP → Report
                    ↑                                       ↑
          Claude 直接生成测试任务            prompt 中注入 test_task
          (无需 CLI 调用)                  作为命令的执行输入

Input Formats

1. --file workflow.json               → JSON definition
2. "cmd1 | cmd2 | cmd3"              → pipe-separated commands
3. "skill-a,skill-b,skill-c"         → comma-separated skills
4. natural language                   → semantic decomposition
   4a: <file-path> <intent>           → extract commands from reference doc via LLM
   4b: <pure intent text>             → intent-verb matching → ccw cli command assembly

ANTI-PATTERN: Steps like { command: "分析 Phase 管线" } are WRONG — descriptions, not commands. Correct: { command: "/workflow-lite-plan analyze auth module" } or { command: "ccw cli -p '...' --tool claude --mode write" }

Utility: Shell Escaping

function escapeForShell(str) {
  // Replace single quotes with escaped version, wrap in single quotes
  return "'" + str.replace(/'/g, "'\\''") + "'";
}

Phase 1: Setup

Step 1.1: Parse Input + Preference Collection

const args = $ARGUMENTS.trim();
const autoYes = /\b(-y|--yes)\b/.test(args);

// Preference collection (skip if -y)
if (autoYes) {
  workflowPreferences = { autoYes: true, analysisDepth: 'standard', autoFix: false };
} else {
  const prefResponse = AskUserQuestion({
    questions: [
      { question: "选择调优配置:", header: "Tune Config", multiSelect: false,
        options: [
          { label: "Quick (轻量分析)", description: "每步简要检查" },
          { label: "Standard (标准分析) (Recommended)", description: "每步详细分析" },
          { label: "Deep (深度分析)", description: "深度审查含架构建议" }
        ]
      },
      { question: "是否自动应用优化建议?", header: "Auto Fix", multiSelect: false,
        options: [
          { label: "No (仅报告) (Recommended)", description: "只分析不修改" },
          { label: "Yes (自动应用)", description: "自动应用高优先级建议" }
        ]
      }
    ]
  });
  const depthMap = { "Quick": "quick", "Standard": "standard", "Deep": "deep" };
  const selectedDepth = Object.keys(depthMap).find(k => prefResponse["Tune Config"].startsWith(k)) || "Standard";
  workflowPreferences = {
    autoYes: false,
    analysisDepth: depthMap[selectedDepth],
    autoFix: prefResponse["Auto Fix"].startsWith("Yes")
  };
}

// Parse --depth override
const depthMatch = args.match(/--depth\s+(quick|standard|deep)/);
if (depthMatch) workflowPreferences.analysisDepth = depthMatch[1];

// ── Format Detection ──
let steps = [], workflowName = 'unnamed-workflow', inputFormat = '';
let projectScenario = '';  // ★ 统一虚构项目场景,所有步骤共享(在 Step 1.1a 生成)

const fileMatch = args.match(/--file\s+"?([^\s"]+)"?/);
if (fileMatch) {
  const wfDef = JSON.parse(Read(fileMatch[1]));
  workflowName = wfDef.name || 'unnamed-workflow';
  projectScenario = wfDef.project_scenario || wfDef.description || '';
  steps = wfDef.steps;
  inputFormat = 'json';
}
else if (args.includes('|')) {
  const rawSteps = args.split(/(?:--context|--depth|-y|--yes|--auto-fix)\s+("[^"]*"|\S+)/)[0];
  steps = rawSteps.split('|').map((cmd, i) => ({
    name: `step-${i + 1}`,
    command: cmd.trim(),
    expected_artifacts: [], success_criteria: ''
  }));
  inputFormat = 'pipe';
}
else if (/^[\w-]+(,[\w-]+)+/.test(args.split(/\s/)[0])) {
  const skillNames = args.match(/^([^\s]+)/)[1].split(',');
  steps = skillNames.map(name => ({
    name, command: `/${name}`,
    expected_artifacts: [], success_criteria: ''
  }));
  inputFormat = 'skills';
}
else {
  inputFormat = 'natural-language';
  let naturalLanguageInput = args.replace(/--\w+\s+"[^"]*"/g, '').replace(/--\w+\s+\S+/g, '').replace(/-y|--yes/g, '').trim();
  const filePathPattern = /(?:[A-Za-z]:[\\\/][^\s,;]+|\/[^\s,;]+\.(?:md|txt|json|yaml|yml|toml)|\.\/?[^\s,;]+\.(?:md|txt|json|yaml|yml|toml))/g;
  const detectedPaths = naturalLanguageInput.match(filePathPattern) || [];
  let referenceDocContent = null, referenceDocPath = null;
  if (detectedPaths.length > 0) {
    referenceDocPath = detectedPaths[0];
    try {
      referenceDocContent = Read(referenceDocPath);
      naturalLanguageInput = naturalLanguageInput.replace(referenceDocPath, '').trim();
    } catch (e) { referenceDocContent = null; }
  }
  // → Mode 4a/4b in Step 1.1b
}

// workflowContext 已移除 — 统一使用 projectScenario在 Step 1.1a 生成)

Step 1.1a: Generate Test Task (测试任务直接生成)

核心概念: 所有步骤共享一个统一虚构项目场景(如"在线书店网站"),每个命令根据自身能力获得该场景下的一个子任务。由当前 Claude 直接生成,不需要额外 CLI 调用。所有执行在独立沙箱目录中进行,不影响真实项目。

// ★ 测试任务直接生成 — 无需 CLI 调用
// 来源优先级:
//   1. JSON 定义中的 step.test_task 字段 (已有则跳过)
//   2. 当前 Claude 直接生成

const stepsNeedTask = steps.filter(s => !s.test_task);

if (stepsNeedTask.length > 0) {
  // ── Step A: 生成统一项目场景 ──
  // 根据命令链的整体复杂度,选一个虚构项目作为测试场景
  // 场景必须:完全虚构、与当前工作空间无关、足够支撑所有步骤
  //
  // 场景池示例(根据步骤数量和类型选择合适规模):
  //   1-2 步: 小型项目 — "命令行 TODO 工具" "Markdown 转 HTML 工具" "天气查询 CLI"
  //   3-4 步: 中型项目 — "在线书店网站" "团队任务看板" "博客系统"
  //   5+ 步:  大型项目 — "多租户 SaaS 平台" "电商系统" "在线教育平台"

  projectScenario = /* Claude 从上述池中选择或自创一个场景 */;
  // 例如: "在线书店网站 — 支持用户注册登录、书籍搜索浏览、购物车、订单管理、评论系统"

  // ── Step B: 为每步生成子任务 ──
  for (const step of stepsNeedTask) {
    const cmdFile = resolveCommandFile(step.command);
    const cmdMeta = readCommandMeta(cmdFile);
    const cmdDesc = (cmdMeta?.description || step.command).toLowerCase();

    // 根据命令类型分配场景下的子任务
    // 每个子任务必须按以下模板生成:
    //
    // ┌─────────────────────────────────────────────────┐
    // │ 项目: {projectScenario}                          │
    // │ 任务: {具体子任务描述}                            │
    // │ 功能点:                                          │
    // │   1. {功能点1 — 具体到接口/组件/模块}             │
    // │   2. {功能点2}                                   │
    // │   3. {功能点3}                                   │
    // │ 技术约束: {语言/框架/架构要求}                    │
    // │ 验收标准:                                        │
    // │   1. {可验证的标准1}                              │
    // │   2. {可验证的标准2}                              │
    // └─────────────────────────────────────────────────┘
    //
    // 命令类型 → 子任务映射:
    //   plan/design   → 架构设计任务: "为{场景}设计技术架构包含模块划分、数据模型、API 设计"
    //   implement     → 功能实现任务: "实现{场景}的{某模块},包含{具体功能点}"
    //   analyze/review→ 代码分析任务: "先在沙箱创建{场景}的{某模块}示例代码,然后分析其质量"
    //   test          → 测试任务:     "为{场景}的{某模块}编写测试,覆盖{具体场景}"
    //   fix/debug     → 修复任务:     "先在沙箱创建含已知 bug 的代码,然后诊断修复"
    //   refactor      → 重构任务:     "先在沙箱创建可工作但需重构的代码,然后重构"

    step.test_task = /* 按上述模板生成,必须包含:项目、任务、功能点、技术约束、验收标准 */;
    step.acceptance_criteria = /* 从 test_task 中提取 2-4 条可验证标准 */;
    step.complexity_level = /plan|design|architect/i.test(cmdDesc) ? 'high'
      : /test|lint|format/i.test(cmdDesc) ? 'low' : 'medium';
  }
}

模拟示例 — 输入 workflow-lite-plan,workflow-lite-execute:

场景: 在线书店网站 — 支持用户注册登录、书籍搜索、购物车、订单管理

Step 1 (workflow-lite-plan → plan 类, high):
  项目: 在线书店网站
  任务: 为在线书店设计技术架构和实现计划
  功能点:
    1. 用户模块 — 注册、登录、个人信息管理
    2. 书籍模块 — 搜索、分类浏览、详情页
    3. 交易模块 — 购物车、下单、支付状态
    4. 数据模型 — User, Book, Order, CartItem 表结构设计
  技术约束: TypeScript + Express + SQLite, REST API
  验收标准:
    1. 输出包含模块划分和依赖关系
    2. 包含数据模型定义
    3. 包含 API 路由清单
    4. 包含实现步骤分解

Step 2 (workflow-lite-execute → implement 类, medium):
  项目: 在线书店网站
  任务: 根据 Step 1 的计划,实现书籍搜索和浏览模块
  功能点:
    1. GET /api/books — 分页列表,支持按标题/作者搜索
    2. GET /api/books/:id — 书籍详情
    3. GET /api/categories — 分类列表
    4. Book 数据模型 + seed 数据
  技术约束: TypeScript + Express + SQLite, 沿用 Step 1 架构
  验收标准:
    1. API 可正常调用返回 JSON
    2. 搜索支持模糊匹配
    3. 包含至少 5 条 seed 数据

Step 1.1b: Semantic Decomposition (Format 4 only)

Mode 4a: Reference Document → LLM Extraction

if (inputFormat === 'natural-language' && referenceDocContent) {
  const extractPrompt = `PURPOSE: Extract ACTUAL EXECUTABLE COMMANDS from the reference document. The user wants to TEST these commands by running them.

USER INTENT: ${naturalLanguageInput}
REFERENCE DOCUMENT: ${referenceDocPath}

DOCUMENT CONTENT:
${referenceDocContent}

CRITICAL RULES:
- "command" field MUST be a real executable: slash command (/skill-name args), ccw cli call, or shell command
- CORRECT: { "command": "/workflow-lite-plan analyze auth module" }
- CORRECT: { "command": "ccw cli -p 'review code' --tool claude --mode write" }
- WRONG:  { "command": "分析 Phase 管线" } ← DESCRIPTION, not command
- Default mode to "write"

EXPECTED OUTPUT (strict JSON):
{
  "workflow_name": "<name>",
  "project_scenario": "<虚构项目场景>",
  "steps": [{ "name": "", "command": "<executable>", "expected_artifacts": [], "success_criteria": "" }]
}`;

  Bash({
    command: `ccw cli -p ${escapeForShell(extractPrompt)} --tool claude --mode write --rule universal-rigorous-style`,
    run_in_background: true, timeout: 300000
  });
  // ■ STOP — wait for hook callback, parse JSON → steps[]
}

Mode 4b: Pure Intent → Command Assembly

if (inputFormat === 'natural-language' && !referenceDocContent) {
  // Intent → rule mapping for ccw cli command generation
  const intentMap = [
    { pattern: /分析|analyze|审查|inspect|scan/i, name: 'analyze', rule: 'analysis-analyze-code-patterns' },
    { pattern: /评审|review|code.?review/i, name: 'review', rule: 'analysis-review-code-quality' },
    { pattern: /诊断|debug|排查|diagnose/i, name: 'diagnose', rule: 'analysis-diagnose-bug-root-cause' },
    { pattern: /安全|security|漏洞/i, name: 'security-audit', rule: 'analysis-assess-security-risks' },
    { pattern: /性能|performance|perf/i, name: 'perf-analysis', rule: 'analysis-analyze-performance' },
    { pattern: /架构|architecture/i, name: 'arch-review', rule: 'analysis-review-architecture' },
    { pattern: /修复|fix|repair|解决/i, name: 'fix', rule: 'development-debug-runtime-issues' },
    { pattern: /实现|implement|开发|create|新增/i, name: 'implement', rule: 'development-implement-feature' },
    { pattern: /重构|refactor/i, name: 'refactor', rule: 'development-refactor-codebase' },
    { pattern: /测试|test/i, name: 'test', rule: 'development-generate-tests' },
    { pattern: /规划|plan|设计|design/i, name: 'plan', rule: 'planning-plan-architecture-design' },
  ];

  const segments = naturalLanguageInput
    .split(/[,;、]|(?:然后|接着|之后|最后|再|并|and then|then|finally|next)\s*/i)
    .map(s => s.trim()).filter(Boolean);

  // ★ 将意图文本转化为完整的 ccw cli 命令
  steps = segments.map((segment, i) => {
    const matched = intentMap.find(m => m.pattern.test(segment));
    const rule = matched?.rule || 'universal-rigorous-style';
    // 组装真正可执行的命令
    const command = `ccw cli -p ${escapeForShell('PURPOSE: ' + segment + '\\nTASK: Execute based on intent\\nCONTEXT: @**/*')} --tool claude --mode write --rule ${rule}`;
    return {
      name: matched?.name || `step-${i + 1}`,
      command,
      original_intent: segment,  // 保留原始意图用于分析
      expected_artifacts: [], success_criteria: ''
    };
  });
}

Step 1.1c: Execution Plan Confirmation

function generateCommandDoc(steps, workflowName, projectScenario, analysisDepth) {
  const stepTable = steps.map((s, i) => {
    const cmdPreview = s.command.length > 60 ? s.command.substring(0, 57) + '...' : s.command;
    const taskPreview = (s.test_task || '-').length > 40 ? s.test_task.substring(0, 37) + '...' : (s.test_task || '-');
    return `| ${i + 1} | ${s.name} | \`${cmdPreview}\` | ${taskPreview} |`;
  }).join('\n');

  return `# Workflow Tune — Execution Plan\n\n**Workflow**: ${workflowName}\n**Test Project**: ${projectScenario}\n**Steps**: ${steps.length}\n**Depth**: ${analysisDepth}\n\n| # | Name | Command | Test Task |\n|---|------|---------|-----------|\n${stepTable}`;
}

const commandDoc = generateCommandDoc(steps, workflowName, projectScenario, workflowPreferences.analysisDepth);

if (!workflowPreferences.autoYes) {
  const confirmation = AskUserQuestion({
    questions: [{
      question: commandDoc + "\n\n确认执行以上 Workflow 调优计划?", header: "Confirm Execution", multiSelect: false,
      options: [
        { label: "Execute (确认执行)", description: "按计划开始执行" },
        { label: "Cancel (取消)", description: "取消" }
      ]
    }]
  });
  if (confirmation["Confirm Execution"].startsWith("Cancel")) return;
}

Step 1.2: (Merged into Step 1.1a)

Test requirements (acceptance_criteria) are now generated together with test_task in Step 1.1a, avoiding an extra CLI call.

Step 1.3: Create Workspace + Sandbox Project

const ts = Date.now();
const workDir = `.workflow/.scratchpad/workflow-tune-${ts}`;

// ★ 创建独立沙箱项目目录 — 所有命令执行在此目录中,不影响真实项目
const sandboxDir = `${workDir}/sandbox`;
Bash(`mkdir -p "${workDir}/steps" "${sandboxDir}"`);
// 初始化沙箱为独立 git 仓库(部分命令依赖 git 环境)
Bash(`cd "${sandboxDir}" && git init && echo "# Sandbox Project" > README.md && git add . && git commit -m "init sandbox"`);

for (let i = 0; i < steps.length; i++) Bash(`mkdir -p "${workDir}/steps/step-${i + 1}/artifacts"`);

Write(`${workDir}/command-doc.md`, commandDoc);

const initialState = {
  status: 'running', started_at: new Date().toISOString(),
  workflow_name: workflowName, project_scenario: projectScenario,
  analysis_depth: workflowPreferences.analysisDepth, auto_fix: workflowPreferences.autoFix,
  sandbox_dir: sandboxDir,  // ★ 独立沙箱项目目录
  current_step: 0,  // ★ State machine cursor
  current_phase: 'execute',  // 'execute' | 'analyze'
  steps: steps.map((s, i) => ({
    ...s, index: i, status: 'pending',
    test_task: s.test_task || '',  // ★ 每步的测试任务
    execution: null, analysis: null,
    test_requirements: s.test_requirements || null
  })),
  gemini_session_id: null,  // ★ Updated after each gemini callback
  work_dir: workDir,
  errors: [], error_count: 0, max_errors: 3
};

Write(`${workDir}/workflow-state.json`, JSON.stringify(initialState, null, 2));
Write(`${workDir}/process-log.md`, `# Process Log\n\n**Workflow**: ${workflowName}\n**Test Project**: ${projectScenario}\n**Steps**: ${steps.length}\n**Started**: ${new Date().toISOString()}\n\n---\n\n`);

Phase 2: Execute Step

resolveCommandFile — Slash command → file path

function resolveCommandFile(command) {
  const cmdMatch = command.match(/^\/?([^\s]+)/);
  if (!cmdMatch) return null;
  const cmdName = cmdMatch[1];
  const cmdPath = cmdName.replace(/:/g, '/');

  const searchRoots = ['.claude', '~/.claude'];

  for (const root of searchRoots) {
    const candidates = [
      `${root}/commands/${cmdPath}.md`,
      `${root}/commands/${cmdPath}/index.md`,
    ];
    for (const candidate of candidates) {
      try { Read(candidate, { limit: 1 }); return candidate; } catch {}
    }
  }

  for (const root of searchRoots) {
    const candidates = [
      `${root}/skills/${cmdName}/SKILL.md`,
      `${root}/skills/${cmdPath.replace(/\//g, '-')}/SKILL.md`,
    ];
    for (const candidate of candidates) {
      try { Read(candidate, { limit: 1 }); return candidate; } catch {}
    }
  }

  return null;
}

readCommandMeta — Read YAML frontmatter + body summary

function readCommandMeta(filePath) {
  if (!filePath) return null;

  const content = Read(filePath);
  const meta = { filePath, name: '', description: '', argumentHint: '', allowedTools: '', bodySummary: '' };

  const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (yamlMatch) {
    const yaml = yamlMatch[1];
    const nameMatch = yaml.match(/^name:\s*(.+)$/m);
    const descMatch = yaml.match(/^description:\s*(.+)$/m);
    const hintMatch = yaml.match(/^argument-hint:\s*"?(.+?)"?\s*$/m);
    const toolsMatch = yaml.match(/^allowed-tools:\s*(.+)$/m);

    if (nameMatch) meta.name = nameMatch[1].trim();
    if (descMatch) meta.description = descMatch[1].trim();
    if (hintMatch) meta.argumentHint = hintMatch[1].trim();
    if (toolsMatch) meta.allowedTools = toolsMatch[1].trim();
  }

  const bodyStart = content.indexOf('---', content.indexOf('---') + 3);
  if (bodyStart !== -1) {
    const body = content.substring(bodyStart + 3).trim();
    meta.bodySummary = body.split('\n').slice(0, 30).join('\n');
  }

  return meta;
}

assembleStepPrompt — Build execution prompt from command metadata

function assembleStepPrompt(step, stepIdx, state) {
  // ── 1. Resolve command file + metadata ──
  const isSlashCmd = step.command.startsWith('/');
  const cmdFile = isSlashCmd ? resolveCommandFile(step.command) : null;
  const cmdMeta = readCommandMeta(cmdFile);
  const cmdArgs = isSlashCmd ? step.command.replace(/^\/?[^\s]+\s*/, '').trim() : '';

  // ── 2. Prior/next step context ──
  const prevStep = stepIdx > 0 ? state.steps[stepIdx - 1] : null;
  const nextStep = stepIdx < state.steps.length - 1 ? state.steps[stepIdx + 1] : null;

  const priorContext = prevStep
    ? `PRIOR STEP: "${prevStep.name}" — ${prevStep.command}\n  Status: ${prevStep.status} | Artifacts: ${prevStep.execution?.artifact_count || 0}`
    : 'PRIOR STEP: None (first step)';

  const nextContext = nextStep
    ? `NEXT STEP: "${nextStep.name}" — ${nextStep.command}\n  Ensure output is consumable by next step`
    : 'NEXT STEP: None (last step)';

  // ── 3. Acceptance criteria (from test_task generation) ──
  const criteria = step.acceptance_criteria || [];
  const testReqSection = criteria.length > 0
    ? `ACCEPTANCE CRITERIA:\n${criteria.map((c, i) => `  ${i + 1}. ${c}`).join('\n')}`
    : '';

  // ── 4. Test task — the concrete scenario to drive execution ──
  const testTask = step.test_task || '';
  const testTaskSection = testTask
    ? `TEST TASK (用此任务驱动命令执行):\n  ${testTask}`
    : '';

  // ── 5. Build prompt based on whether command has metadata ──
  if (cmdMeta) {
    // Slash command with resolved file — rich context prompt
    return `PURPOSE: Execute workflow step "${step.name}" (${stepIdx + 1}/${state.steps.length}).

COMMAND DEFINITION:
  Name: ${cmdMeta.name}
  Description: ${cmdMeta.description}
  Argument Format: ${cmdMeta.argumentHint || 'none'}
  Allowed Tools: ${cmdMeta.allowedTools || 'default'}
  Source: ${cmdMeta.filePath}

COMMAND TO EXECUTE: ${step.command}
ARGUMENTS: ${cmdArgs || '(no arguments)'}

${testTaskSection}

COMMAND REFERENCE (first 30 lines):
${cmdMeta.bodySummary}

PROJECT: ${state.project_scenario}
SANDBOX PROJECT: ${state.sandbox_dir}
OUTPUT DIR: ${state.work_dir}/steps/step-${stepIdx + 1}

${priorContext}
${nextContext}
${testReqSection}

TASK: Execute the command as described in COMMAND DEFINITION, using TEST TASK as the input/scenario. Use the COMMAND REFERENCE to understand expected behavior. All work happens in the SANDBOX PROJECT directory (an isolated empty project, NOT the real workspace). Auto-confirm all prompts.
CONSTRAINTS: Stay scoped to this step only. Follow the command's own execution flow. The TEST TASK is the real work — treat it as the $ARGUMENTS input to the command. Do NOT read/modify files outside SANDBOX PROJECT.`;

  } else {
    // Shell command, ccw cli command, or unresolved command
    return `PURPOSE: Execute workflow step "${step.name}" (${stepIdx + 1}/${state.steps.length}).
COMMAND: ${step.command}
${testTaskSection}
PROJECT: ${state.project_scenario}
SANDBOX PROJECT: ${state.sandbox_dir}
OUTPUT DIR: ${state.work_dir}/steps/step-${stepIdx + 1}

${priorContext}
${nextContext}
${testReqSection}

TASK: Execute the COMMAND above with TEST TASK as the input scenario. All work happens in the SANDBOX PROJECT directory (an isolated empty project). Auto-confirm all prompts.
CONSTRAINTS: Stay scoped to this step only. The TEST TASK is the real work to execute. Do NOT read/modify files outside SANDBOX PROJECT.`;
  }
}

Step Execution

const stepIdx = state.current_step;
const step = state.steps[stepIdx];
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;

// Pre-execution: snapshot sandbox directory files
const preFiles = Bash(`find "${state.sandbox_dir}" -type f 2>/dev/null | sort`).stdout.trim();
Write(`${stepDir}/pre-exec-snapshot.txt`, preFiles || '(empty)');

const startTime = Date.now();
const prompt = assembleStepPrompt(step, stepIdx, state);

// ★ All steps execute via ccw cli --tool claude --mode write
// ★ --cd 指向沙箱目录(独立项目),不影响真实工作空间
Bash({
  command: `ccw cli -p ${escapeForShell(prompt)} --tool claude --mode write --rule universal-rigorous-style --cd "${state.sandbox_dir}"`,
  run_in_background: true, timeout: 600000
});
// ■ STOP — wait for hook callback

Post-Execute Callback Handler

// ★ This runs after receiving the ccw cli callback

const duration = Date.now() - startTime;

// Collect artifacts by scanning sandbox (not git diff — sandbox is an independent project)
const postFiles = Bash(`find "${state.sandbox_dir}" -type f -newer "${stepDir}/pre-exec-snapshot.txt" 2>/dev/null | sort`).stdout.trim();
const newArtifacts = postFiles ? postFiles.split('\n').filter(f => !f.endsWith('.git/')) : [];

const artifactManifest = {
  step: step.name, step_index: stepIdx,
  success: true, duration_ms: duration,
  artifacts: newArtifacts.map(f => ({
    path: f,
    type: f.endsWith('.md') ? 'markdown' : f.endsWith('.json') ? 'json' : 'other'
  })),
  collected_at: new Date().toISOString()
};
Write(`${stepDir}/artifacts-manifest.json`, JSON.stringify(artifactManifest, null, 2));

// Update state
state.steps[stepIdx].status = 'executed';
state.steps[stepIdx].execution = {
  success: true, duration_ms: duration,
  artifact_count: newArtifacts.length
};
state.current_phase = 'analyze';
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));

// → Proceed to Phase 3 for this step

Phase 3: Analyze Step (per step, via gemini)

const manifest = JSON.parse(Read(`${stepDir}/artifacts-manifest.json`));

// Build artifact content for analysis
let artifactSummary = '';
if (state.analysis_depth === 'quick') {
  artifactSummary = manifest.artifacts.map(a => `- ${a.path} (${a.type})`).join('\n');
} else {
  const maxLines = state.analysis_depth === 'deep' ? 300 : 150;
  artifactSummary = manifest.artifacts.map(a => {
    try { return `--- ${a.path} ---\n${Read(a.path, { limit: maxLines })}`; }
    catch { return `--- ${a.path} --- [unreadable]`; }
  }).join('\n\n');
}

const criteria = step.acceptance_criteria || [];
const testTaskDesc = step.test_task ? `TEST TASK: ${step.test_task}` : '';
const criteriaSection = criteria.length > 0
  ? `ACCEPTANCE CRITERIA:\n${criteria.map((c, i) => `  ${i + 1}. ${c}`).join('\n')}`
  : '';

const analysisPrompt = `PURPOSE: Evaluate execution quality of step "${step.name}" (${stepIdx + 1}/${state.steps.length}).
WORKFLOW: ${state.workflow_name}${state.project_scenario}
COMMAND: ${step.command}
${testTaskDesc}
${criteriaSection}
EXECUTION: Duration ${step.execution.duration_ms}ms | Artifacts: ${manifest.artifacts.length}
ARTIFACTS:\n${artifactSummary}
EXPECTED OUTPUT (strict JSON):
{ "quality_score": <0-100>, "requirement_match": { "pass": <bool>, "criteria_met": [], "criteria_missed": [], "fail_signals_detected": [] }, "execution_assessment": { "success": <bool>, "completeness": "", "notes": "" }, "artifact_assessment": { "count": <n>, "quality": "", "key_outputs": [], "missing_outputs": [] }, "issues": [{ "severity": "critical|high|medium|low", "description": "", "suggestion": "" }], "optimization_opportunities": [{ "area": "", "description": "", "impact": "high|medium|low" }], "step_summary": "" }`;

let cliCommand = `ccw cli -p ${escapeForShell(analysisPrompt)} --tool gemini --mode analysis --rule analysis-review-code-quality`;
if (state.gemini_session_id) cliCommand += ` --resume ${state.gemini_session_id}`;
Bash({ command: cliCommand, run_in_background: true, timeout: 300000 });
// ■ STOP — wait for hook callback

Post-Analyze Callback Handler

// ★ Parse analysis result JSON from callback
const analysisResult = /* parsed from callback output */;

// ★ Capture gemini session ID for resume chain
// Session ID is in stderr: [CCW_EXEC_ID=gem-xxxxxx-xxxx]
state.gemini_session_id = /* captured from callback exec_id */;

Write(`${stepDir}/step-${stepIdx + 1}-analysis.json`, JSON.stringify(analysisResult, null, 2));

// Update state
state.steps[stepIdx].analysis = {
  quality_score: analysisResult.quality_score,
  requirement_pass: analysisResult.requirement_match?.pass,
  issue_count: (analysisResult.issues || []).length
};
state.steps[stepIdx].status = 'completed';

// Append to process log
const logEntry = `## Step ${stepIdx + 1}: ${step.name}\n- Score: ${analysisResult.quality_score}/100\n- Req: ${analysisResult.requirement_match?.pass ? 'PASS' : 'FAIL'}\n- Issues: ${(analysisResult.issues || []).length}\n- Summary: ${analysisResult.step_summary}\n\n`;
Edit(`${state.work_dir}/process-log.md`, /* append logEntry */);

// ★ Advance state machine
state.current_step = stepIdx + 1;
state.current_phase = 'execute';
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));

// ★ Decision: advance or synthesize
if (state.current_step < state.steps.length) {
  // → Back to Phase 2 for next step
} else {
  // → Phase 4: Synthesize
}

Step Loop — State Machine

NOT a sync for-loop. Each step follows this state machine:

  ┌─────────────────────────────────────────────────────┐
  │ state.current_step = N, state.current_phase = X     │
  ├─────────────────────────────────────────────────────┤
  │ phase='execute' → Phase 2 → ccw cli claude → STOP  │
  │   callback → collect artifacts → phase='analyze'    │
  │ phase='analyze' → Phase 3 → ccw cli gemini → STOP  │
  │   callback → save analysis → current_step++         │
  │   if current_step < total → phase='execute' (loop)  │
  │   else → Phase 4 (synthesize)                       │
  └─────────────────────────────────────────────────────┘

Error handling:
  - Execute timeout → retry once, then mark failed, advance
  - Analyze failure → retry without --resume, then skip analysis
  - 3+ consecutive errors → terminate, jump to Phase 5 partial report

Phase 4: Synthesize (via gemini)

const stepAnalyses = state.steps.map((step, i) => {
  try { return { step: step.name, content: Read(`${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.json`) }; }
  catch { return { step: step.name, content: '[Not available]' }; }
});

const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;

const synthesisPrompt = `PURPOSE: Synthesize all step analyses into holistic workflow assessment with actionable optimization plan.
WORKFLOW: ${state.workflow_name}${state.project_scenario}
Steps: ${state.steps.length} | Avg Quality: ${avgScore}/100
STEP ANALYSES:\n${stepAnalyses.map(a => `### ${a.step}\n${a.content}`).join('\n\n---\n\n')}
Evaluate: coherence across steps, handoff quality, redundancy, bottlenecks.
EXPECTED OUTPUT (strict JSON):
{ "workflow_score": <0-100>, "coherence": { "score": <0-100>, "assessment": "", "gaps": [] }, "bottlenecks": [{ "step": "", "issue": "", "suggestion": "" }], "per_step_improvements": [{ "step": "", "priority": "high|medium|low", "action": "" }], "workflow_improvements": [{ "area": "", "description": "", "impact": "high|medium|low" }], "summary": "" }`;

let cliCommand = `ccw cli -p ${escapeForShell(synthesisPrompt)} --tool gemini --mode analysis --rule analysis-review-architecture`;
if (state.gemini_session_id) cliCommand += ` --resume ${state.gemini_session_id}`;
Bash({ command: cliCommand, run_in_background: true, timeout: 300000 });
// ■ STOP — wait for hook callback → parse JSON, write synthesis.json, update state

Phase 5: Report

const synthesis = JSON.parse(Read(`${state.work_dir}/synthesis.json`));
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);

const stepTable = state.steps.map((s, i) => {
  const reqStr = s.analysis?.requirement_pass === true ? 'PASS' : s.analysis?.requirement_pass === false ? 'FAIL' : '-';
  return `| ${i + 1} | ${s.name} | ${s.execution?.success ? 'OK' : 'FAIL'} | ${reqStr} | ${s.analysis?.quality_score || '-'} | ${s.analysis?.issue_count || 0} |`;
}).join('\n');

const improvements = (synthesis.per_step_improvements || [])
  .filter(imp => imp.priority === 'high')
  .map(imp => `- **${imp.step}**: ${imp.action}`)
  .join('\n');

const report = `# Workflow Tune Report

| Field | Value |
|---|---|
| Workflow | ${state.workflow_name} |
| Test Project | ${state.project_scenario} |
| Workflow Score | ${synthesis.workflow_score || avgScore}/100 |
| Avg Step Score | ${avgScore}/100 |
| Total Issues | ${totalIssues} |
| Coherence | ${synthesis.coherence?.score || '-'}/100 |

## Step Results

| # | Step | Exec | Req | Quality | Issues |
|---|------|------|-----|---------|--------|
${stepTable}

## High Priority Improvements

${improvements || 'None'}

## Workflow-Level Improvements

${(synthesis.workflow_improvements || []).map(w => `- **${w.area}** (${w.impact}): ${w.description}`).join('\n') || 'None'}

## Bottlenecks

${(synthesis.bottlenecks || []).map(b => `- **${b.step}**: ${b.issue}${b.suggestion}`).join('\n') || 'None'}

## Summary

${synthesis.summary || 'N/A'}
`;

Write(`${state.work_dir}/final-report.md`, report);
state.status = 'completed';
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));

// Output report to user

Resume Chain

Step 1 Execute → ccw cli claude --mode write --rule universal-rigorous-style --cd step-1/ → STOP → callback → artifacts
Step 1 Analyze → ccw cli gemini --mode analysis --rule analysis-review-code-quality         → STOP → callback → gemini_session_id = exec_id
Step 2 Execute → ccw cli claude --mode write --rule universal-rigorous-style --cd step-2/ → STOP → callback → artifacts
Step 2 Analyze → ccw cli gemini --mode analysis --resume gemini_session_id                 → STOP → callback → gemini_session_id = exec_id
  ...
Synthesize   → ccw cli gemini --mode analysis --resume gemini_session_id                   → STOP → callback → synthesis
Report       → local generation (no CLI call)

Error Handling

Phase Error Recovery
Execute CLI timeout Retry once, then mark step failed and advance
Execute Command not found Skip step, note in process-log
Analyze CLI fails Retry without --resume, then skip analysis
Synthesize CLI fails Generate report from step analyses only
Any 3+ consecutive errors Terminate, produce partial report

Core Rules

  1. STOP After Each CLI Call: Every ccw cli call runs in background — STOP output immediately, wait for hook callback
  2. State Machine: Advance via current_step + current_phase, never use sync loops for async operations
  3. Test Task Drives Execution: 每个命令必须有 test_task完整需求说明作为命令的 $ARGUMENTS 输入。test_task 由当前 Claude 直接根据命令链复杂度生成,不需要额外 CLI 调用
  4. All Execution via claude: ccw cli --tool claude --mode write --rule universal-rigorous-style
  5. All Analysis via gemini: ccw cli --tool gemini --mode analysis, chained via --resume
  6. Session Capture: After each gemini callback, capture exec_id → gemini_session_id for resume chain
  7. Sandbox Isolation: 所有命令在独立沙箱目录(sandbox/)中执行,使用虚构测试任务,不影响真实项目
  8. Artifact Collection: Scan sandbox filesystem (not git diff), compare pre/post snapshots
  9. Prompt Assembly: Every step goes through assembleStepPrompt() — resolves command file, reads YAML metadata, injects test_task, builds rich context
  10. Auto-Confirm: All prompts auto-confirmed, no blocking interactions during execution