feat: implement workflow yaml validation and schema

This commit is contained in:
Alex Verkhovsky 2025-11-21 11:34:58 -07:00
parent 9d510fc075
commit 229fb8240b
84 changed files with 1062 additions and 92 deletions

View File

@ -43,9 +43,9 @@
"test": "npm run test:schemas && npm run test:install && npm run validate:bundles && npm run validate:schemas && npm run lint && npm run format:check",
"test:coverage": "c8 --reporter=text --reporter=html npm run test:schemas",
"test:install": "node test/test-installation-components.js",
"test:schemas": "node test/test-agent-schema.js",
"test:schemas": "node test/test-agent-schema.js && node test/test-workflow-schema.js",
"validate:bundles": "node tools/validate-bundles.js",
"validate:schemas": "node tools/validate-agent-schema.js"
"validate:schemas": "node tools/validate-agent-schema.js && node tools/validate-workflow-schema.js"
},
"lint-staged": {
"*.{js,cjs,mjs}": [

View File

@ -1,7 +1,6 @@
# Brainstorming Session Workflow Configuration
name: "brainstorming"
description: "Facilitate interactive brainstorming sessions using diverse creative techniques. This workflow facilitates interactive brainstorming sessions using diverse creative techniques. The session is highly interactive, with the AI acting as a facilitator to guide the user through various ideation methods to generate and refine creative solutions."
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/cis/config.yaml"
@ -27,7 +26,6 @@ standalone: true
web_bundle:
name: "brainstorming"
description: "Facilitate interactive brainstorming sessions using diverse creative techniques. This workflow facilitates interactive brainstorming sessions using diverse creative techniques. The session is highly interactive, with the AI acting as a facilitator to guide the user through various ideation methods to generate and refine creative solutions."
author: "BMad"
template: "{bmad_folder}/core/workflows/brainstorming/template.md"
instructions: "{bmad_folder}/core/workflows/brainstorming/instructions.md"
brain_techniques: "{bmad_folder}/core/workflows/brainstorming/brain-methods.csv"

View File

@ -1,7 +1,6 @@
# Party Mode - Multi-Agent Group Discussion Workflow
name: "party-mode"
description: "Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations"
author: "BMad"
# Critical data sources - manifest and config overrides
agent_manifest: "{project-root}/{bmad_folder}/_cfg/agent-manifest.csv"
@ -20,7 +19,6 @@ standalone: true
web_bundle:
name: "party-mode"
description: "Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations"
author: "BMad"
instructions: "{bmad_folder}/core/workflows/party-mode/instructions.md"
agent_manifest: "{bmad_folder}/_cfg/agent-manifest.csv"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Audit Workflow Configuration
name: "audit-workflow"
description: "Comprehensive workflow quality audit - validates structure, config standards, variable usage, bloat detection, and web_bundle completeness. Performs deep analysis of workflow.yaml, instructions.md, template.md, and web_bundle configuration against BMAD v6 standards."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Convert Legacy - BMAD v4 to v6 Converter Configuration
name: "convert-legacy"
description: "Converts legacy BMAD v4 or similar items (agents, workflows, modules) to BMad Core compliant format with proper structure and conventions"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Build Agent Workflow Configuration
name: create-agent
description: "Interactive workflow to build BMAD Core compliant agents (YAML source compiled to .md during install) with optional brainstorming, persona development, and command structure"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"
@ -42,7 +41,6 @@ standalone: true
web_bundle:
name: "create-agent"
description: "Interactive workflow to build BMAD Core compliant agents (simple, expert, or module types) with optional brainstorming for agent ideas, proper persona development, activation rules, and command structure"
author: "BMad"
web_bundle_files:
- "{bmad_folder}/bmb/workflows/create-agent/instructions.md"
- "{bmad_folder}/bmb/workflows/create-agent/checklist.md"

View File

@ -1,7 +1,6 @@
# Build Module Workflow Configuration
name: create-module
description: "Interactive workflow to build complete BMAD modules with agents, workflows, tasks, and installation infrastructure"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# {TITLE} Workflow Template Configuration
name: "{WORKFLOW_CODE}"
description: "{WORKFLOW_DESCRIPTION}"
author: "BMad"
# Critical variables load from config_source
# Add Additional Config Pulled Variables Here
@ -37,7 +36,6 @@ required_tools: #optional, can be omitted
web_bundle: #optional, can be omitted
name: "{WORKFLOW_CODE}"
description: "{WORKFLOW_DESCRIPTION}"
author: "BMad"
# Core workflow files (paths relative to {bmad_folder}/ root)
instructions: "{bmad_folder}/{module-code}/workflows/{workflow-code}/instructions.md"

View File

@ -1,7 +1,6 @@
# Build Workflow - Workflow Builder Configuration
name: create-workflow
description: "Interactive workflow builder that guides creation of new BMAD workflows with proper structure and validation for optimal human-AI collaboration. Includes optional brainstorming phase for workflow ideas and design."
author: "BMad Builder"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Edit Agent - Agent Editor Configuration
name: "edit-agent"
description: "Edit existing BMAD agents while following all best practices and conventions"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Edit Module - Module Editor Configuration
name: "edit-module"
description: "Edit existing BMAD modules (structure, agents, workflows, documentation) while following all best practices"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Edit Workflow - Workflow Editor Configuration
name: "edit-workflow"
description: "Edit existing BMAD workflows while following all best practices and conventions"
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Module Brief Workflow Configuration
name: module-brief
description: "Create a comprehensive Module Brief that serves as the blueprint for building new BMAD modules using strategic analysis and creative vision"
author: "BMad Builder"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmb/config.yaml"

View File

@ -1,7 +1,6 @@
# Brainstorm Game Workflow Configuration
name: "brainstorm-game"
description: "Facilitate game brainstorming sessions by orchestrating the CIS brainstorming workflow with game-specific context, guidance, and additional game design techniques."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "brainstorm-game"
description: "Facilitate game brainstorming sessions by orchestrating the CIS brainstorming workflow with game-specific context, guidance, and additional game design techniques."
author: "BMad"
instructions: "{bmad_folder}/bmgd/workflows/1-preproduction/brainstorm-game/instructions.md"
template: false
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Game Brief - Interactive Workflow Configuration
name: game-brief
description: "Interactive game brief creation workflow that guides users through defining their game vision with multiple input sources and conversational collaboration"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
@ -26,7 +25,6 @@ standalone: true
web_bundle:
name: "game-brief"
description: "Interactive game brief creation workflow that guides users through defining their game vision with multiple input sources and conversational collaboration"
author: "BMad"
instructions: "{bmad_folder}/bmgd/workflows/1-preproduction/game-brief/instructions.md"
validation: "{bmad_folder}/bmgd/workflows/1-preproduction/game-brief/checklist.md"
template: "{bmad_folder}/bmgd/workflows/1-preproduction/game-brief/template.md"

View File

@ -1,7 +1,6 @@
# Game Design Document (GDD) Workflow
name: gdd
description: "Game Design Document workflow for all game project levels - from small prototypes to full AAA games. Generates comprehensive GDD with game mechanics, systems, progression, and implementation guidance."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
@ -49,7 +48,6 @@ standalone: true
web_bundle:
name: "gdd"
description: "Game Design Document workflow for all game project levels - from small prototypes to full AAA games. Generates comprehensive GDD with game mechanics, systems, progression, and implementation guidance."
author: "BMad"
instructions: "{bmad_folder}/bmgd/workflows/2-design/gdd/instructions-gdd.md"
web_bundle_files:
- "{bmad_folder}/bmgd/workflows/2-design/gdd/instructions-gdd.md"

View File

@ -1,7 +1,6 @@
# Narrative Design Workflow
name: narrative
description: "Narrative design workflow for story-driven games and applications. Creates comprehensive narrative documentation including story structure, character arcs, dialogue systems, and narrative implementation guidance."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
@ -25,7 +24,6 @@ standalone: true
web_bundle:
name: "narrative"
description: "Narrative design workflow for story-driven games and applications. Creates comprehensive narrative documentation including story structure, character arcs, dialogue systems, and narrative implementation guidance."
author: "BMad"
instructions: "{bmad_folder}/bmgd/workflows/2-design/narrative/instructions-narrative.md"
web_bundle_files:
- "{bmad_folder}/bmgd/workflows/2-design/narrative/instructions-narrative.md"

View File

@ -1,7 +1,6 @@
# Game Architecture Workflow Configuration
name: game-architecture
description: "Collaborative game architecture workflow for AI-agent consistency. Intelligent, adaptive conversation that produces a decision-focused game architecture document covering engine, systems, networking, and technical design optimized for game development."
author: "BMad"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
@ -53,7 +52,6 @@ pattern_categories: "{installed_path}/pattern-categories.csv"
default_output_file: "{output_folder}/game-architecture.md"
# Workflow metadata
version: "1.3.2"
replaces: "architecture"
paradigm: "facilitation-driven"
execution_time: "30-90 minutes depending on user skill level"

View File

@ -1,7 +1,6 @@
# Review Story Workflow
name: code-review
description: "Perform a Senior Developer code review on a completed story flagged Ready for Review, leveraging story-context, epic tech-spec, repo docs, MCP servers for latest best-practices, and web search as fallback. Appends structured review notes to the story."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Correct Course - Sprint Change Management Workflow
name: "correct-course"
description: "Navigate significant changes during sprint execution by analyzing impact, proposing solutions, and routing for implementation"
author: "BMad Method"
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
output_folder: "{config_source}:output_folder"

View File

@ -1,6 +1,5 @@
name: create-story
description: "Create the next user story markdown from epics/PRD and architecture, using a standard template and saving to the stories folder"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,6 +1,5 @@
name: dev-story
description: "Execute a story by implementing tasks/subtasks, writing tests, validating, and updating the story file per acceptance criteria"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,6 +1,5 @@
name: epic-tech-context
description: "Generate a comprehensive Technical Specification from PRD and Architecture with acceptance criteria and traceability mapping"
author: "BMAD BMM"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Retrospective - Epic Completion Review Workflow
name: "retrospective"
description: "Run after epic completion to review overall success, extract lessons learned, and explore if new information emerged that might impact the next epic"
author: "BMad"
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"
output_folder: "{config_source}:output_folder"

View File

@ -1,6 +1,5 @@
name: sprint-planning
description: "Generate and manage the sprint status tracking file for Phase 4 implementation, extracting all epics and stories from epic files and tracking their status through the development lifecycle"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Context Creation Workflow
name: story-context
description: "Assemble a dynamic Story Context XML by pulling latest documentation and existing code/library artifacts relevant to a drafted story"
author: "BMad"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Done Workflow (DEV Agent)
name: story-done
description: "Marks a story as done (DoD complete) and moves it from its current status → DONE in the status file. Advances the story queue. Simple status-update workflow with no searching required."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Ready Workflow (SM Agent)
name: story-ready
description: "Marks a drafted story as ready for development and moves it from TODO → IN PROGRESS in the status file. Simple status-update workflow with no searching required."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmgd/config.yaml"

View File

@ -1,7 +1,6 @@
# Brainstorm Project Workflow Configuration
name: "brainstorm-project"
description: "Facilitate project brainstorming sessions by orchestrating the CIS brainstorming workflow with project-specific context and guidance."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -28,7 +27,6 @@ standalone: true
web_bundle:
name: "brainstorm-project"
description: "Facilitate project brainstorming sessions by orchestrating the CIS brainstorming workflow with project-specific context and guidance."
author: "BMad"
instructions: "{bmad_folder}/bmm/workflows/1-analysis/brainstorm-project/instructions.md"
template: false
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Domain Research Workflow Configuration
name: domain-research
description: "Collaborative exploration of domain-specific requirements, regulations, and patterns for complex projects"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "domain-research"
description: "Collaborative exploration of domain-specific requirements, regulations, and patterns for complex projects"
author: "BMad"
# Core workflow files ({bmad_folder}/-relative paths)
instructions: "{bmad_folder}/bmm/workflows/1-analysis/domain-research/instructions.md"

View File

@ -1,7 +1,6 @@
# Product Brief - Interactive Workflow Configuration
name: product-brief
description: "Interactive product brief creation workflow that guides users through defining their product vision with multiple input sources and conversational collaboration"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -47,7 +46,6 @@ standalone: true
web_bundle:
name: "product-brief"
description: "Interactive product brief creation workflow that guides users through defining their product vision with multiple input sources and conversational collaboration"
author: "BMad"
instructions: "{bmad_folder}/bmm/workflows/1-analysis/product-brief/instructions.md"
validation: "{bmad_folder}/bmm/workflows/1-analysis/product-brief/checklist.md"
template: "{bmad_folder}/bmm/workflows/1-analysis/product-brief/template.md"

View File

@ -1,7 +1,6 @@
# Research Workflow - Multi-Type Research System
name: research
description: "Adaptive research workflow supporting multiple research types: market research, deep research prompt generation, technical/architecture evaluation, competitive intelligence, user research, and domain analysis"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -46,7 +45,6 @@ standalone: true
web_bundle:
name: "research"
description: "Adaptive research workflow supporting multiple research types: market research, deep research prompt generation, technical/architecture evaluation, competitive intelligence, user research, and domain analysis"
author: "BMad"
instructions: "{bmad_folder}/bmm/workflows/1-analysis/research/instructions-router.md" # Router loads specific instruction sets
validation: "{bmad_folder}/bmm/workflows/1-analysis/research/checklist.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Create UX Design Workflow Configuration
name: create-ux-design
description: "Collaborative UX design facilitation workflow that creates exceptional user experiences through visual exploration and informed decision-making. Unlike template-driven approaches, this workflow facilitates discovery, generates visual options, and collaboratively designs the UX with the user at every step."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -62,7 +61,6 @@ standalone: true
web_bundle:
name: "create-ux-design"
description: "Collaborative UX design facilitation workflow that creates exceptional user experiences through visual exploration and informed decision-making. Unlike template-driven approaches, this workflow facilitates discovery, generates visual options, and collaboratively designs the UX with the user at every step."
author: "BMad"
# Core workflow files ({bmad_folder}/-relative paths)
instructions: "{bmad_folder}/bmm/workflows/2-plan-workflows/create-ux-design/instructions.md"

View File

@ -1,7 +1,6 @@
# Product Requirements Document (PRD) Workflow
name: prd
description: "Unified PRD workflow for BMad Method and Enterprise Method tracks. Produces strategic PRD and tactical epic breakdown. Hands off to architecture workflow for technical design. Note: Quick Flow track uses tech-spec workflow."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -54,7 +53,6 @@ standalone: true
web_bundle:
name: "prd"
description: "Unified PRD workflow for BMad Method and Enterprise Method tracks. Produces strategic PRD and tactical epic breakdown. Hands off to architecture workflow for technical design. Note: Quick Flow track uses tech-spec workflow."
author: "BMad"
instructions: "{bmad_folder}/bmm/workflows/2-plan-workflows/prd/instructions.md"
validation: "{bmad_folder}/bmm/workflows/2-plan-workflows/prd/checklist.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Technical Specification
name: tech-spec
description: "Technical specification workflow for quick-flow projects. Creates focused tech spec and generates epic + stories (1 story for simple changes, 2-5 stories for features). Tech-spec only - no PRD needed."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Architecture Workflow Configuration
name: architecture
description: "Collaborative architectural decision facilitation for AI-agent consistency. Replaces template-driven architecture with intelligent, adaptive conversation that produces a decision-focused architecture document optimized for preventing agent conflicts."
author: "BMad"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -56,7 +55,6 @@ standalone: true
web_bundle:
name: "architecture"
description: "Collaborative architectural decision facilitation for AI-agent consistency. Replaces template-driven architecture with intelligent, adaptive conversation that produces a decision-focused architecture document optimized for preventing agent conflicts."
author: "BMad"
# Core workflow files ({bmad_folder}/-relative paths)
instructions: "{bmad_folder}/bmm/workflows/3-solutioning/architecture/instructions.md"

View File

@ -1,7 +1,6 @@
# Epic and Story Decomposition Workflow
name: create-epics-and-stories
description: "Transform PRD requirements into bite-sized stories organized into deliverable functional epics. This workflow takes a Product Requirements Document (PRD) and breaks it down into epics and user stories that can be easily assigned to development teams. It ensures that all functional requirements are captured in a structured format, making it easier for teams to understand and implement the necessary features."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
@ -55,7 +54,6 @@ standalone: true
web_bundle:
name: "create-epics-and-stories"
description: "Transform PRD requirements into bite-sized stories organized in epics for 200k context dev agents"
author: "BMad"
instructions: "{bmad_folder}/bmm/workflows/3-solutioning/create-epics-and-stories/instructions.md"
template: "{bmad_folder}/bmm/workflows/3-solutioning/create-epics-and-stories/epics-template.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Implementation Readiness - Workflow Configuration
name: implementation-readiness
description: "Validate that PRD, UX Design, Architecture, Epics and Stories are complete and aligned before Phase 4 implementation. Ensures all artifacts cover the MVP requirements with no gaps or contradictions."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Review Story Workflow
name: code-review
description: "Perform a Senior Developer code review on a completed story flagged Ready for Review, leveraging story-context, epic tech-spec, repo docs, MCP servers for latest best-practices, and web search as fallback. Appends structured review notes to the story."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Correct Course - Sprint Change Management Workflow
name: "correct-course"
description: "Navigate significant changes during sprint execution by analyzing impact, proposing solutions, and routing for implementation"
author: "BMad Method"
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
output_folder: "{config_source}:output_folder"

View File

@ -1,6 +1,5 @@
name: create-story
description: "Create the next user story markdown from epics/PRD and architecture, using a standard template and saving to the stories folder"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: dev-story
description: "Execute a story by implementing tasks/subtasks, writing tests, validating, and updating the story file per acceptance criteria"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: epic-tech-context
description: "Generate a comprehensive Technical Specification from PRD and Architecture with acceptance criteria and traceability mapping"
author: "BMAD BMM"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Retrospective - Epic Completion Review Workflow
name: "retrospective"
description: "Run after epic completion to review overall success, extract lessons learned, and explore if new information emerged that might impact the next epic"
author: "BMad"
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"
output_folder: "{config_source}:output_folder"

View File

@ -1,6 +1,5 @@
name: sprint-planning
description: "Generate and manage the sprint status tracking file for Phase 4 implementation, extracting all epics and stories from epic files and tracking their status through the development lifecycle"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Context Creation Workflow
name: story-context
description: "Assemble a dynamic Story Context XML by pulling latest documentation and existing code/library artifacts relevant to a drafted story"
author: "BMad"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Done Workflow (DEV Agent)
name: story-done
description: "Marks a story as done (DoD complete) and moves it from its current status → DONE in the status file. Advances the story queue. Simple status-update workflow with no searching required."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Story Ready Workflow (SM Agent)
name: story-ready
description: "Marks a drafted story as ready for development and moves it from TODO → IN PROGRESS in the status file. Simple status-update workflow with no searching required."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: create-excalidraw-dataflow
description: "Create data flow diagrams (DFD) in Excalidraw format"
author: "BMad"
# Config values
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: create-excalidraw-diagram
description: "Create system architecture diagrams, ERDs, UML diagrams, or general technical diagrams in Excalidraw format"
author: "BMad"
# Config values
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: create-excalidraw-flowchart
description: "Create a flowchart visualization in Excalidraw format for processes, pipelines, or logic flows"
author: "BMad"
# Config values
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,6 +1,5 @@
name: create-excalidraw-wireframe
description: "Create website or app wireframes in Excalidraw format"
author: "BMad"
# Config values
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,8 +1,6 @@
# Document Project Workflow Configuration
name: "document-project"
version: "1.2.0"
description: "Analyzes and documents brownfield projects by scanning codebase, architecture, and patterns to create comprehensive reference documentation for AI-assisted development"
author: "BMad"
# Critical variables
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: atdd
name: testarch-atdd
description: "Generate failing acceptance tests before implementation using TDD red-green-refactor cycle"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: automate
name: testarch-automate
description: "Expand test automation coverage after implementation or analyze existing codebase to generate comprehensive test suite"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: ci
name: testarch-ci
description: "Scaffold CI/CD quality pipeline with test execution, burn-in loops, and artifact collection"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: framework
name: testarch-framework
description: "Initialize production-ready test framework architecture (Playwright or Cypress) with fixtures, helpers, and configuration"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: nfr-assess
name: testarch-nfr
description: "Assess non-functional requirements (performance, security, reliability, maintainability) before release with evidence-based validation"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: test-design
name: testarch-test-design
description: "Dual-mode workflow: (1) System-level testability review in Solutioning phase, or (2) Epic-level test planning in Implementation phase. Auto-detects mode based on project phase."
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: test-review
name: testarch-test-review
description: "Review test quality using comprehensive knowledge base and best practices validation"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Test Architect workflow: trace (enhanced with gate decision)
name: testarch-trace
description: "Generate requirements-to-tests traceability matrix, analyze coverage, and make quality gate decision (PASS/CONCERNS/FAIL/WAIVED)"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Workflow Init - Initial Project Setup
name: workflow-init
description: "Initialize a new BMM project by determining level, type, and creating workflow path"
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Workflow Status - Master Router and Status Tracker
name: workflow-status
description: 'Lightweight status checker - answers "what should I do now?" for any agent. Reads YAML status file for workflow tracking. Use workflow-init for new projects.'
author: "BMad"
# Critical variables from config
config_source: "{project-root}/{bmad_folder}/bmm/config.yaml"

View File

@ -1,7 +1,6 @@
# Design Thinking Workflow Configuration
name: "design-thinking"
description: "Guide human-centered design processes using empathy-driven methodologies. This workflow walks through the design thinking phases - Empathize, Define, Ideate, Prototype, and Test - to create solutions deeply rooted in user needs."
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/cis/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "design-thinking"
description: "Guide human-centered design processes using empathy-driven methodologies. This workflow walks through the design thinking phases - Empathize, Define, Ideate, Prototype, and Test - to create solutions deeply rooted in user needs."
author: "BMad"
instructions: "{bmad_folder}/cis/workflows/design-thinking/instructions.md"
template: "{bmad_folder}/cis/workflows/design-thinking/template.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Innovation Strategy Workflow Configuration
name: "innovation-strategy"
description: "Identify disruption opportunities and architect business model innovation. This workflow guides strategic analysis of markets, competitive dynamics, and business model innovation to uncover sustainable competitive advantages and breakthrough opportunities."
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/cis/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "innovation-strategy"
description: "Identify disruption opportunities and architect business model innovation. This workflow guides strategic analysis of markets, competitive dynamics, and business model innovation to uncover sustainable competitive advantages and breakthrough opportunities."
author: "BMad"
instructions: "{bmad_folder}/cis/workflows/innovation-strategy/instructions.md"
template: "{bmad_folder}/cis/workflows/innovation-strategy/template.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Problem Solving Workflow Configuration
name: "problem-solving"
description: "Apply systematic problem-solving methodologies to crack complex challenges. This workflow guides through problem diagnosis, root cause analysis, creative solution generation, evaluation, and implementation planning using proven frameworks."
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/cis/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "problem-solving"
description: "Apply systematic problem-solving methodologies to crack complex challenges. This workflow guides through problem diagnosis, root cause analysis, creative solution generation, evaluation, and implementation planning using proven frameworks."
author: "BMad"
instructions: "{bmad_folder}/cis/workflows/problem-solving/instructions.md"
template: "{bmad_folder}/cis/workflows/problem-solving/template.md"
web_bundle_files:

View File

@ -1,7 +1,6 @@
# Storytelling Workflow Configuration
name: "storytelling"
description: "Craft compelling narratives using proven story frameworks and techniques. This workflow guides users through structured narrative development, applying appropriate story frameworks to create emotionally resonant and engaging stories for any purpose."
author: "BMad"
# Critical variables load from config_source
config_source: "{project-root}/{bmad_folder}/cis/config.yaml"
@ -29,7 +28,6 @@ standalone: true
web_bundle:
name: "storytelling"
description: "Craft compelling narratives using proven story frameworks and techniques. This workflow guides users through structured narrative development, applying appropriate story frameworks to create emotionally resonant and engaging stories for any purpose."
author: "BMad"
instructions: "{bmad_folder}/cis/workflows/storytelling/instructions.md"
template: "{bmad_folder}/cis/workflows/storytelling/template.md"
web_bundle_files:

View File

@ -0,0 +1,9 @@
# Expected: FAIL
# Error code: custom
# Error path: name
# Error message: kebab-case
name: InvalidWorkflow_Name
description: Workflow with invalid name format
author: Test Author
instructions: "{project-root}/path/to/instructions.md"

View File

@ -0,0 +1,10 @@
# Expected: FAIL
# Error code: invalid_type
# Error path: standalone
# Error message: boolean
name: test-workflow
description: Test workflow
author: Test Author
instructions: "{project-root}/path/to/instructions.md"
standalone: "yes"

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: invalid_union
# Error message: Invalid input
name: test-workflow
description: Test workflow
instructions: "{project-root}/path/instructions.md"
template: true

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: invalid_union
# Error message: Invalid input
name: test-workflow
description: Test workflow
instructions: "{project-root}/path/instructions.md"
web_bundle: true

View File

@ -0,0 +1,10 @@
# Expected: FAIL
# Error code: custom
# Error message: Malformed config reference
name: test-workflow
description: Test workflow with malformed config reference
author: Test Author
instructions: "{project-root}/path/instructions.md"
config_source: "{project-root}/.bmad/config.yaml"
output_folder: "{config_source}output_folder"

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: invalid_type
# Error path: description
# Error message: expected string, received undefined
name: test-workflow
author: Test Author
instructions: "{project-root}/path/to/instructions.md"

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: invalid_type
# Error path: instructions
# Error message: expected string, received undefined
name: test-workflow
description: Test workflow
author: Test Author

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: invalid_type
# Error path: name
# Error message: expected string, received undefined
description: Missing name field
author: Test Author
instructions: "{project-root}/path/to/instructions.md"

View File

@ -0,0 +1,9 @@
# Expected: FAIL
# Error code: custom
# Error message: unclosed braces
name: test-workflow
description: Test workflow with unclosed brace
author: Test Author
instructions: "{project-root}/path/instructions.md"
output_folder: "{project-root/docs"

View File

@ -0,0 +1,10 @@
# Expected: FAIL
# Error code: custom
# Error message: Unknown config variable
name: test-workflow
description: Test workflow with typo in config variable
author: Test Author
instructions: "{project-root}/path/instructions.md"
config_source: "{project-root}/.bmad/config.yaml"
output_folder: "{config_source}:ouput_folder"

View File

@ -0,0 +1,8 @@
# Expected: FAIL
# Error code: custom
# Error message: Unknown path placeholder
name: test-workflow
description: Test workflow with typo in placeholder
author: Test Author
instructions: "{project-rot}/path/instructions.md"

View File

@ -0,0 +1,41 @@
# Expected: PASS
# Valid complex workflow with all optional fields
name: complex-workflow
description: A complex workflow with all features
standalone: true
template: "{installed_path}/template.md"
instructions: "{installed_path}/instructions.md"
validation: "{installed_path}/checklist.md"
config_source: "{project-root}/.bmad/bmm/config.yaml"
installed_path: "{project-root}/.bmad/bmm/workflows/complex-workflow"
default_output_file: "{output_folder}/output.md"
autonomous: false
input_file_patterns:
architecture:
description: "System architecture"
whole: "{output_folder}/*architecture*.md"
sharded: "{output_folder}/*architecture*/*.md"
load_strategy: "FULL_LOAD"
epics:
description: "Epic files"
sharded_single: "{output_folder}/*epic*/epic-{{epic_num}}.md"
load_strategy: "SELECTIVE_LOAD"
exit_triggers:
- "*exit"
- "*quit"
web_bundle:
name: "complex-workflow"
description: "Web bundle for complex workflow"
instructions: "{bmad_folder}/workflows/complex/instructions.md"
web_bundle_files:
- "{bmad_folder}/workflows/complex/instructions.md"
- "{bmad_folder}/workflows/complex/template.md"
# Custom workflow variables
output_folder: "{config_source}:output_folder"
user_name: "{config_source}:user_name"
date: system-generated

View File

@ -0,0 +1,7 @@
# Expected: PASS
# Valid minimal workflow with only required fields
name: minimal-workflow
description: A minimal valid workflow
author: Test Author
instructions: "{project-root}/path/to/instructions.md"

View File

@ -0,0 +1,301 @@
/**
* Workflow Schema Validation Test Runner
*
* Runs all test fixtures and verifies expected outcomes.
* Reports pass/fail for each test and overall coverage statistics.
*
* Usage: node test/test-workflow-schema.js
* Exit codes: 0 = all tests pass, 1 = test failures
*
* TODO: Extract shared test utilities (parseTestMetadata, validateError, parsePathString, colors)
* into a common test helper module to reduce duplication with test-agent-schema.js
*/
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('js-yaml');
const { validateWorkflowFile } = require('../tools/schema/workflow.js');
const { glob } = require('glob');
// ANSI color codes
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
yellow: '\u001B[33m',
blue: '\u001B[34m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
/**
* Parse test metadata from YAML comments
* @param {string} filePath
* @returns {{shouldPass: boolean, errorExpectation?: object}}
*/
function parseTestMetadata(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
let shouldPass = true;
const errorExpectation = {};
for (const line of lines) {
if (line.includes('Expected: PASS')) {
shouldPass = true;
} else if (line.includes('Expected: FAIL')) {
shouldPass = false;
}
// Parse error metadata
const codeMatch = line.match(/^# Error code: (.+)$/);
if (codeMatch) {
errorExpectation.code = codeMatch[1].trim();
}
const pathMatch = line.match(/^# Error path: (.+)$/);
if (pathMatch) {
errorExpectation.path = pathMatch[1].trim();
}
const messageMatch = line.match(/^# Error message: (.+)$/);
if (messageMatch) {
errorExpectation.message = messageMatch[1].trim();
}
}
return {
shouldPass,
errorExpectation: Object.keys(errorExpectation).length > 0 ? errorExpectation : null,
};
}
/**
* Convert dot-notation path string to array
* e.g., "name" => ["name"]
*/
function parsePathString(pathString) {
return pathString
.replaceAll(/\[(\d+)\]/g, '.$1')
.split('.')
.map((part) => {
const num = parseInt(part, 10);
return isNaN(num) ? part : num;
});
}
/**
* Validate error against expectations
* @param {object} error - Zod error issue
* @param {object} expectation - Expected error structure
* @returns {{valid: boolean, reason?: string}}
*/
function validateError(error, expectation) {
if (expectation.code && error.code !== expectation.code) {
return { valid: false, reason: `Expected code "${expectation.code}", got "${error.code}"` };
}
if (expectation.path) {
const expectedPath = parsePathString(expectation.path);
const actualPath = error.path;
if (JSON.stringify(expectedPath) !== JSON.stringify(actualPath)) {
return {
valid: false,
reason: `Expected path ${JSON.stringify(expectedPath)}, got ${JSON.stringify(actualPath)}`,
};
}
}
if (expectation.message && !error.message.includes(expectation.message)) {
return {
valid: false,
reason: `Expected message to include "${expectation.message}", got "${error.message}"`,
};
}
return { valid: true };
}
/**
* Run a single test case
* @param {string} filePath
* @returns {{passed: boolean, message: string}}
*/
function runTest(filePath) {
const metadata = parseTestMetadata(filePath);
const { shouldPass, errorExpectation } = metadata;
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
let workflowData;
try {
workflowData = yaml.load(fileContent);
} catch (parseError) {
if (shouldPass) {
return {
passed: false,
message: `Expected PASS but got YAML parse error: ${parseError.message}`,
};
}
return {
passed: true,
message: 'Got expected YAML parse error',
};
}
const result = validateWorkflowFile(filePath, workflowData);
if (result.success && shouldPass) {
return {
passed: true,
message: 'Validation passed as expected',
};
}
if (!result.success && !shouldPass) {
const actualError = result.error.issues[0];
if (errorExpectation) {
const validation = validateError(actualError, errorExpectation);
if (!validation.valid) {
return {
passed: false,
message: `Error validation failed: ${validation.reason}`,
};
}
return {
passed: true,
message: `Got expected error (${errorExpectation.code}): ${actualError.message}`,
};
}
return {
passed: true,
message: `Got expected validation error: ${actualError?.message}`,
};
}
if (result.success && !shouldPass) {
return {
passed: false,
message: 'Expected validation to FAIL but it PASSED',
};
}
if (!result.success && shouldPass) {
return {
passed: false,
message: `Expected validation to PASS but it FAILED: ${result.error.issues[0]?.message}`,
};
}
return {
passed: false,
message: 'Unexpected test state',
};
} catch (error) {
return {
passed: false,
message: `Test execution error: ${error.message}`,
};
}
}
/**
* Main test runner
*/
async function main() {
console.log(`${colors.cyan}╔═══════════════════════════════════════════════════════════╗${colors.reset}`);
console.log(`${colors.cyan}║ Workflow Schema Validation Test Suite ║${colors.reset}`);
console.log(`${colors.cyan}╚═══════════════════════════════════════════════════════════╝${colors.reset}\n`);
// Find all test fixtures
const testFiles = await glob('test/fixtures/workflow-schema/**/*.workflow.yaml', {
cwd: path.join(__dirname, '..'),
absolute: true,
});
if (testFiles.length === 0) {
console.log(`${colors.yellow}⚠️ No test fixtures found${colors.reset}`);
process.exit(0);
}
console.log(`Found ${colors.cyan}${testFiles.length}${colors.reset} test fixture(s)\n`);
// Group tests by category
const categories = {};
for (const testFile of testFiles) {
const relativePath = path.relative(path.join(__dirname, 'fixtures/workflow-schema'), testFile);
const parts = relativePath.split(path.sep);
const validInvalid = parts[0]; // 'valid' or 'invalid'
const category = parts[1] || 'general'; // category folder or 'general'
const categoryKey = `${validInvalid}/${category}`;
if (!categories[categoryKey]) {
categories[categoryKey] = [];
}
categories[categoryKey].push(testFile);
}
// Run tests by category
let totalTests = 0;
let passedTests = 0;
const failures = [];
for (const [categoryKey, files] of Object.entries(categories).sort()) {
const [validInvalid, category] = categoryKey.split('/');
const categoryLabel = category.replaceAll('-', ' ').toUpperCase();
const validLabel = validInvalid === 'valid' ? '✅' : '❌';
console.log(`${colors.blue}${validLabel} ${categoryLabel} (${validInvalid})${colors.reset}`);
for (const testFile of files) {
totalTests++;
const testName = path.basename(testFile, '.workflow.yaml');
const result = runTest(testFile);
if (result.passed) {
passedTests++;
console.log(` ${colors.green}${colors.reset} ${testName} ${colors.dim}${result.message}${colors.reset}`);
} else {
console.log(` ${colors.red}${colors.reset} ${testName} ${colors.red}${result.message}${colors.reset}`);
failures.push({
file: path.relative(process.cwd(), testFile),
message: result.message,
});
}
}
console.log('');
}
// Summary
console.log(`${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
console.log(`${colors.cyan}Test Results:${colors.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` Passed: ${colors.green}${passedTests}${colors.reset}`);
console.log(` Failed: ${passedTests === totalTests ? colors.green : colors.red}${totalTests - passedTests}${colors.reset}`);
console.log(`${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
// Report failures
if (failures.length > 0) {
console.log(`${colors.red}❌ FAILED TESTS:${colors.reset}\n`);
for (const failure of failures) {
console.log(`${colors.red}${colors.reset} ${failure.file}`);
console.log(` ${failure.message}\n`);
}
process.exit(1);
}
console.log(`${colors.green}✨ All tests passed!${colors.reset}\n`);
process.exit(0);
}
// Run
main().catch((error) => {
console.error(`${colors.red}Fatal error:${colors.reset}`, error);
process.exit(1);
});

View File

@ -127,8 +127,7 @@ class WorkflowCommandGenerator {
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('{bmad_folder}', this.bmadFolderName)
.replaceAll('{{interactive}}', workflow.interactive)
.replaceAll('{{author}}', workflow.author || 'BMAD');
.replaceAll('{{interactive}}', workflow.interactive);
}
/**

449
tools/schema/workflow.js Normal file
View File

@ -0,0 +1,449 @@
// Zod schema definition for workflow.yaml files
const { z } = require('zod');
const KEBAB_CASE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
// Public API ---------------------------------------------------------------
/**
* Validate a workflow YAML payload against the schema.
* Exposed as the single public entry point, so callers do not reach into schema internals.
*
* @param {string} filePath Path to the workflow file (for error reporting).
* @param {unknown} workflowYaml Parsed YAML content.
* @returns {object} Result with success, data/error, and warnings array
*/
function validateWorkflowFile(filePath, workflowYaml) {
const schema = workflowSchema();
const result = schema.safeParse(workflowYaml);
const warnings = collectUnknownFieldWarnings(workflowYaml);
return { ...result, warnings };
}
module.exports = { validateWorkflowFile };
// Canonical lists of known variable references (extracted from 65 real workflows)
// These are used to validate variable references and catch typos
const KNOWN_PATH_PLACEHOLDERS = [
'agent_filename',
'bmad_folder',
'custom_agent_location',
'custom_module_location',
'custom_workflow_location',
'date',
'epic_id',
'epic_num',
'game_name',
'installed_path',
'item_name',
'item_type',
'module_code',
'output_folder',
'path',
'prev_epic_num',
'project-root',
'project_name',
'reference_agents',
'research_type',
'shared_path',
'sprint_artifacts',
'status_file',
'story_dir',
'story_id',
'story_key',
'story_path',
'target_module',
'test_dir',
'timestamp',
'workflow_name',
'workflow_template_path',
];
const KNOWN_CONFIG_VARIABLES = [
'communication_language',
'custom_agent_location',
'custom_module_location',
'custom_workflow_location',
'document_output_language',
'game_dev_experience',
'output_folder',
'project_name',
'sprint_artifacts',
'user_name',
'user_skill_level',
];
const KNOWN_TEMPLATE_VARIABLES = [
'agent_filename',
'date',
'epic_id',
'epic_num',
'game_name',
'item_name',
'item_type',
'module_code',
'prev_epic_num',
'project_name',
'research_type',
'story_key',
'target_module',
'workflow_name',
];
// Internal helpers ---------------------------------------------------------
/**
* Build a Zod schema for validating a single workflow definition.
*
* @returns {import('zod').ZodSchema} Configured Zod schema instance.
*/
function workflowSchema() {
return z
.object({
name: createKebabCaseString('name'),
description: createNonEmptyString('description'),
standalone: z.boolean().optional(),
template: z.union([z.literal(false), z.string()]).optional(),
instructions: createNonEmptyString('instructions'),
validation: z.string().optional(),
config_source: z.string().optional(),
installed_path: z.string().optional(),
output_folder: z.string().optional(),
user_name: z.string().optional(),
communication_language: z.string().optional(),
date: z.string().optional(),
brain_techniques: z.string().optional(),
validation_output_file: z.string().optional(),
required_tools: z.array(z.union([z.string(), z.object({ description: z.string().optional() }).catchall(z.unknown())])).optional(),
default_output_file: z.string().optional(),
autonomous: z.boolean().optional(),
web_bundle: z.union([z.literal(false), buildWebBundleSchema()]).optional(),
input_file_patterns: z.record(z.string(), buildInputFilePatternSchema()).optional(),
exit_triggers: z.array(createNonEmptyString('exit_triggers[]')).optional(),
// Common optional fields (often injected variables or context)
document_output_language: z.string().optional(),
user_skill_level: z.string().optional(),
sprint_artifacts: z.string().optional(),
sprint_status: z.string().optional(),
game_dev_experience: z.string().optional(),
variables: z.record(z.string(), z.unknown()).optional(),
checklist: z.string().optional(),
story_dir: z.string().optional(),
story_file: z.string().optional(),
context_file: z.string().optional(),
game_types_csv: z.string().optional(),
game_type_guides: z.string().optional(),
game_context: z.string().optional(),
game_brain_methods: z.string().optional(),
core_brainstorming: z.string().optional(),
decision_catalog: z.string().optional(),
architecture_patterns: z.string().optional(),
pattern_categories: z.string().optional(),
replaces: z.string().optional(),
paradigm: z.string().optional(),
execution_time: z.string().optional(),
features: z.array(z.string()).optional(),
project_context: z.string().optional(),
// Additional common fields
tags: z.array(z.string()).optional(),
execution_hints: z.record(z.string(), z.unknown()).optional(),
project_name: z.string().optional(),
library: z.string().optional(),
helpers: z.string().optional(),
templates: z.string().optional(),
json_validation: z.string().optional(),
shared_path: z.string().optional(),
})
.catchall(z.unknown()) // Allow unknown fields for dynamic workflow variables
.superRefine((data, ctx) => {
// Validate variable references in all string values
validateVariableReferences(data, [], ctx);
});
}
/**
* Schema for web_bundle object when it's not a boolean.
*/
function buildWebBundleSchema() {
return z
.object({
name: createNonEmptyString('web_bundle.name').optional(),
description: createNonEmptyString('web_bundle.description').optional(),
instructions: createNonEmptyString('web_bundle.instructions').optional(),
web_bundle_files: z.array(createNonEmptyString('web_bundle.web_bundle_files[]')).optional(),
validation: z.string().optional(),
template: z.union([z.literal(false), z.string()]).optional(),
existing_workflows: z.array(z.unknown()).optional(),
})
.catchall(z.unknown()); // Allow custom fields like agent_manifest, author, etc.
}
/**
* Schema for individual input_file_patterns entries.
*/
function buildInputFilePatternSchema() {
return z
.object({
description: createNonEmptyString('input_file_patterns[].description').optional(),
whole: z.string().optional(),
sharded: z.string().optional(),
sharded_index: z.string().optional(),
sharded_single: z.string().optional(),
pattern: z.string().optional(), // Legacy support
load_strategy: z.enum(['FULL_LOAD', 'SELECTIVE_LOAD', 'INDEX_GUIDED']).optional(),
})
.strict(); // Warn about unknown fields - patterns have a defined schema
}
/**
* Recursively validate variable references in all string values.
* Checks for:
* 1. Malformed syntax (unclosed braces, invalid patterns)
* 2. Unknown placeholders/variables (warns about potential typos)
*
* @param {any} obj - Object to validate
* @param {Array} path - Current path in object tree
* @param {import('zod').RefinementCtx} ctx - Zod context for adding issues
*/
function collectUnknownFieldWarnings(workflowYaml) {
const warnings = [];
if (typeof workflowYaml === 'object' && workflowYaml !== null) {
const knownFields = new Set([
'name',
'description',
'standalone',
'template',
'instructions',
'validation',
'config_source',
'installed_path',
'output_folder',
'user_name',
'communication_language',
'date',
'brain_techniques',
'validation_output_file',
'required_tools',
'default_output_file',
'autonomous',
'web_bundle',
'input_file_patterns',
'exit_triggers',
// Added common fields
'document_output_language',
'user_skill_level',
'sprint_artifacts',
'sprint_status',
'game_dev_experience',
'variables',
'checklist',
'story_dir',
'story_file',
'context_file',
'game_types_csv',
'game_type_guides',
'game_context',
'game_brain_methods',
'core_brainstorming',
'decision_catalog',
'architecture_patterns',
'pattern_categories',
'replaces',
'paradigm',
'execution_time',
'features',
'project_context',
// Additional common fields
'tags',
'execution_hints',
'project_name',
'library',
'helpers',
'templates',
'json_validation',
'shared_path',
]);
for (const key of Object.keys(workflowYaml)) {
if (!knownFields.has(key)) {
warnings.push({
path: [key],
message: `Unknown field '${key}' - not in workflow schema. This may be a workflow-specific variable or a typo.`,
});
}
}
// web_bundle unknown fields
if (workflowYaml.web_bundle && typeof workflowYaml.web_bundle === 'object') {
const knownWebBundleFields = new Set([
'name',
'description',
'instructions',
'web_bundle_files',
'validation',
'template',
'existing_workflows',
]);
for (const key of Object.keys(workflowYaml.web_bundle)) {
if (!knownWebBundleFields.has(key)) {
warnings.push({
path: ['web_bundle', key],
message: `Unknown field 'web_bundle.${key}' - not in web_bundle schema. This may be a custom field or a typo.`,
});
}
}
}
// input_file_patterns unknown fields
if (workflowYaml.input_file_patterns && typeof workflowYaml.input_file_patterns === 'object') {
const knownPatternFields = new Set([
'description',
'whole',
'sharded',
'sharded_index',
'sharded_single',
'load_strategy',
'pattern',
]);
for (const [patternName, pattern] of Object.entries(workflowYaml.input_file_patterns)) {
if (typeof pattern === 'object' && pattern !== null) {
for (const key of Object.keys(pattern)) {
if (!knownPatternFields.has(key)) {
warnings.push({
path: ['input_file_patterns', patternName, key],
message: `Unknown field 'input_file_patterns.${patternName}.${key}' - not in pattern schema. This may be a typo.`,
});
}
}
}
}
}
}
return warnings;
}
/**
* Recursively validate variable references in all string values.
*
* @param {any} data - Data to validate
* @param {Array} path - Current path
* @param {import('zod').RefinementCtx} ctx - Zod context
*/
function validateVariableReferences(data, path, ctx) {
if (typeof data === 'string') {
validateStringReferences(data, path, ctx);
return;
}
if (Array.isArray(data)) {
for (const [index, item] of data.entries()) {
validateVariableReferences(item, [...path, index], ctx);
}
return;
}
if (typeof data === 'object' && data !== null) {
for (const [key, value] of Object.entries(data)) {
validateVariableReferences(value, [...path, key], ctx);
}
}
}
/**
* Validate variable references in a single string value.
*/
function validateStringReferences(str, path, ctx) {
// Check for unclosed braces
const openBraces = (str.match(/\{/g) || []).length;
const closeBraces = (str.match(/\}/g) || []).length;
if (openBraces !== closeBraces) {
ctx.addIssue({
code: 'custom',
path,
message: `Malformed variable reference: unclosed braces (${openBraces} open, ${closeBraces} close)`,
});
return;
}
// Extract and validate path placeholders: {placeholder}
const pathPlaceholderMatches = str.matchAll(/\{([a-z][a-z0-9_-]*)\}/g);
for (const match of pathPlaceholderMatches) {
const placeholder = match[1];
// Skip config_source as it's a special keyword
if (placeholder === 'config_source') continue;
if (!KNOWN_PATH_PLACEHOLDERS.includes(placeholder)) {
ctx.addIssue({
code: 'custom',
path,
message: `Unknown path placeholder: {${placeholder}} - possible typo? Known placeholders: ${KNOWN_PATH_PLACEHOLDERS.slice(0, 5).join(', ')}...`,
});
}
}
// Extract and validate config variables: {config_source}:variable
const configMatches = str.matchAll(/\{config_source\}:([a-z][a-z0-9_]*)/g);
for (const match of configMatches) {
const variable = match[1];
if (!KNOWN_CONFIG_VARIABLES.includes(variable)) {
ctx.addIssue({
code: 'custom',
path,
message: `Unknown config variable: {config_source}:${variable} - possible typo? Known variables: ${KNOWN_CONFIG_VARIABLES.slice(0, 5).join(', ')}...`,
});
}
}
// Extract and validate template variables: {{variable}}
const templateMatches = str.matchAll(/\{\{([a-z][a-z0-9_]*)\}\}/g);
for (const match of templateMatches) {
const variable = match[1];
if (!KNOWN_TEMPLATE_VARIABLES.includes(variable)) {
ctx.addIssue({
code: 'custom',
path,
message: `Unknown template variable: {{${variable}}} - possible typo? Known variables: ${KNOWN_TEMPLATE_VARIABLES.slice(0, 5).join(', ')}...`,
});
}
}
// Check for malformed config_source references (missing colon)
if (str.includes('{config_source}') && !/\{config_source\}:[a-z]/.test(str)) {
const malformedMatch = str.match(/\{config_source\}([^:]|$)/);
if (malformedMatch) {
ctx.addIssue({
code: 'custom',
path,
message: 'Malformed config reference: {config_source} must be followed by :variable_name',
});
}
}
}
// Primitive validators -----------------------------------------------------
/**
* Create a validator for non-empty strings.
* @param {string} label Field label for error messages.
*/
function createNonEmptyString(label) {
return z.string().refine((value) => value.trim().length > 0, {
message: `${label} must be a non-empty string`,
});
}
/**
* Create a validator for kebab-case strings.
* @param {string} label Field label for error messages.
*/
function createKebabCaseString(label) {
return z.string().refine(
(value) => {
const trimmed = value.trim();
return trimmed.length > 0 && KEBAB_CASE_PATTERN.test(trimmed);
},
{
message: `${label} must be kebab-case (lowercase words separated by hyphen)`,
},
);
}

View File

@ -0,0 +1,165 @@
/**
* Workflow Schema Validator CLI
*
* Scans all *.workflow.yaml files in src/
* and validates them against the Zod schema.
*
* Usage: node tools/validate-workflow-schema.js [project_root]
* Exit codes: 0 = success, 1 = validation failures
*
* Optional argument:
* project_root - Directory to scan (defaults to BMAD repo root)
*/
const { glob } = require('glob');
const yaml = require('js-yaml');
const fs = require('node:fs');
const path = require('node:path');
const { validateWorkflowFile } = require('./schema/workflow.js');
/**
* Main validation routine
* @param {string} [customProjectRoot] - Optional project root to scan (for testing)
*/
async function main(customProjectRoot) {
console.log('🔍 Scanning for workflow files...\n');
// Determine project root: use custom path if provided, otherwise default to repo root
const project_root = customProjectRoot || path.join(__dirname, '..');
// Find all workflow files
const workflowFiles = await glob('src/**/workflow.yaml', {
cwd: project_root,
absolute: true,
ignore: ['**/node_modules/**', '**/workflow-template/**'], // Exclude templates and node_modules
});
if (workflowFiles.length === 0) {
console.log('❌ No workflow files found. This likely indicates a configuration error.');
console.log(' Expected to find workflow.yaml files in src/');
process.exit(1);
}
console.log(`Found ${workflowFiles.length} workflow file(s)\n`);
const errors = [];
const warnings = [];
// Validate each file
for (const filePath of workflowFiles) {
const relativePath = path.relative(process.cwd(), filePath);
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const workflowData = yaml.load(fileContent);
const result = validateWorkflowFile(relativePath, workflowData);
if (result.success) {
// Filter out "Unknown field" warnings from the per-file output
const relevantWarnings = (result.warnings || []).filter((w) => !w.message.startsWith('Unknown field'));
if (relevantWarnings.length > 0) {
console.log(`⚠️ ${relativePath} (passed with warnings)`);
warnings.push({
file: relativePath,
issues: relevantWarnings,
});
} else {
console.log(`${relativePath}`);
}
// Still collect ALL warnings (including unknown fields) for the summary
if (result.warnings && result.warnings.length > 0) {
const unknownFieldWarnings = result.warnings.filter((w) => w.message.startsWith('Unknown field'));
if (unknownFieldWarnings.length > 0) {
warnings.push({
file: relativePath,
issues: unknownFieldWarnings,
silent: true, // Mark as silent so we don't double-print in the loop below if mixed
});
}
}
} else {
errors.push({
file: relativePath,
issues: result.error.issues,
});
}
} catch (error) {
errors.push({
file: relativePath,
issues: [
{
code: 'parse_error',
message: `Failed to parse YAML: ${error.message}`,
path: [],
},
],
});
}
}
// Report warnings
if (warnings.length > 0) {
const unknownFields = new Map();
for (const { file, issues } of warnings) {
for (const issue of issues) {
const pathString = issue.path.length > 0 ? issue.path.join('.') : '(root)';
// Only aggregate "Unknown field" warnings
if (issue.message.startsWith('Unknown field')) {
if (!unknownFields.has(pathString)) {
unknownFields.set(pathString, []);
}
unknownFields.get(pathString).push(file);
} else {
// Print non-unknown-field warnings immediately (e.g. malformed vars)
console.log(`\n⚠️ Warning in ${file}:`);
console.log(` Path: ${pathString}`);
console.log(` Message: ${issue.message}`);
}
}
}
if (unknownFields.size > 0) {
console.log('\n Unknown Fields Summary (not in schema, possibly custom variables):');
const sortedFields = [...unknownFields.entries()].sort((a, b) => a[0].localeCompare(b[0]));
for (const [field, files] of sortedFields) {
console.log(` - ${field} (${files.length} files)`);
}
console.log('\n (Run with --verbose to see specific files for each field)');
}
}
// Report errors
if (errors.length > 0) {
console.log('\n❌ Validation failed for the following files:\n');
for (const { file, issues } of errors) {
console.log(`\n📄 ${file}`);
for (const issue of issues) {
const pathString = issue.path.length > 0 ? issue.path.join('.') : '(root)';
console.log(` Path: ${pathString}`);
console.log(` Error: ${issue.message}`);
if (issue.code) {
console.log(` Code: ${issue.code}`);
}
}
}
console.log(`\n\n💥 ${errors.length} file(s) failed validation`);
process.exit(1);
}
console.log(`\n✨ All ${workflowFiles.length} workflow file(s) passed validation!\n`);
process.exit(0);
}
// Run with optional command-line argument for project root
const customProjectRoot = process.argv[2];
main(customProjectRoot).catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});