feat: implement BMAD Auto Loop with automation scripts for Claude Code
This commit is contained in:
parent
323cd75efd
commit
592dfd1da5
|
|
@ -1,4 +1,6 @@
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core Module Installer
|
* Core Module Installer
|
||||||
|
|
@ -40,21 +42,119 @@ async function install(options) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Core module for specific IDE
|
* Configure Core module for specific IDE
|
||||||
|
* @param {string} ide - The IDE code (e.g., 'claude-code', 'cursor')
|
||||||
|
* @param {string} projectRoot - Path to the target project root
|
||||||
|
* @param {Object} config - Module configuration
|
||||||
|
* @param {Object} logger - Logger instance
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async function configureForIDE(ide) {
|
async function configureForIDE(ide, projectRoot, config, logger) {
|
||||||
// Add IDE-specific configurations here
|
|
||||||
switch (ide) {
|
switch (ide) {
|
||||||
case 'claude-code': {
|
case 'claude-code': {
|
||||||
// Claude Code specific Core configurations
|
// Install BMAD Auto scripts for automated development workflow
|
||||||
|
await installBmadAutoScripts(projectRoot, config, logger);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Add more IDEs as needed
|
|
||||||
default: {
|
default: {
|
||||||
// No specific configuration needed
|
// No specific configuration needed for other IDEs
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install BMAD Auto automation scripts for Claude Code
|
||||||
|
*
|
||||||
|
* This function:
|
||||||
|
* 1. Creates the .scripts/bmad-auto/claude/ directory in the target project
|
||||||
|
* 2. Copies the automation scripts (PS1, SH, MD files)
|
||||||
|
* 3. Generates a config file with project-specific paths
|
||||||
|
*
|
||||||
|
* @param {string} projectRoot - Path to the target project root
|
||||||
|
* @param {Object} config - Module configuration containing output_folder
|
||||||
|
* @param {Object} logger - Logger instance for output
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function installBmadAutoScripts(projectRoot, config, logger) {
|
||||||
|
// Source directory: where our template scripts are stored
|
||||||
|
// Located relative to this installer file: ../scripts/bmad-auto/
|
||||||
|
const scriptsSource = path.join(__dirname, '..', 'scripts', 'bmad-auto');
|
||||||
|
|
||||||
|
// Target directory: where scripts will be installed in user's project
|
||||||
|
const scriptsTarget = path.join(projectRoot, '.scripts', 'bmad-auto', 'claude');
|
||||||
|
|
||||||
|
// Check if source scripts exist
|
||||||
|
if (!(await fs.pathExists(scriptsSource))) {
|
||||||
|
logger.log(chalk.yellow(' ⚠ BMAD Auto scripts source not found, skipping'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create target directory
|
||||||
|
await fs.ensureDir(scriptsTarget);
|
||||||
|
|
||||||
|
// List of script files to copy
|
||||||
|
const scriptFiles = [
|
||||||
|
'bmad-loop.ps1', // Windows PowerShell version
|
||||||
|
'bmad-loop.sh', // Linux/macOS Bash version
|
||||||
|
'bmad-prompt.md', // Prompt template for Claude Code
|
||||||
|
'README.md', // Usage documentation
|
||||||
|
];
|
||||||
|
|
||||||
|
// Copy each script file
|
||||||
|
for (const file of scriptFiles) {
|
||||||
|
const sourcePath = path.join(scriptsSource, file);
|
||||||
|
const targetPath = path.join(scriptsTarget, file);
|
||||||
|
|
||||||
|
if (await fs.pathExists(sourcePath)) {
|
||||||
|
await fs.copy(sourcePath, targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the configuration file with project-specific paths
|
||||||
|
// The output_folder may contain template variables like {project-root}/
|
||||||
|
const outputFolder = (config.output_folder || '_bmad-output')
|
||||||
|
.replace('{project-root}/', '')
|
||||||
|
.replace('{project-root}\\', '')
|
||||||
|
.replace(/^\//, '') // Remove leading slash if present
|
||||||
|
.replace(/^\\/, ''); // Remove leading backslash if present
|
||||||
|
|
||||||
|
// Create config YAML content
|
||||||
|
// Using forward slashes for cross-platform compatibility
|
||||||
|
const configContent = `# BMAD Auto Configuration
|
||||||
|
# ============================================================
|
||||||
|
# This file is auto-generated during BMAD installation.
|
||||||
|
# It contains paths specific to your project setup.
|
||||||
|
#
|
||||||
|
# You can modify these values if you change your BMAD configuration:
|
||||||
|
# - output_folder: Where BMAD stores generated files
|
||||||
|
# - implementation_artifacts: Where sprint-status.yaml is located
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# Absolute path to your project root directory
|
||||||
|
project_root: "${projectRoot.replaceAll('\\', '/')}"
|
||||||
|
|
||||||
|
# Relative path to BMAD output folder (from project root)
|
||||||
|
output_folder: "${outputFolder}"
|
||||||
|
|
||||||
|
# Relative path to implementation artifacts (where sprint-status.yaml lives)
|
||||||
|
implementation_artifacts: "${outputFolder}/implementation-artifacts"
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Write the config file
|
||||||
|
const configPath = path.join(scriptsTarget, 'bmad-auto-config.yaml');
|
||||||
|
await fs.writeFile(configPath, configContent, 'utf8');
|
||||||
|
|
||||||
|
// Make bash script executable on Unix systems
|
||||||
|
const bashScriptPath = path.join(scriptsTarget, 'bmad-loop.sh');
|
||||||
|
if (await fs.pathExists(bashScriptPath)) {
|
||||||
|
try {
|
||||||
|
await fs.chmod(bashScriptPath, 0o755);
|
||||||
|
} catch {
|
||||||
|
// Ignore chmod errors on Windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(chalk.green(' ✓ BMAD Auto scripts installed to .scripts/bmad-auto/claude/'));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { install };
|
module.exports = { install };
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
# BMAD Auto - Automated Development Loop for Claude Code
|
||||||
|
|
||||||
|
This folder contains automation scripts that run the complete BMAD story development
|
||||||
|
cycle automatically using Claude Code CLI.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
The automation loop continuously:
|
||||||
|
1. Reads `sprint-status.yaml` to determine story states
|
||||||
|
2. Executes the appropriate BMAD workflow command:
|
||||||
|
- `backlog` stories → `/bmad-bmm-create-story`
|
||||||
|
- `ready-for-dev` stories → `/bmad-bmm-dev-story`
|
||||||
|
- `review` stories → `/bmad-bmm-code-review`
|
||||||
|
3. Auto-commits code after successful code reviews
|
||||||
|
4. Repeats until all stories are `done`
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `bmad-loop.ps1` | Windows PowerShell automation script |
|
||||||
|
| `bmad-loop.sh` | Linux/macOS Bash automation script |
|
||||||
|
| `bmad-prompt.md` | Prompt template sent to Claude Code |
|
||||||
|
| `bmad-auto-config.yaml` | Configuration file (auto-generated) |
|
||||||
|
| `bmad-progress.log` | Execution log (created on first run) |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Windows (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Navigate to your project root
|
||||||
|
cd C:\path\to\your\project
|
||||||
|
|
||||||
|
# Run with default settings (30 max iterations)
|
||||||
|
.\.scripts\bmad-auto\claude\bmad-loop.ps1
|
||||||
|
|
||||||
|
# Run with custom max iterations
|
||||||
|
.\.scripts\bmad-auto\claude\bmad-loop.ps1 -MaxIterations 50
|
||||||
|
|
||||||
|
# Run with verbose logging
|
||||||
|
.\.scripts\bmad-auto\claude\bmad-loop.ps1 -Verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux/macOS (Bash)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to your project root
|
||||||
|
cd /path/to/your/project
|
||||||
|
|
||||||
|
# Make script executable (first time only)
|
||||||
|
chmod +x ./.scripts/bmad-auto/claude/bmad-loop.sh
|
||||||
|
|
||||||
|
# Run with default settings (30 max iterations)
|
||||||
|
./.scripts/bmad-auto/claude/bmad-loop.sh
|
||||||
|
|
||||||
|
# Run with custom max iterations
|
||||||
|
./.scripts/bmad-auto/claude/bmad-loop.sh 50
|
||||||
|
|
||||||
|
# Run with verbose logging
|
||||||
|
./.scripts/bmad-auto/claude/bmad-loop.sh 50 --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before running the automation:
|
||||||
|
|
||||||
|
1. **Claude Code CLI** must be installed and in your system PATH
|
||||||
|
- Test with: `claude --version`
|
||||||
|
|
||||||
|
2. **BMAD installed** with Claude Code support
|
||||||
|
- Run: `npx bmad-method install` and select Claude Code
|
||||||
|
|
||||||
|
3. **Sprint planning completed** - you must have a `sprint-status.yaml` file
|
||||||
|
- Run: `/bmad-bmm-sprint-planning` in Claude Code
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The `bmad-auto-config.yaml` file is automatically generated during BMAD installation.
|
||||||
|
It contains:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project_root: "/path/to/your/project"
|
||||||
|
output_folder: "_bmad-output"
|
||||||
|
implementation_artifacts: "_bmad-output/implementation-artifacts"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you change your output folder after installation, update this file accordingly.
|
||||||
|
|
||||||
|
## Safety Features
|
||||||
|
|
||||||
|
- **Max iterations limit**: Prevents infinite loops (default: 30)
|
||||||
|
- **Git status check**: Only commits if there are actual changes
|
||||||
|
- **Error handling**: Logs errors and continues to next iteration
|
||||||
|
- **Progress logging**: All actions logged to `bmad-progress.log`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Configuration file not found"
|
||||||
|
Re-run `npx bmad-method install` and select Claude Code.
|
||||||
|
|
||||||
|
### "sprint-status.yaml not found"
|
||||||
|
Run `/bmad-bmm-sprint-planning` to create the sprint status file.
|
||||||
|
|
||||||
|
### "Claude Code not found"
|
||||||
|
Ensure Claude Code CLI is installed and in your PATH.
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
BMAD Auto Loop - Automated Story Development Cycle
|
||||||
|
.DESCRIPTION
|
||||||
|
Executes BMAD workflow in a loop: create-story -> dev-story -> code-review -> commit
|
||||||
|
Continues until all stories reach 'done' state in sprint-status.yaml
|
||||||
|
.PARAMETER MaxIterations
|
||||||
|
Safety limit for maximum loop iterations (default: 30)
|
||||||
|
.PARAMETER Verbose
|
||||||
|
Enable detailed debug logging
|
||||||
|
.EXAMPLE
|
||||||
|
.\bmad-loop.ps1
|
||||||
|
.\bmad-loop.ps1 -MaxIterations 50 -Verbose
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[int]$MaxIterations = 30,
|
||||||
|
[switch]$Verbose
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIGURATION LOADING
|
||||||
|
# ============================================================
|
||||||
|
# The bmad-auto-config.yaml file is generated during BMAD installation.
|
||||||
|
# It contains the project root and output folder paths specific to this project.
|
||||||
|
# This allows the script to work with any output folder configuration.
|
||||||
|
|
||||||
|
$ConfigPath = Join-Path $ScriptDir "bmad-auto-config.yaml"
|
||||||
|
|
||||||
|
if (-not (Test-Path $ConfigPath)) {
|
||||||
|
Write-Host @"
|
||||||
|
[ERROR] Configuration file not found: $ConfigPath
|
||||||
|
|
||||||
|
This file is generated automatically when you install BMAD with Claude Code support.
|
||||||
|
To fix this, run: npx bmad-method install
|
||||||
|
And select 'Claude Code' as your IDE.
|
||||||
|
"@ -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simple YAML parser for flat key-value config
|
||||||
|
# Handles both quoted and unquoted values
|
||||||
|
$config = @{}
|
||||||
|
Get-Content $ConfigPath | ForEach-Object {
|
||||||
|
# Match lines like: key: "value" or key: value (ignore comments starting with #)
|
||||||
|
if ($_ -match '^([^#]\w+):\s*"?([^"]+)"?\s*$') {
|
||||||
|
$config[$Matches[1].Trim()] = $Matches[2].Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required config values
|
||||||
|
$ProjectRoot = $config['project_root']
|
||||||
|
if (-not $ProjectRoot) {
|
||||||
|
Write-Host "[ERROR] 'project_root' not found in config file" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$ImplementationArtifacts = $config['implementation_artifacts']
|
||||||
|
if (-not $ImplementationArtifacts) {
|
||||||
|
# Fallback to default if not specified
|
||||||
|
$ImplementationArtifacts = "_bmad-output/implementation-artifacts"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build paths from config
|
||||||
|
$SprintStatusPath = Join-Path $ProjectRoot $ImplementationArtifacts "sprint-status.yaml"
|
||||||
|
$ProgressLog = Join-Path $ScriptDir "bmad-progress.log"
|
||||||
|
$PromptTemplate = Join-Path $ScriptDir "bmad-prompt.md"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# LOGGING FUNCTION
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param(
|
||||||
|
[string]$Message,
|
||||||
|
[string]$Color = "White"
|
||||||
|
)
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
Write-Host $Message -ForegroundColor $Color
|
||||||
|
Add-Content -Path $ProgressLog -Value "[$timestamp] $Message"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GET NEXT ACTION
|
||||||
|
# ============================================================
|
||||||
|
# Analyzes sprint-status.yaml and returns the next action to take
|
||||||
|
# Priority order: code-review > dev-story > create-story
|
||||||
|
|
||||||
|
function Get-NextAction {
|
||||||
|
if (-not (Test-Path $SprintStatusPath)) {
|
||||||
|
Write-Log "[ERROR] sprint-status.yaml not found: $SprintStatusPath" "Red"
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read file content
|
||||||
|
$content = Get-Content $SprintStatusPath -Raw
|
||||||
|
$lines = $content -split "`n"
|
||||||
|
|
||||||
|
# Filter story lines (pattern: digits-digits-*) excluding retrospectives
|
||||||
|
$storyLines = $lines | Where-Object {
|
||||||
|
$_ -match '^\s*[0-9]+-[0-9]+-' -and $_ -notmatch 'retrospective'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Verbose) {
|
||||||
|
Write-Log "[DEBUG] Found $($storyLines.Count) story lines" "Gray"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Count stories in each state
|
||||||
|
$reviewCount = ($storyLines | Where-Object { $_ -match ':\s*review\s*$' }).Count
|
||||||
|
$readyCount = ($storyLines | Where-Object { $_ -match ':\s*ready-for-dev\s*$' }).Count
|
||||||
|
$backlogCount = ($storyLines | Where-Object { $_ -match ':\s*backlog\s*$' }).Count
|
||||||
|
$doneCount = ($storyLines | Where-Object { $_ -match ':\s*done\s*$' }).Count
|
||||||
|
$totalCount = $storyLines.Count
|
||||||
|
|
||||||
|
if ($Verbose) {
|
||||||
|
Write-Log "[DEBUG] Review: $reviewCount, Ready: $readyCount, Backlog: $backlogCount, Done: $doneCount" "Gray"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Priority order: review > ready-for-dev > backlog
|
||||||
|
if ($reviewCount -gt 0) {
|
||||||
|
Write-Log "[NEXT] Found story in REVIEW state" "Green"
|
||||||
|
return "code-review"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($readyCount -gt 0) {
|
||||||
|
Write-Log "[NEXT] Found story in READY-FOR-DEV state" "Green"
|
||||||
|
return "dev-story"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($backlogCount -gt 0) {
|
||||||
|
Write-Log "[NEXT] Found story in BACKLOG state" "Green"
|
||||||
|
return "create-story"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if all done
|
||||||
|
if ($doneCount -eq $totalCount -and $totalCount -gt 0) {
|
||||||
|
return "complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for in-progress (waiting state)
|
||||||
|
$inProgressCount = ($storyLines | Where-Object { $_ -match ':\s*in-progress\s*$' }).Count
|
||||||
|
if ($inProgressCount -gt 0) {
|
||||||
|
Write-Log "[WAIT] Found $inProgressCount story(ies) in-progress" "Yellow"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "wait"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# INVOKE CLAUDE COMMAND
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
function Invoke-ClaudeCommand {
|
||||||
|
param([string]$Command)
|
||||||
|
|
||||||
|
Write-Log "[EXEC] Executing Claude Code: $Command" "Cyan"
|
||||||
|
|
||||||
|
# Create customized prompt from template
|
||||||
|
$prompt = Get-Content $PromptTemplate -Raw
|
||||||
|
$prompt = $prompt -replace '\{COMMAND\}', $Command
|
||||||
|
$prompt = $prompt -replace '\{TIMESTAMP\}', (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||||
|
|
||||||
|
# Save to temp file
|
||||||
|
$tempPrompt = [System.IO.Path]::GetTempFileName()
|
||||||
|
Set-Content -Path $tempPrompt -Value $prompt
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Execute Claude Code
|
||||||
|
Get-Content $tempPrompt | claude --dangerously-skip-permissions
|
||||||
|
$success = $LASTEXITCODE -eq 0
|
||||||
|
|
||||||
|
if (-not $success) {
|
||||||
|
Write-Log "[ERROR] Claude execution failed with exit code: $LASTEXITCODE" "Red"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item $tempPrompt -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GIT COMMIT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
function Invoke-GitCommit {
|
||||||
|
Write-Log "[GIT] Checking for changes to commit..." "Yellow"
|
||||||
|
|
||||||
|
$status = git status --porcelain
|
||||||
|
|
||||||
|
if (-not $status) {
|
||||||
|
Write-Log "[INFO] No changes to commit" "Gray"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "[GIT] Committing changes..." "Yellow"
|
||||||
|
|
||||||
|
try {
|
||||||
|
git add -A
|
||||||
|
git commit -m "feat: BMAD auto-commit after code-review"
|
||||||
|
Write-Log "[SUCCESS] Changes committed successfully" "Green"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "[ERROR] Git commit failed: $_" "Red"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN LOOP
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
Write-Log "[START] BMAD Auto Loop Started" "Green"
|
||||||
|
Write-Log "Max iterations: $MaxIterations" "Gray"
|
||||||
|
Write-Log "Project root: $ProjectRoot" "Gray"
|
||||||
|
Write-Log "Sprint status: $SprintStatusPath" "Gray"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
for ($iteration = 1; $iteration -le $MaxIterations; $iteration++) {
|
||||||
|
Write-Log "=======================================" "Cyan"
|
||||||
|
Write-Log "=== Iteration $iteration/$MaxIterations ===" "Cyan"
|
||||||
|
Write-Log "=======================================" "Cyan"
|
||||||
|
|
||||||
|
$action = Get-NextAction
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
"create-story" {
|
||||||
|
Write-Log "[ACTION] CREATE STORY" "Yellow"
|
||||||
|
Invoke-ClaudeCommand "/bmad-bmm-create-story"
|
||||||
|
}
|
||||||
|
"dev-story" {
|
||||||
|
Write-Log "[ACTION] DEVELOP STORY" "Yellow"
|
||||||
|
Invoke-ClaudeCommand "/bmad-bmm-dev-story"
|
||||||
|
}
|
||||||
|
"code-review" {
|
||||||
|
Write-Log "[ACTION] CODE REVIEW" "Yellow"
|
||||||
|
$success = Invoke-ClaudeCommand "/bmad-bmm-code-review"
|
||||||
|
if ($success) {
|
||||||
|
Invoke-GitCommit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"complete" {
|
||||||
|
Write-Log "[COMPLETE] ALL STORIES COMPLETED!" "Green"
|
||||||
|
Write-Log "Sprint status: All stories are DONE" "Green"
|
||||||
|
Write-Log "Total iterations: $iteration" "Gray"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
"wait" {
|
||||||
|
Write-Log "[WAIT] Waiting state - story might be in-progress" "Yellow"
|
||||||
|
Write-Log "Skipping this iteration..." "Gray"
|
||||||
|
}
|
||||||
|
"error" {
|
||||||
|
Write-Log "[ERROR] Error state - stopping loop" "Red"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "[TIMEOUT] Max iterations reached without completion" "Yellow"
|
||||||
|
Write-Log "Check sprint-status.yaml for current state" "Gray"
|
||||||
|
exit 1
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# BMAD Auto Loop - Automated Story Development Cycle
|
||||||
|
# ============================================================
|
||||||
|
#
|
||||||
|
# DESCRIPTION:
|
||||||
|
# Executes BMAD workflow in a loop: create-story -> dev-story -> code-review -> commit
|
||||||
|
# Continues until all stories reach 'done' state in sprint-status.yaml
|
||||||
|
#
|
||||||
|
# USAGE:
|
||||||
|
# ./bmad-loop.sh # Run with default 30 max iterations
|
||||||
|
# ./bmad-loop.sh 50 # Run with 50 max iterations
|
||||||
|
# ./bmad-loop.sh 50 --verbose # Run with verbose logging
|
||||||
|
#
|
||||||
|
# PREREQUISITES:
|
||||||
|
# - Claude Code CLI installed and in PATH
|
||||||
|
# - BMAD installed with Claude Code support
|
||||||
|
# - Sprint planning completed (sprint-status.yaml exists)
|
||||||
|
#
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
MAX_ITERATIONS=${1:-30}
|
||||||
|
VERBOSE=false
|
||||||
|
if [[ "$2" == "--verbose" || "$2" == "-v" ]]; then
|
||||||
|
VERBOSE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get script directory (works even if script is called via symlink)
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/bmad-auto-config.yaml"
|
||||||
|
PROGRESS_LOG="$SCRIPT_DIR/bmad-progress.log"
|
||||||
|
PROMPT_TEMPLATE="$SCRIPT_DIR/bmad-prompt.md"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONFIGURATION LOADING
|
||||||
|
# ============================================================
|
||||||
|
# The bmad-auto-config.yaml file is generated during BMAD installation.
|
||||||
|
# It contains the project root and output folder paths.
|
||||||
|
|
||||||
|
if [[ ! -f "$CONFIG_PATH" ]]; then
|
||||||
|
echo "[ERROR] Configuration file not found: $CONFIG_PATH"
|
||||||
|
echo ""
|
||||||
|
echo "This file is generated automatically when you install BMAD with Claude Code support."
|
||||||
|
echo "To fix this, run: npx bmad-method install"
|
||||||
|
echo "And select 'Claude Code' as your IDE."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse YAML config (simple key: value format)
|
||||||
|
# Uses grep and sed to extract values, handles quoted and unquoted values
|
||||||
|
PROJECT_ROOT=$(grep "^project_root:" "$CONFIG_PATH" | sed 's/project_root: *"\{0,1\}\([^"]*\)"\{0,1\}/\1/' | tr -d '\r')
|
||||||
|
IMPL_ARTIFACTS=$(grep "^implementation_artifacts:" "$CONFIG_PATH" | sed 's/implementation_artifacts: *"\{0,1\}\([^"]*\)"\{0,1\}/\1/' | tr -d '\r')
|
||||||
|
|
||||||
|
# Validate required config
|
||||||
|
if [[ -z "$PROJECT_ROOT" ]]; then
|
||||||
|
echo "[ERROR] 'project_root' not found in config file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback for implementation_artifacts
|
||||||
|
if [[ -z "$IMPL_ARTIFACTS" ]]; then
|
||||||
|
IMPL_ARTIFACTS="_bmad-output/implementation-artifacts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build paths from config
|
||||||
|
SPRINT_STATUS_PATH="$PROJECT_ROOT/$IMPL_ARTIFACTS/sprint-status.yaml"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# LOGGING FUNCTION
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local message="$1"
|
||||||
|
local color="${2:-white}"
|
||||||
|
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
# Color codes
|
||||||
|
case $color in
|
||||||
|
red) color_code="\033[0;31m" ;;
|
||||||
|
green) color_code="\033[0;32m" ;;
|
||||||
|
yellow) color_code="\033[0;33m" ;;
|
||||||
|
cyan) color_code="\033[0;36m" ;;
|
||||||
|
gray) color_code="\033[0;90m" ;;
|
||||||
|
*) color_code="\033[0m" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${color_code}${message}\033[0m"
|
||||||
|
echo "[$timestamp] $message" >> "$PROGRESS_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GET NEXT ACTION
|
||||||
|
# ============================================================
|
||||||
|
# Analyzes sprint-status.yaml and returns the next action to take
|
||||||
|
# Priority order: code-review > dev-story > create-story
|
||||||
|
|
||||||
|
get_next_action() {
|
||||||
|
if [[ ! -f "$SPRINT_STATUS_PATH" ]]; then
|
||||||
|
log "[ERROR] sprint-status.yaml not found: $SPRINT_STATUS_PATH" "red"
|
||||||
|
echo "error"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Filter story lines (pattern: digits-digits-*) excluding retrospectives
|
||||||
|
local story_lines=$(grep -E '^\s*[0-9]+-[0-9]+-' "$SPRINT_STATUS_PATH" | grep -v "retrospective")
|
||||||
|
|
||||||
|
if $VERBOSE; then
|
||||||
|
local count=$(echo "$story_lines" | wc -l)
|
||||||
|
log "[DEBUG] Found $count story lines" "gray"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Count stories in each state
|
||||||
|
local review_count=$(echo "$story_lines" | grep -c ': *review *$' || true)
|
||||||
|
local ready_count=$(echo "$story_lines" | grep -c ': *ready-for-dev *$' || true)
|
||||||
|
local backlog_count=$(echo "$story_lines" | grep -c ': *backlog *$' || true)
|
||||||
|
local done_count=$(echo "$story_lines" | grep -c ': *done *$' || true)
|
||||||
|
local total_count=$(echo "$story_lines" | grep -c '.' || true)
|
||||||
|
|
||||||
|
if $VERBOSE; then
|
||||||
|
log "[DEBUG] Review: $review_count, Ready: $ready_count, Backlog: $backlog_count, Done: $done_count" "gray"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority order: review > ready-for-dev > backlog
|
||||||
|
if [[ $review_count -gt 0 ]]; then
|
||||||
|
log "[NEXT] Found story in REVIEW state" "green"
|
||||||
|
echo "code-review"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $ready_count -gt 0 ]]; then
|
||||||
|
log "[NEXT] Found story in READY-FOR-DEV state" "green"
|
||||||
|
echo "dev-story"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $backlog_count -gt 0 ]]; then
|
||||||
|
log "[NEXT] Found story in BACKLOG state" "green"
|
||||||
|
echo "create-story"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if all done
|
||||||
|
if [[ $done_count -eq $total_count && $total_count -gt 0 ]]; then
|
||||||
|
echo "complete"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for in-progress (waiting state)
|
||||||
|
local in_progress_count=$(echo "$story_lines" | grep -c ': *in-progress *$' || true)
|
||||||
|
if [[ $in_progress_count -gt 0 ]]; then
|
||||||
|
log "[WAIT] Found $in_progress_count story(ies) in-progress" "yellow"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "wait"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# INVOKE CLAUDE COMMAND
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
invoke_claude_command() {
|
||||||
|
local command="$1"
|
||||||
|
|
||||||
|
log "[EXEC] Executing Claude Code: $command" "cyan"
|
||||||
|
|
||||||
|
# Create customized prompt from template
|
||||||
|
local prompt=$(cat "$PROMPT_TEMPLATE")
|
||||||
|
prompt="${prompt//\{COMMAND\}/$command}"
|
||||||
|
prompt="${prompt//\{TIMESTAMP\}/$(date "+%Y-%m-%d %H:%M:%S")}"
|
||||||
|
|
||||||
|
# Save to temp file
|
||||||
|
local temp_prompt=$(mktemp)
|
||||||
|
echo "$prompt" > "$temp_prompt"
|
||||||
|
|
||||||
|
# Execute Claude Code
|
||||||
|
if cat "$temp_prompt" | claude --dangerously-skip-permissions 2>&1; then
|
||||||
|
rm -f "$temp_prompt"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "[ERROR] Claude execution failed" "red"
|
||||||
|
rm -f "$temp_prompt"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GIT COMMIT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
invoke_git_commit() {
|
||||||
|
log "[GIT] Checking for changes to commit..." "yellow"
|
||||||
|
|
||||||
|
local status=$(git status --porcelain)
|
||||||
|
|
||||||
|
if [[ -z "$status" ]]; then
|
||||||
|
log "[INFO] No changes to commit" "gray"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "[GIT] Committing changes..." "yellow"
|
||||||
|
|
||||||
|
if git add -A && git commit -m "feat: BMAD auto-commit after code-review"; then
|
||||||
|
log "[SUCCESS] Changes committed successfully" "green"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "[ERROR] Git commit failed" "red"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MAIN LOOP
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
log "[START] BMAD Auto Loop Started" "green"
|
||||||
|
log "Max iterations: $MAX_ITERATIONS" "gray"
|
||||||
|
log "Project root: $PROJECT_ROOT" "gray"
|
||||||
|
log "Sprint status: $SPRINT_STATUS_PATH" "gray"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for ((iteration=1; iteration<=MAX_ITERATIONS; iteration++)); do
|
||||||
|
log "=======================================" "cyan"
|
||||||
|
log "=== Iteration $iteration/$MAX_ITERATIONS ===" "cyan"
|
||||||
|
log "=======================================" "cyan"
|
||||||
|
|
||||||
|
action=$(get_next_action)
|
||||||
|
|
||||||
|
case $action in
|
||||||
|
"create-story")
|
||||||
|
log "[ACTION] CREATE STORY" "yellow"
|
||||||
|
invoke_claude_command "/bmad-bmm-create-story"
|
||||||
|
;;
|
||||||
|
"dev-story")
|
||||||
|
log "[ACTION] DEVELOP STORY" "yellow"
|
||||||
|
invoke_claude_command "/bmad-bmm-dev-story"
|
||||||
|
;;
|
||||||
|
"code-review")
|
||||||
|
log "[ACTION] CODE REVIEW" "yellow"
|
||||||
|
if invoke_claude_command "/bmad-bmm-code-review"; then
|
||||||
|
invoke_git_commit
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"complete")
|
||||||
|
log "[COMPLETE] ALL STORIES COMPLETED!" "green"
|
||||||
|
log "Sprint status: All stories are DONE" "green"
|
||||||
|
log "Total iterations: $iteration" "gray"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"wait")
|
||||||
|
log "[WAIT] Waiting state - story might be in-progress" "yellow"
|
||||||
|
log "Skipping this iteration..." "gray"
|
||||||
|
;;
|
||||||
|
"error")
|
||||||
|
log "[ERROR] Error state - stopping loop" "red"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
log "[TIMEOUT] Max iterations reached without completion" "yellow"
|
||||||
|
log "Check sprint-status.yaml for current state" "gray"
|
||||||
|
exit 1
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# BMAD Automation Prompt
|
||||||
|
|
||||||
|
Execute the following BMAD command and complete the entire workflow:
|
||||||
|
|
||||||
|
```
|
||||||
|
{COMMAND}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
1. Execute the command above completely
|
||||||
|
2. Follow all prompts and complete the workflow
|
||||||
|
3. Do not ask for confirmation - proceed automatically with reasonable defaults
|
||||||
|
4. If you encounter any blocking issues, document them and move on
|
||||||
|
5. Update the sprint-status.yaml file as appropriate
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Timestamp: {TIMESTAMP}
|
||||||
|
- This is an automated execution - proceed without user interaction
|
||||||
|
- Use best judgment for any decisions that would normally require user input
|
||||||
|
- Prioritize completing the workflow over perfection
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
- For `/bmad-bmm-create-story`: Create the story file and update sprint-status.yaml to `ready-for-dev`
|
||||||
|
- For `/bmad-bmm-dev-story`: Implement the story and update sprint-status.yaml to `review`
|
||||||
|
- For `/bmad-bmm-code-review`: Review the implementation and update sprint-status.yaml to `done` if approved
|
||||||
|
|
||||||
|
Proceed with the command execution now.
|
||||||
Loading…
Reference in New Issue