mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
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
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
# Phase 1: Requirements Analysis
|
||||
|
||||
Analyze workflow requirements from various sources (commands, descriptions, requirements docs) to build a structured workflow configuration.
|
||||
|
||||
## Objective
|
||||
|
||||
- Identify all phases/steps in the workflow
|
||||
- Map data flow between phases
|
||||
- Identify agents, tools, and conditional logic
|
||||
- Detect source type and extract content accordingly
|
||||
- Produce `workflowConfig` object for subsequent phases
|
||||
|
||||
## Step 1.1: Identify Input Source
|
||||
|
||||
```javascript
|
||||
// Determine what the user provided
|
||||
const inputType = detectInputType(userInput);
|
||||
// Returns: 'command_set' | 'text_description' | 'requirements_doc' | 'existing_skill'
|
||||
```
|
||||
|
||||
### Source Type Detection
|
||||
|
||||
| Indicator | Type | Action |
|
||||
|-----------|------|--------|
|
||||
| Path to `.claude/commands/**/*.md` | `command_set` | Read orchestrator + discover sub-commands |
|
||||
| Free text describing workflow | `text_description` | Interactive requirements gathering |
|
||||
| Path to `.md` or `.json` requirements | `requirements_doc` | Parse structured requirements |
|
||||
| Path to `.claude/skills/**/*.md` | `existing_skill` | Analyze and restructure |
|
||||
|
||||
## Step 1.2: Source-Specific Analysis
|
||||
|
||||
### Mode A: Command Set Analysis
|
||||
|
||||
When source is an existing orchestrator command + sub-commands:
|
||||
|
||||
```javascript
|
||||
// Step A.1: Read orchestrator command
|
||||
const orchestratorPath = userInput; // e.g., ".claude/commands/workflow/plan.md"
|
||||
const orchestratorContent = Read(orchestratorPath);
|
||||
|
||||
// Step A.2: Extract frontmatter
|
||||
const frontmatter = extractYAMLFrontmatter(orchestratorContent);
|
||||
// Fields: name, description, argument-hint, examples, allowed-tools, group
|
||||
|
||||
// Step A.3: Discover sub-commands by scanning Skill() calls
|
||||
const skillCalls = orchestratorContent.match(/Skill\(skill="([^"]+)"/g);
|
||||
// e.g., ["workflow:session:start", "workflow:tools:context-gather", ...]
|
||||
|
||||
// Step A.4: Map Skill() calls to file paths
|
||||
// Pattern: "workflow:session:start" → ".claude/commands/workflow/session/start.md"
|
||||
// "workflow:tools:context-gather" → ".claude/commands/workflow/tools/context-gather.md"
|
||||
const subCommandPaths = skillCalls.map(call => {
|
||||
const parts = call.replace('Skill(skill="', '').replace('"', '').split(':');
|
||||
return `.claude/commands/${parts.join('/')}.md`;
|
||||
});
|
||||
|
||||
// Step A.5: Read all sub-commands
|
||||
const subCommands = [];
|
||||
for (const path of subCommandPaths) {
|
||||
const content = Read(path);
|
||||
const fm = extractYAMLFrontmatter(content);
|
||||
subCommands.push({
|
||||
path: path,
|
||||
content: content,
|
||||
frontmatter: fm,
|
||||
skillCallName: extractSkillCallName(path),
|
||||
bodyContent: removeYAMLFrontmatter(content)
|
||||
});
|
||||
}
|
||||
|
||||
// Step A.6: Identify phase ordering from orchestrator execution flow
|
||||
// Look for patterns like:
|
||||
// "Phase 1: ..." → first Skill() call
|
||||
// "Phase 2: ..." → second Skill() call
|
||||
// Conditional logic (if/else) → conditional phases
|
||||
const phaseOrder = extractPhaseOrder(orchestratorContent, skillCalls);
|
||||
```
|
||||
|
||||
**Key Extraction Points from Orchestrator**:
|
||||
|
||||
| Section | What to Extract | Maps to |
|
||||
|---------|-----------------|---------|
|
||||
| Coordinator Role / Overview | Workflow description, execution model | SKILL.md description + Architecture |
|
||||
| Core Rules | Orchestration constraints | SKILL.md Core Rules |
|
||||
| Execution Process | Phase sequence + conditions | SKILL.md Execution Flow |
|
||||
| Data Flow | Inter-phase variables | SKILL.md Data Flow |
|
||||
| TodoWrite Pattern | Attachment/collapse examples | SKILL.md TodoWrite Pattern |
|
||||
| Input Processing | Structured format rules | SKILL.md Input Processing |
|
||||
| Error Handling | Recovery strategies | SKILL.md Error Handling |
|
||||
| Coordinator Checklist | Pre/post actions | SKILL.md Coordinator Checklist |
|
||||
| Related Commands | Prerequisites/follow-ups | SKILL.md Related Commands |
|
||||
| Phase N sections | Phase-specific orchestrator instructions | SKILL.md inline (brief), Phase files (detail) |
|
||||
|
||||
**Key Extraction Points from Sub-Commands**:
|
||||
|
||||
| Section | What to Extract | Maps to |
|
||||
|---------|-----------------|---------|
|
||||
| Full body content | Complete execution detail | Phase file (preserved verbatim) |
|
||||
| Agent prompts (Task calls) | Agent delegation logic | Phase file agent sections |
|
||||
| Bash command blocks | Shell execution steps | Phase file step sections |
|
||||
| Validation/Output sections | Phase outputs | Phase file Output section |
|
||||
| Frontmatter | Tools, description | Phase file header context |
|
||||
|
||||
### Mode B: Text Description Analysis
|
||||
|
||||
When source is a natural language workflow description:
|
||||
|
||||
```javascript
|
||||
// Interactive requirements gathering
|
||||
const basicInfo = AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "What is this workflow skill's name? (kebab-case)",
|
||||
header: "Name",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Custom name", description: "Enter a custom skill name" },
|
||||
{ label: "Auto-generate", description: "Generate from workflow description" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "How many main phases does this workflow have?",
|
||||
header: "Phases",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "3 phases", description: "Simple linear workflow" },
|
||||
{ label: "4 phases", description: "Standard workflow with validation" },
|
||||
{ label: "5+ phases", description: "Complex workflow with conditions" }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// For each phase, gather details
|
||||
const phases = [];
|
||||
for (let i = 0; i < phaseCount; i++) {
|
||||
const phaseInfo = AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: `Phase ${i+1}: What does this phase do?`,
|
||||
header: `Phase ${i+1}`,
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Session/Init", description: "Initialize session or state" },
|
||||
{ label: "Context/Gather", description: "Collect information or analyze" },
|
||||
{ label: "Process/Transform", description: "Process data or generate artifacts" },
|
||||
{ label: "Validate/Review", description: "Quality check or user review" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: `Phase ${i+1}: Does it use agents?`,
|
||||
header: "Agents",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No agents", description: "Direct execution only" },
|
||||
{ label: "Single agent", description: "Delegates to one agent" },
|
||||
{ label: "Multiple agents", description: "Parallel or sequential agents" }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
phases.push(phaseInfo);
|
||||
}
|
||||
|
||||
// Gather conditional logic
|
||||
const conditions = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Are any phases conditional (skipped based on previous results)?",
|
||||
header: "Conditions",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "No conditions", description: "All phases always execute" },
|
||||
{ label: "Has conditions", description: "Some phases execute conditionally" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
### Mode C: Requirements Document
|
||||
|
||||
When source is a structured requirements document:
|
||||
|
||||
```javascript
|
||||
// Read and parse requirements
|
||||
const reqContent = Read(requirementsPath);
|
||||
|
||||
// Extract structured fields
|
||||
// Expected format: Markdown with ## sections for each phase
|
||||
// Or JSON with phases array
|
||||
const requirements = parseRequirements(reqContent);
|
||||
```
|
||||
|
||||
### Mode D: Existing Skill Restructure
|
||||
|
||||
When source is an existing skill to refactor:
|
||||
|
||||
```javascript
|
||||
// Read existing SKILL.md
|
||||
const existingSkill = Read(skillPath);
|
||||
|
||||
// Scan for phase files
|
||||
const existingPhases = Glob(`${skillDir}/phases/*.md`);
|
||||
|
||||
// Analyze current structure for improvement
|
||||
const analysis = analyzeExistingStructure(existingSkill, existingPhases);
|
||||
```
|
||||
|
||||
## Step 1.3: Build Workflow Configuration
|
||||
|
||||
Regardless of source type, produce a unified `workflowConfig`:
|
||||
|
||||
```javascript
|
||||
const workflowConfig = {
|
||||
// Metadata
|
||||
skillName: "workflow-plan", // kebab-case
|
||||
title: "Workflow Plan", // Human-readable
|
||||
description: "5-phase planning...", // One-line description
|
||||
triggers: ["workflow:plan"], // Trigger phrases
|
||||
allowedTools: ["Task", "AskUserQuestion", "TodoWrite", "Read", "Write", "Edit", "Bash", "Glob", "Grep", "Skill"],
|
||||
|
||||
// Source information
|
||||
source: {
|
||||
type: "command_set", // input source type
|
||||
orchestratorPath: "...", // original orchestrator file
|
||||
subCommandPaths: ["..."] // original sub-command files
|
||||
},
|
||||
|
||||
// Phase definitions
|
||||
phases: [
|
||||
{
|
||||
number: 1,
|
||||
name: "Session Discovery",
|
||||
slug: "session-discovery", // for filename: 01-session-discovery.md
|
||||
description: "Create or discover workflow session",
|
||||
sourcePath: ".claude/commands/workflow/session/start.md",
|
||||
isConditional: false,
|
||||
condition: null,
|
||||
usesAgents: false,
|
||||
agentTypes: [],
|
||||
todoWriteSubTasks: [], // no sub-tasks (atomic phase)
|
||||
outputVariables: ["sessionId"],
|
||||
outputFiles: ["planning-notes.md"]
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
name: "Context Gathering",
|
||||
slug: "context-gathering",
|
||||
description: "Gather project context via agents",
|
||||
sourcePath: ".claude/commands/workflow/tools/context-gather.md",
|
||||
isConditional: false,
|
||||
condition: null,
|
||||
usesAgents: true,
|
||||
agentTypes: ["cli-explore-agent", "context-search-agent"],
|
||||
todoWriteSubTasks: [
|
||||
"Analyze codebase structure",
|
||||
"Identify integration points",
|
||||
"Generate context package"
|
||||
],
|
||||
outputVariables: ["contextPath", "conflictRisk"],
|
||||
outputFiles: ["context-package.json"]
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
name: "Conflict Resolution",
|
||||
slug: "conflict-resolution",
|
||||
description: "Detect and resolve conflicts",
|
||||
sourcePath: ".claude/commands/workflow/tools/conflict-resolution.md",
|
||||
isConditional: true,
|
||||
condition: "conflictRisk >= 'medium'",
|
||||
usesAgents: true,
|
||||
agentTypes: ["cli-execution-agent"],
|
||||
todoWriteSubTasks: [
|
||||
"Detect conflicts with CLI analysis",
|
||||
"Present conflicts to user",
|
||||
"Apply resolution strategies"
|
||||
],
|
||||
outputVariables: [],
|
||||
outputFiles: ["conflict-resolution.json"]
|
||||
},
|
||||
{
|
||||
number: 4,
|
||||
name: "Task Generation",
|
||||
slug: "task-generation",
|
||||
description: "Generate implementation plan and task JSONs",
|
||||
sourcePath: ".claude/commands/workflow/tools/task-generate-agent.md",
|
||||
isConditional: false,
|
||||
condition: null,
|
||||
usesAgents: true,
|
||||
agentTypes: ["action-planning-agent"],
|
||||
todoWriteSubTasks: [], // single agent task
|
||||
outputVariables: [],
|
||||
outputFiles: ["IMPL_PLAN.md", "IMPL-*.json", "TODO_LIST.md"]
|
||||
}
|
||||
],
|
||||
|
||||
// Data flow
|
||||
dataFlow: [
|
||||
{ from: "input", to: "phase1", variables: ["structuredDescription"] },
|
||||
{ from: "phase1", to: "phase2", variables: ["sessionId"] },
|
||||
{ from: "phase2", to: "phase3", variables: ["contextPath", "conflictRisk"] },
|
||||
{ from: "phase2", to: "phase4", variables: ["contextPath"] },
|
||||
{ from: "phase3", to: "phase4", variables: ["resolvedArtifacts"] }
|
||||
],
|
||||
|
||||
// Features
|
||||
features: {
|
||||
hasAutoMode: true, // --yes flag support
|
||||
hasConditionalPhases: true, // some phases may be skipped
|
||||
hasTodoWriteSubTasks: true, // phases expand into sub-tasks
|
||||
hasPlanningNotes: true, // accumulated state document
|
||||
hasPostPhaseUpdates: true, // state updates between phases
|
||||
hasMemoryCompaction: true, // compact after heavy phases
|
||||
hasUserDecisionGate: true // user choice after final phase
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Step 1.4: User Confirmation
|
||||
|
||||
Present the analyzed structure to the user for confirmation:
|
||||
|
||||
```javascript
|
||||
// Display summary
|
||||
console.log(`
|
||||
Workflow Analysis Complete:
|
||||
Name: ${workflowConfig.skillName}
|
||||
Phases: ${workflowConfig.phases.length}
|
||||
${workflowConfig.phases.map(p =>
|
||||
` ${p.number}. ${p.name}${p.isConditional ? ' (conditional)' : ''}${p.usesAgents ? ` [${p.agentTypes.join(', ')}]` : ''}`
|
||||
).join('\n')}
|
||||
Data Flow: ${workflowConfig.dataFlow.length} connections
|
||||
Features: ${Object.entries(workflowConfig.features).filter(([,v]) => v).map(([k]) => k).join(', ')}
|
||||
`);
|
||||
|
||||
const confirm = AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Proceed with this workflow structure?",
|
||||
header: "Confirm",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes, proceed", description: "Generate skill with this structure" },
|
||||
{ label: "Modify phases", description: "Adjust phase count or ordering" },
|
||||
{ label: "Add features", description: "Enable additional patterns (auto mode, conditions, etc.)" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Variable**: `workflowConfig` (structured configuration object)
|
||||
- **TodoWrite**: Mark Phase 1 completed, Phase 2 in_progress
|
||||
|
||||
## Next Phase
|
||||
|
||||
Return to orchestrator, then auto-continue to [Phase 2: Orchestrator Design](02-orchestrator-design.md).
|
||||
@@ -0,0 +1,381 @@
|
||||
# Phase 2: Orchestrator Design
|
||||
|
||||
Generate the SKILL.md orchestrator file from workflowConfig, applying all coordination patterns (progressive loading, TodoWrite, data flow, conditional execution).
|
||||
|
||||
## Objective
|
||||
|
||||
- Create `.claude/skills/{skillName}/SKILL.md` as pure coordinator
|
||||
- Apply frontmatter conversion rules
|
||||
- Generate architecture diagram from phase structure
|
||||
- Build execution flow with `Ref:` markers and phase reference table
|
||||
- Generate data flow diagram
|
||||
- Build TodoWrite attachment/collapse patterns from phase definitions
|
||||
- Include all orchestrator-level sections
|
||||
|
||||
## Step 2.1: Create Directory Structure
|
||||
|
||||
```bash
|
||||
skillDir=".claude/skills/${workflowConfig.skillName}"
|
||||
mkdir -p "${skillDir}/phases"
|
||||
|
||||
# Optional directories based on features
|
||||
# mkdir -p "${skillDir}/specs" # if has domain specifications
|
||||
# mkdir -p "${skillDir}/templates" # if has reusable templates
|
||||
```
|
||||
|
||||
## Step 2.2: Generate Frontmatter
|
||||
|
||||
```javascript
|
||||
function generateFrontmatter(config) {
|
||||
return `---
|
||||
name: ${config.skillName}
|
||||
description: ${config.description}. Triggers on ${config.triggers.map(t => `"${t}"`).join(', ')}.
|
||||
allowed-tools: ${config.allowedTools.join(', ')}
|
||||
---`;
|
||||
}
|
||||
```
|
||||
|
||||
**Conversion from command frontmatter**:
|
||||
|
||||
```javascript
|
||||
// If source is command_set, convert fields:
|
||||
function convertCommandFrontmatter(commandFm, config) {
|
||||
return {
|
||||
name: commandFm.group
|
||||
? `${commandFm.group}-${commandFm.name}` // "workflow" + "plan" → "workflow-plan"
|
||||
: commandFm.name,
|
||||
description: commandFm.description,
|
||||
// argument-hint → removed (handled in Input Processing section)
|
||||
// examples → removed (moved to inline docs)
|
||||
// group → embedded in name prefix
|
||||
allowedTools: expandToolWildcards(commandFm['allowed-tools'])
|
||||
// "Skill(*), TodoWrite(*), Read(*)" → "Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep, Skill"
|
||||
};
|
||||
}
|
||||
|
||||
// Expand tool wildcards
|
||||
function expandToolWildcards(toolsStr) {
|
||||
const expanded = toolsStr
|
||||
.replace(/Skill\(\*\)/g, 'Skill')
|
||||
.replace(/TodoWrite\(\*\)/g, 'TodoWrite')
|
||||
.replace(/Read\(\*\)/g, 'Read')
|
||||
.replace(/Bash\(\*\)/g, 'Bash')
|
||||
.replace(/Glob\(\*\)/g, 'Glob')
|
||||
.replace(/Grep\(\*\)/g, 'Grep')
|
||||
.replace(/Task\(\*\)/g, 'Task');
|
||||
|
||||
// Add commonly needed tools if not present
|
||||
const baseTools = ['Task', 'AskUserQuestion', 'TodoWrite', 'Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'];
|
||||
const current = expanded.split(',').map(t => t.trim());
|
||||
const merged = [...new Set([...current, ...baseTools])];
|
||||
return merged;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.3: Generate Architecture Diagram
|
||||
|
||||
```javascript
|
||||
function generateArchitectureDiagram(config) {
|
||||
const phases = config.phases;
|
||||
const maxWidth = 65;
|
||||
|
||||
let diagram = '```\n';
|
||||
diagram += '┌' + '─'.repeat(maxWidth) + '┐\n';
|
||||
diagram += `│ ${config.title} Orchestrator (SKILL.md)${' '.repeat(maxWidth - config.title.length - 30)}│\n`;
|
||||
diagram += `│ → Pure coordinator: Execute phases, parse outputs, pass context${' '.repeat(maxWidth - 64)}│\n`;
|
||||
diagram += '└' + '─'.repeat(Math.floor(maxWidth/2)) + '┬' + '─'.repeat(maxWidth - Math.floor(maxWidth/2) - 1) + '┘\n';
|
||||
|
||||
// Phase boxes
|
||||
diagram += ' │\n';
|
||||
diagram += ' ' + phases.map(() => '┌─────────┐').join(' ') + '\n';
|
||||
diagram += ' ' + phases.map((p, i) => {
|
||||
const label = `Phase ${p.number}`.padEnd(9);
|
||||
return `│${label}│`;
|
||||
}).join(' ') + '\n';
|
||||
diagram += ' ' + phases.map(p => {
|
||||
const name = p.name.substring(0, 9).padEnd(9);
|
||||
return `│${name}│`;
|
||||
}).join(' ') + '\n';
|
||||
diagram += ' ' + phases.map(() => '└─────────┘').join(' ') + '\n';
|
||||
|
||||
// Output labels
|
||||
diagram += ' ' + phases.map(p => {
|
||||
const vars = p.outputVariables.join(', ').substring(0, 11).padEnd(11);
|
||||
return ` ${vars}`;
|
||||
}).join('') + '\n';
|
||||
|
||||
diagram += '```';
|
||||
return diagram;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.4: Generate Execution Flow
|
||||
|
||||
The execution flow uses `Ref:` markers to point to phase documents, with a Phase Reference Documents table inline.
|
||||
|
||||
```javascript
|
||||
function generateExecutionFlow(config) {
|
||||
let flow = '## Execution Flow\n\n```\n';
|
||||
flow += 'Input Parsing:\n';
|
||||
flow += ' └─ Convert user input to structured format (GOAL/SCOPE/CONTEXT)\n\n';
|
||||
|
||||
for (const phase of config.phases) {
|
||||
flow += `Phase ${phase.number}: ${phase.name}\n`;
|
||||
|
||||
if (phase.isConditional) {
|
||||
flow += ` └─ Decision (${phase.condition}):\n`;
|
||||
flow += ` ├─ condition met → Ref: phases/${String(phase.number).padStart(2, '0')}-${phase.slug}.md\n`;
|
||||
if (phase.todoWriteSubTasks.length > 0) {
|
||||
flow += ` │ ├─ Tasks attached: ${phase.todoWriteSubTasks.join(' → ')}\n`;
|
||||
}
|
||||
flow += ` │ └─ Output: ${phase.outputFiles.join(', ') || phase.outputVariables.join(', ')}\n`;
|
||||
flow += ` └─ condition not met → Skip to Phase ${phase.number + 1}\n`;
|
||||
} else {
|
||||
flow += ` └─ Ref: phases/${String(phase.number).padStart(2, '0')}-${phase.slug}.md\n`;
|
||||
if (phase.todoWriteSubTasks.length > 0) {
|
||||
flow += ` ├─ Tasks attached: ${phase.todoWriteSubTasks.join(' → ')}\n`;
|
||||
}
|
||||
flow += ` └─ Output: ${[...phase.outputVariables, ...phase.outputFiles].join(', ')}\n`;
|
||||
}
|
||||
flow += '\n';
|
||||
}
|
||||
|
||||
flow += 'Return:\n └─ Summary with recommended next steps\n';
|
||||
flow += '```\n\n';
|
||||
|
||||
// Phase Reference Documents table
|
||||
flow += '**Phase Reference Documents** (read on-demand when phase executes):\n\n';
|
||||
flow += '| Phase | Document | Purpose |\n';
|
||||
flow += '|-------|----------|---------|\n';
|
||||
for (const phase of config.phases) {
|
||||
const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
|
||||
flow += `| ${phase.number} | [phases/${filename}](phases/${filename}) | ${phase.description} |\n`;
|
||||
}
|
||||
|
||||
return flow;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.5: Generate Data Flow Section
|
||||
|
||||
```javascript
|
||||
function generateDataFlow(config) {
|
||||
let section = '## Data Flow\n\n```\n';
|
||||
section += 'User Input (task description)\n';
|
||||
section += ' ↓\n';
|
||||
section += '[Convert to Structured Format]\n';
|
||||
|
||||
for (const phase of config.phases) {
|
||||
const inputVars = config.dataFlow
|
||||
.filter(d => d.to === `phase${phase.number}`)
|
||||
.flatMap(d => d.variables);
|
||||
const outputVars = [...phase.outputVariables, ...phase.outputFiles];
|
||||
|
||||
section += ' ↓\n';
|
||||
section += `Phase ${phase.number}: ${phase.name}\n`;
|
||||
if (inputVars.length > 0) {
|
||||
section += ` ↓ Input: ${inputVars.join(' + ')}\n`;
|
||||
}
|
||||
if (outputVars.length > 0) {
|
||||
section += ` ↓ Output: ${outputVars.join(' + ')}\n`;
|
||||
}
|
||||
if (phase.isConditional) {
|
||||
section += ` ↓ Skip if ${phase.condition} is false → proceed to Phase ${phase.number + 1}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
section += ' ↓\n';
|
||||
section += 'Return summary to user\n';
|
||||
section += '```\n';
|
||||
return section;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.6: Generate TodoWrite Pattern
|
||||
|
||||
```javascript
|
||||
function generateTodoWritePattern(config) {
|
||||
let section = '## TodoWrite Pattern\n\n';
|
||||
section += '**Core Concept**: Dynamic task attachment and collapse for real-time visibility.\n\n';
|
||||
|
||||
section += '### Key Principles\n\n';
|
||||
section += '1. **Task Attachment** (when phase executed):\n';
|
||||
section += ' - Sub-tasks are **attached** to orchestrator\'s TodoWrite\n';
|
||||
|
||||
// Identify which phases have sub-tasks
|
||||
const phasesWithSubTasks = config.phases.filter(p => p.todoWriteSubTasks.length > 0);
|
||||
const phasesWithoutSubTasks = config.phases.filter(p => p.todoWriteSubTasks.length === 0);
|
||||
|
||||
if (phasesWithSubTasks.length > 0) {
|
||||
section += ` - **${phasesWithSubTasks.map(p => `Phase ${p.number}`).join(', ')}**: Multiple sub-tasks attached\n`;
|
||||
}
|
||||
if (phasesWithoutSubTasks.length > 0) {
|
||||
section += ` - **${phasesWithoutSubTasks.map(p => `Phase ${p.number}`).join(', ')}**: Single task (atomic)\n`;
|
||||
}
|
||||
|
||||
section += '\n2. **Task Collapse** (after sub-tasks complete):\n';
|
||||
if (phasesWithSubTasks.length > 0) {
|
||||
section += ` - **Applies to ${phasesWithSubTasks.map(p => `Phase ${p.number}`).join(', ')}**: Remove sub-tasks, collapse to summary\n`;
|
||||
}
|
||||
section += ' - Maintains clean orchestrator-level view\n';
|
||||
|
||||
section += '\n3. **Continuous Execution**: After completion, automatically proceed to next phase\n\n';
|
||||
|
||||
// Generate TodoWrite examples for phases with sub-tasks
|
||||
for (const phase of phasesWithSubTasks) {
|
||||
section += `### Phase ${phase.number} (Tasks Attached):\n`;
|
||||
section += '```json\n[\n';
|
||||
|
||||
// Previous phases completed
|
||||
for (const prev of config.phases.filter(p => p.number < phase.number)) {
|
||||
section += ` {"content": "Phase ${prev.number}: ${prev.name}", "status": "completed"},\n`;
|
||||
}
|
||||
|
||||
// Current phase in_progress with sub-tasks
|
||||
section += ` {"content": "Phase ${phase.number}: ${phase.name}", "status": "in_progress"},\n`;
|
||||
phase.todoWriteSubTasks.forEach((task, i) => {
|
||||
const status = i === 0 ? 'in_progress' : 'pending';
|
||||
section += ` {"content": " → ${task}", "status": "${status}"},\n`;
|
||||
});
|
||||
|
||||
// Remaining phases pending
|
||||
for (const next of config.phases.filter(p => p.number > phase.number && !p.isConditional)) {
|
||||
section += ` {"content": "Phase ${next.number}: ${next.name}", "status": "pending"},\n`;
|
||||
}
|
||||
|
||||
section += ']\n```\n\n';
|
||||
|
||||
// Collapsed version
|
||||
section += `### Phase ${phase.number} (Collapsed):\n`;
|
||||
section += '```json\n[\n';
|
||||
for (const p of config.phases.filter(pp => !pp.isConditional || pp.number <= phase.number)) {
|
||||
const status = p.number <= phase.number ? 'completed' : 'pending';
|
||||
section += ` {"content": "Phase ${p.number}: ${p.name}", "status": "${status}"},\n`;
|
||||
}
|
||||
section += ']\n```\n\n';
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.7: Generate Remaining Sections
|
||||
|
||||
Extract from source orchestrator or generate from config:
|
||||
|
||||
```javascript
|
||||
function generateOrchestratorSections(config, sourceContent) {
|
||||
const sections = [];
|
||||
|
||||
// Auto Mode (if feature enabled)
|
||||
if (config.features.hasAutoMode) {
|
||||
sections.push(extractOrGenerate(sourceContent, 'Auto Mode',
|
||||
'## Auto Mode\n\nWhen `--yes` or `-y`: Auto-continue all phases, use recommended defaults.\n'));
|
||||
}
|
||||
|
||||
// Core Rules
|
||||
sections.push(extractOrGenerate(sourceContent, 'Core Rules',
|
||||
generateDefaultCoreRules(config)));
|
||||
|
||||
// Input Processing
|
||||
sections.push(extractOrGenerate(sourceContent, 'Input Processing',
|
||||
generateDefaultInputProcessing(config)));
|
||||
|
||||
// Post-Phase Updates (if feature enabled)
|
||||
if (config.features.hasPostPhaseUpdates) {
|
||||
sections.push(extractOrGenerate(sourceContent, 'Post-Phase Updates',
|
||||
generatePostPhaseUpdates(config)));
|
||||
}
|
||||
|
||||
// Error Handling
|
||||
sections.push(extractOrGenerate(sourceContent, 'Error Handling',
|
||||
generateDefaultErrorHandling()));
|
||||
|
||||
// Coordinator Checklist
|
||||
sections.push(extractOrGenerate(sourceContent, 'Coordinator Checklist',
|
||||
generateCoordinatorChecklist(config)));
|
||||
|
||||
// Related Commands
|
||||
sections.push(extractOrGenerate(sourceContent, 'Related Commands',
|
||||
generateRelatedCommands(config)));
|
||||
|
||||
return sections.join('\n\n');
|
||||
}
|
||||
|
||||
// Extract section from source if exists, otherwise generate default
|
||||
function extractOrGenerate(sourceContent, sectionName, defaultContent) {
|
||||
if (sourceContent) {
|
||||
const extracted = extractSection(sourceContent, sectionName);
|
||||
if (extracted) return extracted;
|
||||
}
|
||||
return defaultContent;
|
||||
}
|
||||
|
||||
// Default Core Rules template
|
||||
function generateDefaultCoreRules(config) {
|
||||
return `## Core Rules
|
||||
|
||||
1. **Start Immediately**: First action is TodoWrite initialization, second action is Phase 1 execution
|
||||
2. **No Preliminary Analysis**: Do not read files or gather context before Phase 1
|
||||
3. **Parse Every Output**: Extract required data from each phase for next phase
|
||||
4. **Auto-Continue**: Check TodoList status to execute next pending phase automatically
|
||||
5. **Track Progress**: Update TodoWrite dynamically with task attachment/collapse pattern
|
||||
6. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute
|
||||
7. **DO NOT STOP**: Continuous multi-phase workflow until all phases complete`;
|
||||
}
|
||||
|
||||
// Default Error Handling template
|
||||
function generateDefaultErrorHandling() {
|
||||
return `## Error Handling
|
||||
|
||||
- **Parsing Failure**: If output parsing fails, retry once, then report error
|
||||
- **Validation Failure**: Report which file/data is missing
|
||||
- **Command Failure**: Keep phase \`in_progress\`, report error, do not proceed`;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2.8: Assemble SKILL.md
|
||||
|
||||
```javascript
|
||||
function assembleSkillMd(config, sourceContent) {
|
||||
const parts = [
|
||||
generateFrontmatter(config),
|
||||
'',
|
||||
`# ${config.title}`,
|
||||
'',
|
||||
config.description,
|
||||
'',
|
||||
generateArchitectureDiagram(config),
|
||||
'',
|
||||
generateDesignPrinciples(config),
|
||||
'',
|
||||
generateExecutionFlow(config),
|
||||
'',
|
||||
generateDataFlow(config),
|
||||
'',
|
||||
generateTodoWritePattern(config),
|
||||
'',
|
||||
generateOrchestratorSections(config, sourceContent)
|
||||
];
|
||||
|
||||
const skillMdContent = parts.join('\n');
|
||||
Write(`${skillDir}/SKILL.md`, skillMdContent);
|
||||
}
|
||||
```
|
||||
|
||||
**Critical Quality Rules**:
|
||||
|
||||
1. SKILL.md must NOT contain full execution detail (agent prompts, bash commands)
|
||||
2. SKILL.md MUST contain `Ref:` markers pointing to phase files
|
||||
3. SKILL.md MUST contain Phase Reference Documents table
|
||||
4. Every phase mentioned in Execution Flow must have a corresponding phase file
|
||||
5. Data flow variables must be consistent across sections
|
||||
|
||||
## Output
|
||||
|
||||
- **File**: `.claude/skills/{skillName}/SKILL.md`
|
||||
- **TodoWrite**: Mark Phase 2 completed, Phase 3 in_progress
|
||||
|
||||
## Next Phase
|
||||
|
||||
Return to orchestrator, then auto-continue to [Phase 3: Phase Files Design](03-phase-design.md).
|
||||
356
.claude/skills/workflow-skill-designer/phases/03-phase-design.md
Normal file
356
.claude/skills/workflow-skill-designer/phases/03-phase-design.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# 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
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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](04-validation.md).
|
||||
397
.claude/skills/workflow-skill-designer/phases/04-validation.md
Normal file
397
.claude/skills/workflow-skill-designer/phases/04-validation.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# Phase 4: Validation & Integration
|
||||
|
||||
Validate the generated skill package for structural completeness, reference integrity, and content quality. Produce a validation report and integration summary.
|
||||
|
||||
## Objective
|
||||
|
||||
- Verify all required files exist
|
||||
- Validate SKILL.md references match actual phase files
|
||||
- Check content preservation (for command extraction source)
|
||||
- Verify cross-phase data flow consistency
|
||||
- Report validation results to user
|
||||
|
||||
## Step 4.1: Structural Validation
|
||||
|
||||
```javascript
|
||||
function validateStructure(config) {
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
const results = { errors: [], warnings: [], info: [] };
|
||||
|
||||
// Check SKILL.md exists
|
||||
const skillMdExists = fileExists(`${skillDir}/SKILL.md`);
|
||||
if (!skillMdExists) {
|
||||
results.errors.push('SKILL.md not found');
|
||||
}
|
||||
|
||||
// Check all phase files exist
|
||||
for (const phase of config.phases) {
|
||||
const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
|
||||
const filepath = `${skillDir}/phases/${filename}`;
|
||||
if (!fileExists(filepath)) {
|
||||
results.errors.push(`Phase file missing: ${filepath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check SKILL.md frontmatter
|
||||
if (skillMdExists) {
|
||||
const skillMd = Read(`${skillDir}/SKILL.md`);
|
||||
const fm = extractYAMLFrontmatter(skillMd);
|
||||
|
||||
if (!fm.name) results.errors.push('Frontmatter missing: name');
|
||||
if (!fm.description) results.errors.push('Frontmatter missing: description');
|
||||
if (!fm['allowed-tools']) results.errors.push('Frontmatter missing: allowed-tools');
|
||||
|
||||
// Check description has trigger phrase
|
||||
if (fm.description && !fm.description.includes('Triggers on')) {
|
||||
results.warnings.push('Description missing trigger phrase (Triggers on "...")');
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.2: Reference Integrity
|
||||
|
||||
```javascript
|
||||
function validateReferences(config) {
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
const results = { errors: [], warnings: [], info: [] };
|
||||
const skillMd = Read(`${skillDir}/SKILL.md`);
|
||||
|
||||
// Extract all Ref: markers from SKILL.md
|
||||
const refMarkers = skillMd.match(/Ref: phases\/\S+\.md/g) || [];
|
||||
const linkedFiles = skillMd.match(/\[phases\/\S+\.md\]\(phases\/\S+\.md\)/g) || [];
|
||||
|
||||
// Collect all referenced phase files
|
||||
const referencedFiles = new Set();
|
||||
for (const ref of refMarkers) {
|
||||
referencedFiles.add(ref.replace('Ref: ', ''));
|
||||
}
|
||||
for (const link of linkedFiles) {
|
||||
const match = link.match(/\(phases\/\S+\.md\)/);
|
||||
if (match) referencedFiles.add(match[0].replace(/[()]/g, ''));
|
||||
}
|
||||
|
||||
// Check each referenced file exists
|
||||
for (const refFile of referencedFiles) {
|
||||
if (!fileExists(`${skillDir}/${refFile}`)) {
|
||||
results.errors.push(`Referenced file not found: ${refFile}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check each phase file is referenced in SKILL.md
|
||||
for (const phase of config.phases) {
|
||||
const filename = `phases/${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
|
||||
if (!referencedFiles.has(filename)) {
|
||||
results.warnings.push(`Phase file not referenced in SKILL.md: ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Phase Reference Documents table exists
|
||||
if (!skillMd.includes('Phase Reference Documents')) {
|
||||
results.errors.push('SKILL.md missing Phase Reference Documents table');
|
||||
}
|
||||
|
||||
// Check Phase Reference Documents table has entries for all phases
|
||||
for (const phase of config.phases) {
|
||||
const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
|
||||
if (!skillMd.includes(filename)) {
|
||||
results.errors.push(`Phase Reference table missing entry for: ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.3: Content Quality (Command Extraction Only)
|
||||
|
||||
```javascript
|
||||
function validateContentQuality(config) {
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
const results = { errors: [], warnings: [], info: [] };
|
||||
|
||||
if (config.source.type !== 'command_set') {
|
||||
results.info.push('Content quality check skipped (not command extraction)');
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const phase of config.phases) {
|
||||
if (!phase.sourcePath) continue;
|
||||
|
||||
const sourceContent = Read(phase.sourcePath);
|
||||
const sourceBody = removeYAMLFrontmatter(sourceContent);
|
||||
const filename = `${String(phase.number).padStart(2, '0')}-${phase.slug}.md`;
|
||||
const phaseContent = Read(`${skillDir}/phases/${filename}`);
|
||||
|
||||
// Line count comparison
|
||||
const sourceLines = sourceBody.split('\n').length;
|
||||
const phaseLines = phaseContent.split('\n').length;
|
||||
const ratio = phaseLines / sourceLines;
|
||||
|
||||
if (ratio < 0.7) {
|
||||
results.errors.push(
|
||||
`Phase ${phase.number} content loss: source=${sourceLines} lines, phase=${phaseLines} lines (${Math.round(ratio * 100)}%)`
|
||||
);
|
||||
} else if (ratio < 0.9) {
|
||||
results.warnings.push(
|
||||
`Phase ${phase.number} possible content reduction: source=${sourceLines}, phase=${phaseLines} (${Math.round(ratio * 100)}%)`
|
||||
);
|
||||
} else {
|
||||
results.info.push(
|
||||
`Phase ${phase.number} content preserved: source=${sourceLines}, phase=${phaseLines} (${Math.round(ratio * 100)}%)`
|
||||
);
|
||||
}
|
||||
|
||||
// Code block count comparison
|
||||
const sourceBlocks = (sourceBody.match(/```/g) || []).length / 2;
|
||||
const phaseBlocks = (phaseContent.match(/```/g) || []).length / 2;
|
||||
if (phaseBlocks < sourceBlocks) {
|
||||
results.warnings.push(
|
||||
`Phase ${phase.number} missing code blocks: source=${sourceBlocks}, phase=${phaseBlocks}`
|
||||
);
|
||||
}
|
||||
|
||||
// Agent prompt preservation
|
||||
const sourceAgents = (sourceBody.match(/Task\(|subagent_type/g) || []).length;
|
||||
const phaseAgents = (phaseContent.match(/Task\(|subagent_type/g) || []).length;
|
||||
if (phaseAgents < sourceAgents) {
|
||||
results.errors.push(
|
||||
`Phase ${phase.number} missing agent calls: source=${sourceAgents}, phase=${phaseAgents}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.4: Data Flow Consistency
|
||||
|
||||
```javascript
|
||||
function validateDataFlow(config) {
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
const results = { errors: [], warnings: [], info: [] };
|
||||
const skillMd = Read(`${skillDir}/SKILL.md`);
|
||||
|
||||
// Check all data flow variables are mentioned in SKILL.md
|
||||
for (const flow of config.dataFlow) {
|
||||
for (const variable of flow.variables) {
|
||||
if (!skillMd.includes(variable)) {
|
||||
results.warnings.push(
|
||||
`Data flow variable '${variable}' (${flow.from} → ${flow.to}) not found in SKILL.md`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check conditional phases have their condition in SKILL.md
|
||||
for (const phase of config.phases) {
|
||||
if (phase.isConditional && phase.condition) {
|
||||
// Extract the key variable from condition
|
||||
const condVar = phase.condition.match(/\w+/)?.[0];
|
||||
if (condVar && !skillMd.includes(condVar)) {
|
||||
results.errors.push(
|
||||
`Conditional Phase ${phase.number} condition variable '${condVar}' not found in SKILL.md`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.5: SKILL.md Section Completeness
|
||||
|
||||
```javascript
|
||||
function validateSkillMdSections(config) {
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
const results = { errors: [], warnings: [], info: [] };
|
||||
const skillMd = Read(`${skillDir}/SKILL.md`);
|
||||
|
||||
// Required sections
|
||||
const requiredSections = [
|
||||
{ name: 'Architecture Overview', pattern: /## Architecture Overview/ },
|
||||
{ name: 'Execution Flow', pattern: /## Execution Flow/ },
|
||||
{ name: 'Core Rules', pattern: /## Core Rules/ },
|
||||
{ name: 'Data Flow', pattern: /## Data Flow/ },
|
||||
{ name: 'Error Handling', pattern: /## Error Handling/ }
|
||||
];
|
||||
|
||||
// Recommended sections
|
||||
const recommendedSections = [
|
||||
{ name: 'Key Design Principles', pattern: /## Key Design Principles/ },
|
||||
{ name: 'Input Processing', pattern: /## Input Processing/ },
|
||||
{ name: 'TodoWrite Pattern', pattern: /## TodoWrite Pattern/ },
|
||||
{ name: 'Coordinator Checklist', pattern: /## Coordinator Checklist/ },
|
||||
{ name: 'Related Commands', pattern: /## Related Commands/ }
|
||||
];
|
||||
|
||||
// Conditional sections
|
||||
const conditionalSections = [
|
||||
{ name: 'Auto Mode', pattern: /## Auto Mode/, condition: config.features.hasAutoMode },
|
||||
{ name: 'Post-Phase Updates', pattern: /## Post-Phase Updates/, condition: config.features.hasPostPhaseUpdates }
|
||||
];
|
||||
|
||||
for (const section of requiredSections) {
|
||||
if (!section.pattern.test(skillMd)) {
|
||||
results.errors.push(`Missing required section: ${section.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const section of recommendedSections) {
|
||||
if (!section.pattern.test(skillMd)) {
|
||||
results.warnings.push(`Missing recommended section: ${section.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const section of conditionalSections) {
|
||||
if (section.condition && !section.pattern.test(skillMd)) {
|
||||
results.warnings.push(`Missing conditional section: ${section.name} (feature enabled but section absent)`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.6: Aggregate Results and Report
|
||||
|
||||
```javascript
|
||||
function generateValidationReport(config) {
|
||||
const structural = validateStructure(config);
|
||||
const references = validateReferences(config);
|
||||
const content = validateContentQuality(config);
|
||||
const dataFlow = validateDataFlow(config);
|
||||
const sections = validateSkillMdSections(config);
|
||||
|
||||
// Aggregate
|
||||
const allErrors = [
|
||||
...structural.errors,
|
||||
...references.errors,
|
||||
...content.errors,
|
||||
...dataFlow.errors,
|
||||
...sections.errors
|
||||
];
|
||||
const allWarnings = [
|
||||
...structural.warnings,
|
||||
...references.warnings,
|
||||
...content.warnings,
|
||||
...dataFlow.warnings,
|
||||
...sections.warnings
|
||||
];
|
||||
const allInfo = [
|
||||
...structural.info,
|
||||
...references.info,
|
||||
...content.info,
|
||||
...dataFlow.info,
|
||||
...sections.info
|
||||
];
|
||||
|
||||
// Quality gate
|
||||
const gate = allErrors.length === 0 ? 'PASS' :
|
||||
allErrors.length <= 2 ? 'REVIEW' : 'FAIL';
|
||||
|
||||
// Display report
|
||||
const skillDir = `.claude/skills/${config.skillName}`;
|
||||
|
||||
console.log(`
|
||||
╔══════════════════════════════════════╗
|
||||
║ Workflow Skill Validation Report ║
|
||||
╠══════════════════════════════════════╣
|
||||
║ Skill: ${config.skillName.padEnd(28)}║
|
||||
║ Gate: ${gate.padEnd(28)}║
|
||||
╚══════════════════════════════════════╝
|
||||
|
||||
Structure:
|
||||
${skillDir}/
|
||||
├── SKILL.md ${fileExists(`${skillDir}/SKILL.md`) ? '✓' : '✗'}
|
||||
└── phases/
|
||||
${config.phases.map(p => {
|
||||
const fn = `${String(p.number).padStart(2, '0')}-${p.slug}.md`;
|
||||
return ` ├── ${fn.padEnd(30)} ${fileExists(`${skillDir}/phases/${fn}`) ? '✓' : '✗'}`;
|
||||
}).join('\n')}
|
||||
|
||||
${allErrors.length > 0 ? `Errors (${allErrors.length}):\n${allErrors.map(e => ` ✗ ${e}`).join('\n')}` : 'Errors: None ✓'}
|
||||
|
||||
${allWarnings.length > 0 ? `Warnings (${allWarnings.length}):\n${allWarnings.map(w => ` ⚠ ${w}`).join('\n')}` : 'Warnings: None ✓'}
|
||||
|
||||
${allInfo.length > 0 ? `Info:\n${allInfo.map(i => ` ℹ ${i}`).join('\n')}` : ''}
|
||||
`);
|
||||
|
||||
return { gate, errors: allErrors, warnings: allWarnings, info: allInfo };
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.7: Error Recovery
|
||||
|
||||
If validation fails, offer recovery options:
|
||||
|
||||
```javascript
|
||||
if (report.gate === 'FAIL') {
|
||||
const recovery = AskUserQuestion({
|
||||
questions: [{
|
||||
question: `Validation found ${report.errors.length} errors. How to proceed?`,
|
||||
header: "Recovery",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Auto-fix", description: "Attempt automatic fixes for common issues" },
|
||||
{ label: "Regenerate phases", description: "Re-run Phase 3 with stricter preservation" },
|
||||
{ label: "Accept as-is", description: "Proceed despite errors (manual fix later)" }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
if (recovery === 'Auto-fix') {
|
||||
// Common auto-fixes:
|
||||
// 1. Missing Next Phase links → add them
|
||||
// 2. Missing Output sections → add from config
|
||||
// 3. Missing Phase Reference table → generate from config
|
||||
autoFixCommonIssues(config, report.errors);
|
||||
// Re-validate
|
||||
return generateValidationReport(config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4.8: Integration Summary
|
||||
|
||||
```javascript
|
||||
function displayIntegrationSummary(config) {
|
||||
console.log(`
|
||||
Integration Complete:
|
||||
Location: .claude/skills/${config.skillName}/
|
||||
Files: ${config.phases.length + 1} (SKILL.md + ${config.phases.length} phases)
|
||||
|
||||
Usage:
|
||||
Trigger: ${config.triggers.map(t => `"${t}"`).join(', ')}
|
||||
Auto: /${config.triggers[0]} --yes "task description"
|
||||
|
||||
Design Patterns Applied:
|
||||
✓ Progressive phase loading (Ref: markers)
|
||||
✓ Phase Reference Documents table
|
||||
${config.features.hasTodoWriteSubTasks ? '✓' : '○'} TodoWrite attachment/collapse
|
||||
${config.features.hasConditionalPhases ? '✓' : '○'} Conditional phase execution
|
||||
${config.features.hasAutoMode ? '✓' : '○'} Auto mode (--yes flag)
|
||||
${config.features.hasPostPhaseUpdates ? '✓' : '○'} Post-phase state updates
|
||||
${config.features.hasPlanningNotes ? '✓' : '○'} Accumulated planning notes
|
||||
|
||||
Next Steps:
|
||||
1. Review SKILL.md orchestrator logic
|
||||
2. Review each phase file for completeness
|
||||
3. Test skill invocation: /${config.triggers[0]} "test task"
|
||||
4. Iterate based on execution results
|
||||
`);
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Report**: Validation results with quality gate (PASS/REVIEW/FAIL)
|
||||
- **TodoWrite**: Mark Phase 4 completed (all tasks done)
|
||||
|
||||
## Completion
|
||||
|
||||
Workflow Skill Designer has completed. The generated skill package is ready at `.claude/skills/{skillName}/`.
|
||||
Reference in New Issue
Block a user