Compare commits

...

9 Commits

Author SHA1 Message Date
Dicky Moore 827196614f
Merge ddd0e8fbf2 into 0b9290789e 2025-12-05 15:06:45 +08:00
Brian Madison 0b9290789e installer fixes 2025-12-03 22:44:13 -06:00
Brian Madison aa1cf76f88 new workflow types generate slash commands 2025-12-03 21:36:24 -06:00
Alex Verkhovsky b8b4b65c10
feat(discord): compact plain-text notifications with bug fixes (#1021)
- Fix esc() bracket expression (] must be first in POSIX regex)
- Fix delete job: inline helper to avoid checkout of deleted ref
- Fix issue notifications: attribute close/reopen to actor, not author
- Simplify trunc() comment (remove false Unicode-safe claim)
- Smart truncation with wall-of-text detection
- Escape markdown and @mentions for safe display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brian <bmadcode@gmail.com>
2025-12-03 20:22:59 -06:00
Brian Madison 73db5538bf roo installer improovement 2025-12-03 19:56:23 -06:00
mq-bot ddd0e8fbf2 fix: remind workflows to reload sprint status 2025-11-26 14:06:12 +00:00
mq-bot 9e70b1d41c Merge upstream/main into feature/sprint-status-reload 2025-11-26 13:38:58 +00:00
mq-bot 9a3a46314e fix: remind workflows to reload sprint status 2025-11-24 19:31:35 +00:00
mq-bot a4c394fc78 Ensure workflow launcher loads core workflow 2025-11-24 14:56:30 +00:00
35 changed files with 675 additions and 277 deletions

15
.github/scripts/discord-helpers.sh vendored Normal file
View File

@ -0,0 +1,15 @@
#!/bin/bash
# Discord notification helper functions
# Escape markdown special chars and @mentions for safe Discord display
# Bracket expression: ] must be first, then other chars. In POSIX bracket expr, \ is literal.
esc() { sed -e 's/[][\*_()~`>]/\\&/g' -e 's/@/@ /g'; }
# Truncate to $1 chars (or 80 if wall-of-text with <3 spaces)
trunc() {
local max=$1
local txt=$(tr '\n\r' ' ' | cut -c1-"$max")
local spaces=$(printf '%s' "$txt" | tr -cd ' ' | wc -c)
[ "$spaces" -lt 3 ] && [ ${#txt} -gt 80 ] && txt=$(printf '%s' "$txt" | cut -c1-80)
printf '%s' "$txt"
}

View File

@ -1,16 +1,286 @@
name: Discord Notification name: Discord Notification
"on": [pull_request, release, create, delete, issue_comment, pull_request_review, pull_request_review_comment] on:
pull_request:
types: [opened, closed, reopened, ready_for_review]
release:
types: [published]
create:
delete:
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
pull_request_review_comment:
types: [created]
issues:
types: [opened, closed, reopened]
env:
MAX_TITLE: 100
MAX_BODY: 250
jobs: jobs:
notify: pull_request:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
ACTION: ${{ github.event.action }}
MERGED: ${{ github.event.pull_request.merged }}
PR_NUM: ${{ github.event.pull_request.number }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_USER: ${{ github.event.pull_request.user.login }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
if [ "$ACTION" = "opened" ]; then ICON="🔀"; LABEL="New PR"
elif [ "$ACTION" = "closed" ] && [ "$MERGED" = "true" ]; then ICON="🎉"; LABEL="Merged"
elif [ "$ACTION" = "closed" ]; then ICON="❌"; LABEL="Closed"
elif [ "$ACTION" = "reopened" ]; then ICON="🔄"; LABEL="Reopened"
else ICON="📋"; LABEL="Ready"; fi
TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc)
[ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..."
BODY=$(printf '%s' "$PR_BODY" | trunc $MAX_BODY | esc)
[ -n "$PR_BODY" ] && [ ${#PR_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
[ -n "$BODY" ] && BODY=" · $BODY"
USER=$(printf '%s' "$PR_USER" | esc)
MSG="$ICON **[$LABEL #$PR_NUM: $TITLE](<$PR_URL>)**"$'\n'"by @$USER$BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
issues:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
ACTION: ${{ github.event.action }}
ISSUE_NUM: ${{ github.event.issue.number }}
ISSUE_URL: ${{ github.event.issue.html_url }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_USER: ${{ github.event.issue.user.login }}
ISSUE_BODY: ${{ github.event.issue.body }}
ACTOR: ${{ github.actor }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
if [ "$ACTION" = "opened" ]; then ICON="🐛"; LABEL="New Issue"; USER="$ISSUE_USER"
elif [ "$ACTION" = "closed" ]; then ICON="✅"; LABEL="Closed"; USER="$ACTOR"
else ICON="🔄"; LABEL="Reopened"; USER="$ACTOR"; fi
TITLE=$(printf '%s' "$ISSUE_TITLE" | trunc $MAX_TITLE | esc)
[ ${#ISSUE_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..."
BODY=$(printf '%s' "$ISSUE_BODY" | trunc $MAX_BODY | esc)
[ -n "$ISSUE_BODY" ] && [ ${#ISSUE_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
[ -n "$BODY" ] && BODY=" · $BODY"
USER=$(printf '%s' "$USER" | esc)
MSG="$ICON **[$LABEL #$ISSUE_NUM: $TITLE](<$ISSUE_URL>)**"$'\n'"by @$USER$BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
issue_comment:
if: github.event_name == 'issue_comment'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
IS_PR: ${{ github.event.issue.pull_request && 'true' || 'false' }}
ISSUE_NUM: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
COMMENT_URL: ${{ github.event.comment.html_url }}
COMMENT_USER: ${{ github.event.comment.user.login }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
[ "$IS_PR" = "true" ] && TYPE="PR" || TYPE="Issue"
TITLE=$(printf '%s' "$ISSUE_TITLE" | trunc $MAX_TITLE | esc)
[ ${#ISSUE_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..."
BODY=$(printf '%s' "$COMMENT_BODY" | trunc $MAX_BODY | esc)
[ ${#COMMENT_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
USER=$(printf '%s' "$COMMENT_USER" | esc)
MSG="💬 **[Comment on $TYPE #$ISSUE_NUM: $TITLE](<$COMMENT_URL>)**"$'\n'"@$USER: $BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
pull_request_review:
if: github.event_name == 'pull_request_review'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
STATE: ${{ github.event.review.state }}
PR_NUM: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
REVIEW_URL: ${{ github.event.review.html_url }}
REVIEW_USER: ${{ github.event.review.user.login }}
REVIEW_BODY: ${{ github.event.review.body }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
if [ "$STATE" = "approved" ]; then ICON="✅"; LABEL="Approved"
elif [ "$STATE" = "changes_requested" ]; then ICON="🔧"; LABEL="Changes Requested"
else ICON="👀"; LABEL="Reviewed"; fi
TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc)
[ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..."
BODY=$(printf '%s' "$REVIEW_BODY" | trunc $MAX_BODY | esc)
[ -n "$REVIEW_BODY" ] && [ ${#REVIEW_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
[ -n "$BODY" ] && BODY=": $BODY"
USER=$(printf '%s' "$REVIEW_USER" | esc)
MSG="$ICON **[$LABEL PR #$PR_NUM: $TITLE](<$REVIEW_URL>)**"$'\n'"@$USER$BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
pull_request_review_comment:
if: github.event_name == 'pull_request_review_comment'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
PR_NUM: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
COMMENT_URL: ${{ github.event.comment.html_url }}
COMMENT_USER: ${{ github.event.comment.user.login }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
TITLE=$(printf '%s' "$PR_TITLE" | trunc $MAX_TITLE | esc)
[ ${#PR_TITLE} -gt $MAX_TITLE ] && TITLE="${TITLE}..."
BODY=$(printf '%s' "$COMMENT_BODY" | trunc $MAX_BODY | esc)
[ ${#COMMENT_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
USER=$(printf '%s' "$COMMENT_USER" | esc)
MSG="💭 **[Review Comment PR #$PR_NUM: $TITLE](<$COMMENT_URL>)**"$'\n'"@$USER: $BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
release:
if: github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
TAG: ${{ github.event.release.tag_name }}
NAME: ${{ github.event.release.name }}
URL: ${{ github.event.release.html_url }}
RELEASE_BODY: ${{ github.event.release.body }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
REL_NAME=$(printf '%s' "$NAME" | trunc $MAX_TITLE | esc)
[ ${#NAME} -gt $MAX_TITLE ] && REL_NAME="${REL_NAME}..."
BODY=$(printf '%s' "$RELEASE_BODY" | trunc $MAX_BODY | esc)
[ -n "$RELEASE_BODY" ] && [ ${#RELEASE_BODY} -gt $MAX_BODY ] && BODY="${BODY}..."
[ -n "$BODY" ] && BODY=" · $BODY"
TAG_ESC=$(printf '%s' "$TAG" | esc)
MSG="🚀 **[Release $TAG_ESC: $REL_NAME](<$URL>)**"$'\n'"$BODY"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
create:
if: github.event_name == 'create'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false
- name: Notify Discord
env:
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
REF_TYPE: ${{ github.event.ref_type }}
REF: ${{ github.event.ref }}
ACTOR: ${{ github.actor }}
REPO_URL: ${{ github.event.repository.html_url }}
run: |
set -o pipefail
source .github/scripts/discord-helpers.sh
[ -z "$WEBHOOK" ] && exit 0
[ "$REF_TYPE" = "branch" ] && ICON="🌿" || ICON="🏷️"
REF_TRUNC=$(printf '%s' "$REF" | trunc $MAX_TITLE)
[ ${#REF} -gt $MAX_TITLE ] && REF_TRUNC="${REF_TRUNC}..."
REF_ESC=$(printf '%s' "$REF_TRUNC" | esc)
REF_URL=$(jq -rn --arg ref "$REF" '$ref | @uri')
ACTOR_ESC=$(printf '%s' "$ACTOR" | esc)
MSG="$ICON **${REF_TYPE^} created: [$REF_ESC](<$REPO_URL/tree/$REF_URL>)** by @$ACTOR_ESC"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-
delete:
if: github.event_name == 'delete'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Notify Discord - name: Notify Discord
uses: sarisia/actions-status-discord@v1 env:
if: always() WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with: REF_TYPE: ${{ github.event.ref_type }}
webhook: ${{ secrets.DISCORD_WEBHOOK }} REF: ${{ github.event.ref }}
status: ${{ job.status }} ACTOR: ${{ github.actor }}
title: "Triggered by ${{ github.event_name }}" run: |
color: 0x5865F2 set -o pipefail
[ -z "$WEBHOOK" ] && exit 0
esc() { sed -e 's/[][\*_()~`>]/\\&/g' -e 's/@/@ /g'; }
trunc() { tr '\n\r' ' ' | cut -c1-"$1"; }
REF_TRUNC=$(printf '%s' "$REF" | trunc 100)
[ ${#REF} -gt 100 ] && REF_TRUNC="${REF_TRUNC}..."
REF_ESC=$(printf '%s' "$REF_TRUNC" | esc)
ACTOR_ESC=$(printf '%s' "$ACTOR" | esc)
MSG="🗑️ **${REF_TYPE^} deleted: $REF_ESC** by @$ACTOR_ESC"
jq -n --arg content "$MSG" '{content: $content}' | curl -sf --retry 2 -X POST "$WEBHOOK" -H "Content-Type: application/json" -d @-

1
.gitignore vendored
View File

@ -72,3 +72,4 @@ z*/
.agent .agent
.agentvibes/ .agentvibes/
.kiro/ .kiro/
.roo

View File

@ -1,5 +1,5 @@
--- ---
name: Brainstorming Session name: brainstorming-session
description: Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods description: Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods
context_file: '' # Optional context file path for project-specific guidance context_file: '' # Optional context file path for project-specific guidance
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Party Mode name: party-mode
description: Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations description: Orchestrates group discussions between all installed BMAD agents, enabling natural multi-agent conversations
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Create Agent name: create-agent
description: Interactive workflow to build BMAD Core compliant agents with optional brainstorming, persona development, and command structure description: Interactive workflow to build BMAD Core compliant agents with optional brainstorming, persona development, and command structure
web_bundle: true web_bundle: true
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Create Workflow name: create-workflow
description: Create structured standalone workflows using markdown-based step architecture description: Create structured standalone workflows using markdown-based step architecture
web_bundle: true web_bundle: true
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Edit Agent name: edit-agent
description: Edit existing BMAD agents while following all best practices and conventions description: Edit existing BMAD agents while following all best practices and conventions
web_bundle: false web_bundle: false
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Edit Workflow name: edit-workflow
description: Intelligent workflow editor that helps modify existing workflows while following best practices description: Intelligent workflow editor that helps modify existing workflows while following best practices
web_bundle: true web_bundle: true
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Workflow Compliance Check name: workflow-compliance-check
description: Systematic validation of workflows against BMAD standards with adversarial analysis and detailed reporting description: Systematic validation of workflows against BMAD standards with adversarial analysis and detailed reporting
web_bundle: false web_bundle: false
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: Product Brief Workflow name: create-product-brief
description: Create comprehensive product briefs through collaborative step-by-step discovery as creative Business Analyst working with the user as peers. description: Create comprehensive product briefs through collaborative step-by-step discovery as creative Business Analyst working with the user as peers.
web_bundle: true web_bundle: true
--- ---

View File

@ -1,6 +1,7 @@
--- ---
name: Research Workflow name: research
description: Conduct comprehensive research across multiple domains using current web data and verified sources - Market, Technical, Domain and other research types. description: Conduct comprehensive research across multiple domains using current web data and verified sources - Market, Technical, Domain and other research types.
web_bundle: true
--- ---
# Research Workflow # Research Workflow

View File

@ -1,3 +1,9 @@
---
name: create-ux-design
description: Work with a peer UX Design expert to plan your applications UX patterns, look and feel.
web_bundle: true
---
# Create UX Design Workflow # Create UX Design Workflow
**Goal:** Create comprehensive UX design specifications through collaborative visual exploration and informed decision-making where you act as a UX facilitator working with a product stakeholder. **Goal:** Create comprehensive UX design specifications through collaborative visual exploration and informed decision-making where you act as a UX facilitator working with a product stakeholder.

View File

@ -1,7 +1,7 @@
--- ---
name: PRD Workflow name: create-prd
description: Creates a comprehensive PRDs through collaborative step-by-step discovery between two product managers working as peers. description: Creates a comprehensive PRDs through collaborative step-by-step discovery between two product managers working as peers.
main_config: `{project-root}/{bmad_folder}/bmm/config.yaml` main_config: '{project-root}/{bmad_folder}/bmm/config.yaml'
web_bundle: true web_bundle: true
--- ---

View File

@ -1,6 +1,7 @@
--- ---
name: Architecture Workflow name: create-architecture
description: Collaborative architectural decision facilitation for AI-agent consistency. Replaces template-driven architecture with intelligent, adaptive conversation that produces a decision-focused architecture document optimized for preventing agent conflicts. description: Collaborative architectural decision facilitation for AI-agent consistency. Replaces template-driven architecture with intelligent, adaptive conversation that produces a decision-focused architecture document optimized for preventing agent conflicts.
web_bundle: true
--- ---
# Architecture Workflow # Architecture Workflow

View File

@ -1,5 +1,5 @@
--- ---
name: 'Create Epics and Stories' name: create-epics-stories
description: 'Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value. This workflow requires completed PRD + Architecture documents (UX recommended if UI exists) and breaks down requirements into implementation-ready epics and user stories that incorporate all available technical and design context. Creates detailed, actionable stories with complete acceptance criteria for development teams.' description: 'Transform PRD requirements and Architecture decisions into comprehensive stories organized by user value. This workflow requires completed PRD + Architecture documents (UX recommended if UI exists) and breaks down requirements into implementation-ready epics and user stories that incorporate all available technical and design context. Creates detailed, actionable stories with complete acceptance criteria for development teams.'
web_bundle: true web_bundle: true
--- ---

View File

@ -1,5 +1,5 @@
--- ---
name: 'Implementation Readiness' name: check-implementation-readiness
description: 'Critical validation workflow that assesses PRD, Architecture, and Epics & Stories for completeness and alignment before implementation. Uses adversarial review approach to find gaps and issues.' description: 'Critical validation workflow that assesses PRD, Architecture, and Epics & Stories for completeness and alignment before implementation. Uses adversarial review approach to find gaps and issues.'
web_bundle: false web_bundle: false
--- ---

View File

@ -3,6 +3,8 @@
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical> <critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical> <critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
<critical>Generate all documents in {document_output_language}</critical> <critical>Generate all documents in {document_output_language}</critical>
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
<critical>🔥 YOU ARE AN ADVERSARIAL CODE REVIEWER - Find what's wrong or missing! 🔥</critical> <critical>🔥 YOU ARE AN ADVERSARIAL CODE REVIEWER - Find what's wrong or missing! 🔥</critical>
<critical>Your purpose: Validate story file claims against actual implementation</critical> <critical>Your purpose: Validate story file claims against actual implementation</critical>

View File

@ -2,6 +2,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>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical> <critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
<critical>Communicate all responses in {communication_language} and generate all documents in {document_output_language}</critical> <critical>Communicate all responses in {communication_language} and generate all documents in {document_output_language}</critical>
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
<critical>🔥 CRITICAL MISSION: You are creating the ULTIMATE story context engine that prevents LLM developer mistakes, omissions or <critical>🔥 CRITICAL MISSION: You are creating the ULTIMATE story context engine that prevents LLM developer mistakes, omissions or
disasters! 🔥</critical> disasters! 🔥</critical>

View File

@ -3,6 +3,8 @@
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical> <critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical> <critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
<critical>Generate all documents in {document_output_language}</critical> <critical>Generate all documents in {document_output_language}</critical>
<critical>ALWAYS reload {{sprint_status}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or
updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
<critical>Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List, <critical>Only modify the story file in these areas: Tasks/Subtasks checkboxes, Dev Agent Record (Debug Log, Completion Notes), File List,
Change Log, and Status</critical> Change Log, and Status</critical>
<critical>Execute ALL steps in exact order; do NOT skip steps</critical> <critical>Execute ALL steps in exact order; do NOT skip steps</critical>

View File

@ -26,6 +26,7 @@ PARTY MODE PROTOCOL:
- Create natural back-and-forth with user actively participating - Create natural back-and-forth with user actively participating
- Show disagreements, diverse perspectives, authentic team dynamics - Show disagreements, diverse perspectives, authentic team dynamics
</critical> </critical>
<critical>ALWAYS reload {{sprint_status_file}} (docs/sprint-artifacts/sprint-status.yaml by default) from disk immediately before reading or updating statuses. Multiple workflows change this file between runs; never rely on a previously loaded copy or cached memory.</critical>
<workflow> <workflow>

View File

@ -1,5 +1,5 @@
--- ---
name: Generate Project Context name: generate-project-context
description: Creates a concise project_context.md file with critical rules and patterns that AI agents must follow when implementing code. Optimized for LLM context efficiency. description: Creates a concise project_context.md file with critical rules and patterns that AI agents must follow when implementing code. Optimized for LLM context efficiency.
--- ---

View File

@ -105,7 +105,7 @@ class ManifestGenerator {
} }
/** /**
* Recursively find and parse workflow.yaml files * Recursively find and parse workflow.yaml and workflow.md files
*/ */
async getWorkflowsFromPath(basePath, moduleName) { async getWorkflowsFromPath(basePath, moduleName) {
const workflows = []; const workflows = [];
@ -126,11 +126,23 @@ class ManifestGenerator {
// Recurse into subdirectories // Recurse into subdirectories
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name; const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
await findWorkflows(fullPath, newRelativePath); await findWorkflows(fullPath, newRelativePath);
} else if (entry.name === 'workflow.yaml') { } else if (entry.name === 'workflow.yaml' || entry.name === 'workflow.md') {
// Parse workflow file // Parse workflow file (both YAML and MD formats)
try { try {
const content = await fs.readFile(fullPath, 'utf8'); const content = await fs.readFile(fullPath, 'utf8');
const workflow = yaml.load(content);
let workflow;
if (entry.name === 'workflow.yaml') {
// Parse YAML workflow
workflow = yaml.load(content);
} else {
// Parse MD workflow with YAML frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!frontmatterMatch) {
continue; // Skip MD files without frontmatter
}
workflow = yaml.load(frontmatterMatch[1]);
}
// Skip template workflows (those with placeholder values) // Skip template workflows (those with placeholder values)
if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) { if (workflow.name && workflow.name.includes('{') && workflow.name.includes('}')) {
@ -141,18 +153,15 @@ class ManifestGenerator {
// Build relative path for installation // Build relative path for installation
const installPath = const installPath =
moduleName === 'core' moduleName === 'core'
? `${this.bmadFolderName}/core/workflows/${relativePath}/workflow.yaml` ? `${this.bmadFolderName}/core/workflows/${relativePath}/${entry.name}`
: `${this.bmadFolderName}/${moduleName}/workflows/${relativePath}/workflow.yaml`; : `${this.bmadFolderName}/${moduleName}/workflows/${relativePath}/${entry.name}`;
// Check for standalone property (default: false)
const standalone = workflow.standalone === true;
// ALL workflows now generate commands - no standalone property needed
workflows.push({ workflows.push({
name: workflow.name, name: workflow.name,
description: workflow.description.replaceAll('"', '""'), // Escape quotes for CSV description: workflow.description.replaceAll('"', '""'), // Escape quotes for CSV
module: moduleName, module: moduleName,
path: installPath, path: installPath,
standalone: standalone,
}); });
// Add to files list // Add to files list
@ -541,12 +550,12 @@ class ManifestGenerator {
async writeWorkflowManifest(cfgDir) { async writeWorkflowManifest(cfgDir) {
const csvPath = path.join(cfgDir, 'workflow-manifest.csv'); const csvPath = path.join(cfgDir, 'workflow-manifest.csv');
// Create CSV header with standalone column // Create CSV header - removed standalone column as ALL workflows now generate commands
let csv = 'name,description,module,path,standalone\n'; let csv = 'name,description,module,path\n';
// Add all workflows // Add all workflows - no standalone property needed anymore
for (const workflow of this.workflows) { for (const workflow of this.workflows) {
csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}","${workflow.standalone}"\n`; csv += `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}"\n`;
} }
await fs.writeFile(csvPath, csv); await fs.writeFile(csvPath, csv);

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/** /**
* Auggie CLI setup handler * Auggie CLI setup handler
@ -33,10 +34,23 @@ class AuggieSetup extends BaseIdeSetup {
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks, tools, and workflows (standalone only) // Get tasks, tools, and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir, true); const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Convert workflow artifacts to expected format
const workflows = workflowArtifacts
.filter((artifact) => artifact.type === 'workflow-command')
.map((artifact) => ({
module: artifact.module,
name: path.basename(artifact.relativePath, '.md'),
path: artifact.sourcePath,
content: artifact.content,
}));
const bmadCommandsDir = path.join(location, 'bmad'); const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents'); const agentsDir = path.join(bmadCommandsDir, 'agents');
@ -73,13 +87,11 @@ class AuggieSetup extends BaseIdeSetup {
await this.writeFile(targetPath, commandContent); await this.writeFile(targetPath, commandContent);
} }
// Install workflows // Install workflows (already generated commands)
for (const workflow of workflows) { for (const workflow of workflows) {
const content = await this.readFile(workflow.path); // Use the pre-generated workflow command content
const commandContent = this.createWorkflowCommand(workflow, content);
const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`); const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`);
await this.writeFile(targetPath, commandContent); await this.writeFile(targetPath, workflow.content);
} }
const totalInstalled = agentArtifacts.length + tasks.length + tools.length + workflows.length; const totalInstalled = agentArtifacts.length + tasks.length + tools.length + workflows.length;

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/** /**
* Crush IDE setup handler * Crush IDE setup handler
@ -34,10 +35,23 @@ class CrushSetup extends BaseIdeSetup {
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks, tools, and workflows (standalone only) // Get tasks, tools, and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir, true); const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Convert workflow artifacts to expected format for organizeByModule
const workflows = workflowArtifacts
.filter((artifact) => artifact.type === 'workflow-command')
.map((artifact) => ({
module: artifact.module,
name: path.basename(artifact.relativePath, '.md'),
path: artifact.sourcePath,
content: artifact.content,
}));
// Organize by module // Organize by module
const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir); const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir);
@ -113,13 +127,12 @@ class CrushSetup extends BaseIdeSetup {
toolCount++; toolCount++;
} }
// Copy module-specific workflows // Copy module-specific workflow commands (already generated)
const moduleWorkflows = workflows.filter((w) => w.module === module); const moduleWorkflows = workflows.filter((w) => w.module === module);
for (const workflow of moduleWorkflows) { for (const workflow of moduleWorkflows) {
const content = await this.readFile(workflow.path); // Use the pre-generated workflow command content
const commandContent = this.createWorkflowCommand(workflow, content);
const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`); const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
await this.writeFile(targetPath, commandContent); await this.writeFile(targetPath, workflow.content);
workflowCount++; workflowCount++;
} }
} }

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/** /**
* Cursor IDE setup handler * Cursor IDE setup handler
@ -53,10 +54,22 @@ class CursorSetup extends BaseIdeSetup {
// Convert artifacts to agent format for index creation // Convert artifacts to agent format for index creation
const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name })); const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name }));
// Get tasks, tools, and workflows (standalone only) // Get tasks, tools, and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir, true); const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true); const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Convert artifacts to workflow objects for directory creation
const workflows = workflowArtifacts
.filter((artifact) => artifact.type === 'workflow-command')
.map((artifact) => ({
module: artifact.module,
name: path.basename(artifact.relativePath, '.md'),
path: artifact.sourcePath,
}));
// Create directories for each module // Create directories for each module
const modules = new Set(); const modules = new Set();
@ -113,18 +126,21 @@ class CursorSetup extends BaseIdeSetup {
toolCount++; toolCount++;
} }
// Process and copy workflows // Process and copy workflow commands (generated, not raw workflows)
let workflowCount = 0; let workflowCount = 0;
for (const workflow of workflows) { for (const artifact of workflowArtifacts) {
const content = await this.readAndProcess(workflow.path, { if (artifact.type === 'workflow-command') {
module: workflow.module, // Add MDC metadata header to workflow command
name: workflow.name, const content = this.wrapLauncherWithMDC(artifact.content, {
}); module: artifact.module,
name: path.basename(artifact.relativePath, '.md'),
});
const targetPath = path.join(bmadRulesDir, workflow.module, 'workflows', `${workflow.name}.mdc`); const targetPath = path.join(bmadRulesDir, artifact.module, 'workflows', `${path.basename(artifact.relativePath, '.md')}.mdc`);
await this.writeFile(targetPath, content); await this.writeFile(targetPath, content);
workflowCount++; workflowCount++;
}
} }
// Create BMAD index file (but NOT .cursorrules - user manages that) // Create BMAD index file (but NOT .cursorrules - user manages that)

View File

@ -4,6 +4,7 @@ const yaml = require('js-yaml');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/** /**
* Gemini CLI setup handler * Gemini CLI setup handler
@ -68,9 +69,13 @@ class GeminiSetup extends BaseIdeSetup {
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks // Get tasks and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir); const tasks = await this.getTasks(bmadDir);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Install agents as TOML files with bmad- prefix (flat structure) // Install agents as TOML files with bmad- prefix (flat structure)
let agentCount = 0; let agentCount = 0;
for (const artifact of agentArtifacts) { for (const artifact of agentArtifacts) {
@ -98,17 +103,37 @@ class GeminiSetup extends BaseIdeSetup {
console.log(chalk.green(` ✓ Added task: /bmad:tasks:${task.module}:${task.name}`)); console.log(chalk.green(` ✓ Added task: /bmad:tasks:${task.module}:${task.name}`));
} }
// Install workflows as TOML files with bmad- prefix (flat structure)
let workflowCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
// Create TOML wrapper around workflow command content
const tomlContent = await this.createWorkflowToml(artifact);
// Flat structure: bmad-workflow-{module}-{name}.toml
const workflowName = path.basename(artifact.relativePath, '.md');
const tomlPath = path.join(commandsDir, `bmad-workflow-${artifact.module}-${workflowName}.toml`);
await this.writeFile(tomlPath, tomlContent);
workflowCount++;
console.log(chalk.green(` ✓ Added workflow: /bmad:workflows:${artifact.module}:${workflowName}`));
}
}
console.log(chalk.green(`${this.name} configured:`)); console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents configured`)); console.log(chalk.dim(` - ${agentCount} agents configured`));
console.log(chalk.dim(` - ${taskCount} tasks configured`)); console.log(chalk.dim(` - ${taskCount} tasks configured`));
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
console.log(chalk.dim(` - Agent activation: /bmad:agents:{agent-name}`)); console.log(chalk.dim(` - Agent activation: /bmad:agents:{agent-name}`));
console.log(chalk.dim(` - Task activation: /bmad:tasks:{task-name}`)); console.log(chalk.dim(` - Task activation: /bmad:tasks:{task-name}`));
console.log(chalk.dim(` - Workflow activation: /bmad:workflows:{workflow-name}`));
return { return {
success: true, success: true,
agents: agentCount, agents: agentCount,
tasks: taskCount, tasks: taskCount,
workflows: workflowCount,
}; };
} }
@ -179,6 +204,27 @@ ${contentWithoutFrontmatter}
return tomlContent; return tomlContent;
} }
/**
* Create workflow TOML content from artifact
*/
async createWorkflowToml(artifact) {
// Extract description from artifact content
const descriptionMatch = artifact.content.match(/description:\s*"([^"]+)"/);
const description = descriptionMatch
? descriptionMatch[1]
: `BMAD ${artifact.module.toUpperCase()} Workflow: ${path.basename(artifact.relativePath, '.md')}`;
// Strip frontmatter from command content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim();
return `description = "${description}"
prompt = """
${contentWithoutFrontmatter}
"""
`;
}
/** /**
* Cleanup Gemini configuration - surgically remove only BMAD files * Cleanup Gemini configuration - surgically remove only BMAD files
*/ */

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/** /**
* iFlow CLI setup handler * iFlow CLI setup handler
@ -29,9 +30,11 @@ class IFlowSetup extends BaseIdeSetup {
const commandsDir = path.join(iflowDir, this.commandsDir, 'bmad'); const commandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
const agentsDir = path.join(commandsDir, 'agents'); const agentsDir = path.join(commandsDir, 'agents');
const tasksDir = path.join(commandsDir, 'tasks'); const tasksDir = path.join(commandsDir, 'tasks');
const workflowsDir = path.join(commandsDir, 'workflows');
await this.ensureDir(agentsDir); await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir); await this.ensureDir(tasksDir);
await this.ensureDir(workflowsDir);
// Generate agent launchers // Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
@ -47,9 +50,13 @@ class IFlowSetup extends BaseIdeSetup {
agentCount++; agentCount++;
} }
// Get tasks // Get tasks and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir); const tasks = await this.getTasks(bmadDir);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Setup tasks as commands // Setup tasks as commands
let taskCount = 0; let taskCount = 0;
for (const task of tasks) { for (const task of tasks) {
@ -61,15 +68,27 @@ class IFlowSetup extends BaseIdeSetup {
taskCount++; taskCount++;
} }
// Setup workflows as commands (already generated)
let workflowCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const targetPath = path.join(workflowsDir, `${artifact.module}-${path.basename(artifact.relativePath, '.md')}.md`);
await this.writeFile(targetPath, artifact.content);
workflowCount++;
}
}
console.log(chalk.green(`${this.name} configured:`)); console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agent commands created`)); console.log(chalk.dim(` - ${agentCount} agent commands created`));
console.log(chalk.dim(` - ${taskCount} task commands created`)); console.log(chalk.dim(` - ${taskCount} task commands created`));
console.log(chalk.dim(` - ${workflowCount} workflow commands created`));
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`)); console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
return { return {
success: true, success: true,
agents: agentCount, agents: agentCount,
tasks: taskCount, tasks: taskCount,
workflows: workflowCount,
}; };
} }

View File

@ -156,16 +156,41 @@ class KiroCliSetup extends BaseIdeSetup {
return; return;
} }
// Extract agent name from ID path (e.g., "{bmad_folder}/bmm/agents/analyst.md" -> "analyst") // Extract module from file path
const idPath = agentData.agent.metadata.id; const normalizedPath = path.normalize(agentFile);
const basename = path.basename(idPath, '.md'); const pathParts = normalizedPath.split(path.sep);
const agentName = basename.startsWith('bmad-') ? basename : `bmad-${basename}`; const basename = path.basename(agentFile, '.agent.yaml');
// Find the module name from path
let moduleName = 'unknown';
if (pathParts.includes('src')) {
const srcIndex = pathParts.indexOf('src');
if (srcIndex + 3 < pathParts.length) {
const folderAfterSrc = pathParts[srcIndex + 1];
// Handle both src/core/agents and src/modules/[module]/agents patterns
if (folderAfterSrc === 'core') {
moduleName = 'core';
} else if (folderAfterSrc === 'modules') {
moduleName = pathParts[srcIndex + 2]; // The actual module name
}
}
}
// Extract the agent name from the ID path in YAML if available
let agentBaseName = basename;
if (agentData.agent && agentData.agent.metadata && agentData.agent.metadata.id) {
const idPath = agentData.agent.metadata.id;
agentBaseName = path.basename(idPath, '.md');
}
const agentName = `bmad-${moduleName}-${agentBaseName}`;
const sanitizedAgentName = this.sanitizeAgentName(agentName);
// Create JSON definition // Create JSON definition
await this.createAgentDefinitionFromYaml(agentsDir, agentName, agentData); await this.createAgentDefinitionFromYaml(agentsDir, sanitizedAgentName, agentData);
// Create prompt file // Create prompt file
await this.createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir); await this.createAgentPromptFromYaml(agentsDir, sanitizedAgentName, agentData, projectDir);
} }
/** /**

View File

@ -47,7 +47,7 @@ class OpenCodeSetup extends BaseIdeSetup {
agentCount++; agentCount++;
} }
// Install workflow commands with flat naming: bmad-workflow-{module}-{name}.md // Install workflow commands with flat naming: bmad-{module}-{workflow-name}
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName); const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
@ -55,10 +55,10 @@ class OpenCodeSetup extends BaseIdeSetup {
for (const artifact of workflowArtifacts) { for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') { if (artifact.type === 'workflow-command') {
const commandContent = artifact.content; const commandContent = artifact.content;
// Flat structure: bmad-workflow-{module}-{name}.md // Flat structure: bmad-{module}-{workflow-name}.md
// artifact.relativePath is like: bmm/workflows/plan-project.md // artifact.relativePath is like: bmm/workflows/plan-project.md
const workflowName = path.basename(artifact.relativePath, '.md'); const workflowName = path.basename(artifact.relativePath, '.md');
const targetPath = path.join(commandsBaseDir, `bmad-workflow-${artifact.module}-${workflowName}.md`); const targetPath = path.join(commandsBaseDir, `bmad-${artifact.module}-${workflowName}.md`);
await this.writeFile(targetPath, commandContent); await this.writeFile(targetPath, commandContent);
workflowCommandCount++; workflowCommandCount++;
} }

View File

@ -5,34 +5,13 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/** /**
* Roo IDE setup handler * Roo IDE setup handler
* Creates custom modes in .roomodes file * Creates custom commands in .roo/commands directory
*/ */
class RooSetup extends BaseIdeSetup { class RooSetup extends BaseIdeSetup {
constructor() { constructor() {
super('roo', 'Roo Code'); super('roo', 'Roo Code');
this.configFile = '.roomodes'; this.configDir = '.roo';
this.defaultPermissions = { this.commandsDir = 'commands';
dev: {
description: 'Development files',
fileRegex: String.raw`.*\.(js|jsx|ts|tsx|py|java|cpp|c|h|cs|go|rs|php|rb|swift)$`,
},
config: {
description: 'Configuration files',
fileRegex: String.raw`.*\.(json|yaml|yml|toml|xml|ini|env|config)$`,
},
docs: {
description: 'Documentation files',
fileRegex: String.raw`.*\.(md|mdx|rst|txt|doc|docx)$`,
},
styles: {
description: 'Style and design files',
fileRegex: String.raw`.*\.(css|scss|sass|less|stylus)$`,
},
all: {
description: 'All files',
fileRegex: '.*',
},
};
} }
/** /**
@ -44,94 +23,96 @@ class RooSetup extends BaseIdeSetup {
async setup(projectDir, bmadDir, options = {}) { async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`)); console.log(chalk.cyan(`Setting up ${this.name}...`));
// Check for existing .roomodes file // Create .roo/commands directory
const roomodesPath = path.join(projectDir, this.configFile); const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
let existingModes = []; await this.ensureDir(rooCommandsDir);
let existingContent = '';
if (await this.pathExists(roomodesPath)) { // Generate agent launchers
existingContent = await this.readFile(roomodesPath);
// Parse existing modes to avoid duplicates
const modeMatches = existingContent.matchAll(/- slug: ([\w-]+)/g);
for (const match of modeMatches) {
existingModes.push(match[1]);
}
console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`));
}
// Generate agent launchers (though Roo will reference the actual .bmad agents)
const agentGen = new AgentCommandGenerator(this.bmadFolderName); const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Always use 'all' permissions - users can customize in .roomodes file
const permissionChoice = 'all';
// Create modes content
let newModesContent = '';
let addedCount = 0; let addedCount = 0;
let skippedCount = 0; let skippedCount = 0;
for (const artifact of agentArtifacts) { for (const artifact of agentArtifacts) {
const slug = `bmad-${artifact.module}-${artifact.name}`; const commandName = `bmad-${artifact.module}-agent-${artifact.name}`;
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
// Skip if already exists // Skip if already exists
if (existingModes.includes(slug)) { if (await this.pathExists(commandPath)) {
console.log(chalk.dim(` Skipping ${slug} - already exists`)); console.log(chalk.dim(` Skipping ${commandName} - already exists`));
skippedCount++; skippedCount++;
continue; continue;
} }
// Read the actual agent file from .bmad for metadata extraction // Read the actual agent file from .bmad for metadata extraction (installed agents are .md files)
const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`); const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`);
const content = await this.readFile(agentPath); const content = await this.readFile(agentPath);
// Create mode entry that references the actual .bmad agent // Create command file that references the actual .bmad agent
const modeEntry = await this.createModeEntry( await this.createCommandFile({ module: artifact.module, name: artifact.name, path: agentPath }, content, commandPath, projectDir);
{ module: artifact.module, name: artifact.name, path: agentPath },
content,
permissionChoice,
projectDir,
);
newModesContent += modeEntry;
addedCount++; addedCount++;
console.log(chalk.green(` ✓ Added mode: ${slug}`)); console.log(chalk.green(` ✓ Added command: ${commandName}`));
} }
// Build final content
let finalContent = '';
if (existingContent) {
// Append to existing content
finalContent = existingContent.trim() + '\n' + newModesContent;
} else {
// Create new .roomodes file
finalContent = 'customModes:\n' + newModesContent;
}
// Write .roomodes file
await this.writeFile(roomodesPath, finalContent);
console.log(chalk.green(`${this.name} configured:`)); console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${addedCount} modes added`)); console.log(chalk.dim(` - ${addedCount} commands added`));
if (skippedCount > 0) { if (skippedCount > 0) {
console.log(chalk.dim(` - ${skippedCount} modes skipped (already exist)`)); console.log(chalk.dim(` - ${skippedCount} commands skipped (already exist)`));
} }
console.log(chalk.dim(` - Configuration file: ${this.configFile}`)); console.log(chalk.dim(` - Commands directory: ${this.configDir}/${this.commandsDir}/bmad/`));
console.log(chalk.dim(` - Permission level: all (unrestricted)`)); console.log(chalk.dim(` Commands will be available when you open this project in Roo Code`));
console.log(chalk.yellow(`\n 💡 Tip: Edit ${this.configFile} to customize file permissions per agent`));
console.log(chalk.dim(` Modes will be available when you open this project in Roo Code`));
return { return {
success: true, success: true,
modes: addedCount, commands: addedCount,
skipped: skippedCount, skipped: skippedCount,
}; };
} }
/** /**
* Create a mode entry for an agent * Create a unified command file for agents
* @param {string} commandPath - Path where to write the command file
* @param {Object} options - Command options
* @param {string} options.name - Display name for the command
* @param {string} options.description - Description for the command
* @param {string} options.agentPath - Path to the agent file (relative to project root)
* @param {string} [options.icon] - Icon emoji (defaults to 🤖)
* @param {string} [options.extraContent] - Additional content to include before activation
*/ */
async createModeEntry(agent, content, permissionChoice, projectDir) { async createAgentCommandFile(commandPath, options) {
const { name, description, agentPath, icon = '🤖', extraContent = '' } = options;
// Build command content with YAML frontmatter
let commandContent = `---\n`;
commandContent += `name: '${icon} ${name}'\n`;
commandContent += `description: '${description}'\n`;
commandContent += `---\n\n`;
commandContent += `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.\n\n`;
// Add any extra content (e.g., warnings for custom agents)
if (extraContent) {
commandContent += `${extraContent}\n\n`;
}
commandContent += `<agent-activation CRITICAL="TRUE">\n`;
commandContent += `1. LOAD the FULL agent file from @${agentPath}\n`;
commandContent += `2. READ its entire contents - this contains the complete agent persona, menu, and instructions\n`;
commandContent += `3. Execute ALL activation steps exactly as written in the agent file\n`;
commandContent += `4. Follow the agent's persona and menu system precisely\n`;
commandContent += `5. Stay in character throughout the session\n`;
commandContent += `</agent-activation>\n`;
// Write command file
await this.writeFile(commandPath, commandContent);
}
/**
* Create a command file for an agent
*/
async createCommandFile(agent, content, commandPath, projectDir) {
// Extract metadata from agent content // Extract metadata from agent content
const titleMatch = content.match(/title="([^"]+)"/); const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name); const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@ -142,66 +123,16 @@ class RooSetup extends BaseIdeSetup {
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/); const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`; const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/);
const roleDefinition = roleDefinitionMatch
? roleDefinitionMatch[1]
: `You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`;
// Get relative path // Get relative path
const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/'); const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/');
// Determine permissions // Use unified method
const permissions = this.getPermissionsForAgent(agent, permissionChoice); await this.createAgentCommandFile(commandPath, {
name: title,
// Build mode entry description: whenToUse,
const slug = `bmad-${agent.module}-${agent.name}`; agentPath: relativePath,
let modeEntry = ` - slug: ${slug}\n`; icon: icon,
modeEntry += ` name: '${icon} ${title}'\n`; });
if (permissions && permissions.description) {
modeEntry += ` description: '${permissions.description}'\n`;
}
modeEntry += ` roleDefinition: ${roleDefinition}\n`;
modeEntry += ` whenToUse: ${whenToUse}\n`;
modeEntry += ` customInstructions: ${activationHeader} Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
modeEntry += ` groups:\n`;
modeEntry += ` - read\n`;
if (permissions && permissions.fileRegex) {
modeEntry += ` - - edit\n`;
modeEntry += ` - fileRegex: ${permissions.fileRegex}\n`;
modeEntry += ` description: ${permissions.description}\n`;
} else {
modeEntry += ` - edit\n`;
}
return modeEntry;
}
/**
* Get permissions configuration for an agent
*/
getPermissionsForAgent(agent, permissionChoice) {
if (permissionChoice === 'custom') {
// Custom logic based on agent name/module
if (agent.name.includes('dev') || agent.name.includes('code')) {
return this.defaultPermissions.dev;
} else if (agent.name.includes('doc') || agent.name.includes('write')) {
return this.defaultPermissions.docs;
} else if (agent.name.includes('config') || agent.name.includes('setup')) {
return this.defaultPermissions.config;
} else if (agent.name.includes('style') || agent.name.includes('css')) {
return this.defaultPermissions.styles;
}
// Default to all for custom agents
return this.defaultPermissions.all;
}
return this.defaultPermissions[permissionChoice] || null;
} }
/** /**
@ -219,8 +150,26 @@ class RooSetup extends BaseIdeSetup {
*/ */
async cleanup(projectDir) { async cleanup(projectDir) {
const fs = require('fs-extra'); const fs = require('fs-extra');
const roomodesPath = path.join(projectDir, this.configFile); const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
if (await fs.pathExists(rooCommandsDir)) {
const files = await fs.readdir(rooCommandsDir);
let removedCount = 0;
for (const file of files) {
if (file.startsWith('bmad-') && file.endsWith('.md')) {
await fs.remove(path.join(rooCommandsDir, file));
removedCount++;
}
}
if (removedCount > 0) {
console.log(chalk.dim(`Removed ${removedCount} BMAD commands from .roo/commands/`));
}
}
// Also clean up old .roomodes file if it exists
const roomodesPath = path.join(projectDir, '.roomodes');
if (await fs.pathExists(roomodesPath)) { if (await fs.pathExists(roomodesPath)) {
const content = await fs.readFile(roomodesPath, 'utf8'); const content = await fs.readFile(roomodesPath, 'utf8');
@ -245,7 +194,9 @@ class RooSetup extends BaseIdeSetup {
// Write back filtered content // Write back filtered content
await fs.writeFile(roomodesPath, filteredLines.join('\n')); await fs.writeFile(roomodesPath, filteredLines.join('\n'));
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .roomodes`)); if (removedCount > 0) {
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from legacy .roomodes file`));
}
} }
} }
@ -254,68 +205,53 @@ class RooSetup extends BaseIdeSetup {
* @param {string} projectDir - Project directory * @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet") * @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root) * @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata * @param {Object} metadata - Agent metadata (unused, kept for compatibility)
* @returns {Object} Installation result * @returns {Object} Installation result
*/ */
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const roomodesPath = path.join(projectDir, this.configFile); const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
let existingContent = ''; await this.ensureDir(rooCommandsDir);
// Read existing .roomodes file const commandName = `bmad-custom-agent-${agentName.toLowerCase()}`;
if (await this.pathExists(roomodesPath)) { const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
existingContent = await this.readFile(roomodesPath);
}
// Create custom agent mode entry // Check if command already exists
const slug = `bmad-custom-${agentName.toLowerCase()}`; if (await this.pathExists(commandPath)) {
const modeEntry = ` - slug: ${slug}
name: 'BMAD Custom: ${agentName}'
description: |
Custom BMAD agent: ${agentName}
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}". The agent will follow the persona and instructions from the main agent file.
prompt: |
@${agentPath}
always: false
permissions: all
`;
// Check if mode already exists
if (existingContent.includes(slug)) {
return { return {
ide: 'roo', ide: 'roo',
path: this.configFile, path: path.join(this.configDir, this.commandsDir, `${commandName}.md`),
command: agentName, command: commandName,
type: 'custom-agent-launcher', type: 'custom-agent-launcher',
alreadyExists: true, alreadyExists: true,
}; };
} }
// Build final content // Read the custom agent file to extract metadata (same as regular agents)
let finalContent = ''; const fullAgentPath = path.join(projectDir, agentPath);
if (existingContent) { const content = await this.readFile(fullAgentPath);
// Find customModes section or add it
if (existingContent.includes('customModes:')) {
// Append to existing customModes
finalContent = existingContent + modeEntry;
} else {
// Add customModes section
finalContent = existingContent.trim() + '\n\ncustomModes:\n' + modeEntry;
}
} else {
// Create new .roomodes file with customModes
finalContent = 'customModes:\n' + modeEntry;
}
// Write .roomodes file // Extract metadata from agent content
await this.writeFile(roomodesPath, finalContent); const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agentName);
const iconMatch = content.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Use unified method without extra content (clean)
await this.createAgentCommandFile(commandPath, {
name: title,
description: whenToUse,
agentPath: agentPath,
icon: icon,
});
return { return {
ide: 'roo', ide: 'roo',
path: this.configFile, path: path.join(this.configDir, this.commandsDir, `${commandName}.md`),
command: slug, command: commandName,
type: 'custom-agent-launcher', type: 'custom-agent-launcher',
}; };
} }

View File

@ -90,6 +90,11 @@ async function getAgentsFromDir(dirPath, moduleName) {
continue; continue;
} }
// Skip README files and other non-agent files
if (file.toLowerCase() === 'readme.md' || file.toLowerCase().startsWith('readme-')) {
continue;
}
if (file.includes('.customize.')) { if (file.includes('.customize.')) {
continue; continue;
} }
@ -101,6 +106,11 @@ async function getAgentsFromDir(dirPath, moduleName) {
continue; continue;
} }
// Only include files that have agent-specific content (compiled agents have <agent> tag)
if (!content.includes('<agent')) {
continue;
}
agents.push({ agents.push({
path: filePath, path: filePath,
name: file.replace('.md', ''), name: file.replace('.md', ''),

View File

@ -25,16 +25,16 @@ class WorkflowCommandGenerator {
return { generated: 0 }; return { generated: 0 };
} }
// Filter to only standalone workflows // ALL workflows now generate commands - no standalone filtering
const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true); const allWorkflows = workflows;
// Base commands directory // Base commands directory
const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad'); const baseCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
let generatedCount = 0; let generatedCount = 0;
// Generate a command file for each standalone workflow, organized by module // Generate a command file for each workflow, organized by module
for (const workflow of standaloneWorkflows) { for (const workflow of allWorkflows) {
const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows'); const moduleWorkflowsDir = path.join(baseCommandsDir, workflow.module, 'workflows');
await fs.ensureDir(moduleWorkflowsDir); await fs.ensureDir(moduleWorkflowsDir);
@ -46,7 +46,7 @@ class WorkflowCommandGenerator {
} }
// Also create a workflow launcher README in each module // Also create a workflow launcher README in each module
const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows); const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows);
await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows); await this.createModuleWorkflowLaunchers(baseCommandsDir, groupedWorkflows);
return { generated: generatedCount }; return { generated: generatedCount };
@ -59,12 +59,12 @@ class WorkflowCommandGenerator {
return { artifacts: [], counts: { commands: 0, launchers: 0 } }; return { artifacts: [], counts: { commands: 0, launchers: 0 } };
} }
// Filter to only standalone workflows // ALL workflows now generate commands - no standalone filtering
const standaloneWorkflows = workflows.filter((w) => w.standalone === 'true' || w.standalone === true); const allWorkflows = workflows;
const artifacts = []; const artifacts = [];
for (const workflow of standaloneWorkflows) { for (const workflow of allWorkflows) {
const commandContent = await this.generateCommandContent(workflow, bmadDir); const commandContent = await this.generateCommandContent(workflow, bmadDir);
artifacts.push({ artifacts.push({
type: 'workflow-command', type: 'workflow-command',
@ -75,7 +75,7 @@ class WorkflowCommandGenerator {
}); });
} }
const groupedWorkflows = this.groupWorkflowsByModule(standaloneWorkflows); const groupedWorkflows = this.groupWorkflowsByModule(allWorkflows);
for (const [module, launcherContent] of Object.entries(this.buildModuleWorkflowLaunchers(groupedWorkflows))) { for (const [module, launcherContent] of Object.entries(this.buildModuleWorkflowLaunchers(groupedWorkflows))) {
artifacts.push({ artifacts.push({
type: 'workflow-launcher', type: 'workflow-launcher',
@ -89,7 +89,7 @@ class WorkflowCommandGenerator {
return { return {
artifacts, artifacts,
counts: { counts: {
commands: standaloneWorkflows.length, commands: allWorkflows.length,
launchers: Object.keys(groupedWorkflows).length, launchers: Object.keys(groupedWorkflows).length,
}, },
}; };
@ -99,8 +99,13 @@ class WorkflowCommandGenerator {
* Generate command content for a workflow * Generate command content for a workflow
*/ */
async generateCommandContent(workflow, bmadDir) { async generateCommandContent(workflow, bmadDir) {
// Load the template // Determine template based on workflow file type
const template = await fs.readFile(this.templatePath, 'utf8'); const isMarkdownWorkflow = workflow.path.endsWith('workflow.md');
const templateName = isMarkdownWorkflow ? 'workflow-commander.md' : 'workflow-command-template.md';
const templatePath = path.join(path.dirname(this.templatePath), templateName);
// Load the appropriate template
const template = await fs.readFile(templatePath, 'utf8');
// Convert source path to installed path // Convert source path to installed path
// From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml // From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml
@ -120,16 +125,17 @@ class WorkflowCommandGenerator {
} }
} }
const coreWorkflowPath = `${this.bmadFolderName}/core/tasks/workflow.xml`;
// Replace template variables // Replace template variables
return template return template
.replaceAll('{{name}}', workflow.name) .replaceAll('{{name}}', workflow.name)
.replaceAll('{{module}}', workflow.module) .replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description) .replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath) .replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('{{core_workflow_path}}', coreWorkflowPath)
.replaceAll('{bmad_folder}', this.bmadFolderName) .replaceAll('{bmad_folder}', this.bmadFolderName)
.replaceAll('{*bmad_folder*}', '{bmad_folder}') .replaceAll('{*bmad_folder*}', '{bmad_folder}');
.replaceAll('{{interactive}}', workflow.interactive)
.replaceAll('{{author}}', workflow.author || 'BMAD');
} }
/** /**

View File

@ -5,7 +5,7 @@ description: '{{description}}'
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE"> <steps CRITICAL="TRUE">
1. Always LOAD the FULL @{bmad_folder}/core/tasks/workflow.xml 1. Always LOAD the FULL @{{core_workflow_path}}
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}} 2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}}
3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions 3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions 4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions

View File

@ -0,0 +1,5 @@
---
description: '{{description}}'
---
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{{workflow_path}}, READ its entire contents and follow its directions exactly!