Files
Claude-Code-Workflow/.claude/skills/workflow-skill-designer/phases/03-phase-design.md
catlog22 47c192f584 Add orchestrator design, phase file generation, and validation processes
- Implement Phase 2: Orchestrator Design with detailed steps for generating SKILL.md
- Introduce Phase 3: Phase Files Design to create phase files with content fidelity
- Establish Phase 4: Validation & Integration to ensure structural completeness and reference integrity
- Include comprehensive validation checks for content quality and data flow consistency
- Enhance documentation with clear objectives and critical rules for each phase
2026-02-05 19:29:45 +08:00

13 KiB

Phase 3: Phase Files Design

Generate phase files in phases/ directory, preserving full execution detail from source content. Each phase file is a complete execution instruction.

Objective

  • Create phases/0N-{slug}.md for each phase in workflowConfig
  • Preserve full source content (agent prompts, bash commands, code, validation)
  • Add standard phase structure (header, objective, output, next phase)
  • Handle different source types (command extraction vs new generation)

Critical Rule

Content Fidelity: Phase files must be content-faithful to their source. Do NOT summarize, abbreviate, or simplify execution detail. The phase file IS the execution instruction.

Content Type Rule
Agent prompts (Task calls) Preserve verbatim including all prompt text, variables, constraints
Bash command blocks Preserve verbatim including all flags, paths, error handling
Code implementations Preserve verbatim including all functions, validation logic
Validation checklists Preserve verbatim including all check items
Error handling details Preserve verbatim including recovery strategies
Tables and specifications Preserve verbatim including all rows and columns
Comments and notes Preserve verbatim including inline documentation

Anti-Pattern: Creating a phase file that says "See original command for details" or "Execute the agent with appropriate parameters" - this defeats the purpose of the skill structure. The phase file must be self-contained.

Step 3.1: Phase File Generation Strategy

function selectGenerationStrategy(phase, config) {
  if (config.source.type === 'command_set' && phase.sourcePath) {
    return 'extract';    // Extract from existing command file
  } else if (config.source.type === 'text_description') {
    return 'generate';   // Generate from requirements
  } else if (config.source.type === 'existing_skill') {
    return 'restructure'; // Restructure existing content
  }
  return 'generate';
}

Step 3.2: Mode A - Extract from Command

When source is an existing command file, transform its content into phase file format:

function extractPhaseFromCommand(phase, config) {
  const sourceContent = Read(phase.sourcePath);
  const sourceFrontmatter = extractYAMLFrontmatter(sourceContent);
  const sourceBody = removeYAMLFrontmatter(sourceContent);

  // Phase file structure:
  // 1. Phase header (new)
  // 2. Source body content (preserved verbatim)
  // 3. Output section (extracted or added)
  // 4. Next Phase link (new)

  let phaseContent = '';

  // 1. Phase header
  phaseContent += `# Phase ${phase.number}: ${phase.name}\n\n`;
  phaseContent += `${phase.description}.\n\n`;

  // 2. Source body content - PRESERVED VERBATIM
  // Only modifications:
  //   a. Remove original H1 title (replaced by phase header)
  //   b. Remove command-specific frontmatter references
  //   c. Preserve everything else as-is

  // Remove original H1 title line(s)
  let bodyContent = sourceBody;
  bodyContent = bodyContent.replace(/^# .+\n+/, '');

  // Remove command-specific overview if it just restates what the phase header says
  // But KEEP any overview content that adds execution detail

  phaseContent += bodyContent;

  // 3. Ensure Output section exists
  if (!bodyContent.includes('## Output')) {
    phaseContent += '\n## Output\n\n';
    if (phase.outputVariables.length > 0) {
      phaseContent += phase.outputVariables.map(v => `- **Variable**: \`${v}\``).join('\n') + '\n';
    }
    if (phase.outputFiles.length > 0) {
      phaseContent += phase.outputFiles.map(f => `- **File**: \`${f}\``).join('\n') + '\n';
    }
    phaseContent += `- **TodoWrite**: Mark Phase ${phase.number} completed, Phase ${phase.number + 1} in_progress\n`;
  }

  // 4. Ensure Next Phase link exists
  if (!bodyContent.includes('## Next Phase')) {
    const nextPhase = config.phases.find(p => p.number === phase.number + 1);
    if (nextPhase) {
      const nextFilename = `${String(nextPhase.number).padStart(2, '0')}-${nextPhase.slug}.md`;
      phaseContent += `\n## Next Phase\n\n`;
      phaseContent += `Return to orchestrator, then auto-continue to [Phase ${nextPhase.number}: ${nextPhase.name}](${nextFilename}).\n`;
    }
  }

  return phaseContent;
}

Content Preservation Checklist

When extracting from commands, verify these content types are preserved:

function verifyContentPreservation(sourceContent, phaseContent) {
  const checks = {
    // Count code blocks
    sourceCodeBlocks: (sourceContent.match(/```/g) || []).length / 2,
    phaseCodeBlocks: (phaseContent.match(/```/g) || []).length / 2,

    // Count Task/Agent calls
    sourceAgentCalls: (sourceContent.match(/Task\(/g) || []).length,
    phaseAgentCalls: (phaseContent.match(/Task\(/g) || []).length,

    // Count bash commands
    sourceBashBlocks: (sourceContent.match(/```bash/g) || []).length,
    phaseBashBlocks: (phaseContent.match(/```bash/g) || []).length,

    // Count tables
    sourceTables: (sourceContent.match(/\|.*\|.*\|/g) || []).length,
    phaseTables: (phaseContent.match(/\|.*\|.*\|/g) || []).length,

    // Count AskUserQuestion calls
    sourceAUQ: (sourceContent.match(/AskUserQuestion/g) || []).length,
    phaseAUQ: (phaseContent.match(/AskUserQuestion/g) || []).length,

    // Line count comparison (phase should be >= source minus frontmatter)
    sourceLines: sourceContent.split('\n').length,
    phaseLines: phaseContent.split('\n').length
  };

  const issues = [];
  if (checks.phaseCodeBlocks < checks.sourceCodeBlocks) {
    issues.push(`Missing code blocks: source=${checks.sourceCodeBlocks}, phase=${checks.phaseCodeBlocks}`);
  }
  if (checks.phaseAgentCalls < checks.sourceAgentCalls) {
    issues.push(`Missing agent calls: source=${checks.sourceAgentCalls}, phase=${checks.phaseAgentCalls}`);
  }
  if (checks.phaseBashBlocks < checks.sourceBashBlocks) {
    issues.push(`Missing bash blocks: source=${checks.sourceBashBlocks}, phase=${checks.phaseBashBlocks}`);
  }
  if (checks.phaseTables < checks.sourceTables * 0.8) {
    issues.push(`Missing tables: source=${checks.sourceTables}, phase=${checks.phaseTables}`);
  }
  if (checks.phaseAUQ < checks.sourceAUQ) {
    issues.push(`Missing AskUserQuestion: source=${checks.sourceAUQ}, phase=${checks.phaseAUQ}`);
  }

  return { checks, issues, passed: issues.length === 0 };
}

Handling Orchestrator-Level Content in Source Commands

Some commands mix orchestrator-level instructions (coordination, TodoWrite) with execution detail. Separation rules:

Content in Source Command Goes To Rule
Phase execution steps, agent prompts, bash commands Phase file Preserve verbatim
TodoWrite update examples specific to this phase Phase file (optional) Keep if useful for context
Inter-phase data passing code SKILL.md Post-Phase Updates Extract to orchestrator
Coordinator instructions ("after this phase, auto-continue") SKILL.md Core Rules Extract to orchestrator
Conditional logic ("if conflict_risk >= medium") SKILL.md Execution Flow Extract to orchestrator

When in doubt, keep content in the phase file. It's better to have slight overlap than to lose execution detail.

Step 3.3: Mode B - Generate from Requirements

When source is a text description, generate phase files interactively:

function generatePhaseFromRequirements(phase, config) {
  let phaseContent = '';

  // Phase header
  phaseContent += `# Phase ${phase.number}: ${phase.name}\n\n`;
  phaseContent += `${phase.description}.\n\n`;

  // Objective
  phaseContent += `## Objective\n\n`;
  phaseContent += `- ${phase.description}\n`;
  if (phase.outputVariables.length > 0) {
    phaseContent += `- Produce: ${phase.outputVariables.join(', ')}\n`;
  }
  if (phase.outputFiles.length > 0) {
    phaseContent += `- Generate: ${phase.outputFiles.join(', ')}\n`;
  }
  phaseContent += '\n';

  // Execution steps
  phaseContent += `## Execution\n\n`;

  if (phase.usesAgents) {
    // Generate agent delegation skeleton
    for (const agentType of phase.agentTypes) {
      phaseContent += `### Step: ${agentType} Delegation\n\n`;
      phaseContent += '```javascript\n';
      phaseContent += `const result = Task({\n`;
      phaseContent += `  subagent_type: "${mapAgentType(agentType)}",\n`;
      phaseContent += `  prompt: \`\n`;
      phaseContent += `    [ROLE] ${agentType}\n`;
      phaseContent += `    [TASK] ${phase.description}\n`;
      phaseContent += `    [INPUT] \${inputData}\n`;
      phaseContent += `    [OUTPUT] \${outputPath}\n`;
      phaseContent += `  \`,\n`;
      phaseContent += `  run_in_background: false\n`;
      phaseContent += `});\n`;
      phaseContent += '```\n\n';
    }
  } else {
    // Generate direct execution skeleton
    phaseContent += `### Step ${phase.number}.1: Execute\n\n`;
    phaseContent += `TODO: Add execution detail for ${phase.name}\n\n`;
  }

  // Output
  phaseContent += `## Output\n\n`;
  phase.outputVariables.forEach(v => {
    phaseContent += `- **Variable**: \`${v}\`\n`;
  });
  phase.outputFiles.forEach(f => {
    phaseContent += `- **File**: \`${f}\`\n`;
  });
  phaseContent += `- **TodoWrite**: Mark Phase ${phase.number} completed\n\n`;

  // Next Phase
  const nextPhase = config.phases.find(p => p.number === phase.number + 1);
  if (nextPhase) {
    const nextFilename = `${String(nextPhase.number).padStart(2, '0')}-${nextPhase.slug}.md`;
    phaseContent += `## Next Phase\n\n`;
    phaseContent += `Return to orchestrator, then auto-continue to [Phase ${nextPhase.number}: ${nextPhase.name}](${nextFilename}).\n`;
  }

  return phaseContent;
}

// Map custom agent type names to Task subagent_types
function mapAgentType(agentType) {
  const mapping = {
    'cli-explore-agent': 'cli-explore-agent',
    'context-search-agent': 'context-search-agent',
    'cli-execution-agent': 'cli-execution-agent',
    'action-planning-agent': 'action-planning-agent',
    'code-developer': 'code-developer',
    'test-fix-agent': 'test-fix-agent',
    'general-purpose': 'general-purpose',
    'Explore': 'Explore'
  };
  return mapping[agentType] || 'general-purpose';
}

Step 3.4: Write Phase Files

function writePhaseFiles(config) {
  const skillDir = `.claude/skills/${config.skillName}`;

  for (const phase of config.phases) {
    const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
    const filepath = `${skillDir}/phases/${filename}`;

    const strategy = selectGenerationStrategy(phase, config);
    let content;

    switch (strategy) {
      case 'extract':
        content = extractPhaseFromCommand(phase, config);
        // Verify content preservation
        const sourceContent = Read(phase.sourcePath);
        const verification = verifyContentPreservation(sourceContent, content);
        if (!verification.passed) {
          console.warn(`⚠️ Content preservation issues for Phase ${phase.number}:`);
          verification.issues.forEach(issue => console.warn(`  - ${issue}`));
          // Re-extract with more aggressive preservation
          content = extractPhaseFromCommand(phase, config, { aggressive: true });
        }
        break;

      case 'generate':
        content = generatePhaseFromRequirements(phase, config);
        break;

      case 'restructure':
        content = restructureExistingPhase(phase, config);
        break;
    }

    Write(filepath, content);
    console.log(`✓ Generated: ${filepath} (${content.split('\n').length} lines)`);
  }
}

Step 3.5: Cross-Phase Consistency Check

After generating all phase files, verify cross-phase consistency:

function checkCrossPhaseConsistency(config) {
  const skillDir = `.claude/skills/${config.skillName}`;
  const issues = [];

  for (const phase of config.phases) {
    const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
    const content = Read(`${skillDir}/phases/${filename}`);

    // Check: Next Phase links point to correct file
    const nextPhaseMatch = content.match(/\[Phase (\d+): (.+?)\]\((.+?)\)/);
    if (nextPhaseMatch) {
      const nextNum = parseInt(nextPhaseMatch[1]);
      const nextPhase = config.phases.find(p => p.number === nextNum);
      if (!nextPhase) {
        issues.push(`Phase ${phase.number}: Next Phase link points to non-existent Phase ${nextNum}`);
      }
    }

    // Check: Output variables match config
    for (const varName of phase.outputVariables) {
      if (!content.includes(varName)) {
        issues.push(`Phase ${phase.number}: Output variable '${varName}' not mentioned in content`);
      }
    }
  }

  return issues;
}

Size Comparison Reference

Expected phase file sizes relative to their source commands:

Scenario Phase File Size vs Source Reason
Command extraction ≥ 90% of source Minor removals (H1 title, frontmatter)
New generation (with agents) 50-200 lines Agent prompt skeletons
New generation (direct) 30-80 lines Step skeletons
Restructure ~100% of source Content reorganization only

Red Flag: If a phase file is significantly smaller than its source (< 70%), content was likely lost during extraction. Re-check with verifyContentPreservation().

Output

  • Files: .claude/skills/{skillName}/phases/0N-{slug}.md for each phase
  • TodoWrite: Mark Phase 3 completed, Phase 4 in_progress

Next Phase

Return to orchestrator, then auto-continue to Phase 4: Validation & Integration.