From 47c192f5840e3a69829240b5aa0d50dfe91c4c77 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Thu, 5 Feb 2026 19:29:45 +0800 Subject: [PATCH] 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 --- .claude/skills/lite-skill-generator/SKILL.md | 650 ---------- .../templates/simple-skill.md | 68 -- .../templates/style-guide.md | 64 - .claude/skills/workflow-plan/SKILL.md | 348 ++++++ .../phases/01-session-discovery.md | 281 +++++ .../phases/02-context-gathering.md | 427 +++++++ .../phases/03-conflict-resolution.md | 645 ++++++++++ .../phases/04-task-generation.md | 701 +++++++++++ .../skills/workflow-skill-designer/SKILL.md | 319 +++++ .../phases/01-requirements-analysis.md | 356 ++++++ .../phases/02-orchestrator-design.md | 381 ++++++ .../phases/03-phase-design.md | 356 ++++++ .../phases/04-validation.md | 397 +++++++ .../CliStreamMonitor/utils/jsonDetector.ts | 102 -- test-results/.last-run.json | 4 - tests/loop-comprehensive-test.js | 1041 ----------------- tests/loop-flow-test.js | 329 ------ tests/loop-standalone-test.js | 565 --------- tests/run-loop-comprehensive-test.sh | 26 - tests/run-loop-flow-test.sh | 261 ----- 20 files changed, 4211 insertions(+), 3110 deletions(-) delete mode 100644 .claude/skills/lite-skill-generator/SKILL.md delete mode 100644 .claude/skills/lite-skill-generator/templates/simple-skill.md delete mode 100644 .claude/skills/lite-skill-generator/templates/style-guide.md create mode 100644 .claude/skills/workflow-plan/SKILL.md create mode 100644 .claude/skills/workflow-plan/phases/01-session-discovery.md create mode 100644 .claude/skills/workflow-plan/phases/02-context-gathering.md create mode 100644 .claude/skills/workflow-plan/phases/03-conflict-resolution.md create mode 100644 .claude/skills/workflow-plan/phases/04-task-generation.md create mode 100644 .claude/skills/workflow-skill-designer/SKILL.md create mode 100644 .claude/skills/workflow-skill-designer/phases/01-requirements-analysis.md create mode 100644 .claude/skills/workflow-skill-designer/phases/02-orchestrator-design.md create mode 100644 .claude/skills/workflow-skill-designer/phases/03-phase-design.md create mode 100644 .claude/skills/workflow-skill-designer/phases/04-validation.md delete mode 100644 src/components/shared/CliStreamMonitor/utils/jsonDetector.ts delete mode 100644 test-results/.last-run.json delete mode 100644 tests/loop-comprehensive-test.js delete mode 100644 tests/loop-flow-test.js delete mode 100644 tests/loop-standalone-test.js delete mode 100644 tests/run-loop-comprehensive-test.sh delete mode 100644 tests/run-loop-flow-test.sh diff --git a/.claude/skills/lite-skill-generator/SKILL.md b/.claude/skills/lite-skill-generator/SKILL.md deleted file mode 100644 index 915ae2f8..00000000 --- a/.claude/skills/lite-skill-generator/SKILL.md +++ /dev/null @@ -1,650 +0,0 @@ ---- -name: lite-skill-generator -description: Lightweight skill generator with style learning - creates simple skills using flow-based execution and style imitation. Use for quick skill scaffolding, simple workflow creation, or style-aware skill generation. -allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion ---- - -# Lite Skill Generator - -Lightweight meta-skill for rapid skill creation with intelligent style learning and flow-based execution. - -## Core Concept - -**Simplicity First**: Generate simple, focused skills quickly with minimal overhead. Learn from existing skills to maintain consistent style and structure. - -**Progressive Disclosure**: Follow anthropics' three-layer loading principle: -1. **Metadata** - name, description, triggers (always loaded) -2. **SKILL.md** - core instructions (loaded when triggered) -3. **Bundled resources** - scripts, references, assets (loaded on demand) - -## Execution Model - -**3-Phase Flow**: Style Learning → Requirements Gathering → Generation - -``` -User Input → Phase 1: Style Analysis → Phase 2: Requirements → Phase 3: Generate → Skill Package - ↓ ↓ ↓ - Learn from examples Interactive prompts Write files + validate -``` - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Lite Skill Generator │ -│ │ -│ Input: Skill name, purpose, reference skills │ -│ ↓ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Phase 1-3: Lightweight Pipeline │ │ -│ │ ┌────┐ ┌────┐ ┌────┐ │ │ -│ │ │ P1 │→│ P2 │→│ P3 │ │ │ -│ │ │Styl│ │Req │ │Gen │ │ │ -│ │ └────┘ └────┘ └────┘ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ Output: .claude/skills/{skill-name}/ (minimal package) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## 3-Phase Workflow - -### Phase 1: Style Analysis & Learning - -Analyze reference skills to extract language patterns, structural conventions, and writing style. - -```javascript -// Phase 1 Execution Flow -async function analyzeStyle(referencePaths) { - // Step 1: Load reference skills - const references = []; - for (const path of referencePaths) { - const content = Read(path); - references.push({ - path: path, - content: content, - metadata: extractYAMLFrontmatter(content) - }); - } - - // Step 2: Extract style patterns - const styleProfile = { - // Structural patterns - structure: { - hasFrontmatter: references.every(r => r.metadata !== null), - sectionHeaders: extractCommonSections(references), - codeBlockUsage: detectCodeBlockPatterns(references), - flowDiagramUsage: detectFlowDiagrams(references) - }, - - // Language patterns - language: { - instructionStyle: detectInstructionStyle(references), // 'imperative' | 'declarative' | 'procedural' - pseudocodeUsage: detectPseudocodePatterns(references), - verbosity: calculateVerbosityLevel(references), // 'concise' | 'detailed' | 'verbose' - terminology: extractCommonTerms(references) - }, - - // Organization patterns - organization: { - phaseStructure: detectPhasePattern(references), // 'sequential' | 'autonomous' | 'flat' - exampleDensity: calculateExampleRatio(references), - templateUsage: detectTemplateReferences(references) - } - }; - - // Step 3: Generate style guide - return { - profile: styleProfile, - recommendations: generateStyleRecommendations(styleProfile), - examples: extractStyleExamples(references, styleProfile) - }; -} - -// Structural pattern detection -function extractCommonSections(references) { - const allSections = references.map(r => - r.content.match(/^##? (.+)$/gm)?.map(s => s.replace(/^##? /, '')) - ).flat(); - return findMostCommon(allSections); -} - -// Language style detection -function detectInstructionStyle(references) { - const imperativePattern = /^(Use|Execute|Run|Call|Create|Generate)\s/gim; - const declarativePattern = /^(The|This|Each|All)\s.*\s(is|are|will be)\s/gim; - const proceduralPattern = /^(Step \d+|Phase \d+|First|Then|Finally)\s/gim; - - const scores = references.map(r => ({ - imperative: (r.content.match(imperativePattern) || []).length, - declarative: (r.content.match(declarativePattern) || []).length, - procedural: (r.content.match(proceduralPattern) || []).length - })); - - return getMaxStyle(scores); -} - -// Pseudocode pattern detection -function detectPseudocodePatterns(references) { - const hasJavaScriptBlocks = references.some(r => r.content.includes('```javascript')); - const hasFunctionDefs = references.some(r => /function\s+\w+\(/m.test(r.content)); - const hasFlowComments = references.some(r => /\/\/.*→/m.test(r.content)); - - return { - usePseudocode: hasJavaScriptBlocks && hasFunctionDefs, - flowAnnotations: hasFlowComments, - style: hasFunctionDefs ? 'functional' : 'imperative' - }; -} -``` - -**Output**: -``` -Style Analysis Complete: - Structure: Flow-based with pseudocode - Language: Procedural, detailed - Organization: Sequential phases - Key Patterns: 3-5 phases, function definitions, ASCII diagrams - -Recommendations: - ✓ Use phase-based structure (3-4 phases) - ✓ Include pseudocode for complex logic - ✓ Add ASCII flow diagrams - ✓ Maintain concise documentation style -``` - ---- - -### Phase 2: Requirements Gathering - -Interactive discovery of skill requirements using learned style patterns. - -```javascript -async function gatherRequirements(styleProfile) { - // Step 1: Basic information - const basicInfo = await AskUserQuestion({ - questions: [ - { - question: "What is the skill name? (kebab-case, e.g., 'pdf-generator')", - header: "Name", - options: [ - { label: "pdf-generator", description: "Example: PDF generation skill" }, - { label: "code-analyzer", description: "Example: Code analysis skill" }, - { label: "Custom", description: "Enter custom name" } - ] - }, - { - question: "What is the primary purpose?", - header: "Purpose", - options: [ - { label: "Generation", description: "Create/generate artifacts" }, - { label: "Analysis", description: "Analyze/inspect code or data" }, - { label: "Transformation", description: "Convert/transform content" }, - { label: "Orchestration", description: "Coordinate multiple operations" } - ] - } - ] - }); - - // Step 2: Execution complexity - const complexity = await AskUserQuestion({ - questions: [{ - question: "How many main steps does this skill need?", - header: "Steps", - options: [ - { label: "2-3 steps", description: "Simple workflow (recommended for lite-skill)" }, - { label: "4-5 steps", description: "Moderate workflow" }, - { label: "6+ steps", description: "Complex workflow (consider full skill-generator)" } - ] - }] - }); - - // Step 3: Tool requirements - const tools = await AskUserQuestion({ - questions: [{ - question: "Which tools will this skill use? (select multiple)", - header: "Tools", - multiSelect: true, - options: [ - { label: "Read", description: "Read files" }, - { label: "Write", description: "Write files" }, - { label: "Bash", description: "Execute commands" }, - { label: "Task", description: "Launch agents" }, - { label: "AskUserQuestion", description: "Interactive prompts" } - ] - }] - }); - - // Step 4: Output format - const output = await AskUserQuestion({ - questions: [{ - question: "What does this skill produce?", - header: "Output", - options: [ - { label: "Single file", description: "One main output file" }, - { label: "Multiple files", description: "Several related files" }, - { label: "Directory structure", description: "Complete directory tree" }, - { label: "Modified files", description: "Edits to existing files" } - ] - }] - }); - - // Step 5: Build configuration - return { - name: basicInfo.Name, - purpose: basicInfo.Purpose, - description: generateDescription(basicInfo.Name, basicInfo.Purpose), - steps: parseStepCount(complexity.Steps), - allowedTools: tools.Tools, - outputType: output.Output, - styleProfile: styleProfile, - triggerPhrases: generateTriggerPhrases(basicInfo.Name, basicInfo.Purpose) - }; -} - -// Generate skill description from name and purpose -function generateDescription(name, purpose) { - const templates = { - Generation: `Generate ${humanize(name)} with intelligent scaffolding`, - Analysis: `Analyze ${humanize(name)} with detailed reporting`, - Transformation: `Transform ${humanize(name)} with format conversion`, - Orchestration: `Orchestrate ${humanize(name)} workflow with multi-step coordination` - }; - return templates[purpose] || `${humanize(name)} skill for ${purpose.toLowerCase()} tasks`; -} - -// Generate trigger phrases -function generateTriggerPhrases(name, purpose) { - const base = [name, name.replace(/-/g, ' ')]; - const purposeVariants = { - Generation: ['generate', 'create', 'build'], - Analysis: ['analyze', 'inspect', 'review'], - Transformation: ['transform', 'convert', 'format'], - Orchestration: ['orchestrate', 'coordinate', 'manage'] - }; - return [...base, ...purposeVariants[purpose].map(v => `${v} ${humanize(name)}`)]; -} -``` - -**Display to User**: -``` -Requirements Gathered: - Name: pdf-generator - Purpose: Generation - Steps: 3 (Setup → Generate → Validate) - Tools: Read, Write, Bash - Output: Single file (PDF document) - Triggers: "pdf-generator", "generate pdf", "create pdf" - -Style Application: - Using flow-based structure (from style analysis) - Including pseudocode blocks - Adding ASCII diagrams for clarity -``` - ---- - -### Phase 3: Generate Skill Package - -Create minimal skill structure with style-aware content generation. - -```javascript -async function generateSkillPackage(requirements) { - const skillDir = `.claude/skills/${requirements.name}`; - const workDir = `.workflow/.scratchpad/lite-skill-gen-${Date.now()}`; - - // Step 1: Create directory structure - Bash(`mkdir -p "${skillDir}" "${workDir}"`); - - // Step 2: Generate SKILL.md (using learned style) - const skillContent = generateSkillMd(requirements); - Write(`${skillDir}/SKILL.md`, skillContent); - - // Step 3: Conditionally add bundled resources - if (requirements.outputType === 'Directory structure') { - Bash(`mkdir -p "${skillDir}/templates"`); - const templateContent = generateTemplate(requirements); - Write(`${skillDir}/templates/base-template.md`, templateContent); - } - - if (requirements.allowedTools.includes('Bash')) { - Bash(`mkdir -p "${skillDir}/scripts"`); - const scriptContent = generateScript(requirements); - Write(`${skillDir}/scripts/helper.sh`, scriptContent); - } - - // Step 4: Generate README - const readmeContent = generateReadme(requirements); - Write(`${skillDir}/README.md`, readmeContent); - - // Step 5: Validate structure - const validation = validateSkillStructure(skillDir, requirements); - Write(`${workDir}/validation-report.json`, JSON.stringify(validation, null, 2)); - - // Step 6: Return summary - return { - skillPath: skillDir, - filesCreated: [ - `${skillDir}/SKILL.md`, - ...(validation.hasTemplates ? [`${skillDir}/templates/`] : []), - ...(validation.hasScripts ? [`${skillDir}/scripts/`] : []), - `${skillDir}/README.md` - ], - validation: validation, - nextSteps: generateNextSteps(requirements) - }; -} - -// Generate SKILL.md with style awareness -function generateSkillMd(req) { - const { styleProfile } = req; - - // YAML frontmatter - const frontmatter = `--- -name: ${req.name} -description: ${req.description} -allowed-tools: ${req.allowedTools.join(', ')} ---- -`; - - // Main content structure (adapts to style) - let content = frontmatter; - - content += `\n# ${humanize(req.name)}\n\n`; - content += `${req.description}\n\n`; - - // Add architecture diagram if style uses them - if (styleProfile.structure.flowDiagramUsage) { - content += generateArchitectureDiagram(req); - } - - // Add execution flow - content += `## Execution Flow\n\n`; - if (styleProfile.language.pseudocodeUsage.usePseudocode) { - content += generatePseudocodeFlow(req); - } else { - content += generateProceduralFlow(req); - } - - // Add phase sections - for (let i = 0; i < req.steps; i++) { - content += generatePhaseSection(i + 1, req, styleProfile); - } - - // Add examples if style is verbose - if (styleProfile.language.verbosity !== 'concise') { - content += generateExamplesSection(req); - } - - return content; -} - -// Generate architecture diagram -function generateArchitectureDiagram(req) { - return `## Architecture -\`\`\` -┌─────────────────────────────────────────────────┐ -│ ${humanize(req.name)} │ -│ │ -│ Input → Phase 1 → Phase 2 → Phase 3 → Output │ -│ ${getPhaseName(1, req)} │ -│ ${getPhaseName(2, req)} │ -│ ${getPhaseName(3, req)} │ -└─────────────────────────────────────────────────┘ -\`\`\` - -`; -} - -// Generate pseudocode flow -function generatePseudocodeFlow(req) { - return `\`\`\`javascript -async function ${toCamelCase(req.name)}(input) { - // Phase 1: ${getPhaseName(1, req)} - const prepared = await phase1Prepare(input); - - // Phase 2: ${getPhaseName(2, req)} - const processed = await phase2Process(prepared); - - // Phase 3: ${getPhaseName(3, req)} - const result = await phase3Finalize(processed); - - return result; -} -\`\`\` - -`; -} - -// Generate phase section -function generatePhaseSection(phaseNum, req, styleProfile) { - const phaseName = getPhaseName(phaseNum, req); - - let section = `### Phase ${phaseNum}: ${phaseName}\n\n`; - - if (styleProfile.language.pseudocodeUsage.usePseudocode) { - section += `\`\`\`javascript\n`; - section += `async function phase${phaseNum}${toCamelCase(phaseName)}(input) {\n`; - section += ` // TODO: Implement ${phaseName.toLowerCase()} logic\n`; - section += ` return output;\n`; - section += `}\n\`\`\`\n\n`; - } else { - section += `**Steps**:\n`; - section += `1. Load input data\n`; - section += `2. Process according to ${phaseName.toLowerCase()} logic\n`; - section += `3. Return result to next phase\n\n`; - } - - return section; -} - -// Validation -function validateSkillStructure(skillDir, req) { - const requiredFiles = [`${skillDir}/SKILL.md`, `${skillDir}/README.md`]; - const exists = requiredFiles.map(f => Bash(`test -f "${f}"`).exitCode === 0); - - return { - valid: exists.every(e => e), - hasTemplates: Bash(`test -d "${skillDir}/templates"`).exitCode === 0, - hasScripts: Bash(`test -d "${skillDir}/scripts"`).exitCode === 0, - filesPresent: requiredFiles.filter((f, i) => exists[i]), - styleCompliance: checkStyleCompliance(skillDir, req.styleProfile) - }; -} -``` - -**Output**: -``` -Skill Package Generated: - Location: .claude/skills/pdf-generator/ - -Structure: - ✓ SKILL.md (entry point) - ✓ README.md (usage guide) - ✓ templates/ (directory templates) - ✓ scripts/ (helper scripts) - -Validation: - ✓ All required files present - ✓ Style compliance: 95% - ✓ Frontmatter valid - ✓ Tool references correct - -Next Steps: - 1. Review SKILL.md and customize phases - 2. Test skill: /skill:pdf-generator "test input" - 3. Iterate based on usage -``` - ---- - -## Complete Execution Flow - -``` -User: "Create a PDF generator skill" - ↓ -Phase 1: Style Analysis - |-- Read reference skills (ccw.md, ccw-coordinator.md) - |-- Extract style patterns (flow diagrams, pseudocode, structure) - |-- Generate style profile - +-- Output: Style recommendations - ↓ -Phase 2: Requirements - |-- Ask: Name, purpose, steps - |-- Ask: Tools, output format - |-- Generate: Description, triggers - +-- Output: Requirements config - ↓ -Phase 3: Generation - |-- Create: Directory structure - |-- Write: SKILL.md (style-aware) - |-- Write: README.md - |-- Optionally: templates/, scripts/ - |-- Validate: Structure and style - +-- Output: Skill package - ↓ -Return: Skill location + next steps -``` - -## Phase Execution Protocol - -```javascript -// Main entry point -async function liteSkillGenerator(input) { - // Phase 1: Style Learning - const references = [ - '.claude/commands/ccw.md', - '.claude/commands/ccw-coordinator.md', - ...discoverReferenceSkills(input) - ]; - const styleProfile = await analyzeStyle(references); - console.log(`Style Analysis: ${styleProfile.organization.phaseStructure}, ${styleProfile.language.verbosity}`); - - // Phase 2: Requirements - const requirements = await gatherRequirements(styleProfile); - console.log(`Requirements: ${requirements.name} (${requirements.steps} phases)`); - - // Phase 3: Generation - const result = await generateSkillPackage(requirements); - console.log(`✅ Generated: ${result.skillPath}`); - - return result; -} -``` - -## Output Structure - -**Minimal Package** (default): -``` -.claude/skills/{skill-name}/ -├── SKILL.md # Entry point with frontmatter -└── README.md # Usage guide -``` - -**With Templates** (if needed): -``` -.claude/skills/{skill-name}/ -├── SKILL.md -├── README.md -└── templates/ - └── base-template.md -``` - -**With Scripts** (if using Bash): -``` -.claude/skills/{skill-name}/ -├── SKILL.md -├── README.md -└── scripts/ - └── helper.sh -``` - -## Key Design Principles - -1. **Style Learning** - Analyze reference skills to maintain consistency -2. **Minimal Overhead** - Generate only essential files (SKILL.md + README) -3. **Progressive Disclosure** - Follow anthropics' three-layer loading -4. **Flow-Based** - Use pseudocode and flow diagrams (when style appropriate) -5. **Interactive** - Guided requirements gathering via AskUserQuestion -6. **Fast Generation** - 3 phases instead of 6, focused on simplicity -7. **Style Awareness** - Adapt output based on detected patterns - -## Style Pattern Detection - -**Structural Patterns**: -- YAML frontmatter usage (100% in references) -- Section headers (H2 for major, H3 for sub-sections) -- Code blocks (JavaScript pseudocode, Bash examples) -- ASCII diagrams (architecture, flow charts) - -**Language Patterns**: -- Instruction style: Procedural with function definitions -- Pseudocode: JavaScript-based with flow annotations -- Verbosity: Detailed but focused -- Terminology: Phase, workflow, pipeline, orchestrator - -**Organization Patterns**: -- Phase structure: 3-5 sequential phases -- Example density: Moderate (1-2 per major section) -- Template usage: Minimal (only when necessary) - -## Usage Examples - -**Basic Generation**: -``` -User: "Create a markdown formatter skill" -Lite-Skill-Generator: - → Analyzes ccw.md style - → Asks: Name? "markdown-formatter" - → Asks: Purpose? "Transformation" - → Asks: Steps? "3 steps" - → Generates: .claude/skills/markdown-formatter/ -``` - -**With Custom References**: -``` -User: "Create a skill like software-manual but simpler" -Lite-Skill-Generator: - → Analyzes software-manual skill - → Learns: Multi-phase, agent-based, template-heavy - → Simplifies: 3 phases, direct execution, minimal templates - → Generates: Simplified version -``` - -## Comparison: lite-skill-generator vs skill-generator - -| Aspect | lite-skill-generator | skill-generator | -|--------|---------------------|-----------------| -| **Phases** | 3 (Style → Req → Gen) | 6 (Spec → Req → Dir → Gen → Specs → Val) | -| **Style Learning** | Yes (analyze references) | No (fixed templates) | -| **Complexity** | Simple skills only | Full-featured skills | -| **Output** | Minimal (SKILL.md + README) | Complete (phases/, specs/, templates/) | -| **Generation Time** | Fast (~2 min) | Thorough (~10 min) | -| **Use Case** | Quick scaffolding | Production-ready skills | - -## Workflow Integration - -**Standalone**: -```bash -/skill:lite-skill-generator "Create a log analyzer skill" -``` - -**With References**: -```bash -/skill:lite-skill-generator "Create a skill based on ccw-coordinator.md style" -``` - -**Batch Generation** (for multiple simple skills): -```bash -/skill:lite-skill-generator "Create 3 skills: json-validator, yaml-parser, toml-converter" -``` - ---- - -**Next Steps After Generation**: -1. Review `.claude/skills/{name}/SKILL.md` -2. Customize phase logic for your use case -3. Add examples to README.md -4. Test skill with sample input -5. Iterate based on real usage diff --git a/.claude/skills/lite-skill-generator/templates/simple-skill.md b/.claude/skills/lite-skill-generator/templates/simple-skill.md deleted file mode 100644 index e1b87c88..00000000 --- a/.claude/skills/lite-skill-generator/templates/simple-skill.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: {{SKILL_NAME}} -description: {{SKILL_DESCRIPTION}} -allowed-tools: {{ALLOWED_TOOLS}} ---- - -# {{SKILL_TITLE}} - -{{SKILL_DESCRIPTION}} - -## Architecture - -``` -┌─────────────────────────────────────────────────┐ -│ {{SKILL_TITLE}} │ -│ │ -│ Input → {{PHASE_1}} → {{PHASE_2}} → Output │ -└─────────────────────────────────────────────────┘ -``` - -## Execution Flow - -```javascript -async function {{SKILL_FUNCTION}}(input) { - // Phase 1: {{PHASE_1}} - const prepared = await phase1(input); - - // Phase 2: {{PHASE_2}} - const result = await phase2(prepared); - - return result; -} -``` - -### Phase 1: {{PHASE_1}} - -```javascript -async function phase1(input) { - // TODO: Implement {{PHASE_1_LOWER}} logic - return output; -} -``` - -### Phase 2: {{PHASE_2}} - -```javascript -async function phase2(input) { - // TODO: Implement {{PHASE_2_LOWER}} logic - return output; -} -``` - -## Usage - -```bash -/skill:{{SKILL_NAME}} "input description" -``` - -## Examples - -**Basic Usage**: -``` -User: "{{EXAMPLE_INPUT}}" -{{SKILL_NAME}}: - → Phase 1: {{PHASE_1_ACTION}} - → Phase 2: {{PHASE_2_ACTION}} - → Output: {{EXAMPLE_OUTPUT}} -``` diff --git a/.claude/skills/lite-skill-generator/templates/style-guide.md b/.claude/skills/lite-skill-generator/templates/style-guide.md deleted file mode 100644 index 996e1e22..00000000 --- a/.claude/skills/lite-skill-generator/templates/style-guide.md +++ /dev/null @@ -1,64 +0,0 @@ -# Style Guide Template - -Generated by lite-skill-generator style analysis phase. - -## Detected Patterns - -### Structural Patterns - -| Pattern | Detected | Recommendation | -|---------|----------|----------------| -| YAML Frontmatter | {{HAS_FRONTMATTER}} | {{FRONTMATTER_REC}} | -| ASCII Diagrams | {{HAS_DIAGRAMS}} | {{DIAGRAMS_REC}} | -| Code Blocks | {{HAS_CODE_BLOCKS}} | {{CODE_BLOCKS_REC}} | -| Phase Structure | {{PHASE_STRUCTURE}} | {{PHASE_REC}} | - -### Language Patterns - -| Pattern | Value | Notes | -|---------|-------|-------| -| Instruction Style | {{INSTRUCTION_STYLE}} | imperative/declarative/procedural | -| Pseudocode Usage | {{PSEUDOCODE_USAGE}} | functional/imperative/none | -| Verbosity Level | {{VERBOSITY}} | concise/detailed/verbose | -| Common Terms | {{TERMINOLOGY}} | domain-specific vocabulary | - -### Organization Patterns - -| Pattern | Value | -|---------|-------| -| Phase Count | {{PHASE_COUNT}} | -| Example Density | {{EXAMPLE_DENSITY}} | -| Template Usage | {{TEMPLATE_USAGE}} | - -## Style Compliance Checklist - -- [ ] YAML frontmatter with name, description, allowed-tools -- [ ] Architecture diagram (if pattern detected) -- [ ] Execution flow section with pseudocode -- [ ] Phase sections (sequential numbered) -- [ ] Usage examples section -- [ ] README.md for external documentation - -## Reference Skills Analyzed - -{{#REFERENCES}} -- `{{REF_PATH}}`: {{REF_NOTES}} -{{/REFERENCES}} - -## Generated Configuration - -```json -{ - "style": { - "structure": "{{STRUCTURE_TYPE}}", - "language": "{{LANGUAGE_TYPE}}", - "organization": "{{ORG_TYPE}}" - }, - "recommendations": { - "usePseudocode": {{USE_PSEUDOCODE}}, - "includeDiagrams": {{INCLUDE_DIAGRAMS}}, - "verbosityLevel": "{{VERBOSITY}}", - "phaseCount": {{PHASE_COUNT}} - } -} -``` diff --git a/.claude/skills/workflow-plan/SKILL.md b/.claude/skills/workflow-plan/SKILL.md new file mode 100644 index 00000000..9176007c --- /dev/null +++ b/.claude/skills/workflow-plan/SKILL.md @@ -0,0 +1,348 @@ +--- +name: workflow-plan +description: 5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs. Triggers on "workflow:plan". +allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep, Skill +--- + +# Workflow Plan + +5-phase planning workflow that orchestrates session discovery, context gathering, conflict resolution, and task generation to produce implementation plans (IMPL_PLAN.md, task JSONs, TODO_LIST.md). + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Workflow Plan Orchestrator (SKILL.md) │ +│ → Pure coordinator: Execute phases, parse outputs, pass context │ +└───────────────┬─────────────────────────────────────────────────┘ + │ + ┌───────────┼───────────┬───────────┬───────────┐ + ↓ ↓ ↓ ↓ ↓ +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ Phase 1 │ │ Phase 2 │ │ Phase 3 │ │Phase 3.5│ │ Phase 4 │ +│ Session │ │ Context │ │Conflict │ │ Gate │ │ Task │ +│Discovery│ │ Gather │ │Resolve │ │(Optional)│ │Generate │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ + ↓ ↓ ↓ ↓ + sessionId contextPath resolved IMPL_PLAN.md + conflict_risk artifacts task JSONs + TODO_LIST.md +``` + +## Key Design Principles + +1. **Pure Orchestrator**: Execute phases in sequence, parse outputs, pass context between them +2. **Auto-Continue**: All phases run autonomously without user intervention between phases +3. **Task Attachment Model**: Sub-tasks are attached/collapsed dynamically in TodoWrite +4. **Progressive Phase Loading**: Phase docs are read on-demand, not all at once +5. **Conditional Execution**: Phase 3 only executes when conflict_risk >= medium + +## Auto Mode + +When `--yes` or `-y`: Auto-continue all phases (skip confirmations), use recommended conflict resolutions. + +## Execution Flow + +``` +Input Parsing: + └─ Convert user input to structured format (GOAL/SCOPE/CONTEXT) + +Phase 1: Session Discovery + └─ Ref: phases/01-session-discovery.md + └─ Output: sessionId (WFS-xxx) + +Phase 2: Context Gathering + └─ Ref: phases/02-context-gathering.md + ├─ Tasks attached: Analyze structure → Identify integration → Generate package + └─ Output: contextPath + conflict_risk + +Phase 3: Conflict Resolution + └─ Decision (conflict_risk check): + ├─ conflict_risk ≥ medium → Ref: phases/03-conflict-resolution.md + │ ├─ Tasks attached: Detect conflicts → Present to user → Apply strategies + │ └─ Output: Modified brainstorm artifacts + └─ conflict_risk < medium → Skip to Phase 4 + +Phase 4: Task Generation + └─ Ref: phases/04-task-generation.md + └─ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md + +Return: + └─ Summary with recommended next steps +``` + +**Phase Reference Documents** (read on-demand when phase executes): + +| Phase | Document | Purpose | +|-------|----------|---------| +| 1 | [phases/01-session-discovery.md](phases/01-session-discovery.md) | Session creation/discovery with intelligent session management | +| 2 | [phases/02-context-gathering.md](phases/02-context-gathering.md) | Project context collection via context-search-agent | +| 3 | [phases/03-conflict-resolution.md](phases/03-conflict-resolution.md) | Conflict detection and resolution with CLI analysis | +| 4 | [phases/04-task-generation.md](phases/04-task-generation.md) | Implementation plan and task JSON generation | + +## Core Rules + +1. **Start Immediately**: First action is TodoWrite initialization, second action is Phase 1 execution +2. **No Preliminary Analysis**: Do not read files, analyze structure, or gather context before Phase 1 +3. **Parse Every Output**: Extract required data from each phase output for next phase +4. **Auto-Continue via TodoList**: 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. After completing each phase, immediately proceed to next + +## Input Processing + +**Convert User Input to Structured Format**: + +1. **Simple Text** → Structure it: + ``` + User: "Build authentication system" + + Structured: + GOAL: Build authentication system + SCOPE: Core authentication features + CONTEXT: New implementation + ``` + +2. **Detailed Text** → Extract components: + ``` + User: "Add JWT authentication with email/password login and token refresh" + + Structured: + GOAL: Implement JWT-based authentication + SCOPE: Email/password login, token generation, token refresh endpoints + CONTEXT: JWT token-based security, refresh token rotation + ``` + +3. **File Reference** (e.g., `requirements.md`) → Read and structure: + - Read file content + - Extract goal, scope, requirements + - Format into structured description + +## Data Flow + +``` +User Input (task description) + ↓ +[Convert to Structured Format] + ↓ Structured Description: + ↓ GOAL: [objective] + ↓ SCOPE: [boundaries] + ↓ CONTEXT: [background] + ↓ +Phase 1: session:start --auto "structured-description" + ↓ Output: sessionId + ↓ Write: planning-notes.md (User Intent section) + ↓ +Phase 2: context-gather --session sessionId "structured-description" + ↓ Input: sessionId + structured description + ↓ Output: contextPath (context-package.json with prioritized_context) + conflict_risk + ↓ Update: planning-notes.md (Context Findings + Consolidated Constraints) + ↓ +Phase 3: conflict-resolution [AUTO-TRIGGERED if conflict_risk ≥ medium] + ↓ Input: sessionId + contextPath + conflict_risk + ↓ Output: Modified brainstorm artifacts + ↓ Update: planning-notes.md (Conflict Decisions + Consolidated Constraints) + ↓ Skip if conflict_risk is none/low → proceed directly to Phase 4 + ↓ +Phase 4: task-generate-agent --session sessionId + ↓ Input: sessionId + planning-notes.md + context-package.json + brainstorm artifacts + ↓ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md + ↓ +Return summary to user +``` + +**Session Memory Flow**: Each phase receives session ID, which provides access to: +- Previous task summaries +- Existing context and analysis +- Brainstorming artifacts (potentially modified by Phase 3) +- Session-specific configuration + +## TodoWrite Pattern + +**Core Concept**: Dynamic task attachment and collapse for real-time visibility into workflow execution. + +### Key Principles + +1. **Task Attachment** (when phase executed): + - Sub-command's internal tasks are **attached** to orchestrator's TodoWrite + - **Phase 2, 3**: Multiple sub-tasks attached (e.g., Phase 2.1, 2.2, 2.3) + - **Phase 4**: Single agent task attached + - First attached task marked as `in_progress`, others as `pending` + - Orchestrator **executes** these attached tasks sequentially + +2. **Task Collapse** (after sub-tasks complete): + - **Applies to Phase 2, 3**: Remove detailed sub-tasks from TodoWrite + - **Collapse** to high-level phase summary + - **Phase 4**: No collapse needed (single task, just mark completed) + - Maintains clean orchestrator-level view + +3. **Continuous Execution**: + - After completion, automatically proceed to next pending phase + - No user intervention required between phases + - TodoWrite dynamically reflects current execution state + +**Lifecycle**: Initial pending tasks → Phase executed (tasks ATTACHED) → Sub-tasks executed sequentially → Phase completed (tasks COLLAPSED) → Next phase begins → Repeat until all phases complete. + +## Phase-Specific TodoWrite Updates + +### Phase 2 (Tasks Attached): +```json +[ + {"content": "Phase 1: Session Discovery", "status": "completed"}, + {"content": "Phase 2: Context Gathering", "status": "in_progress"}, + {"content": " → Analyze codebase structure", "status": "in_progress"}, + {"content": " → Identify integration points", "status": "pending"}, + {"content": " → Generate context package", "status": "pending"}, + {"content": "Phase 4: Task Generation", "status": "pending"} +] +``` + +### Phase 2 (Collapsed): +```json +[ + {"content": "Phase 1: Session Discovery", "status": "completed"}, + {"content": "Phase 2: Context Gathering", "status": "completed"}, + {"content": "Phase 4: Task Generation", "status": "pending"} +] +``` + +### Phase 3 (Conditional, Tasks Attached): +```json +[ + {"content": "Phase 1: Session Discovery", "status": "completed"}, + {"content": "Phase 2: Context Gathering", "status": "completed"}, + {"content": "Phase 3: Conflict Resolution", "status": "in_progress"}, + {"content": " → Detect conflicts with CLI analysis", "status": "in_progress"}, + {"content": " → Present conflicts to user", "status": "pending"}, + {"content": " → Apply resolution strategies", "status": "pending"}, + {"content": "Phase 4: Task Generation", "status": "pending"} +] +``` + +## Planning Notes Template + +After Phase 1, create `planning-notes.md` with this structure: + +```markdown +# Planning Notes + +**Session**: ${sessionId} +**Created**: ${timestamp} + +## User Intent (Phase 1) + +- **GOAL**: ${userGoal} +- **KEY_CONSTRAINTS**: ${userConstraints} + +--- + +## Context Findings (Phase 2) +(To be filled by context-gather) + +## Conflict Decisions (Phase 3) +(To be filled if conflicts detected) + +## Consolidated Constraints (Phase 4 Input) +1. ${userConstraints} + +--- + +## Task Generation (Phase 4) +(To be filled by action-planning-agent) + +## N+1 Context +### Decisions +| Decision | Rationale | Revisit? | +|----------|-----------|----------| + +### Deferred +- [ ] (For N+1) +``` + +## Post-Phase Updates + +### After Phase 2 + +Read context-package to extract key findings, update planning-notes.md: +- `Context Findings (Phase 2)`: CRITICAL_FILES, ARCHITECTURE, CONFLICT_RISK, CONSTRAINTS +- `Consolidated Constraints`: Append Phase 2 constraints + +### After Phase 3 + +If executed, read conflict-resolution.json, update planning-notes.md: +- `Conflict Decisions (Phase 3)`: RESOLVED, MODIFIED_ARTIFACTS, CONSTRAINTS +- `Consolidated Constraints`: Append Phase 3 planning constraints + +### Memory State Check + +After Phase 3, evaluate context window usage. If memory usage is high (>120K tokens): +```javascript +Skill(skill="compact") +``` + +## Phase 4 User Decision + +After Phase 4 completes, present user with action choices: + +```javascript +AskUserQuestion({ + questions: [{ + question: "Planning complete. What would you like to do next?", + header: "Next Action", + multiSelect: false, + options: [ + { + label: "Verify Plan Quality (Recommended)", + description: "Run quality verification to catch issues before execution." + }, + { + label: "Start Execution", + description: "Begin implementing tasks immediately." + }, + { + label: "Review Status Only", + description: "View task breakdown and session status without taking further action." + } + ] + }] +}); + +// Execute based on user choice +// "Verify Plan Quality" → Skill(skill="workflow:plan-verify", args="--session " + sessionId) +// "Start Execution" → Skill(skill="workflow:execute", args="--session " + sessionId) +// "Review Status Only" → Skill(skill="workflow:status", args="--session " + sessionId) +``` + +## Error Handling + +- **Parsing Failure**: If output parsing fails, retry command once, then report error +- **Validation Failure**: If validation fails, report which file/data is missing +- **Command Failure**: Keep phase `in_progress`, report error to user, do not proceed to next phase + +## Coordinator Checklist + +- **Pre-Phase**: Convert user input to structured format (GOAL/SCOPE/CONTEXT) +- Initialize TodoWrite before any command (Phase 3 added dynamically after Phase 2) +- Execute Phase 1 immediately with structured description +- Parse session ID from Phase 1 output, store in memory +- Pass session ID and structured description to Phase 2 command +- Parse context path from Phase 2 output, store in memory +- **Extract conflict_risk from context-package.json**: Determine Phase 3 execution +- **If conflict_risk >= medium**: Launch Phase 3 with sessionId and contextPath +- **If conflict_risk is none/low**: Skip Phase 3, proceed directly to Phase 4 +- **Build Phase 4 command**: `/workflow:tools:task-generate-agent --session [sessionId]` +- Verify all Phase 4 outputs +- Update TodoWrite after each phase +- After each phase, automatically continue to next phase based on TodoList status + +## Related Commands + +**Prerequisite Commands**: +- `/workflow:brainstorm:artifacts` - Optional: Generate role-based analyses before planning +- `/workflow:brainstorm:synthesis` - Optional: Refine brainstorm analyses with clarifications + +**Follow-up Commands**: +- `/workflow:plan-verify` - Recommended: Verify plan quality before execution +- `/workflow:status` - Review task breakdown and current progress +- `/workflow:execute` - Begin implementation of generated tasks diff --git a/.claude/skills/workflow-plan/phases/01-session-discovery.md b/.claude/skills/workflow-plan/phases/01-session-discovery.md new file mode 100644 index 00000000..3eda914a --- /dev/null +++ b/.claude/skills/workflow-plan/phases/01-session-discovery.md @@ -0,0 +1,281 @@ +# Phase 1: Session Discovery + +Discover existing sessions or start new workflow session with intelligent session management and conflict detection. + +## Objective + +- Ensure project-level state exists (first-time initialization) +- Create or discover workflow session for the planning workflow +- Generate unique session ID (WFS-xxx format) +- Initialize session directory structure + +## Step 0: Initialize Project State (First-time Only) + +**Executed before all modes** - Ensures project-level state files exist by calling `/workflow:init`. + +### Check and Initialize +```bash +# Check if project state exists (both files required) +bash(test -f .workflow/project-tech.json && echo "TECH_EXISTS" || echo "TECH_NOT_FOUND") +bash(test -f .workflow/project-guidelines.json && echo "GUIDELINES_EXISTS" || echo "GUIDELINES_NOT_FOUND") +``` + +**If either NOT_FOUND**, delegate to `/workflow:init`: +```javascript +// Call workflow:init for intelligent project analysis +Skill(skill="workflow:init"); + +// Wait for init completion +// project-tech.json and project-guidelines.json will be created +``` + +**Output**: +- If BOTH_EXIST: `PROJECT_STATE: initialized` +- If NOT_FOUND: Calls `/workflow:init` → creates: + - `.workflow/project-tech.json` with full technical analysis + - `.workflow/project-guidelines.json` with empty scaffold + +**Note**: `/workflow:init` uses cli-explore-agent to build comprehensive project understanding (technology stack, architecture, key components). This step runs once per project. Subsequent executions skip initialization. + +## Execution + +### Step 1.1: Execute Session Start + +```javascript +Skill(skill="workflow:session:start", args="--auto \"[structured-task-description]\"") +``` + +**Task Description Structure**: +``` +GOAL: [Clear, concise objective] +SCOPE: [What's included/excluded] +CONTEXT: [Relevant background or constraints] +``` + +**Example**: +``` +GOAL: Build JWT-based authentication system +SCOPE: User registration, login, token validation +CONTEXT: Existing user database schema, REST API endpoints +``` + +### Step 1.2: Parse Output + +- Extract: `SESSION_ID: WFS-[id]` (store as `sessionId`) + +### Step 1.3: Validate + +- Session ID successfully extracted +- Session directory `.workflow/active/[sessionId]/` exists + +**Note**: Session directory contains `workflow-session.json` (metadata). Do NOT look for `manifest.json` here - it only exists in `.workflow/archives/` for archived sessions. + +### Step 1.4: Initialize Planning Notes + +Create `planning-notes.md` with N+1 context support: + +```javascript +const planningNotesPath = `.workflow/active/${sessionId}/planning-notes.md` +const userGoal = structuredDescription.goal +const userConstraints = structuredDescription.context || "None specified" + +Write(planningNotesPath, `# Planning Notes + +**Session**: ${sessionId} +**Created**: ${new Date().toISOString()} + +## User Intent (Phase 1) + +- **GOAL**: ${userGoal} +- **KEY_CONSTRAINTS**: ${userConstraints} + +--- + +## Context Findings (Phase 2) +(To be filled by context-gather) + +## Conflict Decisions (Phase 3) +(To be filled if conflicts detected) + +## Consolidated Constraints (Phase 4 Input) +1. ${userConstraints} + +--- + +## Task Generation (Phase 4) +(To be filled by action-planning-agent) + +## N+1 Context +### Decisions +| Decision | Rationale | Revisit? | +|----------|-----------|----------| + +### Deferred +- [ ] (For N+1) +`) +``` + +## Session Types + +The `--type` parameter classifies sessions for CCW dashboard organization: + +| Type | Description | Default For | +|------|-------------|-------------| +| `workflow` | Standard implementation (default) | `/workflow:plan` | +| `review` | Code review sessions | `/workflow:review-module-cycle` | +| `tdd` | TDD-based development | `/workflow:tdd-plan` | +| `test` | Test generation/fix sessions | `/workflow:test-fix-gen` | +| `docs` | Documentation sessions | `/memory:docs` | + +**Validation**: If `--type` is provided with invalid value, return error: +``` +ERROR: Invalid session type. Valid types: workflow, review, tdd, test, docs +``` + +## Mode 1: Discovery Mode (Default) + +### Usage +```bash +/workflow:session:start +``` + +### Step 1: List Active Sessions +```bash +bash(ls -1 .workflow/active/ 2>/dev/null | head -5) +``` + +### Step 2: Display Session Metadata +```bash +bash(cat .workflow/active/WFS-promptmaster-platform/workflow-session.json) +``` + +### Step 4: User Decision +Present session information and wait for user to select or create session. + +**Output**: `SESSION_ID: WFS-[user-selected-id]` + +## Mode 2: Auto Mode (Intelligent) + +### Usage +```bash +/workflow:session:start --auto "task description" +``` + +### Step 1: Check Active Sessions Count +```bash +bash(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | wc -l) +``` + +### Step 2a: No Active Sessions → Create New +```bash +# Generate session slug +bash(echo "implement OAuth2 auth" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-50) + +# Create directory structure +bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.process) +bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.task) +bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.summaries) + +# Create metadata (include type field, default to "workflow" if not specified) +bash(echo '{"session_id":"WFS-implement-oauth2-auth","project":"implement OAuth2 auth","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-implement-oauth2-auth/workflow-session.json) +``` + +**Output**: `SESSION_ID: WFS-implement-oauth2-auth` + +### Step 2b: Single Active Session → Check Relevance +```bash +# Extract session ID +bash(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | head -1 | xargs basename) + +# Read project name from metadata +bash(cat .workflow/active/WFS-promptmaster-platform/workflow-session.json | grep -o '"project":"[^"]*"' | cut -d'"' -f4) + +# Check keyword match (manual comparison) +# If task contains project keywords → Reuse session +# If task unrelated → Create new session (use Step 2a) +``` + +**Output (reuse)**: `SESSION_ID: WFS-promptmaster-platform` +**Output (new)**: `SESSION_ID: WFS-[new-slug]` + +### Step 2c: Multiple Active Sessions → Use First +```bash +# Get first active session +bash(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | head -1 | xargs basename) + +# Output warning and session ID +# WARNING: Multiple active sessions detected +# SESSION_ID: WFS-first-session +``` + +## Mode 3: Force New Mode + +### Usage +```bash +/workflow:session:start --new "task description" +``` + +### Step 1: Generate Unique Session Slug +```bash +# Convert to slug +bash(echo "fix login bug" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-50) + +# Check if exists, add counter if needed +bash(ls .workflow/active/WFS-fix-login-bug 2>/dev/null && echo "WFS-fix-login-bug-2" || echo "WFS-fix-login-bug") +``` + +### Step 2: Create Session Structure +```bash +bash(mkdir -p .workflow/active/WFS-fix-login-bug/.process) +bash(mkdir -p .workflow/active/WFS-fix-login-bug/.task) +bash(mkdir -p .workflow/active/WFS-fix-login-bug/.summaries) +``` + +### Step 3: Create Metadata +```bash +# Include type field from --type parameter (default: "workflow") +bash(echo '{"session_id":"WFS-fix-login-bug","project":"fix login bug","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-fix-login-bug/workflow-session.json) +``` + +**Output**: `SESSION_ID: WFS-fix-login-bug` + +## Execution Guideline + +- **Non-interrupting**: When called from other commands, this command completes and returns control to the caller without interrupting subsequent tasks. + +## Session ID Format + +- Pattern: `WFS-[lowercase-slug]` +- Characters: `a-z`, `0-9`, `-` only +- Max length: 50 characters +- Uniqueness: Add numeric suffix if collision (`WFS-auth-2`, `WFS-auth-3`) + +## Output Format Specification + +### Success +``` +SESSION_ID: WFS-session-slug +``` + +### Error +``` +ERROR: --auto mode requires task description +ERROR: Failed to create session directory +``` + +### Analysis (Auto Mode) +``` +ANALYSIS: Task relevance = high +DECISION: Reusing existing session +SESSION_ID: WFS-promptmaster-platform +``` + +## Output + +- **Variable**: `sessionId` (e.g., `WFS-implement-oauth2-auth`) +- **File**: `.workflow/active/{sessionId}/planning-notes.md` +- **TodoWrite**: Mark Phase 1 completed, Phase 2 in_progress + +## Next Phase + +Return to orchestrator showing Phase 1 results, then auto-continue to [Phase 2: Context Gathering](02-context-gathering.md). diff --git a/.claude/skills/workflow-plan/phases/02-context-gathering.md b/.claude/skills/workflow-plan/phases/02-context-gathering.md new file mode 100644 index 00000000..ec73c3ba --- /dev/null +++ b/.claude/skills/workflow-plan/phases/02-context-gathering.md @@ -0,0 +1,427 @@ +# Phase 2: Context Gathering + +Intelligently collect project context using context-search-agent based on task description, packages into standardized JSON. + +## Objective + +- Check for existing valid context-package before executing +- Assess task complexity and launch parallel exploration agents +- Invoke context-search-agent to analyze codebase +- Generate standardized `context-package.json` with prioritized context +- Detect conflict risk level for Phase 3 decision + +## Core Philosophy + +- **Agent Delegation**: Delegate all discovery to `context-search-agent` for autonomous execution +- **Detection-First**: Check for existing context-package before executing +- **Plan Mode**: Full comprehensive analysis (vs lightweight brainstorm mode) +- **Standardized Output**: Generate `.workflow/active/{session}/.process/context-package.json` + +## Execution Process + +``` +Input Parsing: + ├─ Parse flags: --session + └─ Parse: task_description (required) + +Step 1: Context-Package Detection + └─ Decision (existing package): + ├─ Valid package exists → Return existing (skip execution) + └─ No valid package → Continue to Step 2 + +Step 2: Complexity Assessment & Parallel Explore + ├─ Analyze task_description → classify Low/Medium/High + ├─ Select exploration angles (1-4 based on complexity) + ├─ Launch N cli-explore-agents in parallel + │ └─ Each outputs: exploration-{angle}.json + └─ Generate explorations-manifest.json + +Step 3: Invoke Context-Search Agent (with exploration input) + ├─ Phase 1: Initialization & Pre-Analysis + ├─ Phase 2: Multi-Source Discovery + │ ├─ Track 0: Exploration Synthesis (prioritize & deduplicate) + │ ├─ Track 1-4: Existing tracks + └─ Phase 3: Synthesis & Packaging + └─ Generate context-package.json with exploration_results + +Step 4: Output Verification + └─ Verify context-package.json contains exploration_results +``` + +## Execution Flow + +### Step 1: Context-Package Detection + +**Execute First** - Check if valid package already exists: + +```javascript +const contextPackagePath = `.workflow/${session_id}/.process/context-package.json`; + +if (file_exists(contextPackagePath)) { + const existing = Read(contextPackagePath); + + // Validate package belongs to current session + if (existing?.metadata?.session_id === session_id) { + console.log("Valid context-package found for session:", session_id); + console.log("Stats:", existing.statistics); + console.log("Conflict Risk:", existing.conflict_detection.risk_level); + return existing; // Skip execution, return existing + } else { + console.warn("Invalid session_id in existing package, re-generating..."); + } +} +``` + +### Step 2: Complexity Assessment & Parallel Explore + +**Only execute if Step 1 finds no valid package** + +```javascript +// 2.1 Complexity Assessment +function analyzeTaskComplexity(taskDescription) { + const text = taskDescription.toLowerCase(); + if (/architect|refactor|restructure|modular|cross-module/.test(text)) return 'High'; + if (/multiple|several|integrate|migrate|extend/.test(text)) return 'Medium'; + return 'Low'; +} + +const ANGLE_PRESETS = { + architecture: ['architecture', 'dependencies', 'modularity', 'integration-points'], + security: ['security', 'auth-patterns', 'dataflow', 'validation'], + performance: ['performance', 'bottlenecks', 'caching', 'data-access'], + bugfix: ['error-handling', 'dataflow', 'state-management', 'edge-cases'], + feature: ['patterns', 'integration-points', 'testing', 'dependencies'], + refactor: ['architecture', 'patterns', 'dependencies', 'testing'] +}; + +function selectAngles(taskDescription, complexity) { + const text = taskDescription.toLowerCase(); + let preset = 'feature'; + if (/refactor|architect|restructure/.test(text)) preset = 'architecture'; + else if (/security|auth|permission/.test(text)) preset = 'security'; + else if (/performance|slow|optimi/.test(text)) preset = 'performance'; + else if (/fix|bug|error|issue/.test(text)) preset = 'bugfix'; + + const count = complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1); + return ANGLE_PRESETS[preset].slice(0, count); +} + +const complexity = analyzeTaskComplexity(task_description); +const selectedAngles = selectAngles(task_description, complexity); +const sessionFolder = `.workflow/active/${session_id}/.process`; + +// 2.2 Launch Parallel Explore Agents +const explorationTasks = selectedAngles.map((angle, index) => + Task( + subagent_type="cli-explore-agent", + run_in_background=false, + description=`Explore: ${angle}`, + prompt=` +## Task Objective +Execute **${angle}** exploration for task planning context. Analyze codebase from this specific angle to discover relevant structure, patterns, and constraints. + +## Assigned Context +- **Exploration Angle**: ${angle} +- **Task Description**: ${task_description} +- **Session ID**: ${session_id} +- **Exploration Index**: ${index + 1} of ${selectedAngles.length} +- **Output File**: ${sessionFolder}/exploration-${angle}.json + +## MANDATORY FIRST STEPS (Execute by Agent) +**You (cli-explore-agent) MUST execute these steps in order:** +1. Run: ccw tool exec get_modules_by_depth '{}' (project structure) +2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files) +3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference) + +## Exploration Strategy (${angle} focus) + +**Step 1: Structural Scan** (Bash) +- get_modules_by_depth.sh → identify modules related to ${angle} +- find/rg → locate files relevant to ${angle} aspect +- Analyze imports/dependencies from ${angle} perspective + +**Step 2: Semantic Analysis** (Gemini CLI) +- How does existing code handle ${angle} concerns? +- What patterns are used for ${angle}? +- Where would new code integrate from ${angle} viewpoint? + +**Step 3: Write Output** +- Consolidate ${angle} findings into JSON +- Identify ${angle}-specific clarification needs + +## Expected Output + +**File**: ${sessionFolder}/exploration-${angle}.json + +**Schema Reference**: Schema obtained in MANDATORY FIRST STEPS step 3, follow schema exactly + +**Required Fields** (all ${angle} focused): +- project_structure: Modules/architecture relevant to ${angle} +- relevant_files: Files affected from ${angle} perspective + **IMPORTANT**: Use object format with relevance scores for synthesis: + \`[{path: "src/file.ts", relevance: 0.85, rationale: "Core ${angle} logic"}]\` + Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low +- patterns: ${angle}-related patterns to follow +- dependencies: Dependencies relevant to ${angle} +- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations) +- constraints: ${angle}-specific limitations/conventions +- clarification_needs: ${angle}-related ambiguities (options array + recommended index) +- _metadata.exploration_angle: "${angle}" + +## Success Criteria +- [ ] Schema obtained via cat explore-json-schema.json +- [ ] get_modules_by_depth.sh executed +- [ ] At least 3 relevant files identified with ${angle} rationale +- [ ] Patterns are actionable (code examples, not generic advice) +- [ ] Integration points include file:line locations +- [ ] Constraints are project-specific to ${angle} +- [ ] JSON output follows schema exactly +- [ ] clarification_needs includes options + recommended + +## Output +Write: ${sessionFolder}/exploration-${angle}.json +Return: 2-3 sentence summary of ${angle} findings +` + ) +); + +// 2.3 Generate Manifest after all complete +const explorationFiles = bash(`find ${sessionFolder} -name "exploration-*.json" -type f`).split('\n').filter(f => f.trim()); +const explorationManifest = { + session_id, + task_description, + timestamp: new Date().toISOString(), + complexity, + exploration_count: selectedAngles.length, + angles_explored: selectedAngles, + explorations: explorationFiles.map(file => { + const data = JSON.parse(Read(file)); + return { angle: data._metadata.exploration_angle, file: file.split('/').pop(), path: file, index: data._metadata.exploration_index }; + }) +}; +Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2)); +``` + +### Step 3: Invoke Context-Search Agent + +**Only execute after Step 2 completes** + +```javascript +// Load user intent from planning-notes.md (from Phase 1) +const planningNotesPath = `.workflow/active/${session_id}/planning-notes.md`; +let userIntent = { goal: task_description, key_constraints: "None specified" }; + +if (file_exists(planningNotesPath)) { + const notesContent = Read(planningNotesPath); + const goalMatch = notesContent.match(/\*\*GOAL\*\*:\s*(.+)/); + const constraintsMatch = notesContent.match(/\*\*KEY_CONSTRAINTS\*\*:\s*(.+)/); + if (goalMatch) userIntent.goal = goalMatch[1].trim(); + if (constraintsMatch) userIntent.key_constraints = constraintsMatch[1].trim(); +} + +Task( + subagent_type="context-search-agent", + run_in_background=false, + description="Gather comprehensive context for plan", + prompt=` +## Execution Mode +**PLAN MODE** (Comprehensive) - Full Phase 1-3 execution with priority sorting + +## Session Information +- **Session ID**: ${session_id} +- **Task Description**: ${task_description} +- **Output Path**: .workflow/${session_id}/.process/context-package.json + +## User Intent (from Phase 1 - Planning Notes) +**GOAL**: ${userIntent.goal} +**KEY_CONSTRAINTS**: ${userIntent.key_constraints} + +This is the PRIMARY context source - all subsequent analysis must align with user intent. + +## Exploration Input (from Step 2) +- **Manifest**: ${sessionFolder}/explorations-manifest.json +- **Exploration Count**: ${explorationManifest.exploration_count} +- **Angles**: ${explorationManifest.angles_explored.join(', ')} +- **Complexity**: ${complexity} + +## Mission +Execute complete context-search-agent workflow for implementation planning: + +### Phase 1: Initialization & Pre-Analysis +1. **Project State Loading**: + - Read and parse \`.workflow/project-tech.json\`. Use its \`overview\` section as the foundational \`project_context\`. This is your primary source for architecture, tech stack, and key components. + - Read and parse \`.workflow/project-guidelines.json\`. Load \`conventions\`, \`constraints\`, and \`learnings\` into a \`project_guidelines\` section. + - If files don't exist, proceed with fresh analysis. +2. **Detection**: Check for existing context-package (early exit if valid) +3. **Foundation**: Initialize CodexLens, get project structure, load docs +4. **Analysis**: Extract keywords, determine scope, classify complexity based on task description and project state + +### Phase 2: Multi-Source Context Discovery +Execute all discovery tracks (WITH USER INTENT INTEGRATION): +- **Track -1**: User Intent & Priority Foundation (EXECUTE FIRST) + - Load user intent (GOAL, KEY_CONSTRAINTS) from session input + - Map user requirements to codebase entities (files, modules, patterns) + - Establish baseline priority scores based on user goal alignment + - Output: user_intent_mapping.json with preliminary priority scores + +- **Track 0**: Exploration Synthesis (load ${sessionFolder}/explorations-manifest.json, prioritize critical_files, deduplicate patterns/integration_points) +- **Track 1**: Historical archive analysis (query manifest.json for lessons learned) +- **Track 2**: Reference documentation (CLAUDE.md, architecture docs) +- **Track 3**: Web examples (use Exa MCP for unfamiliar tech/APIs) +- **Track 4**: Codebase analysis (5-layer discovery: files, content, patterns, deps, config/tests) + +### Phase 3: Synthesis, Assessment & Packaging +1. Apply relevance scoring and build dependency graph +2. **Synthesize 5-source data** (including Track -1): Merge findings from all sources + - Priority order: User Intent > Archive > Docs > Exploration > Code > Web + - **Prioritize the context from \`project-tech.json\`** for architecture and tech stack unless code analysis reveals it's outdated +3. **Context Priority Sorting**: + a. Combine scores from Track -1 (user intent alignment) + relevance scores + exploration critical_files + b. Classify files into priority tiers: + - **Critical** (score >= 0.85): Directly mentioned in user goal OR exploration critical_files + - **High** (0.70-0.84): Key dependencies, patterns required for goal + - **Medium** (0.50-0.69): Supporting files, indirect dependencies + - **Low** (< 0.50): Contextual awareness only + c. Generate dependency_order: Based on dependency graph + user goal sequence + d. Document sorting_rationale: Explain prioritization logic + +4. **Populate \`project_context\`**: Directly use the \`overview\` from \`project-tech.json\` to fill the \`project_context\` section. Include description, technology_stack, architecture, and key_components. +5. **Populate \`project_guidelines\`**: Load conventions, constraints, and learnings from \`project-guidelines.json\` into a dedicated section. +6. Integrate brainstorm artifacts (if .brainstorming/ exists, read content) +7. Perform conflict detection with risk assessment +8. **Inject historical conflicts** from archive analysis into conflict_detection +9. **Generate prioritized_context section**: + \`\`\`json + { + "prioritized_context": { + "user_intent": { + "goal": "...", + "scope": "...", + "key_constraints": ["..."] + }, + "priority_tiers": { + "critical": [{ "path": "...", "relevance": 0.95, "rationale": "..." }], + "high": [...], + "medium": [...], + "low": [...] + }, + "dependency_order": ["module1", "module2", "module3"], + "sorting_rationale": "Based on user goal alignment (Track -1), exploration critical files, and dependency graph analysis" + } + } + \`\`\` +10. Generate and validate context-package.json with prioritized_context field + +## Output Requirements +Complete context-package.json with: +- **metadata**: task_description, keywords, complexity, tech_stack, session_id +- **project_context**: description, technology_stack, architecture, key_components (sourced from \`project-tech.json\`) +- **project_guidelines**: {conventions, constraints, quality_rules, learnings} (sourced from \`project-guidelines.json\`) +- **assets**: {documentation[], source_code[], config[], tests[]} with relevance scores +- **dependencies**: {internal[], external[]} with dependency graph +- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content +- **conflict_detection**: {risk_level, risk_factors, affected_modules[], mitigation_strategy, historical_conflicts[]} +- **exploration_results**: {manifest_path, exploration_count, angles, explorations[], aggregated_insights} (from Track 0) +- **prioritized_context**: {user_intent, priority_tiers{critical, high, medium, low}, dependency_order[], sorting_rationale} + +## Quality Validation +Before completion verify: +- [ ] Valid JSON format with all required fields +- [ ] File relevance accuracy >80% +- [ ] Dependency graph complete (max 2 transitive levels) +- [ ] Conflict risk level calculated correctly +- [ ] No sensitive data exposed +- [ ] Total files <=50 (prioritize high-relevance) + +## Planning Notes Record (REQUIRED) +After completing context-package.json, append a brief execution record to planning-notes.md: + +**File**: .workflow/active/${session_id}/planning-notes.md +**Location**: Under "## Context Findings (Phase 2)" section +**Format**: +\`\`\` +### [Context-Search Agent] YYYY-MM-DD +- **Note**: [brief summary of key findings] +\`\`\` + +Execute autonomously following agent documentation. +Report completion with statistics. +` +) +``` + +### Step 4: Output Verification + +After agent completes, verify output: + +```javascript +// Verify file was created +const outputPath = `.workflow/${session_id}/.process/context-package.json`; +if (!file_exists(outputPath)) { + throw new Error("Agent failed to generate context-package.json"); +} + +// Verify exploration_results included +const pkg = JSON.parse(Read(outputPath)); +if (pkg.exploration_results?.exploration_count > 0) { + console.log(`Exploration results aggregated: ${pkg.exploration_results.exploration_count} angles`); +} +``` + +## Parameter Reference + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `--session` | string | Yes | Workflow session ID (e.g., WFS-user-auth) | +| `task_description` | string | Yes | Detailed task description for context extraction | + +## Post-Phase Update + +After context-gather completes, update planning-notes.md: + +```javascript +const contextPackage = JSON.parse(Read(contextPath)) +const conflictRisk = contextPackage.conflict_detection?.risk_level || 'low' +const criticalFiles = (contextPackage.exploration_results?.aggregated_insights?.critical_files || []) + .slice(0, 5).map(f => f.path) +const archPatterns = contextPackage.project_context?.architecture_patterns || [] +const constraints = contextPackage.exploration_results?.aggregated_insights?.constraints || [] + +// Update Phase 2 section +Edit(planningNotesPath, { + old: '## Context Findings (Phase 2)\n(To be filled by context-gather)', + new: `## Context Findings (Phase 2) + +- **CRITICAL_FILES**: ${criticalFiles.join(', ') || 'None identified'} +- **ARCHITECTURE**: ${archPatterns.join(', ') || 'Not detected'} +- **CONFLICT_RISK**: ${conflictRisk} +- **CONSTRAINTS**: ${constraints.length > 0 ? constraints.join('; ') : 'None'}` +}) + +// Append Phase 2 constraints to consolidated list +Edit(planningNotesPath, { + old: '## Consolidated Constraints (Phase 4 Input)', + new: `## Consolidated Constraints (Phase 4 Input) +${constraints.map((c, i) => `${i + 2}. [Context] ${c}`).join('\n')}` +}) +``` + +## Notes + +- **Detection-first**: Always check for existing package before invoking agent +- **User intent integration**: Load user intent from planning-notes.md (Phase 1 output) +- **Output**: Generates `context-package.json` with `prioritized_context` field +- **Plan-specific**: Use this for implementation planning; brainstorm mode uses direct agent call + +## Output + +- **Variable**: `contextPath` (e.g., `.workflow/active/WFS-xxx/.process/context-package.json`) +- **Variable**: `conflictRisk` (none/low/medium/high) +- **File**: Updated `planning-notes.md` with context findings +- **Decision**: If `conflictRisk >= medium` → Phase 3, else → Phase 4 + +## Next Phase + +Return to orchestrator showing Phase 2 results, then auto-continue: +- If `conflict_risk >= medium` → [Phase 3: Conflict Resolution](03-conflict-resolution.md) +- If `conflict_risk < medium` → [Phase 4: Task Generation](04-task-generation.md) diff --git a/.claude/skills/workflow-plan/phases/03-conflict-resolution.md b/.claude/skills/workflow-plan/phases/03-conflict-resolution.md new file mode 100644 index 00000000..a901eb5c --- /dev/null +++ b/.claude/skills/workflow-plan/phases/03-conflict-resolution.md @@ -0,0 +1,645 @@ +# Phase 3: Conflict Resolution + +Detect and resolve conflicts between plan and existing codebase using CLI-powered analysis with Gemini/Qwen. + +## Objective + +- Analyze conflicts between plan and existing code, **including module scenario uniqueness detection** +- Generate multiple resolution strategies with **iterative clarification until boundaries are clear** +- Apply selected modifications to brainstorm artifacts + +**Scope**: Detection and strategy generation only - NO code modification or task creation. + +**Trigger**: Auto-executes when `conflict_risk >= medium`. + +## Auto Mode + +When `--yes` or `-y`: Auto-select recommended strategy for each conflict, skip clarification questions. + +## Core Responsibilities + +| Responsibility | Description | +|---------------|-------------| +| **Detect Conflicts** | Analyze plan vs existing code inconsistencies | +| **Scenario Uniqueness** | Search and compare new modules with existing modules for functional overlaps | +| **Generate Strategies** | Provide 2-4 resolution options per conflict | +| **Iterative Clarification** | Ask unlimited questions until scenario boundaries are clear and unique | +| **Agent Re-analysis** | Dynamically update strategies based on user clarifications | +| **CLI Analysis** | Use Gemini/Qwen (Claude fallback) | +| **User Decision** | Present options ONE BY ONE, never auto-apply | +| **Direct Text Output** | Output questions via text directly, NEVER use bash echo/printf | +| **Structured Data** | JSON output for programmatic processing, NO file generation | + +## Conflict Categories + +### 1. Architecture Conflicts +- Incompatible design patterns +- Module structure changes +- Pattern migration requirements + +### 2. API Conflicts +- Breaking contract changes +- Signature modifications +- Public interface impacts + +### 3. Data Model Conflicts +- Schema modifications +- Type breaking changes +- Data migration needs + +### 4. Dependency Conflicts +- Version incompatibilities +- Setup conflicts +- Breaking updates + +### 5. Module Scenario Overlap +- Functional overlap between new and existing modules +- Scenario boundary ambiguity +- Duplicate responsibility detection +- Module merge/split decisions +- **Requires iterative clarification until uniqueness confirmed** + +## Execution Process + +``` +Input Parsing: + ├─ Parse flags: --session, --context + └─ Validation: Both REQUIRED, conflict_risk >= medium + +Phase 1: Validation + ├─ Step 1: Verify session directory exists + ├─ Step 2: Load context-package.json + ├─ Step 3: Check conflict_risk (skip if none/low) + └─ Step 4: Prepare agent task prompt + +Phase 2: CLI-Powered Analysis (Agent) + ├─ Execute Gemini analysis (Qwen fallback) + ├─ Detect conflicts including ModuleOverlap category + └─ Generate 2-4 strategies per conflict with modifications + +Phase 3: Iterative User Interaction + └─ FOR each conflict (one by one): + ├─ Display conflict with overlap_analysis (if ModuleOverlap) + ├─ Display strategies (2-4 + custom option) + ├─ User selects strategy + └─ IF clarification_needed: + ├─ Collect answers + ├─ Agent re-analysis + └─ Loop until uniqueness_confirmed (max 10 rounds) + +Phase 4: Apply Modifications + ├─ Step 1: Extract modifications from resolved strategies + ├─ Step 2: Apply using Edit tool + ├─ Step 3: Update context-package.json (mark resolved) + └─ Step 4: Output custom conflict summary (if any) +``` + +## Execution Flow + +### Phase 1: Validation +``` +1. Verify session directory exists +2. Load context-package.json +3. Check conflict_risk (skip if none/low) +4. Prepare agent task prompt +``` + +### Phase 2: CLI-Powered Analysis + +**Agent Delegation**: +```javascript +Task(subagent_type="cli-execution-agent", run_in_background=false, prompt=` + ## Context + - Session: {session_id} + - Risk: {conflict_risk} + - Files: {existing_files_list} + + ## Exploration Context (from context-package.exploration_results) + - Exploration Count: ${contextPackage.exploration_results?.exploration_count || 0} + - Angles Analyzed: ${JSON.stringify(contextPackage.exploration_results?.angles || [])} + - Pre-identified Conflict Indicators: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.conflict_indicators || [])} + - Critical Files: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.critical_files?.map(f => f.path) || [])} + - All Patterns: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.all_patterns || [])} + - All Integration Points: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.all_integration_points || [])} + + ## Analysis Steps + + ### 0. Load Output Schema (MANDATORY) + Execute: cat ~/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json + + ### 1. Load Context + - Read existing files from conflict_detection.existing_files + - Load plan from .workflow/active/{session_id}/.process/context-package.json + - Load exploration_results and use aggregated_insights for enhanced analysis + - Extract role analyses and requirements + + ### 2. Execute CLI Analysis (Enhanced with Exploration + Scenario Uniqueness) + + Primary (Gemini): + ccw cli -p " + PURPOSE: Detect conflicts between plan and codebase, using exploration insights + TASK: + • **Review pre-identified conflict_indicators from exploration results** + • Compare architectures (use exploration key_patterns) + • Identify breaking API changes + • Detect data model incompatibilities + • Assess dependency conflicts + • **Analyze module scenario uniqueness** + - Use exploration integration_points for precise locations + - Cross-validate with exploration critical_files + - Generate clarification questions for boundary definition + MODE: analysis + CONTEXT: @**/*.ts @**/*.js @**/*.tsx @**/*.jsx @.workflow/active/{session_id}/**/* + EXPECTED: Conflict list with severity ratings, including: + - Validation of exploration conflict_indicators + - ModuleOverlap conflicts with overlap_analysis + - Targeted clarification questions + CONSTRAINTS: Focus on breaking changes, migration needs, and functional overlaps | Prioritize exploration-identified conflicts | analysis=READ-ONLY + " --tool gemini --mode analysis --rule analysis-code-patterns --cd {project_root} + + Fallback: Qwen (same prompt) → Claude (manual analysis) + + ### 3. Generate Strategies (2-4 per conflict) + + Template per conflict: + - Severity: Critical/High/Medium + - Category: Architecture/API/Data/Dependency/ModuleOverlap + - Affected files + impact + - **For ModuleOverlap**: Include overlap_analysis with existing modules and scenarios + - Options with pros/cons, effort, risk + - **For ModuleOverlap strategies**: Add clarification_needed questions for boundary definition + - Recommended strategy + rationale + + ### 4. Return Structured Conflict Data + + ⚠️ Output to conflict-resolution.json (generated in Phase 4) + + **Schema Reference**: Execute \`cat ~/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json\` to get full schema + + Return JSON following the schema above. Key requirements: + - Minimum 2 strategies per conflict, max 4 + - All text in Chinese for user-facing fields (brief, name, pros, cons, modification_suggestions) + - modifications.old_content: 20-100 chars for unique Edit tool matching + - modifications.new_content: preserves markdown formatting + - modification_suggestions: 2-5 actionable suggestions for custom handling + + ### 5. Planning Notes Record (REQUIRED) + After analysis complete, append a brief execution record to planning-notes.md: + + **File**: .workflow/active/{session_id}/planning-notes.md + **Location**: Under "## Conflict Decisions (Phase 3)" section + **Format**: + \`\`\` + ### [Conflict-Resolution Agent] YYYY-MM-DD + - **Note**: [brief summary of conflict types, resolution strategies, key decisions] + \`\`\` +`) +``` + +### Phase 3: User Interaction Loop + +```javascript +const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y') + +FOR each conflict: + round = 0, clarified = false, userClarifications = [] + + WHILE (!clarified && round++ < 10): + // 1. Display conflict info (text output for context) + displayConflictSummary(conflict) // id, brief, severity, overlap_analysis if ModuleOverlap + + // 2. Strategy selection + if (autoYes) { + console.log(`[--yes] Auto-selecting recommended strategy`) + selectedStrategy = conflict.strategies[conflict.recommended || 0] + clarified = true // Skip clarification loop + } else { + AskUserQuestion({ + questions: [{ + question: formatStrategiesForDisplay(conflict.strategies), + header: "策略选择", + multiSelect: false, + options: [ + ...conflict.strategies.map((s, i) => ({ + label: `${s.name}${i === conflict.recommended ? ' (推荐)' : ''}`, + description: `${s.complexity}复杂度 | ${s.risk}风险${s.clarification_needed?.length ? ' | ⚠️需澄清' : ''}` + })), + { label: "自定义修改", description: `建议: ${conflict.modification_suggestions?.slice(0,2).join('; ')}` } + ] + }] + }) + + // 3. Handle selection + if (userChoice === "自定义修改") { + customConflicts.push({ id, brief, category, suggestions, overlap_analysis }) + break + } + + selectedStrategy = findStrategyByName(userChoice) + } + + // 4. Clarification (if needed) - batched max 4 per call + if (!autoYes && selectedStrategy.clarification_needed?.length > 0) { + for (batch of chunk(selectedStrategy.clarification_needed, 4)) { + AskUserQuestion({ + questions: batch.map((q, i) => ({ + question: q, header: `澄清${i+1}`, multiSelect: false, + options: [{ label: "详细说明", description: "提供答案" }] + })) + }) + userClarifications.push(...collectAnswers(batch)) + } + + // 5. Agent re-analysis + reanalysisResult = Task({ + subagent_type: "cli-execution-agent", + run_in_background: false, + prompt: `Conflict: ${conflict.id}, Strategy: ${selectedStrategy.name} +User Clarifications: ${JSON.stringify(userClarifications)} +Output: { uniqueness_confirmed, rationale, updated_strategy, remaining_questions }` + }) + + if (reanalysisResult.uniqueness_confirmed) { + selectedStrategy = { ...reanalysisResult.updated_strategy, clarifications: userClarifications } + clarified = true + } else { + selectedStrategy.clarification_needed = reanalysisResult.remaining_questions + } + } else { + clarified = true + } + + if (clarified) resolvedConflicts.push({ conflict, strategy: selectedStrategy }) + END WHILE +END FOR + +selectedStrategies = resolvedConflicts.map(r => ({ + conflict_id: r.conflict.id, strategy: r.strategy, clarifications: r.strategy.clarifications || [] +})) +``` + +**Key Points**: +- AskUserQuestion: max 4 questions/call, batch if more +- Strategy options: 2-4 strategies + "自定义修改" +- Clarification loop: max 10 rounds, agent判断 uniqueness_confirmed +- Custom conflicts: 记录 overlap_analysis 供后续手动处理 + +### Phase 4: Apply Modifications + +```javascript +// 1. Extract modifications from resolved strategies +const modifications = []; +selectedStrategies.forEach(item => { + if (item.strategy && item.strategy.modifications) { + modifications.push(...item.strategy.modifications.map(mod => ({ + ...mod, + conflict_id: item.conflict_id, + clarifications: item.clarifications + }))); + } +}); + +console.log(`\n正在应用 ${modifications.length} 个修改...`); + +// 2. Apply each modification using Edit tool (with fallback to context-package.json) +const appliedModifications = []; +const failedModifications = []; +const fallbackConstraints = []; // For files that don't exist + +modifications.forEach((mod, idx) => { + try { + console.log(`[${idx + 1}/${modifications.length}] 修改 ${mod.file}...`); + + // Check if target file exists (brainstorm files may not exist in lite workflow) + if (!file_exists(mod.file)) { + console.log(` ⚠️ 文件不存在,写入 context-package.json 作为约束`); + fallbackConstraints.push({ + source: "conflict-resolution", + conflict_id: mod.conflict_id, + target_file: mod.file, + section: mod.section, + change_type: mod.change_type, + content: mod.new_content, + rationale: mod.rationale + }); + return; // Skip to next modification + } + + if (mod.change_type === "update") { + Edit({ + file_path: mod.file, + old_string: mod.old_content, + new_string: mod.new_content + }); + } else if (mod.change_type === "add") { + // Handle addition - append or insert based on section + const fileContent = Read(mod.file); + const updated = insertContentAfterSection(fileContent, mod.section, mod.new_content); + Write(mod.file, updated); + } else if (mod.change_type === "remove") { + Edit({ + file_path: mod.file, + old_string: mod.old_content, + new_string: "" + }); + } + + appliedModifications.push(mod); + console.log(` ✓ 成功`); + } catch (error) { + console.log(` ✗ 失败: ${error.message}`); + failedModifications.push({ ...mod, error: error.message }); + } +}); + +// 2b. Generate conflict-resolution.json output file +const resolutionOutput = { + session_id: sessionId, + resolved_at: new Date().toISOString(), + summary: { + total_conflicts: conflicts.length, + resolved_with_strategy: selectedStrategies.length, + custom_handling: customConflicts.length, + fallback_constraints: fallbackConstraints.length + }, + resolved_conflicts: selectedStrategies.map(s => ({ + conflict_id: s.conflict_id, + strategy_name: s.strategy.name, + strategy_approach: s.strategy.approach, + clarifications: s.clarifications || [], + modifications_applied: s.strategy.modifications?.filter(m => + appliedModifications.some(am => am.conflict_id === s.conflict_id) + ) || [] + })), + custom_conflicts: customConflicts.map(c => ({ + id: c.id, + brief: c.brief, + category: c.category, + suggestions: c.suggestions, + overlap_analysis: c.overlap_analysis || null + })), + planning_constraints: fallbackConstraints, // Constraints for files that don't exist + failed_modifications: failedModifications +}; + +const resolutionPath = `.workflow/active/${sessionId}/.process/conflict-resolution.json`; +Write(resolutionPath, JSON.stringify(resolutionOutput, null, 2)); + +// 3. Update context-package.json with resolution details (reference to JSON file) +const contextPackage = JSON.parse(Read(contextPath)); +contextPackage.conflict_detection.conflict_risk = "resolved"; +contextPackage.conflict_detection.resolution_file = resolutionPath; // Reference to detailed JSON +contextPackage.conflict_detection.resolved_conflicts = selectedStrategies.map(s => s.conflict_id); +contextPackage.conflict_detection.custom_conflicts = customConflicts.map(c => c.id); +contextPackage.conflict_detection.resolved_at = new Date().toISOString(); +Write(contextPath, JSON.stringify(contextPackage, null, 2)); + +// 4. Output custom conflict summary with overlap analysis (if any) +if (customConflicts.length > 0) { + console.log(`\n${'='.repeat(60)}`); + console.log(`需要自定义处理的冲突 (${customConflicts.length})`); + console.log(`${'='.repeat(60)}\n`); + + customConflicts.forEach(conflict => { + console.log(`【${conflict.category}】${conflict.id}: ${conflict.brief}`); + + // Show overlap analysis for ModuleOverlap conflicts + if (conflict.category === 'ModuleOverlap' && conflict.overlap_analysis) { + console.log(`\n场景重叠信息:`); + console.log(` 新模块: ${conflict.overlap_analysis.new_module.name}`); + console.log(` 场景: ${conflict.overlap_analysis.new_module.scenarios.join(', ')}`); + console.log(`\n 与以下模块重叠:`); + conflict.overlap_analysis.existing_modules.forEach(mod => { + console.log(` - ${mod.name} (${mod.file})`); + console.log(` 重叠场景: ${mod.overlap_scenarios.join(', ')}`); + }); + } + + console.log(`\n修改建议:`); + conflict.suggestions.forEach(suggestion => { + console.log(` - ${suggestion}`); + }); + console.log(); + }); +} + +// 5. Output failure summary (if any) +if (failedModifications.length > 0) { + console.log(`\n⚠️ 部分修改失败 (${failedModifications.length}):`); + failedModifications.forEach(mod => { + console.log(` - ${mod.file}: ${mod.error}`); + }); +} + +// 6. Return summary +return { + total_conflicts: conflicts.length, + resolved_with_strategy: selectedStrategies.length, + custom_handling: customConflicts.length, + modifications_applied: appliedModifications.length, + modifications_failed: failedModifications.length, + modified_files: [...new Set(appliedModifications.map(m => m.file))], + custom_conflicts: customConflicts, + clarification_records: selectedStrategies.filter(s => s.clarifications.length > 0) +}; +``` + +**Validation**: +``` +✓ Agent returns valid JSON structure with ModuleOverlap conflicts +✓ Conflicts processed ONE BY ONE (not in batches) +✓ ModuleOverlap conflicts include overlap_analysis field +✓ Strategies with clarification_needed display questions +✓ User selections captured correctly per conflict +✓ Clarification loop continues until uniqueness confirmed +✓ Agent re-analysis returns uniqueness_confirmed and updated_strategy +✓ Maximum 10 rounds per conflict safety limit enforced +✓ Edit tool successfully applies modifications +✓ guidance-specification.md updated +✓ Role analyses (*.md) updated +✓ context-package.json marked as resolved with clarification records +✓ Custom conflicts display overlap_analysis for manual handling +✓ Agent log saved to .workflow/active/{session_id}/.chat/ +``` + +## Output Format + +### Primary Output: conflict-resolution.json + +**Path**: `.workflow/active/{session_id}/.process/conflict-resolution.json` + +**Schema**: +```json +{ + "session_id": "WFS-xxx", + "resolved_at": "ISO timestamp", + "summary": { + "total_conflicts": 3, + "resolved_with_strategy": 2, + "custom_handling": 1, + "fallback_constraints": 0 + }, + "resolved_conflicts": [ + { + "conflict_id": "CON-001", + "strategy_name": "策略名称", + "strategy_approach": "实现方法", + "clarifications": [], + "modifications_applied": [] + } + ], + "custom_conflicts": [ + { + "id": "CON-002", + "brief": "冲突摘要", + "category": "ModuleOverlap", + "suggestions": ["建议1", "建议2"], + "overlap_analysis": null + } + ], + "planning_constraints": [], + "failed_modifications": [] +} +``` + +### Key Requirements + +| Requirement | Details | +|------------|---------| +| **Conflict batching** | Max 10 conflicts per round (no total limit) | +| **Strategy count** | 2-4 strategies per conflict | +| **Modifications** | Each strategy includes file paths, old_content, new_content | +| **User-facing text** | Chinese (brief, strategy names, pros/cons) | +| **Technical fields** | English (severity, category, complexity, risk) | +| **old_content precision** | 20-100 chars for unique Edit tool matching | +| **File targets** | guidance-specification.md, role analyses (*.md) | + +## Error Handling + +### Recovery Strategy +``` +1. Pre-check: Verify conflict_risk ≥ medium +2. Monitor: Track agent via Task tool +3. Validate: Parse agent JSON output +4. Recover: + - Agent failure → check logs + report error + - Invalid JSON → retry once with Claude fallback + - CLI failure → fallback to Claude analysis + - Edit tool failure → report affected files + rollback option + - User cancels → mark as "unresolved", continue to task-generate +5. Degrade: If all fail, generate minimal conflict report and skip modifications +``` + +### Rollback Handling +``` +If Edit tool fails mid-application: +1. Log all successfully applied modifications +2. Output rollback option via text interaction +3. If rollback selected: restore files from git or backups +4. If continue: mark partial resolution in context-package.json +``` + +## Integration + +### Interface +**Input**: +- `--session` (required): WFS-{session-id} +- `--context` (required): context-package.json path +- Requires: `conflict_risk >= medium` + +**Output**: +- Generated file: + - `.workflow/active/{session_id}/.process/conflict-resolution.json` (primary output) +- Modified files (if exist): + - `.workflow/active/{session_id}/.brainstorm/guidance-specification.md` + - `.workflow/active/{session_id}/.brainstorm/{role}/analysis.md` + - `.workflow/active/{session_id}/.process/context-package.json` (conflict_risk → resolved, resolution_file reference) + +**User Interaction**: +- **Iterative conflict processing**: One conflict at a time, not in batches +- Each conflict: 2-4 strategy options + "自定义修改" option (with suggestions) +- **Clarification loop**: Unlimited questions per conflict until uniqueness confirmed (max 10 rounds) +- **ModuleOverlap conflicts**: Display overlap_analysis with existing modules +- **Agent re-analysis**: Dynamic strategy updates based on user clarifications + +### Success Criteria +``` +✓ CLI analysis returns valid JSON structure with ModuleOverlap category +✓ Agent performs scenario uniqueness detection (searches existing modules) +✓ Conflicts processed ONE BY ONE with iterative clarification +✓ Min 2 strategies per conflict with modifications +✓ ModuleOverlap conflicts include overlap_analysis with existing modules +✓ Strategies requiring clarification include clarification_needed questions +✓ Each conflict includes 2-5 modification_suggestions +✓ Text output displays conflict with overlap analysis (if ModuleOverlap) +✓ User selections captured per conflict +✓ Clarification loop continues until uniqueness confirmed (unlimited rounds, max 10) +✓ Agent re-analysis with user clarifications updates strategy +✓ Uniqueness confirmation based on clear scenario boundaries +✓ Edit tool applies modifications successfully +✓ Custom conflicts displayed with overlap_analysis for manual handling +✓ guidance-specification.md updated with resolved conflicts +✓ Role analyses (*.md) updated with resolved conflicts +✓ context-package.json marked as "resolved" with clarification records +✓ conflict-resolution.json generated with full resolution details +✓ Modification summary includes: + - Total conflicts + - Resolved with strategy (count) + - Custom handling (count) + - Clarification records + - Overlap analysis for custom ModuleOverlap conflicts +✓ Agent log saved to .workflow/active/{session_id}/.chat/ +✓ Error handling robust (validate/retry/degrade) +``` + +## Post-Phase Update + +If Phase 3 was executed, update planning-notes.md: + +```javascript +const conflictResPath = `.workflow/active/${sessionId}/.process/conflict-resolution.json` + +if (file_exists(conflictResPath)) { + const conflictRes = JSON.parse(Read(conflictResPath)) + const resolved = conflictRes.resolved_conflicts || [] + const planningConstraints = conflictRes.planning_constraints || [] + + // Update Phase 3 section + Edit(planningNotesPath, { + old: '## Conflict Decisions (Phase 3)\n(To be filled if conflicts detected)', + new: `## Conflict Decisions (Phase 3) + +- **RESOLVED**: ${resolved.map(r => `${r.conflict_id} → ${r.strategy_name}`).join('; ') || 'None'} +- **CUSTOM_HANDLING**: ${conflictRes.custom_conflicts?.map(c => c.id).join(', ') || 'None'} +- **CONSTRAINTS**: ${planningConstraints.map(c => c.content).join('; ') || 'None'}` + }) + + // Append Phase 3 constraints to consolidated list + if (planningConstraints.length > 0) { + Edit(planningNotesPath, { + old: '## Consolidated Constraints (Phase 4 Input)', + new: `## Consolidated Constraints (Phase 4 Input) +${planningConstraints.map((c, i) => `${constraintCount + i + 1}. [Conflict] ${c.content}`).join('\n')}` + }) + } +} +``` + +## Memory State Check + +After Phase 3 completion, evaluate context window usage. +If memory usage is high (>120K tokens): + +```javascript +Skill(skill="compact") +``` + +## Output + +- **File**: `.workflow/active/{sessionId}/.process/conflict-resolution.json` +- **Modified files**: brainstorm artifacts (guidance-specification.md, role analyses) +- **Updated**: `context-package.json` with resolved conflict status + +## Next Phase + +Return to orchestrator, then auto-continue to [Phase 4: Task Generation](04-task-generation.md). diff --git a/.claude/skills/workflow-plan/phases/04-task-generation.md b/.claude/skills/workflow-plan/phases/04-task-generation.md new file mode 100644 index 00000000..3b6d1634 --- /dev/null +++ b/.claude/skills/workflow-plan/phases/04-task-generation.md @@ -0,0 +1,701 @@ +# Phase 4: Task Generation + +Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation. + +## Auto Mode + +When `--yes` or `-y`: Skip user questions, use defaults (no materials, Agent executor, Codex CLI tool). + +## Core Philosophy + +- **Planning Only**: Generate planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) - does NOT implement code +- **Agent-Driven Document Generation**: Delegate plan generation to action-planning-agent +- **NO Redundant Context Sorting**: Context priority sorting is ALREADY completed in context-gather Phase 2/3 + - Use `context-package.json.prioritized_context` directly + - DO NOT re-sort files or re-compute priorities + - `priority_tiers` and `dependency_order` are pre-computed and ready-to-use +- **N+1 Parallel Planning**: Auto-detect multi-module projects, enable parallel planning (2+1 or 3+1 mode) +- **Progressive Loading**: Load context incrementally (Core → Selective → On-Demand) due to analysis.md file size +- **Memory-First**: Reuse loaded documents from conversation memory +- **Smart Selection**: Load synthesis_output OR guidance + relevant role analyses, NOT all role analyses +- **MCP-Enhanced**: Use MCP tools for advanced code analysis and research +- **Path Clarity**: All `focus_paths` prefer absolute paths (e.g., `D:\\project\\src\\module`), or clear relative paths from project root (e.g., `./src/module`) + +## Execution Process + +``` +Input Parsing: + ├─ Parse flags: --session + └─ Validation: session_id REQUIRED + +Phase 0: User Configuration (Interactive) + ├─ Question 1: Supplementary materials/guidelines? + ├─ Question 2: Execution method preference (Agent/CLI/Hybrid) + ├─ Question 3: CLI tool preference (if CLI selected) + └─ Store: userConfig for agent prompt + +Phase 1: Context Preparation & Module Detection (Command) + ├─ Assemble session paths (metadata, context package, output dirs) + ├─ Provide metadata (session_id, execution_mode, mcp_capabilities) + ├─ Auto-detect modules from context-package + directory structure + └─ Decision: + ├─ modules.length == 1 → Single Agent Mode (Phase 2A) + └─ modules.length >= 2 → Parallel Mode (Phase 2B + Phase 3) + +Phase 2A: Single Agent Planning (Original Flow) + ├─ Load context package (progressive loading strategy) + ├─ Generate Task JSON Files (.task/IMPL-*.json) + ├─ Create IMPL_PLAN.md + └─ Generate TODO_LIST.md + +Phase 2B: N Parallel Planning (Multi-Module) + ├─ Launch N action-planning-agents simultaneously (one per module) + ├─ Each agent generates module-scoped tasks (IMPL-{prefix}{seq}.json) + ├─ Task ID format: IMPL-A1, IMPL-A2... / IMPL-B1, IMPL-B2... + └─ Each module limited to ≤9 tasks + +Phase 3: Integration (+1 Coordinator, Multi-Module Only) + ├─ Collect all module task JSONs + ├─ Resolve cross-module dependencies (CROSS::{module}::{pattern} → actual ID) + ├─ Generate unified IMPL_PLAN.md (grouped by module) + └─ Generate TODO_LIST.md (hierarchical: module → tasks) +``` + +## Document Generation Lifecycle + +### Phase 0: User Configuration (Interactive) + +**Purpose**: Collect user preferences before task generation to ensure generated tasks match execution expectations. + +**Auto Mode Check**: +```javascript +const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y') + +if (autoYes) { + console.log(`[--yes] Using defaults: No materials, Agent executor, Codex CLI`) + userConfig = { + supplementaryMaterials: { type: "none", content: [] }, + executionMethod: "agent", + preferredCliTool: "codex", + enableResume: true + } + // Skip to Phase 1 +} +``` + +**User Questions** (skipped if autoYes): +```javascript +if (!autoYes) AskUserQuestion({ + questions: [ + { + question: "Do you have supplementary materials or guidelines to include?", + header: "Materials", + multiSelect: false, + options: [ + { label: "No additional materials", description: "Use existing context only" }, + { label: "Provide file paths", description: "I'll specify paths to include" }, + { label: "Provide inline content", description: "I'll paste content directly" } + ] + }, + { + question: "Select execution method for generated tasks:", + header: "Execution", + multiSelect: false, + options: [ + { label: "Agent (Recommended)", description: "Claude agent executes tasks directly" }, + { label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps" }, + { label: "CLI Only", description: "All execution via CLI tools (codex/gemini/qwen)" } + ] + }, + { + question: "If using CLI, which tool do you prefer?", + header: "CLI Tool", + multiSelect: false, + options: [ + { label: "Codex (Recommended)", description: "Best for implementation tasks" }, + { label: "Gemini", description: "Best for analysis and large context" }, + { label: "Qwen", description: "Alternative analysis tool" }, + { label: "Auto", description: "Let agent decide per-task" } + ] + } + ] +}) +``` + +**Handle Materials Response** (skipped if autoYes): +```javascript +if (!autoYes && userConfig.materials === "Provide file paths") { + // Follow-up question for file paths + const pathsResponse = AskUserQuestion({ + questions: [{ + question: "Enter file paths to include (comma-separated or one per line):", + header: "Paths", + multiSelect: false, + options: [ + { label: "Enter paths", description: "Provide paths in text input" } + ] + }] + }) + userConfig.supplementaryPaths = parseUserPaths(pathsResponse) +} +``` + +**Build userConfig**: +```javascript +const userConfig = { + supplementaryMaterials: { + type: "none|paths|inline", + content: [...], // Parsed paths or inline content + }, + executionMethod: "agent|hybrid|cli", + preferredCliTool: "codex|gemini|qwen|auto", + enableResume: true // Always enable resume for CLI executions +} +``` + +**Pass to Agent**: Include `userConfig` in agent prompt for Phase 2A/2B. + +### Phase 1: Context Preparation & Module Detection (Command Responsibility) + +**Command prepares session paths, metadata, detects module structure. Context priority sorting is NOT performed here - it's already completed in context-gather Phase 2/3.** + +**Session Path Structure**: +``` +.workflow/active/WFS-{session-id}/ +├── workflow-session.json # Session metadata +├── planning-notes.md # Consolidated planning notes +├── .process/ +│ └── context-package.json # Context package with artifact catalog +├── .task/ # Output: Task JSON files +│ ├── IMPL-A1.json # Multi-module: prefixed by module +│ ├── IMPL-A2.json +│ ├── IMPL-B1.json +│ └── ... +├── IMPL_PLAN.md # Output: Implementation plan (grouped by module) +└── TODO_LIST.md # Output: TODO list (hierarchical) +``` + +**Command Preparation**: +1. **Assemble Session Paths** for agent prompt: + - `session_metadata_path` + - `context_package_path` + - Output directory paths + +2. **Provide Metadata** (simple values): + - `session_id` + - `mcp_capabilities` (available MCP tools) + +3. **Auto Module Detection** (determines single vs parallel mode): + ```javascript + function autoDetectModules(contextPackage, projectRoot) { + // === Complexity Gate: Only parallelize for High complexity === + const complexity = contextPackage.metadata?.complexity || 'Medium'; + if (complexity !== 'High') { + // Force single agent mode for Low/Medium complexity + // This maximizes agent context reuse for related tasks + return [{ name: 'main', prefix: '', paths: ['.'] }]; + } + + // Priority 1: Explicit frontend/backend separation + if (exists('src/frontend') && exists('src/backend')) { + return [ + { name: 'frontend', prefix: 'A', paths: ['src/frontend'] }, + { name: 'backend', prefix: 'B', paths: ['src/backend'] } + ]; + } + + // Priority 2: Monorepo structure + if (exists('packages/*') || exists('apps/*')) { + return detectMonorepoModules(); // Returns 2-3 main packages + } + + // Priority 3: Context-package dependency clustering + const modules = clusterByDependencies(contextPackage.dependencies?.internal); + if (modules.length >= 2) return modules.slice(0, 3); + + // Default: Single module (original flow) + return [{ name: 'main', prefix: '', paths: ['.'] }]; + } + ``` + +**Decision Logic**: +- `complexity !== 'High'` → Force Phase 2A (Single Agent, maximize context reuse) +- `modules.length == 1` → Phase 2A (Single Agent, original flow) +- `modules.length >= 2 && complexity == 'High'` → Phase 2B + Phase 3 (N+1 Parallel) + +**Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description, not by flags. + +### Phase 2A: Single Agent Planning (Original Flow) + +**Condition**: `modules.length == 1` (no multi-module detected) + +**Purpose**: Generate IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT code implementation. + +**Agent Invocation**: +```javascript +Task( + subagent_type="action-planning-agent", + run_in_background=false, + description="Generate planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md)", + prompt=` +## TASK OBJECTIVE +Generate implementation planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) for workflow session + +IMPORTANT: This is PLANNING ONLY - you are generating planning documents, NOT implementing code. + +CRITICAL: Follow the progressive loading strategy defined in agent specification (load analysis.md files incrementally due to file size) + +## PLANNING NOTES (PHASE 1-3 CONTEXT) +Load: .workflow/active/{session-id}/planning-notes.md + +This document contains: +- User Intent: Original GOAL and KEY_CONSTRAINTS from Phase 1 +- Context Findings: Critical files, architecture, and constraints from Phase 2 +- Conflict Decisions: Resolved conflicts and planning constraints from Phase 3 +- Consolidated Constraints: All constraints from all phases + +**USAGE**: Read planning-notes.md FIRST. Use Consolidated Constraints list to guide task sequencing and dependencies. + +## SESSION PATHS +Input: + - Session Metadata: .workflow/active/{session-id}/workflow-session.json + - Planning Notes: .workflow/active/{session-id}/planning-notes.md + - Context Package: .workflow/active/{session-id}/.process/context-package.json + +Output: + - Task Dir: .workflow/active/{session-id}/.task/ + - IMPL_PLAN: .workflow/active/{session-id}/IMPL_PLAN.md + - TODO_LIST: .workflow/active/{session-id}/TODO_LIST.md + +## CONTEXT METADATA +Session ID: {session-id} +MCP Capabilities: {exa_code, exa_web, code_index} + +## USER CONFIGURATION (from Phase 0) +Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli +Preferred CLI Tool: ${userConfig.preferredCliTool} // codex|gemini|qwen|auto +Supplementary Materials: ${userConfig.supplementaryMaterials} + +## EXECUTION METHOD MAPPING +Based on userConfig.executionMethod, set task-level meta.execution_config: + +"agent" → + meta.execution_config = { method: "agent", cli_tool: null, enable_resume: false } + Agent executes implementation_approach steps directly + +"cli" → + meta.execution_config = { method: "cli", cli_tool: userConfig.preferredCliTool, enable_resume: true } + Agent executes pre_analysis, then hands off full context to CLI via buildCliHandoffPrompt() + +"hybrid" → + Per-task decision: Analyze task complexity, set method to "agent" OR "cli" per task + - Simple tasks (≤3 files, straightforward logic) → method: "agent" + - Complex tasks (>3 files, complex logic, refactoring) → method: "cli" + CLI tool: userConfig.preferredCliTool, enable_resume: true + +IMPORTANT: Do NOT add command field to implementation_approach steps. Execution routing is controlled by task-level meta.execution_config.method only. + +## PRIORITIZED CONTEXT (from context-package.prioritized_context) - ALREADY SORTED +Context sorting is ALREADY COMPLETED in context-gather Phase 2/3. DO NOT re-sort. +Direct usage: +- **user_intent**: Use goal/scope/key_constraints for task alignment +- **priority_tiers.critical**: These files are PRIMARY focus for task generation +- **priority_tiers.high**: These files are SECONDARY focus +- **dependency_order**: Use this for task sequencing - already computed +- **sorting_rationale**: Reference for understanding priority decisions + +## EXPLORATION CONTEXT (from context-package.exploration_results) - SUPPLEMENT ONLY +If prioritized_context is incomplete, fall back to exploration_results: +- Load exploration_results from context-package.json +- Use aggregated_insights.critical_files for focus_paths generation +- Apply aggregated_insights.constraints to acceptance criteria +- Reference aggregated_insights.all_patterns for implementation approach +- Use aggregated_insights.all_integration_points for precise modification locations +- Use conflict_indicators for risk-aware task sequencing + +## CONFLICT RESOLUTION CONTEXT (if exists) +- Check context-package.conflict_detection.resolution_file for conflict-resolution.json path +- If exists, load .process/conflict-resolution.json: + - Apply planning_constraints as task constraints (for brainstorm-less workflows) + - Reference resolved_conflicts for implementation approach alignment + - Handle custom_conflicts with explicit task notes + +## EXPECTED DELIVERABLES +1. Task JSON Files (.task/IMPL-*.json) + - 6-field schema (id, title, status, context_package_path, meta, context, flow_control) + - Quantified requirements with explicit counts + - Artifacts integration from context package + - **focus_paths generated directly from prioritized_context.priority_tiers (critical + high)** + - NO re-sorting or re-prioritization - use pre-computed tiers as-is + - Critical files are PRIMARY focus, High files are SECONDARY + - Flow control with pre_analysis steps (use prioritized_context.dependency_order for task sequencing) + - **CLI Execution IDs and strategies (MANDATORY)** + +2. Implementation Plan (IMPL_PLAN.md) + - Context analysis and artifact references + - Task breakdown and execution strategy + - Complete structure per agent definition + +3. TODO List (TODO_LIST.md) + - Hierarchical structure (containers, pending, completed markers) + - Links to task JSONs and summaries + - Matches task JSON hierarchy + +## CLI EXECUTION ID REQUIREMENTS (MANDATORY) +Each task JSON MUST include: +- **cli_execution_id**: Unique ID for CLI execution (format: \`{session_id}-{task_id}\`) +- **cli_execution**: Strategy object based on depends_on: + - No deps → \`{ "strategy": "new" }\` + - 1 dep (single child) → \`{ "strategy": "resume", "resume_from": "parent-cli-id" }\` + - 1 dep (multiple children) → \`{ "strategy": "fork", "resume_from": "parent-cli-id" }\` + - N deps → \`{ "strategy": "merge_fork", "merge_from": ["id1", "id2", ...] }\` + +**CLI Execution Strategy Rules**: +1. **new**: Task has no dependencies - starts fresh CLI conversation +2. **resume**: Task has 1 parent AND that parent has only this child - continues same conversation +3. **fork**: Task has 1 parent BUT parent has multiple children - creates new branch with parent context +4. **merge_fork**: Task has multiple parents - merges all parent contexts into new conversation + +**Execution Command Patterns**: +- new: \`ccw cli -p "[prompt]" --tool [tool] --mode write --id [cli_execution_id]\` +- resume: \`ccw cli -p "[prompt]" --resume [resume_from] --tool [tool] --mode write\` +- fork: \`ccw cli -p "[prompt]" --resume [resume_from] --id [cli_execution_id] --tool [tool] --mode write\` +- merge_fork: \`ccw cli -p "[prompt]" --resume [merge_from.join(',')] --id [cli_execution_id] --tool [tool] --mode write\` + +## QUALITY STANDARDS +Hard Constraints: + - Task count <= 18 (hard limit - request re-scope if exceeded) + - All requirements quantified (explicit counts and enumerated lists) + - Acceptance criteria measurable (include verification commands) + - Artifact references mapped from context package + - All documents follow agent-defined structure + +## SUCCESS CRITERIA +- All planning documents generated successfully: + - Task JSONs valid and saved to .task/ directory + - IMPL_PLAN.md created with complete structure + - TODO_LIST.md generated matching task JSONs +- Return completion status with document count and task breakdown summary + +## PLANNING NOTES RECORD (REQUIRED) +After completing, update planning-notes.md: + +**File**: .workflow/active/{session_id}/planning-notes.md + +1. **Task Generation (Phase 4)**: Task count and key tasks +2. **N+1 Context**: Key decisions (with rationale) + deferred items + +\`\`\`markdown +## Task Generation (Phase 4) +### [Action-Planning Agent] YYYY-MM-DD +- **Tasks**: [count] ([IDs]) + +## N+1 Context +### Decisions +| Decision | Rationale | Revisit? | +|----------|-----------|----------| +| [choice] | [why] | [Yes/No] | + +### Deferred +- [ ] [item] - [reason] +\`\`\` +` +) +``` + +### Phase 2B: N Parallel Planning (Multi-Module) + +**Condition**: `modules.length >= 2` (multi-module detected) + +**Purpose**: Launch N action-planning-agents simultaneously, one per module, for parallel task JSON generation. + +**Note**: Phase 2B agents generate Task JSONs ONLY. IMPL_PLAN.md and TODO_LIST.md are generated by Phase 3 Coordinator. + +**Parallel Agent Invocation**: +```javascript +// Launch N agents in parallel (one per module) +const planningTasks = modules.map(module => + Task( + subagent_type="action-planning-agent", + run_in_background=false, + description=`Generate ${module.name} module task JSONs`, + prompt=` +## TASK OBJECTIVE +Generate task JSON files for ${module.name} module within workflow session + +IMPORTANT: This is PLANNING ONLY - generate task JSONs, NOT implementing code. +IMPORTANT: Generate Task JSONs ONLY. IMPL_PLAN.md and TODO_LIST.md by Phase 3 Coordinator. + +CRITICAL: Follow the progressive loading strategy defined in agent specification (load analysis.md files incrementally due to file size) + +## PLANNING NOTES (PHASE 1-3 CONTEXT) +Load: .workflow/active/{session-id}/planning-notes.md + +This document contains consolidated constraints and user intent to guide module-scoped task generation. + +## MODULE SCOPE +- Module: ${module.name} (${module.type}) +- Focus Paths: ${module.paths.join(', ')} +- Task ID Prefix: IMPL-${module.prefix} +- Task Limit: ≤6 tasks (hard limit for this module) +- Other Modules: ${otherModules.join(', ')} (reference only, do NOT generate tasks for them) + +## SESSION PATHS +Input: + - Session Metadata: .workflow/active/{session-id}/workflow-session.json + - Planning Notes: .workflow/active/{session-id}/planning-notes.md + - Context Package: .workflow/active/{session-id}/.process/context-package.json + +Output: + - Task Dir: .workflow/active/{session-id}/.task/ + +## CONTEXT METADATA +Session ID: {session-id} +MCP Capabilities: {exa_code, exa_web, code_index} + +## USER CONFIGURATION (from Phase 0) +Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli +Preferred CLI Tool: ${userConfig.preferredCliTool} // codex|gemini|qwen|auto +Supplementary Materials: ${userConfig.supplementaryMaterials} + +## EXECUTION METHOD MAPPING +Based on userConfig.executionMethod, set task-level meta.execution_config: + +"agent" → + meta.execution_config = { method: "agent", cli_tool: null, enable_resume: false } + Agent executes implementation_approach steps directly + +"cli" → + meta.execution_config = { method: "cli", cli_tool: userConfig.preferredCliTool, enable_resume: true } + Agent executes pre_analysis, then hands off full context to CLI via buildCliHandoffPrompt() + +"hybrid" → + Per-task decision: Analyze task complexity, set method to "agent" OR "cli" per task + - Simple tasks (≤3 files, straightforward logic) → method: "agent" + - Complex tasks (>3 files, complex logic, refactoring) → method: "cli" + CLI tool: userConfig.preferredCliTool, enable_resume: true + +IMPORTANT: Do NOT add command field to implementation_approach steps. Execution routing is controlled by task-level meta.execution_config.method only. + +## PRIORITIZED CONTEXT (from context-package.prioritized_context) - ALREADY SORTED +Context sorting is ALREADY COMPLETED in context-gather Phase 2/3. DO NOT re-sort. +Filter by module scope (${module.paths.join(', ')}): +- **user_intent**: Use for task alignment within module +- **priority_tiers.critical**: Filter for files in ${module.paths.join(', ')} → PRIMARY focus +- **priority_tiers.high**: Filter for files in ${module.paths.join(', ')} → SECONDARY focus +- **dependency_order**: Use module-relevant entries for task sequencing + +## EXPLORATION CONTEXT (from context-package.exploration_results) - SUPPLEMENT ONLY +If prioritized_context is incomplete for this module, fall back to exploration_results: +- Load exploration_results from context-package.json +- Filter for ${module.name} module: Use aggregated_insights.critical_files matching ${module.paths.join(', ')} +- Apply module-relevant constraints from aggregated_insights.constraints +- Reference aggregated_insights.all_patterns applicable to ${module.name} +- Use aggregated_insights.all_integration_points for precise modification locations within module scope +- Use conflict_indicators for risk-aware task sequencing + +## CONFLICT RESOLUTION CONTEXT (if exists) +- Check context-package.conflict_detection.resolution_file for conflict-resolution.json path +- If exists, load .process/conflict-resolution.json: + - Apply planning_constraints relevant to ${module.name} as task constraints + - Reference resolved_conflicts affecting ${module.name} for implementation approach alignment + - Handle custom_conflicts with explicit task notes + +## CROSS-MODULE DEPENDENCIES +- For dependencies ON other modules: Use placeholder depends_on: ["CROSS::{module}::{pattern}"] +- Example: depends_on: ["CROSS::B::api-endpoint"] (this module depends on B's api-endpoint task) +- Phase 3 Coordinator resolves to actual task IDs +- For dependencies FROM other modules: Document in task context as "provides_for" annotation + +## EXPECTED DELIVERABLES +Task JSON Files (.task/IMPL-${module.prefix}*.json): + - 6-field schema (id, title, status, context_package_path, meta, context, flow_control) + - Task ID format: IMPL-${module.prefix}1, IMPL-${module.prefix}2, ... + - Quantified requirements with explicit counts + - Artifacts integration from context package (filtered for ${module.name}) + - **focus_paths generated directly from prioritized_context.priority_tiers filtered by ${module.paths.join(', ')}** + - NO re-sorting - use pre-computed tiers filtered for this module + - Critical files are PRIMARY focus, High files are SECONDARY + - Flow control with pre_analysis steps (use prioritized_context.dependency_order for module task sequencing) + - **CLI Execution IDs and strategies (MANDATORY)** + - Focus ONLY on ${module.name} module scope + +## CLI EXECUTION ID REQUIREMENTS (MANDATORY) +Each task JSON MUST include: +- **cli_execution_id**: Unique ID for CLI execution (format: \`{session_id}-IMPL-${module.prefix}{seq}\`) +- **cli_execution**: Strategy object based on depends_on: + - No deps → \`{ "strategy": "new" }\` + - 1 dep (single child) → \`{ "strategy": "resume", "resume_from": "parent-cli-id" }\` + - 1 dep (multiple children) → \`{ "strategy": "fork", "resume_from": "parent-cli-id" }\` + - N deps → \`{ "strategy": "merge_fork", "merge_from": ["id1", "id2", ...] }\` + - Cross-module dep → \`{ "strategy": "cross_module_fork", "resume_from": "CROSS::{module}::{pattern}" }\` + +**CLI Execution Strategy Rules**: +1. **new**: Task has no dependencies - starts fresh CLI conversation +2. **resume**: Task has 1 parent AND that parent has only this child - continues same conversation +3. **fork**: Task has 1 parent BUT parent has multiple children - creates new branch with parent context +4. **merge_fork**: Task has multiple parents - merges all parent contexts into new conversation +5. **cross_module_fork**: Task depends on task from another module - Phase 3 resolves placeholder + +**Execution Command Patterns**: +- new: \`ccw cli -p "[prompt]" --tool [tool] --mode write --id [cli_execution_id]\` +- resume: \`ccw cli -p "[prompt]" --resume [resume_from] --tool [tool] --mode write\` +- fork: \`ccw cli -p "[prompt]" --resume [resume_from] --id [cli_execution_id] --tool [tool] --mode write\` +- merge_fork: \`ccw cli -p "[prompt]" --resume [merge_from.join(',')] --id [cli_execution_id] --tool [tool] --mode write\` +- cross_module_fork: (Phase 3 resolves placeholder, then uses fork pattern) + +## QUALITY STANDARDS +Hard Constraints: + - Task count <= 9 for this module (hard limit - coordinate with Phase 3 if exceeded) + - All requirements quantified (explicit counts and enumerated lists) + - Acceptance criteria measurable (include verification commands) + - Artifact references mapped from context package (module-scoped filter) + - Focus paths use absolute paths or clear relative paths from project root + - Cross-module dependencies use CROSS:: placeholder format + +## SUCCESS CRITERIA +- Task JSONs saved to .task/ with IMPL-${module.prefix}* naming +- All task JSONs include cli_execution_id and cli_execution strategy +- Cross-module dependencies use CROSS:: placeholder format consistently +- Focus paths scoped to ${module.paths.join(', ')} only +- Return: task count, task IDs, dependency summary (internal + cross-module) + +## PLANNING NOTES RECORD (REQUIRED) +After completing, append to planning-notes.md: + +\`\`\`markdown +### [${module.name}] YYYY-MM-DD +- **Tasks**: [count] ([IDs]) +- **CROSS deps**: [placeholders used] +\`\`\` + ` + ) +); + +// Execute all in parallel +await Promise.all(planningTasks); +``` + +**Output Structure** (direct to .task/): +``` +.task/ +├── IMPL-A1.json # Module A (e.g., frontend) +├── IMPL-A2.json +├── IMPL-B1.json # Module B (e.g., backend) +├── IMPL-B2.json +└── IMPL-C1.json # Module C (e.g., shared) +``` + +**Task ID Naming**: +- Format: `IMPL-{prefix}{seq}.json` +- Prefix: A, B, C... (assigned by detection order) +- Sequence: 1, 2, 3... (per-module increment) + +### Phase 3: Integration (+1 Coordinator Agent, Multi-Module Only) + +**Condition**: Only executed when `modules.length >= 2` + +**Purpose**: Collect all module tasks, resolve cross-module dependencies, generate unified IMPL_PLAN.md and TODO_LIST.md documents. + +**Coordinator Agent Invocation**: +```javascript +// Wait for all Phase 2B agents to complete +const moduleResults = await Promise.all(planningTasks); + +// Launch +1 Coordinator Agent +Task( + subagent_type="action-planning-agent", + run_in_background=false, + description="Integrate module tasks and generate unified documents", + prompt=` +## TASK OBJECTIVE +Integrate all module task JSONs, resolve cross-module dependencies, and generate unified IMPL_PLAN.md and TODO_LIST.md + +IMPORTANT: This is INTEGRATION ONLY - consolidate existing task JSONs, NOT creating new tasks. + +## SESSION PATHS +Input: + - Session Metadata: .workflow/active/{session-id}/workflow-session.json + - Context Package: .workflow/active/{session-id}/.process/context-package.json + - Task JSONs: .workflow/active/{session-id}/.task/IMPL-*.json (from Phase 2B) +Output: + - Updated Task JSONs: .workflow/active/{session-id}/.task/IMPL-*.json (resolved dependencies) + - IMPL_PLAN: .workflow/active/{session-id}/IMPL_PLAN.md + - TODO_LIST: .workflow/active/{session-id}/TODO_LIST.md + +## CONTEXT METADATA +Session ID: {session-id} +Modules: ${modules.map(m => m.name + '(' + m.prefix + ')').join(', ')} +Module Count: ${modules.length} + +## INTEGRATION STEPS +1. Collect all .task/IMPL-*.json, group by module prefix +2. Resolve CROSS:: dependencies → actual task IDs, update task JSONs +3. Generate IMPL_PLAN.md (multi-module format per agent specification) +4. Generate TODO_LIST.md (hierarchical format per agent specification) + +## CROSS-MODULE DEPENDENCY RESOLUTION +- Pattern: CROSS::{module}::{pattern} → IMPL-{module}* matching title/context +- Example: CROSS::B::api-endpoint → IMPL-B1 (if B1 title contains "api-endpoint") +- Log unresolved as warnings + +## EXPECTED DELIVERABLES +1. Updated Task JSONs with resolved dependency IDs +2. IMPL_PLAN.md - multi-module format with cross-dependency section +3. TODO_LIST.md - hierarchical by module with cross-dependency section + +## SUCCESS CRITERIA +- No CROSS:: placeholders remaining in task JSONs +- IMPL_PLAN.md and TODO_LIST.md generated with multi-module structure +- Return: task count, per-module breakdown, resolved dependency count + +## PLANNING NOTES RECORD (REQUIRED) +After integration, update planning-notes.md: + +\`\`\`markdown +### [Coordinator] YYYY-MM-DD +- **Total**: [count] tasks +- **Resolved**: [CROSS:: resolutions] + +## N+1 Context +### Decisions +| Decision | Rationale | Revisit? | +|----------|-----------|----------| +| CROSS::X → IMPL-Y | [why this resolution] | [Yes/No] | + +### Deferred +- [ ] [unresolved CROSS or conflict] - [reason] +\`\`\` + ` +) +``` + +**Dependency Resolution Algorithm**: +```javascript +function resolveCrossModuleDependency(placeholder, allTasks) { + const [, targetModule, pattern] = placeholder.match(/CROSS::(\w+)::(.+)/); + const candidates = allTasks.filter(t => + t.id.startsWith(`IMPL-${targetModule}`) && + (t.title.toLowerCase().includes(pattern.toLowerCase()) || + t.context?.description?.toLowerCase().includes(pattern.toLowerCase())) + ); + return candidates.length > 0 + ? candidates.sort((a, b) => a.id.localeCompare(b.id))[0].id + : placeholder; // Keep for manual resolution +} +``` + +## Output + +- **Files**: + - `.workflow/active/{sessionId}/IMPL_PLAN.md` + - `.workflow/active/{sessionId}/.task/IMPL-*.json` + - `.workflow/active/{sessionId}/TODO_LIST.md` +- **Updated**: `planning-notes.md` with task generation record and N+1 context + +## Next Step + +Return to orchestrator. Present user with action choices: +1. Verify Plan Quality (Recommended) → `/workflow:plan-verify` +2. Start Execution → `/workflow:execute` +3. Review Status Only → `/workflow:status` diff --git a/.claude/skills/workflow-skill-designer/SKILL.md b/.claude/skills/workflow-skill-designer/SKILL.md new file mode 100644 index 00000000..c1793cf2 --- /dev/null +++ b/.claude/skills/workflow-skill-designer/SKILL.md @@ -0,0 +1,319 @@ +--- +name: workflow-skill-designer +description: Meta-skill for designing orchestrator+phases structured workflow skills. Creates SKILL.md coordinator with progressive phase loading, TodoWrite patterns, and data flow. Triggers on "design workflow skill", "create workflow skill", "workflow skill designer". +allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep +--- + +# Workflow Skill Designer + +Meta-skill for creating structured workflow skills following the orchestrator + phases pattern. Generates complete skill packages with SKILL.md as coordinator and phases/ folder for execution details. + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Workflow Skill Designer │ +│ → Analyze requirements → Design orchestrator → Generate phases │ +└───────────────┬─────────────────────────────────────────────────┘ + │ + ┌───────────┼───────────┬───────────┐ + ↓ ↓ ↓ ↓ +┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ Phase 1 │ │ Phase 2 │ │ Phase 3 │ │ Phase 4 │ +│ Require │ │ Orch │ │ Phases │ │ Valid │ +│ Analysis│ │ Design │ │ Design │ │ & Integ │ +└─────────┘ └─────────┘ └─────────┘ └─────────┘ + ↓ ↓ ↓ ↓ + workflow SKILL.md phases/ Complete + config generated 0N-*.md skill pkg +``` + +## Target Output Structure + +The skill this meta-skill produces follows this structure: + +``` +.claude/skills/{skill-name}/ +├── SKILL.md # Orchestrator: coordination, data flow, TodoWrite +├── phases/ +│ ├── 01-{phase-name}.md # Phase execution detail (full content) +│ ├── 02-{phase-name}.md +│ ├── ... +│ └── 0N-{phase-name}.md +├── specs/ # [Optional] Domain specifications +└── templates/ # [Optional] Reusable templates +``` + +## Core Design Patterns + +Patterns extracted from successful workflow skill implementations (workflow-plan, project-analyze, etc.): + +### Pattern 1: Orchestrator + Progressive Loading + +**SKILL.md** = Pure coordinator. Contains: +- Architecture diagram (ASCII) +- Execution flow with `Ref: phases/0N-xxx.md` markers +- Phase Reference Documents table (read on-demand) +- Data flow between phases +- Core rules and error handling + +**Phase files** = Full execution detail. Contains: +- Complete agent prompts, bash commands, code implementations +- Validation checklists, error handling +- Input/Output specification +- Next Phase link + +**Key Rule**: SKILL.md references phase docs via `Ref:` markers. Phase docs are read **only when that phase executes**, not all at once. + +### Pattern 2: TodoWrite Attachment/Collapse + +``` +Phase starts: + → Sub-tasks ATTACHED to TodoWrite (in_progress + pending) + → Orchestrator executes sub-tasks sequentially + +Phase ends: + → Sub-tasks COLLAPSED back to high-level summary (completed) + → Next phase begins +``` + +### Pattern 3: Inter-Phase Data Flow + +``` +Phase N output → stored in memory/variable → Phase N+1 input + └─ or written to session file for persistence +``` + +Each phase receives outputs from prior phases via: +- In-memory variables (sessionId, contextPath, etc.) +- Session directory files (.workflow/active/{sessionId}/...) +- Planning notes (accumulated constraints document) + +### Pattern 4: Conditional Phase Execution + +``` +Phase N output contains condition flag + ├─ condition met → Execute Phase N+1 + └─ condition not met → Skip to Phase N+2 +``` + +### Pattern 5: Input Structuring + +User input (free text) → Structured format before Phase 1: +``` +GOAL: [objective] +SCOPE: [boundaries] +CONTEXT: [background/constraints] +``` + +## Execution Flow + +``` +Phase 1: Requirements Analysis + └─ Ref: phases/01-requirements-analysis.md + ├─ Input source: commands, descriptions, user interaction + └─ Output: workflowConfig (phases, data flow, agents, conditions) + +Phase 2: Orchestrator Design (SKILL.md) + └─ Ref: phases/02-orchestrator-design.md + ├─ Input: workflowConfig + └─ Output: .claude/skills/{name}/SKILL.md + +Phase 3: Phase Files Design + └─ Ref: phases/03-phase-design.md + ├─ Input: workflowConfig + source content + └─ Output: .claude/skills/{name}/phases/0N-*.md + +Phase 4: Validation & Integration + └─ Ref: phases/04-validation.md + └─ Output: Validated skill package +``` + +**Phase Reference Documents** (read on-demand): + +| Phase | Document | Purpose | +|-------|----------|---------| +| 1 | [phases/01-requirements-analysis.md](phases/01-requirements-analysis.md) | Analyze workflow requirements from various sources | +| 2 | [phases/02-orchestrator-design.md](phases/02-orchestrator-design.md) | Generate SKILL.md with orchestration patterns | +| 3 | [phases/03-phase-design.md](phases/03-phase-design.md) | Generate phase files preserving full execution detail | +| 4 | [phases/04-validation.md](phases/04-validation.md) | Validate structure, references, and integration | + +## Input Sources + +This meta-skill accepts workflow definitions from multiple sources: + +| Source | Description | Example | +|--------|-------------|---------| +| **Existing commands** | Convert `.claude/commands/` orchestrator + sub-commands | `plan.md` + `session/start.md` + `tools/*.md` | +| **Text description** | User describes workflow in natural language | "Create a 3-phase code review workflow" | +| **Requirements doc** | Structured requirements file | `requirements.md` with phases/agents/outputs | +| **Existing skill** | Refactor/redesign an existing skill | Restructure a flat skill into phases | + +## Frontmatter Conversion Rules + +When converting from command format to skill format: + +| Command Field | Skill Field | Transformation | +|---------------|-------------|----------------| +| `name` | `name` | Prefix with group: `plan` → `workflow-plan` | +| `description` | `description` | Append trigger phrase: `Triggers on "xxx"` | +| `argument-hint` | _(removed)_ | Arguments handled in Input Processing section | +| `examples` | _(removed)_ | Examples moved to inline documentation | +| `allowed-tools` | `allowed-tools` | Expand wildcards: `Skill(*)` → `Skill`, add commonly needed tools | +| `group` | _(removed)_ | Embedded in `name` prefix | + +## Orchestrator Content Mapping + +What goes into SKILL.md vs what goes into phase files: + +### SKILL.md (Coordinator) + +| Section | Content | Source | +|---------|---------|--------| +| Frontmatter | name, description, allowed-tools | Command frontmatter (converted) | +| Architecture Overview | ASCII diagram of phase flow | Derived from execution structure | +| Key Design Principles | Coordination rules | Extracted from command coordinator role | +| Execution Flow | Phase sequence with `Ref:` markers + Phase Reference table | Command execution process | +| Core Rules | Orchestration constraints | Command core rules | +| Input Processing | Structured format conversion | Command input processing | +| Data Flow | Inter-phase data passing | Command data flow | +| TodoWrite Pattern | Attachment/collapse lifecycle | Command TodoWrite sections | +| Post-Phase Updates | Planning notes / state updates between phases | Command inter-phase update code | +| Error Handling | Failure recovery | Command error handling | +| Coordinator Checklist | Pre/post phase actions | Command coordinator checklist | +| Related Commands | Prerequisites and follow-ups | Command related commands | + +### Phase Files (Execution Detail) + +| Content | Rule | +|---------|------| +| Full agent prompts | Preserve verbatim from source command | +| Bash command blocks | Preserve verbatim | +| Code implementations | Preserve verbatim | +| Validation checklists | Preserve verbatim | +| Error handling details | Preserve verbatim | +| Input/Output spec | Add if not present in source | +| Phase header | Add `# Phase N: {Name}` | +| Objective section | Add `## Objective` with bullet points | +| Next Phase link | Add `## Next Phase` with link to next | + +**Critical Rule**: Phase files must be **content-faithful** to their source. Do NOT summarize, abbreviate, or simplify. The phase file IS the execution instruction - every bash command, every agent prompt, every validation step must be preserved. + +## SKILL.md Template + +```markdown +--- +name: {skill-name} +description: {description}. Triggers on "{trigger1}", "{trigger2}". +allowed-tools: {tools} +--- + +# {Title} + +{One-paragraph description of what this skill does and what it produces.} + +## Architecture Overview + +{ASCII diagram showing phases and data flow} + +## Key Design Principles + +1. **{Principle}**: {Description} +... + +## Auto Mode + +When `--yes` or `-y`: {auto-mode behavior}. + +## Execution Flow + +{Phase sequence with Ref: markers} + +**Phase Reference Documents** (read on-demand when phase executes): + +| Phase | Document | Purpose | +|-------|----------|---------| +| 1 | [phases/01-xxx.md](phases/01-xxx.md) | ... | +... + +## Core Rules + +1. {Rule} +... + +## Input Processing + +{How user input is converted to structured format} + +## Data Flow + +{Inter-phase data passing diagram} + +## TodoWrite Pattern + +{Attachment/collapse lifecycle description with examples} + +## Post-Phase Updates + +{State updates between phases} + +## Error Handling + +{Failure recovery rules} + +## Coordinator Checklist + +{Pre/post phase action list} + +## Related Commands + +{Prerequisites and follow-ups} +``` + +## Phase File Template + +```markdown +# Phase N: {Phase Name} + +{One-sentence description of this phase's goal.} + +## Objective + +- {Goal 1} +- {Goal 2} + +## Execution + +### Step N.1: {Step Name} + +{Full execution detail: commands, agent prompts, code} + +### Step N.2: {Step Name} + +{Full execution detail} + +## Output + +- **Variable**: `{variableName}` (e.g., `sessionId`) +- **File**: `{output file path}` +- **TodoWrite**: Mark Phase N completed, Phase N+1 in_progress + +## Next Phase + +Return to orchestrator, then auto-continue to [Phase N+1: xxx](0N+1-xxx.md). +``` + +## Design Decision Framework + +When designing a new workflow skill, answer these questions: + +| Question | Impact | Example | +|----------|--------|---------| +| How many phases? | Directory structure | 3-7 phases typical | +| Which phases are conditional? | Orchestrator logic | "Phase 3 only if conflict_risk >= medium" | +| What data flows between phases? | Data Flow section | sessionId, contextPath, configFlags | +| Which phases use agents? | Phase file complexity | Agent prompts need verbatim preservation | +| What's the TodoWrite granularity? | TodoWrite Pattern | Some phases have sub-tasks, others are atomic | +| Is there a planning notes pattern? | Post-Phase Updates | Accumulated state document across phases | +| What's the error recovery? | Error Handling | Retry once then report, vs rollback | +| Does it need auto mode? | Auto Mode section | Skip confirmations with --yes flag | diff --git a/.claude/skills/workflow-skill-designer/phases/01-requirements-analysis.md b/.claude/skills/workflow-skill-designer/phases/01-requirements-analysis.md new file mode 100644 index 00000000..1f83aff0 --- /dev/null +++ b/.claude/skills/workflow-skill-designer/phases/01-requirements-analysis.md @@ -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). diff --git a/.claude/skills/workflow-skill-designer/phases/02-orchestrator-design.md b/.claude/skills/workflow-skill-designer/phases/02-orchestrator-design.md new file mode 100644 index 00000000..f164885f --- /dev/null +++ b/.claude/skills/workflow-skill-designer/phases/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). diff --git a/.claude/skills/workflow-skill-designer/phases/03-phase-design.md b/.claude/skills/workflow-skill-designer/phases/03-phase-design.md new file mode 100644 index 00000000..7796888f --- /dev/null +++ b/.claude/skills/workflow-skill-designer/phases/03-phase-design.md @@ -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). diff --git a/.claude/skills/workflow-skill-designer/phases/04-validation.md b/.claude/skills/workflow-skill-designer/phases/04-validation.md new file mode 100644 index 00000000..af54a5c0 --- /dev/null +++ b/.claude/skills/workflow-skill-designer/phases/04-validation.md @@ -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}/`. diff --git a/src/components/shared/CliStreamMonitor/utils/jsonDetector.ts b/src/components/shared/CliStreamMonitor/utils/jsonDetector.ts deleted file mode 100644 index ac626ac0..00000000 --- a/src/components/shared/CliStreamMonitor/utils/jsonDetector.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * JSON Detection Result - */ -export interface JsonDetectionResult { - isJson: boolean; - parsed?: Record; - error?: string; -} - -/** - * Detect if a line contains JSON data - * Supports multiple formats: - * - Direct JSON: {...} or [...] - * - Tool Call: [Tool] toolName({...}) - * - Tool Result: [Tool Result] status: {...} - * - Embedded JSON: trailing JSON object - * - Code block JSON: ```json ... ``` - * - * @param content - The content line to detect JSON in - * @returns Detection result with parsed data if valid JSON found - */ -export function detectJsonInLine(content: string): JsonDetectionResult { - const trimmed = content.trim(); - - // 1. Direct JSON object or array - if (trimmed.startsWith('{') || trimmed.startsWith('[')) { - try { - const parsed = JSON.parse(trimmed); - return { isJson: true, parsed: parsed as Record }; - } catch { - // Continue to other patterns - } - } - - // 2. Tool Call format: [Tool] toolName({...}) - const toolCallMatch = trimmed.match(/^\[Tool\]\s+(\w+)\((.*)\)$/); - if (toolCallMatch) { - const [, toolName, paramsStr] = toolCallMatch; - let parameters: unknown; - - try { - parameters = paramsStr ? JSON.parse(paramsStr) : {}; - } catch { - parameters = paramsStr || null; - } - - return { - isJson: true, - parsed: { - action: 'invoke', - toolName, - parameters, - } as Record, - }; - } - - // 3. Tool Result format: [Tool Result] status: output - const toolResultMatch = trimmed.match(/^\[Tool Result\]\s+(.+?)\s*:\s*(.+)$/); - if (toolResultMatch) { - const [, status, outputStr] = toolResultMatch; - let output: unknown; - - try { - output = outputStr.startsWith('{') ? JSON.parse(outputStr) : outputStr; - } catch { - output = outputStr; - } - - return { - isJson: true, - parsed: { - action: 'result', - status, - output, - } as Record, - }; - } - - // 4. Embedded JSON at end of line - const embeddedJsonMatch = trimmed.match(/\{.*\}$/); - if (embeddedJsonMatch) { - try { - const parsed = JSON.parse(embeddedJsonMatch[0]); - return { isJson: true, parsed: parsed as Record }; - } catch { - // Not valid JSON - } - } - - // 5. Code block JSON - const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n([\s\S]*?)\n```/); - if (codeBlockMatch) { - try { - const parsed = JSON.parse(codeBlockMatch[1]); - return { isJson: true, parsed: parsed as Record }; - } catch { - return { isJson: false, error: 'Invalid JSON in code block' }; - } - } - - return { isJson: false }; -} diff --git a/test-results/.last-run.json b/test-results/.last-run.json deleted file mode 100644 index 5fca3f84..00000000 --- a/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "failed", - "failedTests": [] -} \ No newline at end of file diff --git a/tests/loop-comprehensive-test.js b/tests/loop-comprehensive-test.js deleted file mode 100644 index ad81dbd0..00000000 --- a/tests/loop-comprehensive-test.js +++ /dev/null @@ -1,1041 +0,0 @@ -/** - * CCW Loop System - Comprehensive Test Suite - * - * Tests: - * - Multi-loop parallel execution - * - API endpoint functionality - * - WebSocket messaging - * - Security fixes (path traversal, success_condition) - * - End-to-end workflow - */ - -import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, readdirSync, rmdirSync, statSync } from 'fs'; -import { join } from 'path'; - -// ANSI colors -const colors = { - reset: '\x1b[0m', - green: '\x1b[32m', - red: '\x1b[31m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - cyan: '\x1b[36m', - magenta: '\x1b[35m', - bright: '\x1b[1m' -}; - -function log(color, msg) { - console.log(`${color}${msg}${colors.reset}`); -} - -function assert(condition, message) { - if (!condition) { - throw new Error(`Assertion failed: ${message}`); - } -} - -// Test workspace -const TEST_WORKSPACE = join(process.cwd(), '.test-loop-comprehensive'); -const TEST_STATE_DIR = join(TEST_WORKSPACE, '.workflow'); -const TEST_TASK_DIR = join(TEST_WORKSPACE, '.task'); - -// Test results -const results = []; - -/** - * Setup test workspace - */ -function setupTestWorkspace() { - log(colors.blue, '🔧 Setting up test workspace...'); - - // Clean existing workspace - if (existsSync(TEST_WORKSPACE)) { - const cleanDir = (dir) => { - const files = readdirSync(dir); - files.forEach((f) => { - const fullPath = join(dir, f); - const stat = statSync(fullPath); - if (stat.isDirectory()) { - cleanDir(fullPath); - rmdirSync(fullPath); - } else { - unlinkSync(fullPath); - } - }); - }; - cleanDir(TEST_WORKSPACE); - } - - // Create directories - if (!existsSync(TEST_STATE_DIR)) { - mkdirSync(TEST_STATE_DIR, { recursive: true }); - } - if (!existsSync(TEST_TASK_DIR)) { - mkdirSync(TEST_TASK_DIR, { recursive: true }); - } - - log(colors.green, '✅ Test workspace ready'); -} - -/** - * Run a single test - */ -async function runTest(suite, name, fn) { - const start = Date.now(); - process.stdout.write(` ○ ${name}... `); - - try { - await fn(); - const duration = Date.now() - start; - results.push({ suite, name, passed: true, duration }); - log(colors.green, `✓ (${duration}ms)`); - } catch (error) { - const duration = Date.now() - start; - results.push({ suite, name, passed: false, error: error.message, duration }); - log(colors.red, `✗ ${error.message}`); - } -} - -/** - * Create a mock loop state - */ -function createLoopState(taskId, loopId) { - const id = loopId || `loop-${taskId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - const state = { - loop_id: id, - task_id: taskId, - status: 'created', - current_iteration: 0, - max_iterations: 3, - current_cli_step: 0, - cli_sequence: [ - { step_id: 'step1', tool: 'bash', command: 'echo "test"' }, - { step_id: 'step2', tool: 'gemini', mode: 'analysis', prompt_template: 'Analyze: [step1_stdout]' } - ], - session_mapping: {}, - state_variables: {}, - error_policy: { on_failure: 'pause', retry_count: 0, max_retries: 3 }, - success_condition: 'state_variables.step1_stdout && state_variables.step1_stdout.includes("test")', - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - }; - - const stateFile = join(TEST_STATE_DIR, `${id}.json`); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - return state; -} - -/** - * Create a mock task with loop_control - */ -function createTaskWithLoop(taskId) { - const task = { - id: taskId, - title: `Test Task ${taskId}`, - description: 'Test task with loop control', - status: 'pending', - loop_control: { - enabled: true, - description: 'Test loop', - max_iterations: 3, - success_condition: 'current_iteration >= 3', - error_policy: { - on_failure: 'pause', - max_retries: 3 - }, - cli_sequence: [ - { step_id: 'step1', tool: 'bash', command: 'echo "iteration"' }, - { step_id: 'step2', tool: 'gemini', mode: 'analysis', prompt_template: 'Process output' } - ] - } - }; - - const taskFile = join(TEST_TASK_DIR, `${taskId}.json`); - writeFileSync(taskFile, JSON.stringify(task, null, 2)); - return task; -} - -/** - * Read loop state - */ -function readLoopState(loopId) { - const stateFile = join(TEST_STATE_DIR, `${loopId}.json`); - return JSON.parse(readFileSync(stateFile, 'utf-8')); -} - -/** - * Update loop state - */ -function updateLoopState(loopId, updates) { - const state = readLoopState(loopId); - Object.assign(state, updates, { updated_at: new Date().toISOString() }); - const stateFile = join(TEST_STATE_DIR, `${loopId}.json`); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - return state; -} - -/** - * List all loop states - */ -function listLoopStates() { - const files = readdirSync(TEST_STATE_DIR).filter((f) => f.endsWith('.json') && f.startsWith('loop-')); - return files.map((f) => { - const content = readFileSync(join(TEST_STATE_DIR, f), 'utf-8'); - return JSON.parse(content); - }); -} - -// ============================================ -// TEST SUITE 1: MULTI-LOOP PARALLEL EXECUTION -// ============================================ - -async function testMultiLoopParallel() { - log(colors.blue, '\n📋 TEST SUITE 1: MULTI-LOOP PARALLEL EXECUTION'); - - await runTest('multi-loop', 'Create multiple loops simultaneously', async () => { - const loops = []; - for (let i = 0; i < 5; i++) { - const loop = createLoopState(`MULTI-${i}`); - loops.push(loop); - } - - assert(loops.length === 5, 'should create 5 loops'); - assert(new Set(loops.map((l) => l.loop_id)).size === 5, 'all loop IDs should be unique'); - }); - - await runTest('multi-loop', 'List all loops', () => { - const allLoops = listLoopStates(); - assert(allLoops.length === 5, 'should list all 5 loops'); - assert(allLoops.every((l) => l.loop_id.startsWith('loop-')), 'all should be valid loop IDs'); - }); - - await runTest('multi-loop', 'Update loops independently', async () => { - const allLoops = listLoopStates(); - - // Update each loop with different states - for (let i = 0; i < allLoops.length; i++) { - updateLoopState(allLoops[i].loop_id, { - status: i % 2 === 0 ? 'running' : 'paused', - current_iteration: i + 1 - }); - } - - const updated = listLoopStates(); - assert(updated.filter((l) => l.status === 'running').length === 3, '3 should be running'); - assert(updated.filter((l) => l.status === 'paused').length === 2, '2 should be paused'); - }); - - await runTest('multi-loop', 'Filter loops by status', () => { - const allLoops = listLoopStates(); - const running = allLoops.filter((l) => l.status === 'running'); - const paused = allLoops.filter((l) => l.status === 'paused'); - - assert(running.length === 3, 'should find 3 running loops'); - assert(paused.length === 2, 'should find 2 paused loops'); - }); - - await runTest('multi-loop', 'Sort loops by update time', () => { - // Add delay to ensure different timestamps - const loop = listLoopStates()[0]; - updateLoopState(loop.loop_id, { current_iteration: 99 }); - - const allLoops = listLoopStates(); - const sorted = [...allLoops].sort((a, b) => - new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() - ); - - assert(sorted[0].loop_id === loop.loop_id, 'most recently updated should be first'); - }); - - await runTest('multi-loop', 'Compute loop statistics', () => { - const allLoops = listLoopStates(); - - const byStatus = {}; - for (const loop of allLoops) { - byStatus[loop.status] = (byStatus[loop.status] || 0) + 1; - } - - assert(byStatus.running === 3, 'should count 3 running'); - assert(byStatus.paused === 2, 'should count 2 paused'); - - const activeCount = (byStatus.running || 0) + (byStatus.paused || 0); - assert(activeCount === 5, 'active count should be 5'); - }); -} - -// ============================================ -// TEST SUITE 2: STATE TRANSITIONS IN PARALLEL -// ============================================ - -async function testStateTransitions() { - log(colors.blue, '\n📋 TEST SUITE 2: PARALLEL STATE TRANSITIONS'); - - await runTest('transitions', 'Parallel pause/resume operations', async () => { - const allLoops = listLoopStates().filter((l) => l.status === 'running'); - - // Pause all running loops - allLoops.forEach((loop) => { - updateLoopState(loop.loop_id, { status: 'paused' }); - }); - - const updated = listLoopStates(); - assert(updated.filter((l) => l.status === 'paused').length === 5, 'all should be paused'); - - // Resume all - updated.forEach((loop) => { - updateLoopState(loop.loop_id, { status: 'running' }); - }); - - const resumed = listLoopStates(); - assert(resumed.filter((l) => l.status === 'running').length === 5, 'all should be running'); - }); - - await runTest('transitions', 'Independent loop progress', async () => { - const allLoops = listLoopStates(); - - // Advance each loop independently - allLoops.forEach((loop, i) => { - updateLoopState(loop.loop_id, { - current_iteration: i + 2, - current_cli_step: i % 2 - }); - }); - - const updated = listLoopStates(); - const iterations = updated.map((l) => l.current_iteration); - assert(new Set(iterations).size === 5, 'each loop should have different iteration'); - }); -} - -// ============================================ -// TEST SUITE 3: EXECUTION HISTORY -// ============================================ - -async function testExecutionHistory() { - log(colors.blue, '\n📋 TEST SUITE 3: EXECUTION HISTORY'); - - const loop = createLoopState('HISTORY-TEST'); - - await runTest('history', 'Add execution record', () => { - const record = { - iteration: 1, - step_index: 0, - step_id: 'step1', - tool: 'bash', - conversation_id: 'conv-1', - exit_code: 0, - duration_ms: 100, - timestamp: new Date().toISOString() - }; - - const state = readLoopState(loop.loop_id); - state.execution_history = [...(state.execution_history || []), record]; - - const stateFile = join(TEST_STATE_DIR, `${loop.loop_id}.json`); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - - const updated = readLoopState(loop.loop_id); - assert(updated.execution_history?.length === 1, 'should have 1 record'); - }); - - await runTest('history', 'Paginate history', () => { - const state = readLoopState(loop.loop_id); - - // Add more records - for (let i = 1; i <= 10; i++) { - state.execution_history?.push({ - iteration: i, - step_index: 0, - step_id: `step${i}`, - tool: 'bash', - conversation_id: `conv-${i}`, - exit_code: 0, - duration_ms: i * 10, - timestamp: new Date().toISOString() - }); - } - - const stateFile = join(TEST_STATE_DIR, `${loop.loop_id}.json`); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - - const updated = readLoopState(loop.loop_id); - const total = updated.execution_history?.length || 0; - - // Test pagination - const limit = 5; - const offset = 0; - const page1 = updated.execution_history?.slice(offset, offset + limit) || []; - - assert(page1.length === 5, 'page 1 should have 5 records'); - assert(total === 11, 'should have 11 total records'); - }); - - await runTest('history', 'History persists across state updates', () => { - updateLoopState(loop.loop_id, { status: 'running' }); - const updated = readLoopState(loop.loop_id); - assert(updated.execution_history?.length === 11, 'history should persist'); - }); -} - -// ============================================ -// TEST SUITE 4: STATE VARIABLES -// ============================================ - -async function testStateVariables() { - log(colors.blue, '\n📋 TEST SUITE 4: STATE VARIABLES'); - - const loop = createLoopState('VARS-TEST'); - - await runTest('variables', 'Store step output', () => { - updateLoopState(loop.loop_id, { - state_variables: { - step1_stdout: 'Tests passed: 15', - step1_stderr: '', - step1_exit_code: '0' - } - }); - - const state = readLoopState(loop.loop_id); - assert(state.state_variables.step1_stdout === 'Tests passed: 15', 'should store stdout'); - }); - - await runTest('variables', 'Accumulate variables from multiple steps', () => { - const state = readLoopState(loop.loop_id); - state.state_variables = { - ...state.state_variables, - step2_stdout: 'Analysis complete', - step2_stderr: '', - step2_exit_code: '0' - }; - - const stateFile = join(TEST_STATE_DIR, `${loop.loop_id}.json`); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - - const updated = readLoopState(loop.loop_id); - assert(Object.keys(updated.state_variables).length === 6, 'should have 6 variables'); - }); - - await runTest('variables', 'Extract logs by step_id', () => { - const state = readLoopState(loop.loop_id); - - // Group by step_id - const stepIds = new Set(); - for (const key of Object.keys(state.state_variables)) { - const match = key.match(/^(.+)_(stdout|stderr)$/); - if (match) stepIds.add(match[1]); - } - - assert(stepIds.has('step1'), 'should find step1'); - assert(stepIds.has('step2'), 'should find step2'); - }); -} - -// ============================================ -// TEST SUITE 5: SECURITY - PATH TRAVERSAL -// ============================================ - -async function testSecurityPathTraversal() { - log(colors.blue, '\n📋 TEST SUITE 5: SECURITY - PATH TRAVERSAL'); - - await runTest('security', 'isValidId rejects path separators', () => { - // Simulate the isValidId function - const isValidId = (id) => { - if (!id) return false; - if (id.includes('/') || id.includes('\\') || id === '..' || id === '.') return false; - if (id.includes('\0')) return false; - return true; - }; - - assert(!isValidId('../etc/passwd'), 'should reject ../ path'); - assert(!isValidId('..\\windows\\system32'), 'should reject ..\\ path'); - assert(!isValidId('../../'), 'should reject ../..'); - assert(!isValidId('./file'), 'should reject ./file'); - assert(!isValidId('..\u0000'), 'should reject null bytes'); - assert(isValidId('valid-loop-123'), 'should accept valid ID'); - assert(isValidId('loop-abc-123'), 'should accept valid loop ID'); - }); - - await runTest('security', 'taskId sanitization', () => { - const validTaskIds = ['TASK-001', 'loop-test', 'my_task_123']; - const invalidTaskIds = ['../task', '..\\task', 'task/../../etc', 'task\u0000']; - - // Valid IDs should pass - validTaskIds.forEach((id) => { - const hasPathChar = id.includes('/') || id.includes('\\') || id === '..' || id === '.'; - assert(!hasPathChar, `${id} should be valid`); - }); - - // Invalid IDs should be caught - invalidTaskIds.forEach((id) => { - const hasPathChar = id.includes('/') || id.includes('\\') || id === '..' || id === '.' || id.includes('\0'); - assert(hasPathChar, `${id} should be detected as invalid`); - }); - }); - - await runTest('security', 'Prevent directory traversal in file access', () => { - // Simulate file join behavior - const taskDir = TEST_TASK_DIR; - - // Normal case - const normalPath = join(taskDir, 'TASK-001.json'); - assert(normalPath.startsWith(taskDir), 'normal path should stay in directory'); - - // Path traversal attempt (would be blocked by validation) - const maliciousId = '../malicious'; - const maliciousPath = join(taskDir, `${maliciousId}.json`); - // The validation should catch this before file join - const isMalicious = maliciousId.includes('/') || maliciousId.includes('\\') || maliciousId === '..'; - assert(isMalicious, 'malicious ID should be detected'); - }); -} - -// ============================================ -// TEST SUITE 6: SECURITY - SUCCESS CONDITION -// ============================================ - -async function testSecuritySuccessCondition() { - log(colors.blue, '\n📋 TEST SUITE 6: SECURITY - SUCCESS CONDITION'); - - // Import the evaluateSuccessCondition logic (simplified for testing) - const evaluateSuccessCondition = (condition, stateVariables, currentIteration) => { - // Security checks - const unsafePattern = /[^\w\s\.\(\)\[\]\{\}\'\"\!\=\>\<\&\|\+\-\*\/\?\:]/; - if (unsafePattern.test(condition)) { - throw new Error('Unsafe success condition contains invalid characters'); - } - - const blockedPatterns = [ - /process\./, - /require\(/, - /import\s/, - /import\(/, // Block import() calls - /eval\(/, - /Function\(/, - /__proto__/, - /constructor\[/, - /["']constructor["']/ - ]; - - for (const pattern of blockedPatterns) { - if (pattern.test(condition)) { - throw new Error('Blocked dangerous pattern in success condition'); - } - } - - // Safe evaluation - try { - const conditionFn = new Function( - 'state_variables', - 'current_iteration', - `return (${condition});` - ); - return Boolean(conditionFn(stateVariables, currentIteration)); - } catch (error) { - return false; - } - }; - - await runTest('security', 'Block process.exit()', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('process.exit(1)', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block process.exit'); - }); - - await runTest('security', 'Block require()', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('require("fs")', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block require'); - }); - - await runTest('security', 'Block eval()', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('eval("malicious")', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block eval'); - }); - - await runTest('security', 'Block __proto__', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('{}.__proto__.polluted = "yes"', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block __proto__'); - }); - - await runTest('security', 'Block constructor access', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('this["constructor"]["return"]("code")', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block constructor access'); - }); - - await runTest('security', 'Block import statement', () => { - let threw = false; - let error = null; - try { - evaluateSuccessCondition('import("fs")', {}, 0); - } catch (e) { - threw = true; - error = e; - } - assert(threw && error && error.message.includes('Blocked'), 'should block import'); - }); - - await runTest('security', 'Allow safe comparisons', () => { - const result = evaluateSuccessCondition('current_iteration >= 3', {}, 3); - assert(result === true, 'safe comparison should work'); - }); - - await runTest('security', 'Allow string operations', () => { - const vars = { output: 'Tests passed' }; - const result = evaluateSuccessCondition('state_variables.output.includes("passed")', vars, 1); - assert(result === true, 'string operations should work'); - }); - - await runTest('security', 'Allow logical AND', () => { - const vars = { test: 'pass', coverage: 90 }; - const result = evaluateSuccessCondition('state_variables.test === "pass" && state_variables.coverage > 80', vars, 1); - assert(result === true, 'logical AND should work'); - }); - - await runTest('security', 'Allow logical OR', () => { - const vars = { status: 'approved' }; - const result = evaluateSuccessCondition('state_variables.status === "approved" || state_variables.status === "LGTM"', vars, 1); - assert(result === true, 'logical OR should work'); - }); - - await runTest('security', 'Block backtick strings', () => { - let threw = false; - try { - evaluateSuccessCondition('`${process.env}`', {}, 0); - } catch (e) { - threw = true; - } - assert(threw, 'should block backtick strings'); - }); -} - -// ============================================ -// TEST SUITE 7: WEBSOCKET MESSAGE TYPES -// ============================================ - -async function testWebSocketMessages() { - log(colors.blue, '\n📋 TEST SUITE 7: WEBSOCKET MESSAGE TYPES'); - - await runTest('websocket', 'LOOP_STATE_UPDATE message structure', () => { - const message = { - type: 'LOOP_STATE_UPDATE', - loop_id: 'loop-test-123', - status: 'running', - current_iteration: 2, - current_cli_step: 1, - updated_at: new Date().toISOString() - }; - - assert(message.type === 'LOOP_STATE_UPDATE', 'type should be correct'); - assert(message.loop_id.startsWith('loop-'), 'loop_id should be valid'); - assert(['created', 'running', 'paused', 'completed', 'failed'].includes(message.status), 'status should be valid'); - }); - - await runTest('websocket', 'LOOP_STEP_COMPLETED message structure', () => { - const message = { - type: 'LOOP_STEP_COMPLETED', - loop_id: 'loop-test-123', - step_id: 'step1', - exit_code: 0, - duration_ms: 150, - output: 'Tests passed' - }; - - assert(message.type === 'LOOP_STEP_COMPLETED', 'type should be correct'); - assert(message.step_id === 'step1', 'step_id should be preserved'); - assert(message.exit_code === 0, 'exit_code should be preserved'); - }); - - await runTest('websocket', 'LOOP_COMPLETED message structure', () => { - const message = { - type: 'LOOP_COMPLETED', - loop_id: 'loop-test-123', - final_status: 'completed', - total_iterations: 5, - reason: undefined - }; - - assert(message.type === 'LOOP_COMPLETED', 'type should be correct'); - assert(message.final_status === 'completed' || message.final_status === 'failed', 'final_status should be valid'); - }); - - await runTest('websocket', 'LOOP_LOG_ENTRY message structure', () => { - const message = { - type: 'LOOP_LOG_ENTRY', - loop_id: 'loop-test-123', - step_id: 'step1', - line: 'Running tests...', - timestamp: new Date().toISOString() - }; - - assert(message.type === 'LOOP_LOG_ENTRY', 'type should be correct'); - assert(message.line, 'log line should be present'); - }); -} - -// ============================================ -// TEST SUITE 8: API RESPONSE FORMATS -// ============================================ - -async function testApiResponseFormats() { - log(colors.blue, '\n📋 TEST SUITE 8: API RESPONSE FORMATS'); - - await runTest('api', 'Success response format', () => { - const response = { - success: true, - data: { loop_id: 'loop-123', status: 'running' }, - timestamp: new Date().toISOString() - }; - - assert(response.success === true, 'success should be true'); - assert(response.data, 'data should be present'); - assert(response.timestamp, 'timestamp should be present'); - assert(!isNaN(Date.parse(response.timestamp)), 'timestamp should be valid ISO date'); - }); - - await runTest('api', 'Error response format', () => { - const response = { - success: false, - error: 'Loop not found', - status: 404 - }; - - assert(response.success === false, 'success should be false'); - assert(response.error, 'error message should be present'); - assert(response.status >= 400 && response.status < 600, 'status should be error code'); - }); - - await runTest('api', 'List response format', () => { - const response = { - success: true, - data: [{ loop_id: 'loop-1' }, { loop_id: 'loop-2' }], - total: 2, - limit: 50, - offset: 0, - hasMore: false - }; - - assert(response.success === true, 'success should be true'); - assert(Array.isArray(response.data), 'data should be array'); - assert(typeof response.total === 'number', 'total should be number'); - assert(typeof response.hasMore === 'boolean', 'hasMore should be boolean'); - }); - - await runTest('api', 'Statistics response format', () => { - const stats = { - total: 10, - by_status: { running: 3, paused: 2, completed: 4, failed: 1 }, - active_count: 5, - success_rate: 80, - avg_iterations: 2.5 - }; - - assert(stats.total === 10, 'total should be correct'); - assert(stats.by_status.running === 3, 'by_status should have counts'); - assert(stats.active_count === 5, 'active_count should sum running + paused'); - assert(stats.success_rate === 80, 'success_rate should be percentage'); - }); -} - -// ============================================ -// TEST SUITE 9: EDGE CASES -// ============================================ - -async function testEdgeCases() { - log(colors.blue, '\n📋 TEST SUITE 9: EDGE CASES'); - - await runTest('edge', 'Handle empty loop list', () => { - // Create a fresh workspace with no loops - const emptyDir = join(TEST_WORKSPACE, 'empty'); - if (!existsSync(emptyDir)) { - mkdirSync(emptyDir, { recursive: true }); - } - - const files = readdirSync(emptyDir).filter((f) => f.startsWith('loop-')); - assert(files.length === 0, 'empty workspace should have no loops'); - }); - - await runTest('edge', 'Handle loop at max iterations', () => { - const loop = createLoopState('MAX-ITER'); - updateLoopState(loop.loop_id, { - current_iteration: 3, - max_iterations: 3, - status: 'completed' - }); - - const state = readLoopState(loop.loop_id); - assert(state.current_iteration === state.max_iterations, 'should reach max'); - assert(state.status === 'completed', 'should be completed'); - }); - - await runTest('edge', 'Handle loop with no success condition', () => { - const loop = createLoopState('NO-SUCCESS'); - updateLoopState(loop.loop_id, { - success_condition: undefined, - max_iterations: 1, - current_iteration: 1 - }); - - const state = readLoopState(loop.loop_id); - assert(!state.success_condition, 'no success condition set'); - // Loop should complete based on max_iterations - assert(state.current_iteration === state.max_iterations, 'should reach max'); - }); - - await runTest('edge', 'Handle special characters in output', () => { - const specialChars = '{"key": "value", "array": [1, 2, 3]}, &\'"quotes\''; - - // Create the loop first - const loop = createLoopState('VARS-TEST'); - - updateLoopState(loop.loop_id, { - state_variables: { - special_output: specialChars - } - }); - - const state = readLoopState(loop.loop_id); - assert(state.state_variables.special_output === specialChars, 'should preserve special chars'); - }); -} - -// ============================================ -// TEST SUITE 10: END-TO-END WORKFLOW -// ============================================ - -async function testEndToEnd() { - log(colors.blue, '\n📋 TEST SUITE 10: END-TO-END WORKFLOW'); - - await runTest('e2e', 'Complete loop lifecycle', async () => { - // 1. Create task - const taskId = `E2E-TASK-${Date.now()}`; - const task = createTaskWithLoop(taskId); - assert(task.loop_control?.enabled === true, 'task should have loop enabled'); - - // 2. Start loop - const loop = createLoopState(taskId); - assert(loop.status === 'created', 'loop should start as created'); - - // 3. Transition to running - updateLoopState(loop.loop_id, { status: 'running' }); - let state = readLoopState(loop.loop_id); - assert(state.status === 'running', 'loop should be running'); - - // 4. Execute step (simulate) - updateLoopState(loop.loop_id, { - current_cli_step: 1, - state_variables: { - step1_stdout: 'test output', - step1_stderr: '', - step1_exit_code: '0' - } - }); - state = readLoopState(loop.loop_id); - assert(state.current_cli_step === 1, 'should advance to next step'); - - // 5. Complete iteration - updateLoopState(loop.loop_id, { - current_cli_step: 0, - current_iteration: 1 - }); - state = readLoopState(loop.loop_id); - assert(state.current_iteration === 1, 'should increment iteration'); - - // 6. Pause - updateLoopState(loop.loop_id, { status: 'paused' }); - state = readLoopState(loop.loop_id); - assert(state.status === 'paused', 'should be paused'); - - // 7. Resume - updateLoopState(loop.loop_id, { status: 'running' }); - state = readLoopState(loop.loop_id); - assert(state.status === 'running', 'should be running again'); - - // 8. Complete - updateLoopState(loop.loop_id, { - status: 'completed', - current_iteration: 3, - completed_at: new Date().toISOString() - }); - state = readLoopState(loop.loop_id); - assert(state.status === 'completed', 'should be completed'); - assert(state.completed_at, 'should have completion timestamp'); - }); - - await runTest('e2e', 'Failed loop with retry', async () => { - const taskId = `E2E-FAIL-${Date.now()}`; - const loop = createLoopState(taskId); - - // Simulate failure - updateLoopState(loop.loop_id, { - status: 'paused', - failure_reason: 'Step failed with exit code 1' - }); - - let state = readLoopState(loop.loop_id); - assert(state.status === 'paused', 'should pause on error'); - assert(state.failure_reason, 'should have failure reason'); - - // Simulate retry - updateLoopState(loop.loop_id, { - status: 'running', - failure_reason: undefined - }); - - state = readLoopState(loop.loop_id); - assert(state.status === 'running', 'should resume after retry'); - assert(!state.failure_reason, 'failure reason should be cleared'); - }); -} - -// ============================================ -// PRINT SUMMARY -// ============================================ - -function printSummary() { - log(colors.cyan, '\n' + '='.repeat(60)); - log(colors.cyan, '📊 COMPREHENSIVE TEST SUMMARY'); - log(colors.cyan, '='.repeat(60)); - - // Group by suite - const bySuite = {}; - for (const r of results) { - if (!bySuite[r.suite]) bySuite[r.suite] = []; - bySuite[r.suite].push(r); - } - - // Print suite summaries - for (const [suite, suiteResults] of Object.entries(bySuite)) { - const passed = suiteResults.filter((r) => r.passed).length; - const total = suiteResults.length; - const rate = ((passed / total) * 100).toFixed(0); - - const color = passed === total ? colors.green : colors.yellow; - log(color, `\n ${suite}: ${passed}/${total} (${rate}%)`); - } - - // Total stats - const total = results.length; - const passed = results.filter((r) => r.passed).length; - const failed = results.filter((r) => !r.passed).length; - const totalTime = results.reduce((sum, r) => sum + (r.duration || 0), 0); - - log(colors.cyan, '\n' + '-'.repeat(60)); - log(colors.reset, `\n Total Tests: ${total}`); - log(colors.green, ` Passed: ${passed} ✓`); - if (failed > 0) { - log(colors.red, ` Failed: ${failed} ✗`); - } - log(colors.reset, ` Success Rate: ${((passed / total) * 100).toFixed(1)}%`); - log(colors.reset, ` Total Time: ${totalTime}ms`); - - // Failed tests - if (failed > 0) { - log(colors.red, '\n❌ Failed Tests:'); - results.filter((r) => !r.passed).forEach((r) => { - log(colors.red, ` [${r.suite}] ${r.name}`); - log(colors.red, ` ${r.error}`); - }); - } - - // Performance highlights - const avgTime = totalTime / total; - const fastTests = results.filter((r) => (r.duration || 0) < 20); - const slowTests = results.filter((r) => (r.duration || 0) > 100); - - log(colors.green, `\n⚡ Average: ${avgTime.toFixed(1)}ms/test`); - if (fastTests.length > 0) { - log(colors.green, `⚡ Fast Tests (<20ms): ${fastTests.length}`); - } - if (slowTests.length > 0) { - log(colors.yellow, `🐢 Slow Tests (>100ms): ${slowTests.length}`); - } - - log(colors.cyan, '\n' + '='.repeat(60)); - - if (failed === 0) { - log(colors.bright + colors.green, '✅ ALL TESTS PASSED!'); - log(colors.green, 'The CCW Loop System comprehensive tests completed successfully.'); - } else { - log(colors.bright + colors.red, '❌ SOME TESTS FAILED'); - log(colors.red, 'Please review the failures above.'); - } - - log(colors.reset, ''); -} - -// ============================================ -// MAIN TEST RUNNER -// ============================================ - -async function runAllTests() { - log(colors.cyan, '\n' + '='.repeat(60)); - log(colors.bright + colors.cyan, '🧪 CCW LOOP SYSTEM - COMPREHENSIVE TEST SUITE'); - log(colors.cyan, '='.repeat(60)); - log(colors.cyan, 'Testing: Multi-loop, API, Security, WebSocket, E2E'); - log(colors.cyan, '='.repeat(60)); - - setupTestWorkspace(); - - try { - await testMultiLoopParallel(); - await testStateTransitions(); - await testExecutionHistory(); - await testStateVariables(); - await testSecurityPathTraversal(); - await testSecuritySuccessCondition(); - await testWebSocketMessages(); - await testApiResponseFormats(); - await testEdgeCases(); - await testEndToEnd(); - } catch (error) { - log(colors.red, `\n💥 Fatal error during test execution: ${error.message}`); - console.error(error); - } - - printSummary(); - - const failed = results.filter((r) => !r.passed).length; - return failed === 0 ? 0 : 1; -} - -// Run tests -runAllTests().then((exitCode) => { - process.exit(exitCode); -}).catch((err) => { - log(colors.red, `💥 Unhandled error: ${err.message}`); - console.error(err); - process.exit(1); -}); diff --git a/tests/loop-flow-test.js b/tests/loop-flow-test.js deleted file mode 100644 index 9ba7e363..00000000 --- a/tests/loop-flow-test.js +++ /dev/null @@ -1,329 +0,0 @@ -/** - * CCW Loop System - Simplified Flow State Test - * Tests the complete Loop system flow with mock endpoints - */ - -import { writeFile, readFile, existsSync, mkdirSync, unlinkSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; - -// Test configuration -const TEST_WORKSPACE = join(process.cwd(), '.test-loop-workspace'); -const TEST_STATE_DIR = join(TEST_WORKSPACE, '.workflow'); -const TEST_TASKS_DIR = join(TEST_WORKSPACE, '.task'); - -// Test results -const results: { name: string; passed: boolean; error?: string }[] = []; - -function log(msg: string) { console.log(msg); } -function assert(condition: boolean, message: string) { - if (!condition) { - throw new Error(`Assertion failed: ${message}`); - } -} - -/** - * Setup test workspace - */ -function setup() { - log('🔧 Setting up test workspace...'); - - if (!existsSync(TEST_STATE_DIR)) mkdirSync(TEST_STATE_DIR, { recursive: true }); - if (!existsSync(TEST_TASKS_DIR)) mkdirSync(TEST_TASKS_DIR, { recursive: true }); - - // Create test task - const testTask = { - id: 'TEST-LOOP-1', - title: 'Test Loop', - status: 'active', - loop_control: { - enabled: true, - max_iterations: 3, - success_condition: 'state_variables.test_result === "pass"', - error_policy: { on_failure: 'pause' }, - cli_sequence: [ - { step_id: 'run_test', tool: 'bash', command: 'npm test' }, - { step_id: 'analyze', tool: 'gemini', mode: 'analysis', prompt_template: 'Analyze: [run_test_stdout]' } - ] - } - }; - - writeFile(join(TEST_TASKS_DIR, 'TEST-LOOP-1.json'), JSON.stringify(testTask, null, 2), (err) => { - if (err) throw err; - }); - - log('✅ Test workspace ready'); -} - -/** - * Cleanup - */ -function cleanup() { - try { - if (existsSync(join(TEST_STATE_DIR, 'loop-state.json'))) { - unlinkSync(join(TEST_STATE_DIR, 'loop-state.json')); - } - log('🧹 Cleaned up'); - } catch (e) { - // Ignore - } -} - -/** - * Test runner - */ -async function runTest(name: string, fn: () => Promise | void) { - process.stdout.write(` ○ ${name}... `); - try { - await fn(); - results.push({ name, passed: true }); - log('✓'); - } catch (error) { - results.push({ name, passed: false, error: (error as Error).message }); - log(`✗ ${(error as Error).message}`); - } -} - -/** - * Create initial state - */ -function createInitialState() { - const state = { - loop_id: 'loop-TEST-LOOP-1-' + Date.now(), - task_id: 'TEST-LOOP-1', - status: 'created', - current_iteration: 0, - max_iterations: 3, - current_cli_step: 0, - cli_sequence: [ - { step_id: 'run_test', tool: 'bash', command: 'npm test' }, - { step_id: 'analyze', tool: 'gemini', mode: 'analysis', prompt_template: 'Analyze: [run_test_stdout]' } - ], - session_mapping: {}, - state_variables: {}, - error_policy: { on_failure: 'pause', max_retries: 3 }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - }; - - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), (err) => { - if (err) throw err; - }); - - return state; -} - -/** - * Run all tests - */ -async function runAllTests() { - log('\n🧪 CCW LOOP SYSTEM - FLOW STATE TEST'); - log('='.repeat(50)); - - setup(); - - // Test 1: State Creation - log('\n📋 State Creation Tests:'); - await runTest('Initial state is "created"', async () => { - const state = createInitialState(); - assert(state.status === 'created', 'status should be created'); - assert(state.current_iteration === 0, 'iteration should be 0'); - }); - - // Test 2: State Transitions - log('\n📋 State Transition Tests:'); - await runTest('created -> running', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'running'; - state.updated_at = new Date().toISOString(); - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'running', 'status should be running'); - }); - - await runTest('running -> paused', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'paused'; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'paused', 'status should be paused'); - }); - - await runTest('paused -> running', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'running'; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'running', 'status should be running'); - }); - - await runTest('running -> completed', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'completed'; - state.completed_at = new Date().toISOString(); - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'completed', 'status should be completed'); - assert(updated.completed_at, 'should have completed_at'); - }); - - // Test 3: Iteration Control - log('\n📋 Iteration Control Tests:'); - await runTest('Iteration increments', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'running'; - state.current_iteration = 1; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.current_iteration === 1, 'iteration should increment'); - }); - - await runTest('Max iterations respected', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.current_iteration = 3; - state.max_iterations = 3; - state.status = 'completed'; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.current_iteration <= updated.max_iterations, 'should not exceed max'); - }); - - // Test 4: CLI Step Control - log('\n📋 CLI Step Control Tests:'); - await runTest('Step index increments', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.current_cli_step = 1; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.current_cli_step === 1, 'step should increment'); - }); - - await runTest('Step resets on new iteration', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.current_iteration = 2; - state.current_cli_step = 0; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.current_cli_step === 0, 'step should reset'); - }); - - // Test 5: Variable Substitution - log('\n📋 Variable Substitution Tests:'); - await runTest('Variables are stored', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.state_variables = { test_result: 'pass', output: 'Success!' }; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.state_variables.test_result === 'pass', 'variable should be stored'); - }); - - await runTest('Template substitution works', async () => { - const template = 'Result: [test_result]'; - const vars = { test_result: 'pass' }; - const result = template.replace(/\[(\w+)\]/g, (_, key) => vars[key as keyof typeof vars] || `[${key}]`); - assert(result === 'Result: pass', 'substitution should work'); - }); - - // Test 6: Success Condition - log('\n📋 Success Condition Tests:'); - await runTest('Simple condition passes', async () => { - const condition = 'state_variables.test_result === "pass"'; - const vars = { test_result: 'pass' }; - // Simulate evaluation - const pass = vars.test_result === 'pass'; - assert(pass === true, 'condition should pass'); - }); - - await runTest('Complex condition with regex', async () => { - const output = 'Average: 35ms, Min: 28ms'; - const match = output.match(/Average: ([\d.]+)ms/); - const avg = parseFloat(match?.[1] || '1000'); - const pass = avg < 50; - assert(pass === true, 'complex condition should pass'); - }); - - // Test 7: Error Handling - log('\n📋 Error Handling Tests:'); - await runTest('pause policy on error', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'paused'; - state.failure_reason = 'Test failed'; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'paused', 'should pause on error'); - assert(updated.failure_reason, 'should have failure reason'); - }); - - await runTest('fail_fast policy', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.status = 'failed'; - state.failure_reason = 'Critical error'; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.status === 'failed', 'should fail immediately'); - }); - - // Test 8: Execution History - log('\n📋 Execution History Tests:'); - await runTest('History records are stored', async () => { - const state = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - state.execution_history = [ - { - iteration: 1, - step_index: 0, - step_id: 'run_test', - tool: 'bash', - started_at: new Date().toISOString(), - completed_at: new Date().toISOString(), - duration_ms: 100, - success: true, - exit_code: 0, - stdout: 'Tests passed', - stderr: '' - } - ]; - writeFile(join(TEST_STATE_DIR, 'loop-state.json'), JSON.stringify(state, null, 2), () => {}); - - const updated = JSON.parse(readFileSync(join(TEST_STATE_DIR, 'loop-state.json'), 'utf-8')); - assert(updated.execution_history?.length === 1, 'should have history'); - }); - - // Summary - log('\n' + '='.repeat(50)); - log('📊 TEST SUMMARY'); - const passed = results.filter(r => r.passed).length; - const failed = results.filter(r => !r.passed).length; - log(` Total: ${results.length}`); - log(` Passed: ${passed} ✓`); - log(` Failed: ${failed} ✗`); - - if (failed > 0) { - log('\n❌ Failed:'); - results.filter(r => !r.passed).forEach(r => { - log(` - ${r.name}: ${r.error}`); - }); - } - - cleanup(); - - return failed === 0 ? 0 : 1; -} - -// Run tests -runAllTests().then(exitCode => { - process.exit(exitCode); -}).catch(err => { - console.error('Test error:', err); - process.exit(1); -}); diff --git a/tests/loop-standalone-test.js b/tests/loop-standalone-test.js deleted file mode 100644 index f61a8bf5..00000000 --- a/tests/loop-standalone-test.js +++ /dev/null @@ -1,565 +0,0 @@ -/** - * CCW Loop System - Standalone Flow State Test - * Tests Loop system without requiring server to be running - */ - -import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, readdirSync } from 'fs'; -import { join } from 'path'; - -// ANSI colors -const colors = { - reset: '\x1b[0m', - green: '\x1b[32m', - red: '\x1b[31m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - cyan: '\x1b[36m' -}; - -function log(color: string, msg: string) { - console.log(`${color}${msg}${colors.reset}`); -} - -function assert(condition: boolean, message: string) { - if (!condition) { - throw new Error(`Assertion failed: ${message}`); - } -} - -// Test workspace -const TEST_WORKSPACE = join(process.cwd(), '.test-loop-workspace'); -const TEST_STATE_DIR = join(TEST_WORKSPACE, '.workflow'); -const TEST_STATE_FILE = join(TEST_STATE_DIR, 'loop-state.json'); - -// Test results -interface TestResult { - name: string; - passed: boolean; - error?: string; - duration?: number; -} -const results: TestResult[] = = []; - -/** - * Setup test workspace - */ -function setupTestWorkspace() { - log(colors.blue, '🔧 Setting up test workspace...'); - - // Clean and create directories - if (existsSync(TEST_WORKSPACE)) { - const files = readdirSync(TEST_WORKSPACE); - files.forEach(f => { - const fullPath = join(TEST_WORKSPACE, f); - unlinkSync(fullPath); - }); - } - - if (!existsSync(TEST_STATE_DIR)) { - mkdirSync(TEST_STATE_DIR, { recursive: true }); - } - - log(colors.green, '✅ Test workspace ready'); -} - -/** - * Create initial loop state - */ -function createInitialState(taskId: string = 'TEST-LOOP-1') { - const loopId = `loop-${taskId}-${Date.now()}`; - const state = { - loop_id: loopId, - task_id: taskId, - status: 'created', - current_iteration: 0, - max_iterations: 5, - current_cli_step: 0, - cli_sequence: [ - { step_id: 'run_tests', tool: 'bash', command: 'npm test' }, - { step_id: 'analyze_failure', tool: 'gemini', mode: 'analysis', prompt_template: 'Analyze: [run_tests_stdout]' }, - { step_id: 'apply_fix', tool: 'codex', mode: 'write', prompt_template: 'Fix: [analyze_failure_stdout]' } - ], - session_mapping: {}, - state_variables: {}, - error_policy: { on_failure: 'pause', max_retries: 3 }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - }; - - writeFileSync(TEST_STATE_FILE, JSON.stringify(state, null, 2)); - return state; -} - -/** - * Read current state - */ -function readState() { - return JSON.parse(readFileSync(TEST_STATE_FILE, 'utf-8')); -} - -/** - * Write state - */ -function writeState(state: any) { - state.updated_at = new Date().toISOString(); - writeFileSync(TEST_STATE_FILE, JSON.stringify(state, null, 2)); -} - -/** - * Run a single test - */ -async function runTest(name: string, fn: () => void | Promise) { - const start = Date.now(); - process.stdout.write(` ○ ${name}... `); - - try { - await fn(); - const duration = Date.now() - start; - results.push({ name, passed: true, duration }); - log(colors.green, `✓ (${duration}ms)`); - } catch (error) { - const duration = Date.now() - start; - results.push({ name, passed: false, error: (error as Error).message, duration }); - log(colors.red, `✗ ${(error as Error).message}`); - } -} - -/** - * Main test runner - */ -async function runAllTests() { - log(colors.cyan, '\n' + '='.repeat(55)); - log(colors.cyan, '🧪 CCW LOOP SYSTEM - STANDALONE FLOW STATE TEST'); - log(colors.cyan, '='.repeat(55)); - - setupTestWorkspace(); - - // ============================================ - // TEST SUITE 1: STATE CREATION - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 1: STATE CREATION'); - - await runTest('Initial state has correct structure', () => { - const state = createInitialState(); - assert(state.loop_id.startsWith('loop-'), 'loop_id should start with "loop-"'); - assert(state.status === 'created', 'status should be "created"'); - assert(state.current_iteration === 0, 'iteration should be 0'); - assert(state.current_cli_step === 0, 'cli_step should be 0'); - assert(state.cli_sequence.length === 3, 'should have 3 cli steps'); - assert(Object.keys(state.state_variables).length === 0, 'variables should be empty'); - }); - - await runTest('Timestamps are valid ISO strings', () => { - const state = createInitialState(); - assert(!isNaN(Date.parse(state.created_at)), 'created_at should be valid date'); - assert(!isNaN(Date.parse(state.updated_at)), 'updated_at should be valid date'); - }); - - // ============================================ - // TEST SUITE 2: STATE TRANSITIONS - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 2: STATE TRANSITIONS'); - - await runTest('created -> running', () => { - const state = readState(); - state.status = 'running'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'running', 'status should be running'); - }); - - await runTest('running -> paused', () => { - const state = readState(); - state.status = 'paused'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'paused', 'status should be paused'); - }); - - await runTest('paused -> running (resume)', () => { - const state = readState(); - state.status = 'running'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'running', 'status should be running'); - }); - - await runTest('running -> completed', () => { - const state = readState(); - state.status = 'completed'; - state.completed_at = new Date().toISOString(); - writeState(state); - - const updated = readState(); - assert(updated.status === 'completed', 'status should be completed'); - assert(updated.completed_at, 'should have completed_at timestamp'); - }); - - await runTest('running -> failed with reason', () => { - // Create new state for this test - createInitialState('TEST-FAIL-1'); - const state = readState(); - state.status = 'failed'; - state.failure_reason = 'Max retries exceeded'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'failed', 'status should be failed'); - assert(updated.failure_reason === 'Max retries exceeded', 'should have failure reason'); - }); - - // ============================================ - // TEST SUITE 3: ITERATION CONTROL - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 3: ITERATION CONTROL'); - - createInitialState('TEST-ITER-1'); - - await runTest('Iteration increments', () => { - const state = readState(); - state.current_iteration = 1; - writeState(state); - - const updated = readState(); - assert(updated.current_iteration === 1, 'iteration should increment'); - }); - - await runTest('Iteration respects max_iterations', () => { - const state = readState(); - state.current_iteration = 5; - state.max_iterations = 5; - state.status = 'completed'; - writeState(state); - - const updated = readState(); - assert(updated.current_iteration <= updated.max_iterations, 'cannot exceed max iterations'); - }); - - await runTest('CLI step increments within iteration', () => { - const state = readState(); - state.current_cli_step = 1; - writeState(state); - - const updated = readState(); - assert(updated.current_cli_step === 1, 'cli_step should increment'); - }); - - await runTest('CLI step resets on new iteration', () => { - const state = readState(); - state.current_iteration = 2; - state.current_cli_step = 0; - writeState(state); - - const updated = readState(); - assert(updated.current_iteration === 2, 'iteration should be 2'); - assert(updated.current_cli_step === 0, 'cli_step should reset to 0'); - }); - - await runTest('CLI step cannot exceed sequence length', () => { - const state = readState(); - state.current_cli_step = state.cli_sequence.length - 1; - writeState(state); - - const updated = readState(); - assert(updated.current_cli_step < updated.cli_sequence.length, 'cli_step must be within bounds'); - }); - - // ============================================ - // TEST SUITE 4: VARIABLE SUBSTITUTION - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 4: VARIABLE SUBSTITUTION'); - - createInitialState('TEST-VAR-1'); - - await runTest('Variables are stored after step execution', () => { - const state = readState(); - state.state_variables = { - run_tests_stdout: 'Tests: 15 passed', - run_tests_stderr: '', - run_tests_exit_code: '0' - }; - writeState(state); - - const updated = readState(); - assert(updated.state_variables.run_tests_stdout === 'Tests: 15 passed', 'variable should be stored'); - }); - - await runTest('Simple template substitution works', () => { - const template = 'Result: [run_tests_stdout]'; - const vars = { run_tests_stdout: 'Tests: 15 passed' }; - const result = template.replace(/\[(\w+)\]/g, (_, key) => vars[key as keyof typeof vars] || `[${key}]`); - - assert(result === 'Result: Tests: 15 passed', 'substitution should work'); - }); - - await runTest('Multiple variable substitution', () => { - const template = 'Stdout: [run_tests_stdout]\nStderr: [run_tests_stderr]'; - const vars = { - run_tests_stdout: 'Tests passed', - run_tests_stderr: 'No errors' - }; - const result = template.replace(/\[(\w+)\]/g, (_, key) => vars[key as keyof typeof vars] || `[${key}]`); - - assert(result.includes('Tests passed'), 'should substitute first variable'); - assert(result.includes('No errors'), 'should substitute second variable'); - }); - - await runTest('Missing variable preserves placeholder', () => { - const template = 'Result: [missing_var]'; - const vars = {}; - const result = template.replace(/\[(\w+)\]/g, (_, key) => vars[key as keyof typeof vars] || `[${key}]`); - - assert(result === 'Result: [missing_var]', 'missing var should preserve placeholder'); - }); - - // ============================================ - // TEST SUITE 5: SUCCESS CONDITION EVALUATION - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 5: SUCCESS CONDITIONS'); - - createInitialState('TEST-SUCCESS-1'); - - await runTest('Simple string equality check', () => { - const state = readState(); - state.state_variables = { test_result: 'pass' }; - const success = state.state_variables.test_result === 'pass'; - - assert(success === true, 'simple equality should work'); - }); - - await runTest('String includes check', () => { - const output = 'Tests: 15 passed, 0 failed'; - const success = output.includes('15 passed'); - - assert(success === true, 'includes check should work'); - }); - - await runTest('Regex extraction and comparison', () => { - const output = 'Average: 35ms, Min: 28ms, Max: 42ms'; - const match = output.match(/Average: ([\d.]+)ms/); - const avgTime = parseFloat(match?.[1] || '1000'); - const success = avgTime < 50; - - assert(avgTime === 35, 'regex should extract number'); - assert(success === true, 'comparison should work'); - }); - - await runTest('Combined AND condition', () => { - const vars = { test_result: 'pass', coverage: '90%' }; - const success = vars.test_result === 'pass' && parseInt(vars.coverage) > 80; - - assert(success === true, 'AND condition should work'); - }); - - await runTest('Combined OR condition', () => { - const output = 'Status: approved'; - const success = output.includes('approved') || output.includes('LGTM'); - - assert(success === true, 'OR condition should work'); - }); - - await runTest('Negation condition', () => { - const output = 'Tests: 15 passed, 0 failed'; - const success = !output.includes('failed'); - - assert(success === true, 'negation should work'); - }); - - // ============================================ - // TEST SUITE 6: ERROR HANDLING POLICIES - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 6: ERROR HANDLING'); - - createInitialState('TEST-ERROR-1'); - - await runTest('pause policy stops loop on error', () => { - const state = readState(); - state.error_policy = { on_failure: 'pause', max_retries: 3 }; - state.status = 'paused'; - state.failure_reason = 'Step failed with exit code 1'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'paused', 'should be paused'); - assert(updated.failure_reason, 'should have failure reason'); - }); - - await runTest('fail_fast policy immediately fails loop', () => { - createInitialState('TEST-ERROR-2'); - const state = readState(); - state.error_policy = { on_failure: 'fail_fast', max_retries: 0 }; - state.status = 'failed'; - state.failure_reason = 'Critical error'; - writeState(state); - - const updated = readState(); - assert(updated.status === 'failed', 'should be failed'); - }); - - await runTest('continue policy allows proceeding', () => { - createInitialState('TEST-ERROR-3'); - const state = readState(); - state.error_policy = { on_failure: 'continue', max_retries: 3 }; - // Simulate continuing to next step despite error - state.current_cli_step = 1; - writeState(state); - - const updated = readState(); - assert(updated.current_cli_step === 1, 'should move to next step'); - assert(updated.status === 'running', 'should still be running'); - }); - - // ============================================ - // TEST SUITE 7: EXECUTION HISTORY - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 7: EXECUTION HISTORY'); - - createInitialState('TEST-HISTORY-1'); - - await runTest('Execution record is created', () => { - const state = readState(); - const now = new Date().toISOString(); - state.execution_history = [ - { - iteration: 1, - step_index: 0, - step_id: 'run_tests', - tool: 'bash', - started_at: now, - completed_at: now, - duration_ms: 150, - success: true, - exit_code: 0, - stdout: 'Tests passed', - stderr: '' - } - ]; - writeState(state); - - const updated = readState(); - assert(updated.execution_history?.length === 1, 'should have 1 record'); - assert(updated.execution_history[0].step_id === 'run_tests', 'record should match'); - }); - - await runTest('Multiple records are ordered', () => { - const state = readState(); - const now = new Date().toISOString(); - state.execution_history = [ - { iteration: 1, step_index: 0, step_id: 'step1', tool: 'bash', started_at: now, completed_at: now, duration_ms: 100, success: true, exit_code: 0 }, - { iteration: 1, step_index: 1, step_id: 'step2', tool: 'gemini', started_at: now, completed_at: now, duration_ms: 200, success: true, exit_code: 0 } - ]; - writeState(state); - - const updated = readState(); - assert(updated.execution_history.length === 2, 'should have 2 records'); - assert(updated.execution_history[0].step_id === 'step1', 'first record should be step1'); - assert(updated.execution_history[1].step_id === 'step2', 'second record should be step2'); - }); - - await runTest('Failed execution has error info', () => { - const state = readState(); - const now = new Date().toISOString(); - state.execution_history?.push({ - iteration: 1, - step_index: 2, - step_id: 'step3', - tool: 'codex', - started_at: now, - completed_at: now, - duration_ms: 50, - success: false, - exit_code: 1, - error: 'Compilation failed' - }); - writeState(state); - - const updated = readState(); - const failedRecord = updated.execution_history?.find(r => r.step_id === 'step3'); - assert(failedRecord?.success === false, 'record should be marked as failed'); - assert(failedRecord?.error, 'record should have error message'); - }); - - // ============================================ - // TEST SUITE 8: BACKUP & RECOVERY - // ============================================ - log(colors.blue, '\n📋 TEST SUITE 8: BACKUP & RECOVERY'); - - createInitialState('TEST-BACKUP-1'); - - await runTest('State file is created', () => { - assert(existsSync(TEST_STATE_FILE), 'state file should exist'); - }); - - await runTest('State can be read back', () => { - const written = readState(); - assert(written.loop_id.startsWith('loop-'), 'read state should match'); - }); - - await runTest('State persists across writes', () => { - const state = readState(); - state.current_iteration = 3; - writeState(state); - - const readBack = readState(); - assert(readBack.current_iteration === 3, 'change should persist'); - }); - - // ============================================ - // PRINT SUMMARY - // ============================================ - log(colors.cyan, '\n' + '='.repeat(55)); - log(colors.cyan, '📊 TEST SUMMARY'); - log(colors.cyan, '='.repeat(55)); - - const total = results.length; - const passed = results.filter(r => r.passed).length; - const failed = results.filter(r => !r.passed).length; - const totalTime = results.reduce((sum, r) => sum + (r.duration || 0), 0); - - log(colors.reset, `\n Total Tests: ${total}`); - log(colors.green, ` Passed: ${passed} ✓`); - if (failed > 0) { - log(colors.red, ` Failed: ${failed} ✗`); - } - log(colors.reset, ` Success Rate: ${((passed / total) * 100).toFixed(1)}%`); - log(colors.reset, ` Total Time: ${totalTime}ms`); - - if (failed > 0) { - log(colors.red, '\n❌ Failed Tests:'); - results.filter(r => !r.passed).forEach(r => { - log(colors.red, ` - ${r.name}`); - log(colors.red, ` ${r.error}`); - }); - } - - // Fast tests highlight - const fastTests = results.filter(r => (r.duration || 0) < 10); - if (fastTests.length > 0) { - log(colors.green, `\n⚡ Fast Tests (<10ms): ${fastTests.length}`); - } - - log(colors.cyan, '\n' + '='.repeat(55)); - - if (failed === 0) { - log(colors.green, '✅ ALL TESTS PASSED!'); - log(colors.green, 'The CCW Loop system flow state tests completed successfully.'); - } else { - log(colors.red, '❌ SOME TESTS FAILED'); - } - - log(colors.reset, ''); - - return failed === 0 ? 0 : 1; -} - -// Run tests -runAllTests().then(exitCode => { - process.exit(exitCode); -}).catch(err => { - log(colors.red, `💥 Fatal error: ${err.message}`); - console.error(err); - process.exit(1); -}); diff --git a/tests/run-loop-comprehensive-test.sh b/tests/run-loop-comprehensive-test.sh deleted file mode 100644 index 64f70d49..00000000 --- a/tests/run-loop-comprehensive-test.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# CCW Loop System - Comprehensive Test Runner - -echo "============================================" -echo "🧪 CCW LOOP SYSTEM - COMPREHENSIVE TESTS" -echo "============================================" -echo "" - -# Check if Node.js is available -if ! command -v node &> /dev/null; then - echo "❌ Error: Node.js is not installed or not in PATH" - exit 1 -fi - -# Get the project root directory -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$PROJECT_ROOT" - -echo "📁 Project Root: $PROJECT_ROOT" -echo "" - -# Run the comprehensive test -node tests/loop-comprehensive-test.js "$@" - -# Exit with the test's exit code -exit $? diff --git a/tests/run-loop-flow-test.sh b/tests/run-loop-flow-test.sh deleted file mode 100644 index 15f5e145..00000000 --- a/tests/run-loop-flow-test.sh +++ /dev/null @@ -1,261 +0,0 @@ -#!/bin/bash -# CCW Loop System - Complete Flow State Test -# Tests the entire Loop system flow including mock endpoints - -set -e - -echo "==========================================" -echo "🧪 CCW LOOP SYSTEM - FLOW STATE TEST" -echo "==========================================" - -# Colors -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Test workspace -TEST_WORKSPACE=".test-loop-workspace" -TEST_STATE_DIR="$TEST_WORKSPACE/.workflow" -TEST_TASKS_DIR="$TEST_WORKSPACE/.task" - -# Server configuration -SERVER_HOST="localhost" -SERVER_PORT=3000 -BASE_URL="http://$SERVER_HOST:$SERVER_PORT" - -# Cleanup function -cleanup() { - echo "" - echo -e "${YELLOW}🧹 Cleaning up...${NC}" - rm -rf "$TEST_WORKSPACE" - echo "✅ Cleanup complete" -} - -# Setup trap to cleanup on exit -trap cleanup EXIT - -# Step 1: Create test workspace -echo "" -echo -e "${BLUE}📁 Step 1: Creating test workspace...${NC}" -mkdir -p "$TEST_STATE_DIR" -mkdir -p "$TEST_TASKS_DIR" - -# Create test task -cat > "$TEST_TASKS_DIR/TEST-FIX-1.json" << 'EOF' -{ - "id": "TEST-FIX-1", - "title": "Test Fix Loop", - "status": "active", - "meta": { - "type": "test-fix" - }, - "loop_control": { - "enabled": true, - "description": "Test loop for flow validation", - "max_iterations": 3, - "success_condition": "state_variables.test_result === 'pass'", - "error_policy": { - "on_failure": "pause", - "max_retries": 2 - }, - "cli_sequence": [ - { - "step_id": "run_test", - "tool": "bash", - "command": "npm test" - }, - { - "step_id": "analyze", - "tool": "gemini", - "mode": "analysis", - "prompt_template": "Analyze: [run_test_stdout]" - } - ] - } -} -EOF - -echo "✅ Test workspace created: $TEST_WORKSPACE" - -# Step 2: Check if server is running -echo "" -echo -e "${BLUE}🔍 Step 2: Checking server status...${NC}" -if curl -s "$BASE_URL/api/status" > /dev/null 2>&1; then - echo -e "${GREEN}✅ Server is running${NC}" -else - echo -e "${RED}❌ Server is not running${NC}" - echo "Please start the CCW server first:" - echo " npm run dev" - exit 1 -fi - -# Step 3: Test Mock Endpoints -echo "" -echo -e "${BLUE}🧪 Step 3: Testing Mock Endpoints...${NC}" - -# Reset mock store -echo " ○ Reset mock execution store..." -RESET_RESPONSE=$(curl -s -X POST "$BASE_URL/api/test/loop/mock/reset") -if echo "$RESET_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Reset successful" -else - echo " ✗ Reset failed" - exit 1 -fi - -# Test scenario setup -echo " ○ Setup test scenario..." -SCENARIO_RESPONSE=$(curl -s -X POST "$BASE_URL/api/test/loop/run-full-scenario" \ - -H "Content-Type: application/json" \ - -d '{"scenario": "test-fix"}') -if echo "$SCENARIO_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Scenario setup successful" -else - echo " ✗ Scenario setup failed" - exit 1 -fi - -# Step 4: State Transition Tests -echo "" -echo -e "${BLUE}🔄 Step 4: State Transition Tests...${NC}" - -# Test 1: Start loop (created -> running) -echo " ○ Start loop (created -> running)..." -START_RESPONSE=$(curl -s -X POST "$BASE_URL/api/loops" \ - -H "Content-Type: application/json" \ - -d "{\"taskId\": \"TEST-FIX-1\"}") -if echo "$START_RESPONSE" | grep -q '"success":true'; then - LOOP_ID=$(echo "$START_RESPONSE" | grep -o '"loopId":"[^"]*"' | cut -d'"' -f4) - echo " ✓ Loop started: $LOOP_ID" -else - echo " ✗ Failed to start loop" - echo " Response: $START_RESPONSE" - exit 1 -fi - -# Test 2: Check loop status -echo " ○ Check loop status..." -sleep 1 # Wait for state update -STATUS_RESPONSE=$(curl -s "$BASE_URL/api/loops/$LOOP_ID") -if echo "$STATUS_RESPONSE" | grep -q '"success":true'; then - LOOP_STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"status":"[^"]*"' | cut -d'"' -f4) - echo " ✓ Loop status: $LOOP_STATUS" -else - echo " ✗ Failed to get status" -fi - -# Test 3: Pause loop -echo " ○ Pause loop..." -PAUSE_RESPONSE=$(curl -s -X POST "$BASE_URL/api/loops/$LOOP_ID/pause") -if echo "$PAUSE_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Loop paused" -else - echo " ✗ Failed to pause" -fi - -# Test 4: Resume loop -echo " ○ Resume loop..." -RESUME_RESPONSE=$(curl -s -X POST "$BASE_URL/api/loops/$LOOP_ID/resume") -if echo "$RESUME_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Loop resumed" -else - echo " ✗ Failed to resume" -fi - -# Test 5: List loops -echo " ○ List all loops..." -LIST_RESPONSE=$(curl -s "$BASE_URL/api/loops") -if echo "$LIST_RESPONSE" | grep -q '"success":true'; then - TOTAL=$(echo "$LIST_RESPONSE" | grep -o '"total":[0-9]*' | cut -d':' -f2) - echo " ✓ Found $TOTAL loop(s)" -else - echo " ✗ Failed to list loops" -fi - -# Step 5: Variable Substitution Tests -echo "" -echo -e "${BLUE}🔧 Step 5: Variable Substitution Tests...${NC}" - -# Test mock CLI execution with variable capture -echo " ○ Mock CLI execution with variables..." -EXEC_RESPONSE=$(curl -s -X POST "$BASE_URL/api/test/loop/mock/cli/execute" \ - -H "Content-Type: application/json" \ - -d "{\"loopId\": \"$LOOP_ID\", \"stepId\": \"run_test\", \"tool\": \"bash\", \"command\": \"npm test\"}") -if echo "$EXEC_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Mock execution successful" - STDOUT=$(echo "$EXEC_RESPONSE" | grep -o '"stdout":"[^"]*"' | cut -d'"' -f4) - echo " - Captured output: ${STDOUT:0:50}..." -else - echo " ✗ Mock execution failed" -fi - -# Step 6: Success Condition Tests -echo "" -echo -e "${BLUE}✅ Step 6: Success Condition Tests...${NC}" - -echo " ○ Test simple condition..." -# Simulate success condition evaluation -TEST_CONDITION="state_variables.test_result === 'pass'" -if [ "$?" -eq 0 ]; then - echo " ✓ Condition syntax valid" -fi - -echo " ○ Test regex condition..." -TEST_REGEX='state_variables.output.match(/Passed: (\d+)/)' -echo " ✓ Regex condition valid" - -# Step 7: Error Handling Tests -echo "" -echo -e "${BLUE}⚠️ Step 7: Error Handling Tests...${NC}" - -echo " ○ Test pause on error..." -PAUSE_ON_ERROR_RESPONSE=$(curl -s -X POST "$BASE_URL/api/loops/$LOOP_ID/pause") -if echo "$PAUSE_ON_ERROR_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Pause on error works" -else - echo " ⚠ Pause returned: $PAUSE_ON_ERROR_RESPONSE" -fi - -# Step 8: Execution History Tests -echo "" -echo -e "${BLUE}📊 Step 8: Execution History Tests...${NC}" - -echo " ○ Get mock execution history..." -HISTORY_RESPONSE=$(curl -s "$BASE_URL/api/test/loop/mock/history") -if echo "$HISTORY_RESPONSE" | grep -q '"success":true'; then - HISTORY_COUNT=$(echo "$HISTORY_RESPONSE" | grep -o '"total":[0-9]*' | head -1) - echo " ✓ History retrieved: $HISTORY_COUNT records" -else - echo " ✗ Failed to get history" -fi - -# Step 9: Stop loop -echo "" -echo -e "${BLUE}⏹️ Step 9: Cleanup...${NC}" - -echo " ○ Stop test loop..." -STOP_RESPONSE=$(curl -s -X POST "$BASE_URL/api/loops/$LOOP_ID/stop") -if echo "$STOP_RESPONSE" | grep -q '"success":true'; then - echo " ✓ Loop stopped" -else - echo " ⚠ Stop response: $STOP_RESPONSE" -fi - -# Final Summary -echo "" -echo "==========================================" -echo -e "${GREEN}✅ ALL TESTS PASSED${NC}" -echo "==========================================" -echo "" -echo "Test Results Summary:" -echo " ✓ State Transitions: created -> running -> paused -> resumed" -echo " ✓ Loop API Endpoints: start, status, list, pause, resume, stop" -echo " ✓ Mock CLI Execution: variable capture" -echo " ✓ Success Conditions: simple and regex" -echo " ✓ Error Handling: pause on error" -echo " ✓ Execution History: tracking and retrieval" -echo "" -echo "The CCW Loop system flow state tests completed successfully!" -echo ""