BMAD-METHOD/src/modules/bmm/workflows/1-requirements/crowdsource/open-feedback-round/instructions.md

12 KiB

Open Feedback Round - Start Async Stakeholder Review

The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml You MUST have already loaded and processed: {installed_path}/workflow.yaml

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔔 OPEN FEEDBACK ROUND ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Call: mcp__github__get_me() current_user = response.login

GitHub MCP not accessible - required for coordination HALT Which document? Enter key (e.g., "user-auth" for PRD, "2" for Epic): document_key = response doc_path = {{docs_dir}}/prd/{{document_key}}.md document_type = 'prd' doc_prefix = 'PRD' doc_label_prefix = 'prd' doc_path = {{docs_dir}}/epics/epic-{{document_key}}.md doc_prefix = 'Epic' doc_label_prefix = 'epic' Read doc_path Document not found: {{doc_path}}

Please ensure the {{document_type}} exists. Use:

  • "Create PRD" (CP) to create a new PRD

  • Check that the key is correct HALT doc_content = file_content

    // Parse document metadata title = extract_between(doc_content, '# PRD: ', '\n') || extract_between(doc_content, '# Epic: ', '\n') || document_key version = extract_field(doc_content, 'Version') || '1' status = extract_field(doc_content, 'Status') || 'draft' stakeholders = extract_stakeholders(doc_content) owner = extract_field(doc_content, 'Product Owner')?.replace('@', '')

⚠️ This {{document_type}} is currently in status: {{status}}

Feedback rounds can only be opened for documents in 'draft' or 'feedback' status. Current status suggests this may already be in synthesis or sign-off. Continue anyway? (y/n): HALT

📄 Document: {{title}} 📌 Key: {{doc_label_prefix}}:{{document_key}} 📊 Version: {{version}} 👥 Stakeholders: {{stakeholders.length}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📅 FEEDBACK DEADLINE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

How long should stakeholders have to provide feedback?

Days until deadline (default: 5): days = parseInt(response) || 5 deadline = new Date() deadline.setDate(deadline.getDate() + days) deadline_str = deadline.toISOString().split('T')[0]

Deadline: {{deadline_str}} ({{days}} days from now) // Build stakeholder checklist checklist = stakeholders.map(s => `- [ ] @${s.replace('@', '')} - Pending feedback` ).join('\n')
// Build issue body
issue_body = `# 📣 ${doc_prefix} Review: ${title} v${version}

Document Key: `{doc_label_prefix}:{document_key}` Version: ${version} Owner: @${owner || current_user} Status: 🟡 Open for Feedback


📅 Deadline

Feedback Due: ${deadline_str}


📋 Document Summary

${extract_summary(doc_content)}


👥 Stakeholder Feedback Status

${checklist}


📝 How to Provide Feedback

  1. Review the document: `docs/{document_type}/{document_key}.md`
  2. For each piece of feedback, create a new comment or linked issue:
    • Clarification: Something unclear → `/feedback clarification`
    • Concern: Potential issue → `/feedback concern`
    • Suggestion: Improvement idea → `/feedback suggestion`
    • Addition: Missing requirement → `/feedback addition`

Or use the workflow: "Submit feedback on {doc_label_prefix}:{document_key}"


🔄 Review Status

  • All stakeholders have provided feedback
  • Feedback synthesized into new version
  • Ready for sign-off

This review round was opened by @{current_user} on {new Date().toISOString().split('T')[0]} `

Call: mcp__github__search_issues({ query: "repo:{{github_owner}}/{{github_repo}} label:type:{{doc_label_prefix}}-review label:{{doc_label_prefix}}:{{document_key}} is:open" })
<check if="response.items.length > 0">
  <output>

⚠️ An open review round already exists for this document: Issue #{{response.items[0].number}}: {{response.items[0].title}}

Would you like to: [1] Use existing review issue [2] Close old and create new [3] Cancel Choice: review_issue = response.items[0] Goto step 4 (skip issue creation) Call: mcp__github__issue_write({ method: 'update', owner: github_owner, repo: github_repo, issue_number: response.items[0].number, state: 'closed', state_reason: 'not_planned' }) HALT

labels = [ `type:${doc_label_prefix}-review`, `${doc_label_prefix}:${document_key}`, `version:${version}`, 'review-status:open' ]
<action>Call: mcp__github__issue_write({
  method: 'create',
  owner: "{{github_owner}}",
  repo: "{{github_repo}}",
  title: "{{doc_prefix}} Review: {{title}} v{{version}}",
  body: issue_body,
  labels: labels,
  assignees: stakeholders.map(s => s.replace('@', ''))
})</action>

<action>review_issue = response</action>

<output>

Review issue created: #{{review_issue.number}} {{review_issue.html_url}}

// Update the Status field in the document updated_content = doc_content .replace(/\*\*Status:\*\* .+/, '**Status:** Feedback') .replace(/\| Feedback Deadline \| .+ \|/, `| Feedback Deadline | ${deadline_str} |`) Write updated_content to doc_path if (document_type === 'prd') { cacheManager.writePrd(document_key, updated_content, { status: 'feedback', review_issue: review_issue.number, feedback_deadline: deadline_str }) } else { cacheManager.writeEpic(document_key, updated_content, { status: 'feedback', review_issue: review_issue.number, feedback_deadline: deadline_str }) } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📨 STAKEHOLDER NOTIFICATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ mentions = stakeholders.map(s => `@${s.replace('@', '')}`).join(' ') notification = `## 📣 Feedback Requested

${mentions}

You have been asked to review this ${doc_prefix}.

Deadline: ${deadline_str} Document: `docs/{document_type}/{document_key}.md`

Please review and provide your feedback by creating linked feedback issues or comments.


Quick Actions:

  • View document in repo
  • Use "Submit feedback" workflow
  • Comment directly on this issue

Thank you for your input! 🙏`

<action>Call: mcp__github__add_issue_comment({
  owner: "{{github_owner}}",
  repo: "{{github_repo}}",
  issue_number: review_issue.number,
  body: notification
})</action>
Stakeholders notified via GitHub @mentions ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ FEEDBACK ROUND OPENED ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Document: {{title}} v{{version}} Review Issue: #{{review_issue.number}} Deadline: {{deadline_str}} Stakeholders Notified: {{stakeholders.length}}

The following stakeholders have been notified: {{stakeholders.map(s => ' • @' + s.replace('@', '')).join('\n')}}


Next Steps:

  1. Stakeholders submit feedback via GitHub
  2. Monitor progress with: "View feedback for {{doc_label_prefix}}:{{document_key}}"
  3. When ready, synthesize with: "Synthesize feedback for {{doc_label_prefix}}:{{document_key}}"

Quick Commands:

  • [VF] View Feedback
  • [SZ] Synthesize Feedback
  • [PD] PRD Dashboard / [ED] Epic Dashboard

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Helper Functions

// Extract text between markers
function extract_between(content, start, end) {
  const startIdx = content.indexOf(start);
  if (startIdx === -1) return null;
  const endIdx = content.indexOf(end, startIdx + start.length);
  return content.slice(startIdx + start.length, endIdx).trim();
}

// Extract field from markdown table or bold format
function extract_field(content, field) {
  // Try bold format: **Field:** value
  const boldMatch = content.match(new RegExp(`\\*\\*${field}:\\*\\*\\s*(.+?)(?:\\n|$)`));
  if (boldMatch) return boldMatch[1].trim();

  // Try table format: | Field | value |
  const tableMatch = content.match(new RegExp(`\\|\\s*${field}\\s*\\|\\s*(.+?)\\s*\\|`));
  if (tableMatch) return tableMatch[1].trim();

  return null;
}

// Extract stakeholders from document
function extract_stakeholders(content) {
  const field = extract_field(content, 'Stakeholders');
  if (!field) return [];

  return field
    .split(/[,\s]+/)
    .filter(s => s.startsWith('@'))
    .map(s => s.replace('@', ''));
}

// Extract summary section from document
function extract_summary(content) {
  // Try to get Vision + Problem Statement
  const vision = extract_between(content, '## Vision', '##') ||
                 extract_between(content, '## Vision', '\n---');
  const problem = extract_between(content, '## Problem Statement', '##') ||
                  extract_between(content, '## Problem Statement', '\n---');

  if (vision || problem) {
    let summary = '';
    if (vision) summary += `**Vision:** ${vision.slice(0, 200)}...\n\n`;
    if (problem) summary += `**Problem:** ${problem.slice(0, 200)}...`;
    return summary;
  }

  // Fallback: first 500 chars
  return content.slice(0, 500) + '...';
}

Natural Language Triggers

This workflow responds to:

  • "Open feedback for [prd-key]"
  • "Start feedback round for [document]"
  • "Request feedback on PRD"
  • Menu trigger: OF