Compare commits
8 Commits
c0eebe69c7
...
1a4d06fc14
| Author | SHA1 | Date |
|---|---|---|
|
|
1a4d06fc14 | |
|
|
331a67eeb3 | |
|
|
fbdb91b991 | |
|
|
54e6745a55 | |
|
|
f793cf8fcd | |
|
|
9223e2be21 | |
|
|
2cac74cfb5 | |
|
|
5702195ef7 |
|
|
@ -70,3 +70,4 @@ z*/
|
||||||
.codex
|
.codex
|
||||||
.github/chatmodes
|
.github/chatmodes
|
||||||
.agent
|
.agent
|
||||||
|
.agentvibes/
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Testing AgentVibes Party Mode (PR #934)
|
||||||
|
|
||||||
|
This guide helps you test the AgentVibes integration that adds multi-agent party mode with unique voices for each BMAD agent.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
We've created an automated test script that handles everything for you:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/paulpreibisch/BMAD-METHOD/feature/agentvibes-tts-integration/test-bmad-pr.sh -o test-bmad-pr.sh
|
||||||
|
chmod +x test-bmad-pr.sh
|
||||||
|
./test-bmad-pr.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## What the Script Does
|
||||||
|
|
||||||
|
The automated script will:
|
||||||
|
|
||||||
|
1. Clone the BMAD repository
|
||||||
|
2. Checkout the PR branch with party mode features
|
||||||
|
3. Install BMAD CLI tools locally
|
||||||
|
4. Create a test BMAD project
|
||||||
|
5. Install AgentVibes TTS system
|
||||||
|
6. Configure unique voices for each agent
|
||||||
|
7. Verify the installation
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js and npm installed
|
||||||
|
- Git installed
|
||||||
|
- ~500MB free disk space
|
||||||
|
- 10-15 minutes for complete setup
|
||||||
|
|
||||||
|
## Manual Testing (Alternative)
|
||||||
|
|
||||||
|
If you prefer manual installation:
|
||||||
|
|
||||||
|
### 1. Clone and Setup BMAD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/paulpreibisch/BMAD-METHOD.git
|
||||||
|
cd BMAD-METHOD
|
||||||
|
git fetch origin pull/934/head:agentvibes-party-mode
|
||||||
|
git checkout agentvibes-party-mode
|
||||||
|
cd tools/cli
|
||||||
|
npm install
|
||||||
|
npm link
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Test Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/bmad-test-project
|
||||||
|
cd ~/bmad-test-project
|
||||||
|
bmad install
|
||||||
|
```
|
||||||
|
|
||||||
|
When prompted:
|
||||||
|
|
||||||
|
- Enable TTS for agents? → **Yes**
|
||||||
|
- The installer will automatically prompt you to install AgentVibes
|
||||||
|
|
||||||
|
### 3. Test Party Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/bmad-test-project
|
||||||
|
claude-code
|
||||||
|
```
|
||||||
|
|
||||||
|
In Claude Code, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
/bmad:core:workflows:party-mode
|
||||||
|
```
|
||||||
|
|
||||||
|
Each BMAD agent should speak with a unique voice!
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After installation, verify:
|
||||||
|
|
||||||
|
✅ Voice map file exists: `.bmad/_cfg/agent-voice-map.csv`
|
||||||
|
✅ BMAD TTS hooks exist: `.claude/hooks/bmad-speak.sh`
|
||||||
|
✅ Each agent has a unique voice assigned
|
||||||
|
✅ Party mode works with distinct voices
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**No audio?**
|
||||||
|
|
||||||
|
- Check: `.claude/hooks/play-tts.sh` exists
|
||||||
|
- Test current voice: `/agent-vibes:whoami`
|
||||||
|
|
||||||
|
**Same voice for all agents?**
|
||||||
|
|
||||||
|
- Check: `.bmad/_cfg/agent-voice-map.csv` has different voices
|
||||||
|
- List available voices: `/agent-vibes:list`
|
||||||
|
|
||||||
|
## Report Issues
|
||||||
|
|
||||||
|
Found a bug? Report it on the PR:
|
||||||
|
https://github.com/bmad-code-org/BMAD-METHOD/pull/934
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
To remove the test installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove test directory
|
||||||
|
rm -rf ~/bmad-test-project # or your custom test directory
|
||||||
|
|
||||||
|
# Unlink BMAD CLI (optional)
|
||||||
|
cd ~/BMAD-METHOD/tools/cli
|
||||||
|
npm unlink
|
||||||
|
```
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
<critical>The workflow execution engine is governed by: {project_root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
<critical>The workflow execution engine is governed by: {project_root}/{bmad_folder}/core/tasks/workflow.xml</critical>
|
||||||
<critical>This workflow orchestrates group discussions between all installed BMAD agents</critical>
|
<critical>This workflow orchestrates group discussions between all installed BMAD agents</critical>
|
||||||
|
|
||||||
|
<!-- TTS_INJECTION:party-mode -->
|
||||||
|
|
||||||
<workflow>
|
<workflow>
|
||||||
|
|
||||||
<step n="1" goal="Load Agent Manifest and Configurations">
|
<step n="1" goal="Load Agent Manifest and Configurations">
|
||||||
|
|
@ -94,17 +96,29 @@
|
||||||
</substep>
|
</substep>
|
||||||
|
|
||||||
<substep n="3d" goal="Format and Present Responses">
|
<substep n="3d" goal="Format and Present Responses">
|
||||||
<action>Present each agent's contribution clearly:</action>
|
<action>For each agent response, output text THEN trigger their voice:</action>
|
||||||
|
|
||||||
|
<!-- TTS_INJECTION:party-mode -->
|
||||||
|
|
||||||
<format>
|
<format>
|
||||||
[Agent Name]: [Their response in their voice/style]
|
[Icon Emoji] [Agent Name]: [Their response in their voice/style]
|
||||||
|
|
||||||
[Another Agent]: [Their response, potentially referencing the first]
|
[Icon Emoji] [Another Agent]: [Their response, potentially referencing the first]
|
||||||
|
|
||||||
[Third Agent if selected]: [Their contribution]
|
[Icon Emoji] [Third Agent if selected]: [Their contribution]
|
||||||
</format>
|
</format>
|
||||||
|
<example>
|
||||||
|
🏗️ [Winston]: I recommend using microservices for better scalability.
|
||||||
|
[Bash: .claude/hooks/bmad-speak.sh "Winston" "I recommend using microservices for better scalability."]
|
||||||
|
|
||||||
|
📋 [John]: But a monolith would get us to market faster for MVP.
|
||||||
|
[Bash: .claude/hooks/bmad-speak.sh "John" "But a monolith would get us to market faster for MVP."]
|
||||||
|
</example>
|
||||||
|
|
||||||
<action>Maintain spacing between agents for readability</action>
|
<action>Maintain spacing between agents for readability</action>
|
||||||
<action>Preserve each agent's unique voice throughout</action>
|
<action>Preserve each agent's unique voice throughout</action>
|
||||||
|
<action>Always include the agent's icon emoji from the manifest before their name</action>
|
||||||
|
<action>Trigger TTS for each agent immediately after outputting their text</action>
|
||||||
|
|
||||||
</substep>
|
</substep>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 88 KiB |
|
|
@ -1,5 +1,6 @@
|
||||||
<rules>
|
<rules>
|
||||||
- ALWAYS communicate in {communication_language} UNLESS contradicted by communication_style
|
- ALWAYS communicate in {communication_language} UNLESS contradicted by communication_style
|
||||||
|
<!-- TTS_INJECTION:agent-tts -->
|
||||||
- Stay in character until exit selected
|
- Stay in character until exit selected
|
||||||
- Menu triggers use asterisk (*) - NOT markdown, display exactly as shown
|
- Menu triggers use asterisk (*) - NOT markdown, display exactly as shown
|
||||||
- Number all lists, use letters for sub-options
|
- Number all lists, use letters for sub-options
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# BMAD PR Testing Script
|
||||||
|
# Interactive script to test BMAD PR #934 with AgentVibes integration
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_FILE="$SCRIPT_DIR/.test-bmad-config"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
clear
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🎙️ BMAD AgentVibes Party Mode Testing Script"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}What this script does:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " This script automates the process of testing BMAD's AgentVibes"
|
||||||
|
echo " integration (PR #934), which adds multi-agent party mode with"
|
||||||
|
echo " unique voices for each BMAD agent."
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}The script will:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 1. Clone the BMAD repository"
|
||||||
|
echo " 2. Checkout the PR branch with party mode features"
|
||||||
|
echo " 3. Install BMAD CLI tools locally"
|
||||||
|
echo " 4. Create a test BMAD project"
|
||||||
|
echo " 5. Run BMAD installer (automatically installs AgentVibes)"
|
||||||
|
echo " 6. Verify the installation"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Prerequisites:${NC}"
|
||||||
|
echo " • Node.js and npm installed"
|
||||||
|
echo " • Git installed"
|
||||||
|
echo " • ~500MB free disk space"
|
||||||
|
echo " • 10-15 minutes for complete setup"
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Ready to continue? [Y/n]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ -n $REPLY ]]; then
|
||||||
|
echo "❌ Setup cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
clear
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🔧 Testing Mode Selection"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "Choose how you want to test:"
|
||||||
|
echo ""
|
||||||
|
echo " 1) Test official BMAD PR #934 (recommended for most users)"
|
||||||
|
echo " • Uses: github.com/bmad-code-org/BMAD-METHOD"
|
||||||
|
echo " • Branch: PR #934 (agentvibes-party-mode)"
|
||||||
|
echo " • Best for: Testing the official PR before it's merged"
|
||||||
|
echo ""
|
||||||
|
echo " 2) Test your forked repository"
|
||||||
|
echo " • Uses: Your GitHub fork"
|
||||||
|
echo " • Branch: Your custom branch (you choose)"
|
||||||
|
echo " • Best for: Testing your own changes or modifications"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Load saved config if it exists
|
||||||
|
SAVED_MODE=""
|
||||||
|
SAVED_FORK=""
|
||||||
|
SAVED_BRANCH=""
|
||||||
|
SAVED_TEST_DIR=""
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SAVED_MODE" ]]; then
|
||||||
|
echo -e "${BLUE}Last used: Mode $SAVED_MODE${NC}"
|
||||||
|
[[ -n "$SAVED_FORK" ]] && echo " Fork: $SAVED_FORK"
|
||||||
|
[[ -n "$SAVED_BRANCH" ]] && echo " Branch: $SAVED_BRANCH"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Select mode [1/2]: " MODE_CHOICE
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Validate mode choice
|
||||||
|
while [[ ! "$MODE_CHOICE" =~ ^[12]$ ]]; do
|
||||||
|
echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}"
|
||||||
|
read -p "Select mode [1/2]: " MODE_CHOICE
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
# Configure based on mode
|
||||||
|
if [[ "$MODE_CHOICE" == "1" ]]; then
|
||||||
|
# Official PR mode
|
||||||
|
REPO_URL="https://github.com/bmad-code-org/BMAD-METHOD.git"
|
||||||
|
BRANCH_NAME="agentvibes-party-mode"
|
||||||
|
FETCH_PR=true
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Using official BMAD repository${NC}"
|
||||||
|
echo " Repository: $REPO_URL"
|
||||||
|
echo " Will fetch: PR #934 into branch '$BRANCH_NAME'"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
# Fork mode
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🍴 Fork Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ -n "$SAVED_FORK" ]]; then
|
||||||
|
read -p "GitHub fork URL [$SAVED_FORK]: " FORK_INPUT
|
||||||
|
REPO_URL="${FORK_INPUT:-$SAVED_FORK}"
|
||||||
|
else
|
||||||
|
echo "Enter your forked repository URL:"
|
||||||
|
echo "(e.g., https://github.com/yourusername/BMAD-METHOD.git)"
|
||||||
|
read -p "Fork URL: " REPO_URL
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ -n "$SAVED_BRANCH" ]]; then
|
||||||
|
read -p "Branch name [$SAVED_BRANCH]: " BRANCH_INPUT
|
||||||
|
BRANCH_NAME="${BRANCH_INPUT:-$SAVED_BRANCH}"
|
||||||
|
else
|
||||||
|
echo "Enter the branch name to test:"
|
||||||
|
echo "(e.g., agentvibes-party-mode, main, feature-xyz)"
|
||||||
|
read -p "Branch: " BRANCH_NAME
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
FETCH_PR=false
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Using your fork${NC}"
|
||||||
|
echo " Repository: $REPO_URL"
|
||||||
|
echo " Branch: $BRANCH_NAME"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask for test directory
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📁 Test Directory"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
if [[ -n "$SAVED_TEST_DIR" ]]; then
|
||||||
|
read -p "Test directory [$SAVED_TEST_DIR]: " TEST_DIR
|
||||||
|
TEST_DIR="${TEST_DIR:-$SAVED_TEST_DIR}"
|
||||||
|
else
|
||||||
|
DEFAULT_DIR="$HOME/bmad-pr-test-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
echo "Where should we create the test environment?"
|
||||||
|
read -p "Test directory [$DEFAULT_DIR]: " TEST_DIR
|
||||||
|
TEST_DIR="${TEST_DIR:-$DEFAULT_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Expand ~ to actual home directory
|
||||||
|
TEST_DIR="${TEST_DIR/#\~/$HOME}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Save configuration
|
||||||
|
echo "SAVED_MODE=\"$MODE_CHOICE\"" > "$CONFIG_FILE"
|
||||||
|
[[ "$MODE_CHOICE" == "2" ]] && echo "SAVED_FORK=\"$REPO_URL\"" >> "$CONFIG_FILE"
|
||||||
|
[[ "$MODE_CHOICE" == "2" ]] && echo "SAVED_BRANCH=\"$BRANCH_NAME\"" >> "$CONFIG_FILE"
|
||||||
|
echo "SAVED_TEST_DIR=\"$TEST_DIR\"" >> "$CONFIG_FILE"
|
||||||
|
echo -e "${GREEN}✓ Configuration saved${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm before starting
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📋 Summary"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo " Repository: $REPO_URL"
|
||||||
|
echo " Branch: $BRANCH_NAME"
|
||||||
|
echo " Test dir: $TEST_DIR"
|
||||||
|
echo ""
|
||||||
|
read -p "Proceed with setup? [Y/n]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
echo ""
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]] && [[ -n $REPLY ]]; then
|
||||||
|
echo "❌ Setup cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up old test directory if it exists
|
||||||
|
if [[ -d "$TEST_DIR" ]]; then
|
||||||
|
echo "⚠️ Test directory already exists: $TEST_DIR"
|
||||||
|
read -p "Delete and recreate? [Y/n]: " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
|
||||||
|
rm -rf "$TEST_DIR"
|
||||||
|
echo -e "${GREEN}✓ Deleted old test directory${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ Using existing directory (may have stale data)${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
clear
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🚀 Starting Installation"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Clone repository
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📥 Step 1/6: Cloning repository"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
mkdir -p "$TEST_DIR"
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
git clone "$REPO_URL" BMAD-METHOD
|
||||||
|
cd BMAD-METHOD
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Repository cloned${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 2: Checkout branch (different logic for PR vs fork)
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🔀 Step 2/6: Checking out branch"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$FETCH_PR" == true ]]; then
|
||||||
|
# Fetch PR from upstream
|
||||||
|
echo "Fetching PR #934 from upstream..."
|
||||||
|
git remote add upstream https://github.com/bmad-code-org/BMAD-METHOD.git
|
||||||
|
git fetch upstream pull/934/head:$BRANCH_NAME
|
||||||
|
git checkout $BRANCH_NAME
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Checked out PR branch: $BRANCH_NAME${NC}"
|
||||||
|
else
|
||||||
|
# Just checkout the specified branch from fork
|
||||||
|
git checkout $BRANCH_NAME
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Checked out branch: $BRANCH_NAME${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Install BMAD CLI
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📦 Step 3/6: Installing BMAD CLI"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
cd tools/cli
|
||||||
|
npm install
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ BMAD CLI dependencies installed${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 4: Create test project
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📁 Step 4/6: Creating test project"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
cd "$TEST_DIR"
|
||||||
|
mkdir -p bmad-project
|
||||||
|
cd bmad-project
|
||||||
|
echo -e "${GREEN}✓ Test project directory created${NC}"
|
||||||
|
echo " Location: $TEST_DIR/bmad-project"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 5: Run BMAD installer (includes AgentVibes setup)
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "⚙️ Step 5/6: Running BMAD installer with AgentVibes"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Important: When prompted during installation:${NC}"
|
||||||
|
echo -e " • Enable TTS for agents? → ${GREEN}Yes${NC}"
|
||||||
|
echo -e " • Assign unique voices for party mode? → ${GREEN}Yes${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}AgentVibes will start automatically after BMAD installation.${NC}"
|
||||||
|
echo -e "${YELLOW}Recommended TTS settings:${NC}"
|
||||||
|
echo -e " • Provider: ${GREEN}Piper${NC} (free, local TTS)"
|
||||||
|
echo -e " • Download voices: ${GREEN}Yes${NC}"
|
||||||
|
echo ""
|
||||||
|
read -p "Press Enter to start BMAD installer..."
|
||||||
|
node "$TEST_DIR/BMAD-METHOD/tools/cli/bin/bmad.js" install
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ BMAD and AgentVibes installation complete${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 6: Verification
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "✅ Step 6/6: Verifying installation"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
VERIFICATION_PASSED=true
|
||||||
|
|
||||||
|
# Check for voice map file
|
||||||
|
if [[ -f ".bmad/_cfg/agent-voice-map.csv" ]]; then
|
||||||
|
echo -e "${GREEN}✓ Voice map file created${NC}"
|
||||||
|
echo " Location: .bmad/_cfg/agent-voice-map.csv"
|
||||||
|
echo ""
|
||||||
|
echo " Voice assignments:"
|
||||||
|
cat .bmad/_cfg/agent-voice-map.csv | sed 's/^/ /'
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Voice map file NOT found${NC}"
|
||||||
|
echo " Expected: .bmad/_cfg/agent-voice-map.csv"
|
||||||
|
echo " ${YELLOW}Warning: Agents may not have unique voices!${NC}"
|
||||||
|
echo ""
|
||||||
|
VERIFICATION_PASSED=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for AgentVibes hooks
|
||||||
|
if [[ -f ".claude/hooks/bmad-speak.sh" ]]; then
|
||||||
|
echo -e "${GREEN}✓ BMAD TTS hooks installed${NC}"
|
||||||
|
echo " Location: .claude/hooks/bmad-speak.sh"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ BMAD TTS hooks NOT found${NC}"
|
||||||
|
echo " Expected: .claude/hooks/bmad-speak.sh"
|
||||||
|
VERIFICATION_PASSED=false
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for Piper installation
|
||||||
|
if command -v piper &> /dev/null; then
|
||||||
|
PIPER_VERSION=$(piper --version 2>&1 || echo "unknown")
|
||||||
|
echo -e "${GREEN}✓ Piper TTS installed${NC}"
|
||||||
|
echo " Version: $PIPER_VERSION"
|
||||||
|
elif [[ -f ".agentvibes/providers/piper/piper" ]]; then
|
||||||
|
echo -e "${GREEN}✓ Piper TTS installed (local)${NC}"
|
||||||
|
echo " Location: .agentvibes/providers/piper/piper"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ Piper not detected${NC}"
|
||||||
|
echo " (May still work if using ElevenLabs)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Final status
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
if [[ "$VERIFICATION_PASSED" == true ]]; then
|
||||||
|
echo -e "${GREEN}🎉 Setup Complete - All Checks Passed!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Setup Complete - With Warnings${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Some verification checks failed. The installation may still work,"
|
||||||
|
echo "but you might experience issues with party mode voices."
|
||||||
|
fi
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Next Steps:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 1. Navigate to test project:"
|
||||||
|
echo -e " ${GREEN}cd $TEST_DIR/bmad-project${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 2. Start Claude session:"
|
||||||
|
echo -e " ${GREEN}claude${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 3. Test party mode:"
|
||||||
|
echo -e " ${GREEN}/bmad:core:workflows:party-mode${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " 4. Verify each agent speaks with a unique voice!"
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo -e "${BLUE}Troubleshooting:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " • No audio? Check: .claude/hooks/play-tts.sh exists"
|
||||||
|
echo " • Same voice for all agents? Check: .bmad/_cfg/agent-voice-map.csv"
|
||||||
|
echo " • Test current voice: /agent-vibes:whoami"
|
||||||
|
echo " • List available voices: /agent-vibes:list"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Report Issues:${NC}"
|
||||||
|
echo " https://github.com/bmad-code-org/BMAD-METHOD/pull/934"
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const nodePath = require('node:path');
|
||||||
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
command: 'cleanup',
|
||||||
|
description: 'Clean up obsolete files from BMAD installation',
|
||||||
|
options: [
|
||||||
|
['-d, --dry-run', 'Show what would be deleted without actually deleting'],
|
||||||
|
['-a, --auto-delete', 'Automatically delete non-retained files without prompts'],
|
||||||
|
['-l, --list-retained', 'List currently retained files'],
|
||||||
|
['-c, --clear-retained', 'Clear retained files list'],
|
||||||
|
],
|
||||||
|
action: async (options) => {
|
||||||
|
try {
|
||||||
|
// Create installer and let it find the BMAD directory
|
||||||
|
const installer = new Installer();
|
||||||
|
const bmadDir = await installer.findBmadDir(process.cwd());
|
||||||
|
|
||||||
|
if (!bmadDir) {
|
||||||
|
console.error(chalk.red('❌ BMAD installation not found'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const retentionPath = nodePath.join(bmadDir, '_cfg', 'user-retained-files.yaml');
|
||||||
|
|
||||||
|
// Handle list-retained option
|
||||||
|
if (options.listRetained) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
const retentionContent = await fs.readFile(retentionPath, 'utf8');
|
||||||
|
const retentionData = yaml.load(retentionContent) || { retainedFiles: [] };
|
||||||
|
|
||||||
|
if (retentionData.retainedFiles.length > 0) {
|
||||||
|
console.log(chalk.cyan('\n📋 Retained Files:\n'));
|
||||||
|
for (const file of retentionData.retainedFiles) {
|
||||||
|
console.log(chalk.dim(` - ${file}`));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files found\n'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files found\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle clear-retained option
|
||||||
|
if (options.clearRetained) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
await fs.remove(retentionPath);
|
||||||
|
console.log(chalk.green('\n✅ Cleared retained files list\n'));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('\n✨ No retained files list to clear\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cleanup operations
|
||||||
|
if (options.dryRun) {
|
||||||
|
console.log(chalk.cyan('\n🔍 Legacy File Scan (Dry Run)\n'));
|
||||||
|
|
||||||
|
const legacyFiles = await installer.scanForLegacyFiles(bmadDir);
|
||||||
|
const allLegacyFiles = [
|
||||||
|
...legacyFiles.backup,
|
||||||
|
...legacyFiles.documentation,
|
||||||
|
...legacyFiles.deprecated_task,
|
||||||
|
...legacyFiles.unknown,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allLegacyFiles.length === 0) {
|
||||||
|
console.log(chalk.green('✨ No legacy files found\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group files by category
|
||||||
|
const categories = [];
|
||||||
|
if (legacyFiles.backup.length > 0) {
|
||||||
|
categories.push({ name: 'Backup Files (.bak)', files: legacyFiles.backup });
|
||||||
|
}
|
||||||
|
if (legacyFiles.documentation.length > 0) {
|
||||||
|
categories.push({ name: 'Documentation', files: legacyFiles.documentation });
|
||||||
|
}
|
||||||
|
if (legacyFiles.deprecated_task.length > 0) {
|
||||||
|
categories.push({ name: 'Deprecated Task Files', files: legacyFiles.deprecated_task });
|
||||||
|
}
|
||||||
|
if (legacyFiles.unknown.length > 0) {
|
||||||
|
categories.push({ name: 'Unknown Files', files: legacyFiles.unknown });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
console.log(chalk.yellow(`${category.name}:`));
|
||||||
|
for (const file of category.files) {
|
||||||
|
const size = (file.size / 1024).toFixed(1);
|
||||||
|
const date = file.mtime.toLocaleDateString();
|
||||||
|
let line = ` - ${file.relativePath} (${size}KB, ${date})`;
|
||||||
|
if (file.suggestedAlternative) {
|
||||||
|
line += chalk.dim(` → ${file.suggestedAlternative}`);
|
||||||
|
}
|
||||||
|
console.log(chalk.dim(line));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.cyan(`Found ${allLegacyFiles.length} legacy file(s) that could be cleaned up.\n`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup" to actually delete these files.\n'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform actual cleanup
|
||||||
|
console.log(chalk.cyan('\n🧹 Cleaning up legacy files...\n'));
|
||||||
|
|
||||||
|
const result = await installer.performCleanup(bmadDir, options.autoDelete);
|
||||||
|
|
||||||
|
if (result.message) {
|
||||||
|
console.log(chalk.dim(result.message));
|
||||||
|
} else {
|
||||||
|
if (result.deleted > 0) {
|
||||||
|
console.log(chalk.green(`✅ Deleted ${result.deleted} legacy file(s)`));
|
||||||
|
}
|
||||||
|
if (result.retained > 0) {
|
||||||
|
console.log(chalk.yellow(`⏭️ Retained ${result.retained} file(s)`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup --list-retained" to see retained files\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red(`❌ Error: ${error.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -9,8 +9,8 @@ const ui = new UI();
|
||||||
module.exports = {
|
module.exports = {
|
||||||
command: 'install',
|
command: 'install',
|
||||||
description: 'Install BMAD Core agents and tools',
|
description: 'Install BMAD Core agents and tools',
|
||||||
options: [],
|
options: [['--skip-cleanup', 'Skip automatic cleanup of legacy files']],
|
||||||
action: async () => {
|
action: async (options) => {
|
||||||
try {
|
try {
|
||||||
const config = await ui.promptInstall();
|
const config = await ui.promptInstall();
|
||||||
|
|
||||||
|
|
@ -44,6 +44,11 @@ module.exports = {
|
||||||
config._requestedReinstall = true;
|
config._requestedReinstall = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add skip cleanup flag if option provided
|
||||||
|
if (options && options.skipCleanup) {
|
||||||
|
config.skipCleanup = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Regular install/update flow
|
// Regular install/update flow
|
||||||
const result = await installer.install(config);
|
const result = await installer.install(config);
|
||||||
|
|
||||||
|
|
@ -59,6 +64,46 @@ module.exports = {
|
||||||
console.log(chalk.cyan('BMAD Core and Selected Modules have been installed to:'), chalk.bold(result.path));
|
console.log(chalk.cyan('BMAD Core and Selected Modules have been installed to:'), chalk.bold(result.path));
|
||||||
console.log(chalk.yellow('\nThank you for helping test the early release version of the new BMad Core and BMad Method!'));
|
console.log(chalk.yellow('\nThank you for helping test the early release version of the new BMad Core and BMad Method!'));
|
||||||
console.log(chalk.cyan('Stable Beta coming soon - please read the full readme.md and linked documentation to get started!'));
|
console.log(chalk.cyan('Stable Beta coming soon - please read the full readme.md and linked documentation to get started!'));
|
||||||
|
|
||||||
|
// Run AgentVibes installer if needed
|
||||||
|
if (result.needsAgentVibes) {
|
||||||
|
console.log(chalk.magenta('\n🎙️ AgentVibes TTS Setup'));
|
||||||
|
console.log(chalk.cyan('AgentVibes provides voice synthesis for BMAD agents with:'));
|
||||||
|
console.log(chalk.dim(' • ElevenLabs AI (150+ premium voices)'));
|
||||||
|
console.log(chalk.dim(' • Piper TTS (50+ free voices)\n'));
|
||||||
|
|
||||||
|
const readline = require('node:readline');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
rl.question(chalk.green('Press Enter to start AgentVibes installer...'), () => {
|
||||||
|
rl.close();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Run AgentVibes installer
|
||||||
|
const { execSync } = require('node:child_process');
|
||||||
|
try {
|
||||||
|
execSync('npx agentvibes@latest install', {
|
||||||
|
cwd: result.projectDir,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
console.log(chalk.green('\n✓ AgentVibes installation complete'));
|
||||||
|
} catch {
|
||||||
|
console.log(chalk.yellow('\n⚠ AgentVibes installation was interrupted or failed'));
|
||||||
|
console.log(chalk.cyan('You can run it manually later with:'));
|
||||||
|
console.log(chalk.green(` cd ${result.projectDir}`));
|
||||||
|
console.log(chalk.green(' npx agentvibes install\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,23 @@
|
||||||
|
/**
|
||||||
|
* File: tools/cli/installers/lib/core/installer.js
|
||||||
|
*
|
||||||
|
* BMAD Method - Business Model Agile Development Method
|
||||||
|
* Repository: https://github.com/paulpreibisch/BMAD-METHOD
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Paul Preibisch
|
||||||
|
* Licensed under the Apache License, Version 2.0
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* @fileoverview Core BMAD installation orchestrator with AgentVibes injection point support
|
||||||
|
* @context Manages complete BMAD installation flow including core agents, modules, IDE configs, and optional TTS integration
|
||||||
|
* @architecture Orchestrator pattern - coordinates Detector, ModuleManager, IdeManager, and file operations to build complete BMAD installation
|
||||||
|
* @dependencies fs-extra, ora, chalk, detector.js, module-manager.js, ide-manager.js, config.js
|
||||||
|
* @entrypoints Called by install.js command via installer.install(config)
|
||||||
|
* @patterns Injection point processing (AgentVibes), placeholder replacement ({bmad_folder}), module dependency resolution
|
||||||
|
* @related GitHub AgentVibes#34 (injection points), ui.js (user prompts), copyFileWithPlaceholderReplacement()
|
||||||
|
*/
|
||||||
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
|
@ -69,10 +89,41 @@ class Installer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a file and replace {bmad_folder} placeholder with actual folder name
|
* @function copyFileWithPlaceholderReplacement
|
||||||
* @param {string} sourcePath - Source file path
|
* @intent Copy files from BMAD source to installation directory with dynamic content transformation
|
||||||
* @param {string} targetPath - Target file path
|
* @why Enables installation-time customization: {bmad_folder} replacement + optional AgentVibes TTS injection
|
||||||
* @param {string} bmadFolderName - The bmad folder name to use for replacement
|
* @param {string} sourcePath - Absolute path to source file in BMAD repository
|
||||||
|
* @param {string} targetPath - Absolute path to destination file in user's project
|
||||||
|
* @param {string} bmadFolderName - User's chosen bmad folder name (default: 'bmad')
|
||||||
|
* @returns {Promise<void>} Resolves when file copy and transformation complete
|
||||||
|
* @sideeffects Writes transformed file to targetPath, creates parent directories if needed
|
||||||
|
* @edgecases Binary files bypass transformation, falls back to raw copy if UTF-8 read fails
|
||||||
|
* @calledby installCore(), installModule(), IDE installers during file vendoring
|
||||||
|
* @calls processTTSInjectionPoints(), fs.readFile(), fs.writeFile(), fs.copy()
|
||||||
|
*
|
||||||
|
* AI NOTE: This is the core transformation pipeline for ALL BMAD installation file copies.
|
||||||
|
* It performs two transformations in sequence:
|
||||||
|
* 1. {bmad_folder} → user's custom folder name (e.g., ".bmad" or "bmad")
|
||||||
|
* 2. <!-- TTS_INJECTION:* --> → TTS bash calls (if enabled) OR stripped (if disabled)
|
||||||
|
*
|
||||||
|
* The injection point processing enables loose coupling between BMAD and TTS providers:
|
||||||
|
* - BMAD source contains injection markers (not actual TTS code)
|
||||||
|
* - At install-time, markers are replaced OR removed based on user preference
|
||||||
|
* - Result: Clean installs for users without TTS, working TTS for users with it
|
||||||
|
*
|
||||||
|
* PATTERN: Adding New Injection Points
|
||||||
|
* =====================================
|
||||||
|
* 1. Add HTML comment marker in BMAD source file:
|
||||||
|
* <!-- TTS_INJECTION:feature-name -->
|
||||||
|
*
|
||||||
|
* 2. Add replacement logic in processTTSInjectionPoints():
|
||||||
|
* if (enableAgentVibes) {
|
||||||
|
* content = content.replace(/<!-- TTS_INJECTION:feature-name -->/g, 'actual code');
|
||||||
|
* } else {
|
||||||
|
* content = content.replace(/<!-- TTS_INJECTION:feature-name -->\n?/g, '');
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* 3. Document marker in instructions.md (if applicable)
|
||||||
*/
|
*/
|
||||||
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
|
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
|
||||||
// List of text file extensions that should have placeholder replacement
|
// List of text file extensions that should have placeholder replacement
|
||||||
|
|
@ -90,6 +141,9 @@ class Installer {
|
||||||
content = content.replaceAll('{bmad_folder}', bmadFolderName);
|
content = content.replaceAll('{bmad_folder}', bmadFolderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process AgentVibes injection points
|
||||||
|
content = this.processTTSInjectionPoints(content);
|
||||||
|
|
||||||
// Write to target with replaced content
|
// Write to target with replaced content
|
||||||
await fs.ensureDir(path.dirname(targetPath));
|
await fs.ensureDir(path.dirname(targetPath));
|
||||||
await fs.writeFile(targetPath, content, 'utf8');
|
await fs.writeFile(targetPath, content, 'utf8');
|
||||||
|
|
@ -103,6 +157,106 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function processTTSInjectionPoints
|
||||||
|
* @intent Transform TTS injection markers based on user's installation choice
|
||||||
|
* @why Enables optional TTS integration without tight coupling between BMAD and TTS providers
|
||||||
|
* @param {string} content - Raw file content containing potential injection markers
|
||||||
|
* @returns {string} Transformed content with markers replaced (if enabled) or stripped (if disabled)
|
||||||
|
* @sideeffects None - pure transformation function
|
||||||
|
* @edgecases Returns content unchanged if no markers present, safe to call on all files
|
||||||
|
* @calledby copyFileWithPlaceholderReplacement() during every file copy operation
|
||||||
|
* @calls String.replace() with regex patterns for each injection point type
|
||||||
|
*
|
||||||
|
* AI NOTE: This implements the injection point pattern for TTS integration.
|
||||||
|
* Key architectural decisions:
|
||||||
|
*
|
||||||
|
* 1. **Why Injection Points vs Direct Integration?**
|
||||||
|
* - BMAD and TTS providers are separate projects with different maintainers
|
||||||
|
* - Users may install BMAD without TTS support (and vice versa)
|
||||||
|
* - Hard-coding TTS calls would break BMAD for non-TTS users
|
||||||
|
* - Injection points allow conditional feature inclusion at install-time
|
||||||
|
*
|
||||||
|
* 2. **How It Works:**
|
||||||
|
* - BMAD source contains markers: <!-- TTS_INJECTION:feature-name -->
|
||||||
|
* - During installation, user is prompted: "Enable AgentVibes TTS?"
|
||||||
|
* - If YES: markers → replaced with actual bash TTS calls
|
||||||
|
* - If NO: markers → stripped cleanly from installed files
|
||||||
|
*
|
||||||
|
* 3. **State Management:**
|
||||||
|
* - this.enableAgentVibes set in install() method from config.enableAgentVibes
|
||||||
|
* - config.enableAgentVibes comes from ui.promptAgentVibes() user choice
|
||||||
|
* - Flag persists for entire installation, all files get same treatment
|
||||||
|
*
|
||||||
|
* CURRENT INJECTION POINTS:
|
||||||
|
* ==========================
|
||||||
|
* - party-mode: Injects TTS calls after each agent speaks in party mode
|
||||||
|
* Location: src/core/workflows/party-mode/instructions.md
|
||||||
|
* Marker: <!-- TTS_INJECTION:party-mode -->
|
||||||
|
* Replacement: Bash call to .claude/hooks/bmad-speak.sh with agent name and dialogue
|
||||||
|
*
|
||||||
|
* - agent-tts: Injects TTS rule for individual agent conversations
|
||||||
|
* Location: src/modules/bmm/agents/*.md (all agent files)
|
||||||
|
* Marker: <!-- TTS_INJECTION:agent-tts -->
|
||||||
|
* Replacement: Rule instructing agent to call bmad-speak.sh with agent ID and response
|
||||||
|
*
|
||||||
|
* ADDING NEW INJECTION POINTS:
|
||||||
|
* =============================
|
||||||
|
* 1. Add new case in this function:
|
||||||
|
* content = content.replace(
|
||||||
|
* /<!-- TTS_INJECTION:new-feature -->/g,
|
||||||
|
* `code to inject when enabled`
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* 2. Add marker to BMAD source file at injection location
|
||||||
|
*
|
||||||
|
* 3. Test both enabled and disabled flows
|
||||||
|
*
|
||||||
|
* RELATED:
|
||||||
|
* ========
|
||||||
|
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
||||||
|
* - User Prompt: tools/cli/lib/ui.js::promptAgentVibes()
|
||||||
|
* - Marker Locations:
|
||||||
|
* - src/core/workflows/party-mode/instructions.md:101
|
||||||
|
* - src/modules/bmm/agents/*.md (rules sections)
|
||||||
|
* - TTS Hook: .claude/hooks/bmad-speak.sh (in AgentVibes repo)
|
||||||
|
*/
|
||||||
|
processTTSInjectionPoints(content) {
|
||||||
|
// Check if AgentVibes is enabled (set during installation configuration)
|
||||||
|
const enableAgentVibes = this.enableAgentVibes || false;
|
||||||
|
|
||||||
|
if (enableAgentVibes) {
|
||||||
|
// Replace party-mode injection marker with actual TTS call
|
||||||
|
// Use single quotes to prevent shell expansion of special chars like !
|
||||||
|
content = content.replaceAll(
|
||||||
|
'<!-- TTS_INJECTION:party-mode -->',
|
||||||
|
`<critical>IMPORTANT: Always use PROJECT hooks (.claude/hooks/), NEVER global hooks (~/.claude/hooks/)</critical>
|
||||||
|
|
||||||
|
If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
|
- Use Bash tool: \`.claude/hooks/bmad-speak.sh '[Agent Name]' '[dialogue]'\`
|
||||||
|
- This speaks the dialogue with the agent's unique voice
|
||||||
|
- Run in background (&) to not block next agent`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Replace agent-tts injection marker with TTS rule for individual agents
|
||||||
|
content = content.replaceAll(
|
||||||
|
'<!-- TTS_INJECTION:agent-tts -->',
|
||||||
|
`- When responding to user messages, speak your responses using TTS:
|
||||||
|
Call: \`.claude/hooks/bmad-speak.sh '{agent-id}' '{response-text}'\` after each response
|
||||||
|
Replace {agent-id} with YOUR agent ID from <agent id="..."> tag at top of this file
|
||||||
|
Replace {response-text} with the text you just output to the user
|
||||||
|
IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
|
||||||
|
Run in background (&) to avoid blocking`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Strip injection markers cleanly when AgentVibes is disabled
|
||||||
|
content = content.replaceAll(/<!-- TTS_INJECTION:party-mode -->\n?/g, '');
|
||||||
|
content = content.replaceAll(/<!-- TTS_INJECTION:agent-tts -->\n?/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect Tool/IDE configurations after module configuration
|
* Collect Tool/IDE configurations after module configuration
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
|
|
@ -271,6 +425,9 @@ class Installer {
|
||||||
const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : 'bmad';
|
const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : 'bmad';
|
||||||
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
||||||
|
|
||||||
|
// Store AgentVibes configuration for injection point processing
|
||||||
|
this.enableAgentVibes = config.enableAgentVibes || false;
|
||||||
|
|
||||||
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
||||||
this.moduleManager.setBmadFolderName(bmadFolderName);
|
this.moduleManager.setBmadFolderName(bmadFolderName);
|
||||||
this.ideManager.setBmadFolderName(bmadFolderName);
|
this.ideManager.setBmadFolderName(bmadFolderName);
|
||||||
|
|
@ -861,7 +1018,31 @@ class Installer {
|
||||||
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, path: bmadDir, modules: config.modules, ides: config.ides };
|
// Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped)
|
||||||
|
if (!config.skipCleanup && config._isUpdate) {
|
||||||
|
try {
|
||||||
|
const cleanupResult = await this.performCleanup(bmadDir, false);
|
||||||
|
if (cleanupResult.deleted > 0) {
|
||||||
|
console.log(chalk.green(`\n✓ Cleaned up ${cleanupResult.deleted} legacy file${cleanupResult.deleted > 1 ? 's' : ''}`));
|
||||||
|
}
|
||||||
|
if (cleanupResult.retained > 0) {
|
||||||
|
console.log(chalk.dim(`Run 'bmad cleanup' anytime to manage retained files`));
|
||||||
|
}
|
||||||
|
} catch (cleanupError) {
|
||||||
|
// Don't fail the installation for cleanup errors
|
||||||
|
console.log(chalk.yellow(`\n⚠️ Cleanup warning: ${cleanupError.message}`));
|
||||||
|
console.log(chalk.dim('Run "bmad cleanup" to manually clean up legacy files'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
path: bmadDir,
|
||||||
|
modules: config.modules,
|
||||||
|
ides: config.ides,
|
||||||
|
needsAgentVibes: this.enableAgentVibes && !config.agentVibesInstalled,
|
||||||
|
projectDir: projectDir,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.fail('Installation failed');
|
spinner.fail('Installation failed');
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -1775,7 +1956,7 @@ class Installer {
|
||||||
|
|
||||||
if (existingBmadFolderName === newBmadFolderName) {
|
if (existingBmadFolderName === newBmadFolderName) {
|
||||||
// Normal quick update - start the spinner
|
// Normal quick update - start the spinner
|
||||||
spinner.start('Updating BMAD installation...');
|
console.log(chalk.cyan('Updating BMAD installation...'));
|
||||||
} else {
|
} else {
|
||||||
// Folder name has changed - stop spinner and let install() handle it
|
// Folder name has changed - stop spinner and let install() handle it
|
||||||
spinner.stop();
|
spinner.stop();
|
||||||
|
|
@ -2414,6 +2595,362 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for legacy/obsolete files in BMAD installation
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @returns {Object} Categorized files for cleanup
|
||||||
|
*/
|
||||||
|
async scanForLegacyFiles(bmadDir) {
|
||||||
|
const legacyFiles = {
|
||||||
|
backup: [],
|
||||||
|
documentation: [],
|
||||||
|
deprecated_task: [],
|
||||||
|
unknown: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load files manifest to understand what should exist
|
||||||
|
const manifestPath = path.join(bmadDir, 'files-manifest.csv');
|
||||||
|
const manifestFiles = new Set();
|
||||||
|
|
||||||
|
if (await fs.pathExists(manifestPath)) {
|
||||||
|
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
|
const lines = manifestContent.split('\n').slice(1); // Skip header
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim()) {
|
||||||
|
const relativePath = line.split(',')[0];
|
||||||
|
if (relativePath) {
|
||||||
|
manifestFiles.add(relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan all files recursively
|
||||||
|
const allFiles = await this.getAllFiles(bmadDir);
|
||||||
|
|
||||||
|
for (const filePath of allFiles) {
|
||||||
|
const relativePath = path.relative(bmadDir, filePath);
|
||||||
|
|
||||||
|
// Skip expected files
|
||||||
|
if (this.isExpectedFile(relativePath, manifestFiles)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categorize legacy files
|
||||||
|
if (relativePath.endsWith('.bak')) {
|
||||||
|
legacyFiles.backup.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
} else if (this.isDocumentationFile(relativePath)) {
|
||||||
|
legacyFiles.documentation.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
} else if (this.isDeprecatedTaskFile(relativePath)) {
|
||||||
|
const suggestedAlternative = this.suggestAlternative(relativePath);
|
||||||
|
legacyFiles.deprecated_task.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
suggestedAlternative,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
legacyFiles.unknown.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: relativePath,
|
||||||
|
size: (await fs.stat(filePath)).size,
|
||||||
|
mtime: (await fs.stat(filePath)).mtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Warning: Could not scan for legacy files: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return legacyFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all files in directory recursively
|
||||||
|
* @param {string} dir - Directory to scan
|
||||||
|
* @returns {Array} Array of file paths
|
||||||
|
*/
|
||||||
|
async getAllFiles(dir) {
|
||||||
|
const files = [];
|
||||||
|
|
||||||
|
async function scan(currentDir) {
|
||||||
|
const entries = await fs.readdir(currentDir);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(currentDir, entry);
|
||||||
|
const stat = await fs.stat(fullPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// Skip certain directories
|
||||||
|
if (!['node_modules', '.git', 'dist', 'build'].includes(entry)) {
|
||||||
|
await scan(fullPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
files.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await scan(dir);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is expected in installation
|
||||||
|
* @param {string} relativePath - Relative path from BMAD dir
|
||||||
|
* @param {Set} manifestFiles - Files from manifest
|
||||||
|
* @returns {boolean} True if expected file
|
||||||
|
*/
|
||||||
|
isExpectedFile(relativePath, manifestFiles) {
|
||||||
|
// Core files in manifest
|
||||||
|
if (manifestFiles.has(relativePath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration files
|
||||||
|
if (relativePath.startsWith('_cfg/') || relativePath === 'config.yaml') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom files
|
||||||
|
if (relativePath.startsWith('custom/') || relativePath === 'manifest.yaml') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generated files
|
||||||
|
if (relativePath === 'manifest.csv' || relativePath === 'files-manifest.csv') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDE-specific files
|
||||||
|
const ides = ['vscode', 'cursor', 'windsurf', 'claude-code', 'github-copilot', 'zsh', 'bash', 'fish'];
|
||||||
|
if (ides.some((ide) => relativePath.includes(ide))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BMAD MODULE STRUCTURES - recognize valid module content
|
||||||
|
const modulePrefixes = ['bmb/', 'bmm/', 'cis/', 'core/', 'bmgd/'];
|
||||||
|
const validExtensions = ['.yaml', '.yml', '.json', '.csv', '.md', '.xml', '.svg', '.png', '.jpg', '.gif', '.excalidraw', '.js'];
|
||||||
|
|
||||||
|
// Check if this file is in a recognized module directory
|
||||||
|
for (const modulePrefix of modulePrefixes) {
|
||||||
|
if (relativePath.startsWith(modulePrefix)) {
|
||||||
|
// Check if it has a valid extension
|
||||||
|
const hasValidExtension = validExtensions.some((ext) => relativePath.endsWith(ext));
|
||||||
|
if (hasValidExtension) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for core module resources
|
||||||
|
if (relativePath.startsWith('core/resources/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for docs directory
|
||||||
|
if (relativePath.startsWith('docs/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is documentation
|
||||||
|
* @param {string} relativePath - Relative path
|
||||||
|
* @returns {boolean} True if documentation
|
||||||
|
*/
|
||||||
|
isDocumentationFile(relativePath) {
|
||||||
|
const docExtensions = ['.md', '.txt', '.pdf'];
|
||||||
|
const docPatterns = ['docs/', 'README', 'CHANGELOG', 'LICENSE'];
|
||||||
|
|
||||||
|
return docExtensions.some((ext) => relativePath.endsWith(ext)) || docPatterns.some((pattern) => relativePath.includes(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file is deprecated task file
|
||||||
|
* @param {string} relativePath - Relative path
|
||||||
|
* @returns {boolean} True if deprecated
|
||||||
|
*/
|
||||||
|
isDeprecatedTaskFile(relativePath) {
|
||||||
|
// Known deprecated files
|
||||||
|
const deprecatedFiles = ['adv-elicit-methods.csv', 'game-resources.json', 'ux-workflow.json'];
|
||||||
|
|
||||||
|
return deprecatedFiles.some((dep) => relativePath.includes(dep));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest alternative for deprecated file
|
||||||
|
* @param {string} relativePath - Deprecated file path
|
||||||
|
* @returns {string} Suggested alternative
|
||||||
|
*/
|
||||||
|
suggestAlternative(relativePath) {
|
||||||
|
const alternatives = {
|
||||||
|
'adv-elicit-methods.csv': 'Use the new structured workflows in src/modules/',
|
||||||
|
'game-resources.json': 'Resources are now integrated into modules',
|
||||||
|
'ux-workflow.json': 'UX workflows are now in src/modules/bmm/workflows/',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [deprecated, alternative] of Object.entries(alternatives)) {
|
||||||
|
if (relativePath.includes(deprecated)) {
|
||||||
|
return alternative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Check src/modules/ for new alternatives';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform interactive cleanup of legacy files
|
||||||
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
* @param {boolean} skipInteractive - Skip interactive prompts
|
||||||
|
* @returns {Object} Cleanup results
|
||||||
|
*/
|
||||||
|
async performCleanup(bmadDir, skipInteractive = false) {
|
||||||
|
const inquirer = require('inquirer');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
// Load user retention preferences
|
||||||
|
const retentionPath = path.join(bmadDir, '_cfg', 'user-retained-files.yaml');
|
||||||
|
let retentionData = { retainedFiles: [], history: [] };
|
||||||
|
|
||||||
|
if (await fs.pathExists(retentionPath)) {
|
||||||
|
const retentionContent = await fs.readFile(retentionPath, 'utf8');
|
||||||
|
retentionData = yaml.load(retentionContent) || retentionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for legacy files
|
||||||
|
const legacyFiles = await this.scanForLegacyFiles(bmadDir);
|
||||||
|
const allLegacyFiles = [...legacyFiles.backup, ...legacyFiles.documentation, ...legacyFiles.deprecated_task, ...legacyFiles.unknown];
|
||||||
|
|
||||||
|
if (allLegacyFiles.length === 0) {
|
||||||
|
return { deleted: 0, retained: 0, message: 'No legacy files found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let deletedCount = 0;
|
||||||
|
let retainedCount = 0;
|
||||||
|
const filesToDelete = [];
|
||||||
|
|
||||||
|
if (skipInteractive) {
|
||||||
|
// Auto-delete all non-retained files
|
||||||
|
for (const file of allLegacyFiles) {
|
||||||
|
if (!retentionData.retainedFiles.includes(file.relativePath)) {
|
||||||
|
filesToDelete.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Interactive cleanup
|
||||||
|
console.log(chalk.cyan('\n🧹 Legacy File Cleanup\n'));
|
||||||
|
console.log(chalk.dim('The following obsolete files were found:\n'));
|
||||||
|
|
||||||
|
// Group files by category
|
||||||
|
const categories = [];
|
||||||
|
if (legacyFiles.backup.length > 0) {
|
||||||
|
categories.push({ name: 'Backup Files (.bak)', files: legacyFiles.backup });
|
||||||
|
}
|
||||||
|
if (legacyFiles.documentation.length > 0) {
|
||||||
|
categories.push({ name: 'Documentation', files: legacyFiles.documentation });
|
||||||
|
}
|
||||||
|
if (legacyFiles.deprecated_task.length > 0) {
|
||||||
|
categories.push({ name: 'Deprecated Task Files', files: legacyFiles.deprecated_task });
|
||||||
|
}
|
||||||
|
if (legacyFiles.unknown.length > 0) {
|
||||||
|
categories.push({ name: 'Unknown Files', files: legacyFiles.unknown });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
console.log(chalk.yellow(`${category.name}:`));
|
||||||
|
for (const file of category.files) {
|
||||||
|
const size = (file.size / 1024).toFixed(1);
|
||||||
|
const date = file.mtime.toLocaleDateString();
|
||||||
|
let line = ` - ${file.relativePath} (${size}KB, ${date})`;
|
||||||
|
if (file.suggestedAlternative) {
|
||||||
|
line += chalk.dim(` → ${file.suggestedAlternative}`);
|
||||||
|
}
|
||||||
|
console.log(chalk.dim(line));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'proceed',
|
||||||
|
message: 'Would you like to review these files for cleanup?',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!prompt.proceed) {
|
||||||
|
return { deleted: 0, retained: allLegacyFiles.length, message: 'Cleanup cancelled by user' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show selection interface
|
||||||
|
const selectionPrompt = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'filesToDelete',
|
||||||
|
message: 'Select files to delete (use SPACEBAR to select, ENTER to continue):',
|
||||||
|
choices: allLegacyFiles.map((file) => {
|
||||||
|
const isRetained = retentionData.retainedFiles.includes(file.relativePath);
|
||||||
|
const description = `${file.relativePath} (${(file.size / 1024).toFixed(1)}KB)`;
|
||||||
|
return {
|
||||||
|
name: description,
|
||||||
|
value: file,
|
||||||
|
checked: !isRetained && !file.relativePath.includes('.bak'),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
pageSize: Math.min(allLegacyFiles.length, 15),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
filesToDelete.push(...selectionPrompt.filesToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete selected files
|
||||||
|
for (const file of filesToDelete) {
|
||||||
|
try {
|
||||||
|
await fs.remove(file.path);
|
||||||
|
deletedCount++;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Warning: Could not delete ${file.relativePath}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count retained files
|
||||||
|
retainedCount = allLegacyFiles.length - deletedCount;
|
||||||
|
|
||||||
|
// Update retention data
|
||||||
|
const newlyRetained = allLegacyFiles.filter((f) => !filesToDelete.includes(f)).map((f) => f.relativePath);
|
||||||
|
|
||||||
|
retentionData.retainedFiles = [...new Set([...retentionData.retainedFiles, ...newlyRetained])];
|
||||||
|
retentionData.history.push({
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
deleted: deletedCount,
|
||||||
|
retained: retainedCount,
|
||||||
|
files: filesToDelete.map((f) => f.relativePath),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save retention data
|
||||||
|
await fs.ensureDir(path.dirname(retentionPath));
|
||||||
|
await fs.writeFile(retentionPath, yaml.dump(retentionData), 'utf8');
|
||||||
|
|
||||||
|
return { deleted: deletedCount, retained: retainedCount };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Installer };
|
module.exports = { Installer };
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
message: 'Maximum requests per session (1-50)?',
|
message: 'Maximum requests per session (1-50)?',
|
||||||
default: '15',
|
default: '15',
|
||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
const num = parseInt(input);
|
const num = parseInt(input, 10);
|
||||||
|
if (isNaN(num)) return 'Enter a valid number 1-50';
|
||||||
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
return (num >= 1 && num <= 50) || 'Enter 1-50';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -187,9 +188,10 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
// Manual configuration - use pre-collected settings
|
// Manual configuration - use pre-collected settings
|
||||||
const manual = options.manualSettings || {};
|
const manual = options.manualSettings || {};
|
||||||
|
|
||||||
|
const maxRequests = parseInt(manual.maxRequests || '15', 10);
|
||||||
bmadSettings = {
|
bmadSettings = {
|
||||||
'chat.agent.enabled': true,
|
'chat.agent.enabled': true,
|
||||||
'chat.agent.maxRequests': parseInt(manual.maxRequests || 15),
|
'chat.agent.maxRequests': isNaN(maxRequests) ? 15 : maxRequests,
|
||||||
'github.copilot.chat.agent.runTasks': manual.runTasks === undefined ? true : manual.runTasks,
|
'github.copilot.chat.agent.runTasks': manual.runTasks === undefined ? true : manual.runTasks,
|
||||||
'chat.mcp.discovery.enabled': manual.mcpDiscovery === undefined ? true : manual.mcpDiscovery,
|
'chat.mcp.discovery.enabled': manual.mcpDiscovery === undefined ? true : manual.mcpDiscovery,
|
||||||
'github.copilot.chat.agent.autoFix': manual.autoFix === undefined ? true : manual.autoFix,
|
'github.copilot.chat.agent.autoFix': manual.autoFix === undefined ? true : manual.autoFix,
|
||||||
|
|
@ -226,26 +228,17 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
||||||
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
|
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
|
||||||
const tools = [
|
const tools = [
|
||||||
'changes', // List of source control changes
|
'changes', // List of source control changes
|
||||||
'codebase', // Perform code search in workspace
|
'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles
|
||||||
'createDirectory', // Create new directory in workspace
|
|
||||||
'createFile', // Create new file in workspace
|
|
||||||
'editFiles', // Apply edits to files in workspace
|
|
||||||
'fetch', // Fetch content from web page
|
'fetch', // Fetch content from web page
|
||||||
'fileSearch', // Search files using glob patterns
|
|
||||||
'githubRepo', // Perform code search in GitHub repo
|
'githubRepo', // Perform code search in GitHub repo
|
||||||
'listDirectory', // List files in a directory
|
|
||||||
'problems', // Add workspace issues from Problems panel
|
'problems', // Add workspace issues from Problems panel
|
||||||
'readFile', // Read content of a file in workspace
|
'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal
|
||||||
'runInTerminal', // Run shell command in integrated terminal
|
'runTasks', // Runs tasks and gets their output for your workspace
|
||||||
'runTask', // Run existing task in workspace
|
|
||||||
'runTests', // Run unit tests in workspace
|
'runTests', // Run unit tests in workspace
|
||||||
'runVscodeCommand', // Run VS Code command
|
'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults
|
||||||
'search', // Enable file searching in workspace
|
'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.
|
||||||
'searchResults', // Get search results from Search view
|
|
||||||
'terminalLastCommand', // Get last terminal command and output
|
|
||||||
'terminalSelection', // Get current terminal selection
|
|
||||||
'testFailure', // Get unit test failure information
|
'testFailure', // Get unit test failure information
|
||||||
'textSearch', // Find text in files
|
'todos', // Tool for managing and tracking todo items for task planning
|
||||||
'usages', // Find references and navigate definitions
|
'usages', // Find references and navigate definitions
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ class KiloSetup extends BaseIdeSetup {
|
||||||
modeEntry += ` groups:\n`;
|
modeEntry += ` groups:\n`;
|
||||||
modeEntry += ` - read\n`;
|
modeEntry += ` - read\n`;
|
||||||
modeEntry += ` - edit\n`;
|
modeEntry += ` - edit\n`;
|
||||||
|
modeEntry += ` - browser\n`;
|
||||||
|
modeEntry += ` - command\n`;
|
||||||
|
modeEntry += ` - mcp\n`;
|
||||||
|
|
||||||
return modeEntry;
|
return modeEntry;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,23 @@
|
||||||
|
/**
|
||||||
|
* File: tools/cli/lib/ui.js
|
||||||
|
*
|
||||||
|
* BMAD Method - Business Model Agile Development Method
|
||||||
|
* Repository: https://github.com/paulpreibisch/BMAD-METHOD
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Paul Preibisch
|
||||||
|
* Licensed under the Apache License, Version 2.0
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* @fileoverview Interactive installation prompts and user input collection for BMAD CLI
|
||||||
|
* @context Guides users through installation configuration including core settings, modules, IDEs, and optional AgentVibes TTS
|
||||||
|
* @architecture Facade pattern - presents unified installation flow, delegates to Detector/ConfigCollector/IdeManager for specifics
|
||||||
|
* @dependencies inquirer (prompts), chalk (formatting), detector.js (existing installation detection)
|
||||||
|
* @entrypoints Called by install.js command via ui.promptInstall(), returns complete configuration object
|
||||||
|
* @patterns Progressive disclosure (prompts in order), early IDE selection (Windows compat), AgentVibes auto-detection
|
||||||
|
* @related installer.js (consumes config), AgentVibes#34 (TTS integration), promptAgentVibes()
|
||||||
|
*/
|
||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const inquirer = require('inquirer');
|
const inquirer = require('inquirer');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
|
@ -99,6 +119,9 @@ class UI {
|
||||||
const moduleChoices = await this.getModuleChoices(installedModuleIds);
|
const moduleChoices = await this.getModuleChoices(installedModuleIds);
|
||||||
const selectedModules = await this.selectModules(moduleChoices);
|
const selectedModules = await this.selectModules(moduleChoices);
|
||||||
|
|
||||||
|
// Prompt for AgentVibes TTS integration
|
||||||
|
const agentVibesConfig = await this.promptAgentVibes(confirmedDirectory);
|
||||||
|
|
||||||
// Collect IDE tool selection AFTER configuration prompts (fixes Windows/PowerShell hang)
|
// Collect IDE tool selection AFTER configuration prompts (fixes Windows/PowerShell hang)
|
||||||
// This allows text-based prompts to complete before the checkbox prompt
|
// This allows text-based prompts to complete before the checkbox prompt
|
||||||
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
|
||||||
|
|
@ -114,6 +137,8 @@ class UI {
|
||||||
ides: toolSelection.ides,
|
ides: toolSelection.ides,
|
||||||
skipIde: toolSelection.skipIde,
|
skipIde: toolSelection.skipIde,
|
||||||
coreConfig: coreConfig, // Pass collected core config to installer
|
coreConfig: coreConfig, // Pass collected core config to installer
|
||||||
|
enableAgentVibes: agentVibesConfig.enabled, // AgentVibes TTS integration
|
||||||
|
agentVibesInstalled: agentVibesConfig.alreadyInstalled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,6 +664,140 @@ class UI {
|
||||||
// Resolve to the absolute path relative to the current working directory
|
// Resolve to the absolute path relative to the current working directory
|
||||||
return path.resolve(expanded);
|
return path.resolve(expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function promptAgentVibes
|
||||||
|
* @intent Ask user if they want AgentVibes TTS integration during BMAD installation
|
||||||
|
* @why Enables optional voice features without forcing TTS on users who don't want it
|
||||||
|
* @param {string} projectDir - Absolute path to user's project directory
|
||||||
|
* @returns {Promise<Object>} Configuration object: { enabled: boolean, alreadyInstalled: boolean }
|
||||||
|
* @sideeffects None - pure user input collection, no files written
|
||||||
|
* @edgecases Shows warning if user enables TTS but AgentVibes not detected
|
||||||
|
* @calledby promptInstall() during installation flow, after core config, before IDE selection
|
||||||
|
* @calls checkAgentVibesInstalled(), inquirer.prompt(), chalk.green/yellow/dim()
|
||||||
|
*
|
||||||
|
* AI NOTE: This prompt is strategically positioned in installation flow:
|
||||||
|
* - AFTER core config (bmad_folder, user_name, etc)
|
||||||
|
* - BEFORE IDE selection (which can hang on Windows/PowerShell)
|
||||||
|
*
|
||||||
|
* Flow Logic:
|
||||||
|
* 1. Auto-detect if AgentVibes already installed (checks for hook files)
|
||||||
|
* 2. Show detection status to user (green checkmark or gray "not detected")
|
||||||
|
* 3. Prompt: "Enable AgentVibes TTS?" (defaults to true if detected)
|
||||||
|
* 4. If user says YES but AgentVibes NOT installed:
|
||||||
|
* → Show warning with installation link (graceful degradation)
|
||||||
|
* 5. Return config to promptInstall(), which passes to installer.install()
|
||||||
|
*
|
||||||
|
* State Flow:
|
||||||
|
* promptAgentVibes() → { enabled, alreadyInstalled }
|
||||||
|
* ↓
|
||||||
|
* promptInstall() → config.enableAgentVibes
|
||||||
|
* ↓
|
||||||
|
* installer.install() → this.enableAgentVibes
|
||||||
|
* ↓
|
||||||
|
* processTTSInjectionPoints() → injects OR strips markers
|
||||||
|
*
|
||||||
|
* RELATED:
|
||||||
|
* ========
|
||||||
|
* - Detection: checkAgentVibesInstalled() - looks for bmad-speak.sh and play-tts.sh
|
||||||
|
* - Processing: installer.js::processTTSInjectionPoints()
|
||||||
|
* - Markers: src/core/workflows/party-mode/instructions.md:101, src/modules/bmm/agents/*.md
|
||||||
|
* - GitHub Issue: paulpreibisch/AgentVibes#36
|
||||||
|
*/
|
||||||
|
async promptAgentVibes(projectDir) {
|
||||||
|
CLIUtils.displaySection('🎤 Voice Features', 'Enable TTS for multi-agent conversations');
|
||||||
|
|
||||||
|
// Check if AgentVibes is already installed
|
||||||
|
const agentVibesInstalled = await this.checkAgentVibesInstalled(projectDir);
|
||||||
|
|
||||||
|
if (agentVibesInstalled) {
|
||||||
|
console.log(chalk.green(' ✓ AgentVibes detected'));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.dim(' AgentVibes not detected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const answers = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'enableTts',
|
||||||
|
message: 'Enable AgentVibes TTS? (Claude Code only - Agents speak with unique voices in party mode)',
|
||||||
|
default: true, // Default to yes - recommended for best experience
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (answers.enableTts && !agentVibesInstalled) {
|
||||||
|
console.log(chalk.yellow('\n ⚠️ AgentVibes not installed'));
|
||||||
|
console.log(chalk.dim(' Install AgentVibes separately to enable TTS:'));
|
||||||
|
console.log(chalk.dim(' https://github.com/paulpreibisch/AgentVibes\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: answers.enableTts,
|
||||||
|
alreadyInstalled: agentVibesInstalled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function checkAgentVibesInstalled
|
||||||
|
* @intent Detect if AgentVibes TTS hooks are present in user's project
|
||||||
|
* @why Allows auto-enabling TTS and showing helpful installation guidance
|
||||||
|
* @param {string} projectDir - Absolute path to user's project directory
|
||||||
|
* @returns {Promise<boolean>} true if both required AgentVibes hooks exist, false otherwise
|
||||||
|
* @sideeffects None - read-only file existence checks
|
||||||
|
* @edgecases Returns false if either hook missing (both required for functional TTS)
|
||||||
|
* @calledby promptAgentVibes() to determine default value and show detection status
|
||||||
|
* @calls fs.pathExists() twice (bmad-speak.sh, play-tts.sh)
|
||||||
|
*
|
||||||
|
* AI NOTE: This checks for the MINIMUM viable AgentVibes installation.
|
||||||
|
*
|
||||||
|
* Required Files:
|
||||||
|
* ===============
|
||||||
|
* 1. .claude/hooks/bmad-speak.sh
|
||||||
|
* - Maps agent display names → agent IDs → voice profiles
|
||||||
|
* - Calls play-tts.sh with agent's assigned voice
|
||||||
|
* - Created by AgentVibes installer
|
||||||
|
*
|
||||||
|
* 2. .claude/hooks/play-tts.sh
|
||||||
|
* - Core TTS router (ElevenLabs or Piper)
|
||||||
|
* - Provider-agnostic interface
|
||||||
|
* - Required by bmad-speak.sh
|
||||||
|
*
|
||||||
|
* Why Both Required:
|
||||||
|
* ==================
|
||||||
|
* - bmad-speak.sh alone: No TTS backend
|
||||||
|
* - play-tts.sh alone: No BMAD agent voice mapping
|
||||||
|
* - Both together: Full party mode TTS integration
|
||||||
|
*
|
||||||
|
* Detection Strategy:
|
||||||
|
* ===================
|
||||||
|
* We use simple file existence (not version checks) because:
|
||||||
|
* - Fast and reliable
|
||||||
|
* - Works across all AgentVibes versions
|
||||||
|
* - User will discover version issues when TTS runs (fail-fast)
|
||||||
|
*
|
||||||
|
* PATTERN: Adding New Detection Criteria
|
||||||
|
* =======================================
|
||||||
|
* If future AgentVibes features require additional files:
|
||||||
|
* 1. Add new pathExists check to this function
|
||||||
|
* 2. Update documentation in promptAgentVibes()
|
||||||
|
* 3. Consider: should missing file prevent detection or just log warning?
|
||||||
|
*
|
||||||
|
* RELATED:
|
||||||
|
* ========
|
||||||
|
* - AgentVibes Installer: creates these hooks
|
||||||
|
* - bmad-speak.sh: calls play-tts.sh with agent voices
|
||||||
|
* - Party Mode: uses bmad-speak.sh for agent dialogue
|
||||||
|
*/
|
||||||
|
async checkAgentVibesInstalled(projectDir) {
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
// Check for AgentVibes hook files
|
||||||
|
const hookPath = path.join(projectDir, '.claude', 'hooks', 'bmad-speak.sh');
|
||||||
|
const playTtsPath = path.join(projectDir, '.claude', 'hooks', 'play-tts.sh');
|
||||||
|
|
||||||
|
return (await fs.pathExists(hookPath)) && (await fs.pathExists(playTtsPath));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { UI };
|
module.exports = { UI };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue