# View Feedback - Review All Stakeholder Input 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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 👁️ VIEW FEEDBACK ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Call: mcp__github__get_me() ❌ GitHub MCP not accessible HALT Which document? Enter key (e.g., "user-auth" for PRD, "2" for Epic): document_key = response Is this a [P]RD or [E]pic? document_type = (response.toLowerCase().startsWith('p')) ? 'prd' : 'epic' doc_label = `${document_type}:${document_key}` feedback_label = `type:${document_type}-feedback` Call: mcp__github__search_issues({ query: "repo:{{github_owner}}/{{github_repo}} label:{{feedback_label}} label:{{doc_label}} is:open" }) feedback_issues = response.items || [] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📭 NO FEEDBACK FOUND ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ No feedback has been submitted for {{doc_label}} yet. **Actions:** [SF] Submit Feedback [MT] My Tasks [Q] Quit Choice: Load workflow: submit-feedback with document_key, document_type Load workflow: my-tasks HALT // Parse feedback issues into structured data all_feedback = [] by_section = {} by_type = {} by_status = { new: [], reviewed: [], incorporated: [], deferred: [] } conflicts = [] for (issue of feedback_issues) { const labels = issue.labels.map(l => l.name) const fb = { id: issue.number, url: issue.html_url, title: issue.title.replace(/^[^\s]+\s+Feedback:\s*/, ''), section: extract_label(labels, 'feedback-section:') || 'General', type: extract_label(labels, 'feedback-type:') || 'suggestion', status: extract_label(labels, 'feedback-status:') || 'new', priority: extract_label(labels, 'priority:') || 'medium', submittedBy: issue.user?.login, createdAt: issue.created_at, body: issue.body } all_feedback.push(fb) // Group by section if (!by_section[fb.section]) by_section[fb.section] = [] by_section[fb.section].push(fb) // Group by type if (!by_type[fb.type]) by_type[fb.type] = [] by_type[fb.type].push(fb) // Group by status if (by_status[fb.status]) by_status[fb.status].push(fb) } // Detect potential conflicts (multiple feedback on same section) for (const [section, items] of Object.entries(by_section)) { if (items.length >= 2) { const concerns = items.filter(i => i.type === 'concern') const suggestions = items.filter(i => i.type === 'suggestion') if (concerns.length > 1 || (concerns.length >= 1 && suggestions.length >= 1)) { conflicts.push({ section, count: items.length, items: items }) } } } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📊 FEEDBACK SUMMARY: {{doc_label}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **Total Feedback:** {{all_feedback.length}} items **By Status:** 🆕 New: {{by_status.new.length}} 👀 Reviewed: {{by_status.reviewed.length}} ✅ Incorporated: {{by_status.incorporated.length}} ⏸️ Deferred: {{by_status.deferred.length}} **By Type:** {{#each by_type as |items type|}} {{get_type_emoji type}} {{type}}: {{items.length}} {{/each}} **By Section:** {{#each by_section as |items section|}} • {{section}}: {{items.length}} item(s) {{/each}} {{#if conflicts.length}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⚠️ POTENTIAL CONFLICTS DETECTED: {{conflicts.length}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{#each conflicts}} **{{section}}** - {{count}} stakeholders have input: {{#each items}} • @{{submittedBy}}: "{{title}}" {{/each}} {{/each}} {{/if}} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **View Options:** [1] View by Section [2] View by Type [3] View Conflicts Only [4] View All Details [5] Export to Markdown **Actions:** [S] Synthesize Feedback (incorporate into document) [R] Refresh [Q] Quit Choice: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📂 FEEDBACK BY SECTION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{#each by_section as |items section|}} ## {{section}} ({{items.length}} items) {{#each items}} ┌──────────────────────────────────────────── │ #{{id}}: {{title}} │ Type: {{get_type_emoji type}} {{type}} | Priority: {{priority}} | Status: {{status}} │ By: @{{submittedBy}} on {{format_date createdAt}} └──────────────────────────────────────────── {{/each}} {{/each}} Goto step 5 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🏷️ FEEDBACK BY TYPE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{#each by_type as |items type|}} ## {{get_type_emoji type}} {{type}} ({{items.length}} items) {{#each items}} | #{{id}} | {{title}} | @{{submittedBy}} | {{section}} | {{/each}} {{/each}} Goto step 5 ✅ No conflicts detected! All feedback is non-overlapping. Goto step 5 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⚠️ CONFLICTS REQUIRING RESOLUTION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{#each conflicts}} ## Conflict in: {{section}} Multiple stakeholders have provided feedback on this section: {{#each items}} ### @{{submittedBy}} - {{type}} **{{title}}** {{extract_feedback body}} --- {{/each}} **Suggested Resolution:** Use synthesis workflow to generate proposed resolution. {{/each}} Goto step 5 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📋 ALL FEEDBACK DETAILS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{#each all_feedback}} ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┃ #{{id}}: {{title}} ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┃ Type: {{get_type_emoji type}} {{type}} ┃ Section: {{section}} ┃ Priority: {{priority}} ┃ Status: {{status}} ┃ By: @{{submittedBy}} ┃ Date: {{format_date createdAt}} ┃ URL: {{url}} ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┃ FEEDBACK: ┃ {{extract_feedback body}} ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ {{/each}} Goto step 5 // Generate markdown export export_content = `# Feedback Report: ${doc_label} Generated: ${new Date().toISOString()} Total Feedback: ${all_feedback.length} ## Summary | Type | Count | |------|-------| ${Object.entries(by_type).map(([t, items]) => `| ${t} | ${items.length} |`).join('\n')} ## By Section ${Object.entries(by_section).map(([section, items]) => ` ### ${section} ${items.map(fb => `- **${fb.title}** (${fb.type}, ${fb.priority}) - @${fb.submittedBy} #${fb.id}`).join('\n')} `).join('\n')} ## Conflicts ${conflicts.length === 0 ? 'No conflicts detected.' : conflicts.map(c => ` ### ${c.section} ${c.items.map(fb => `- @${fb.submittedBy}: "${fb.title}"`).join('\n')} `).join('\n')} ` export_path = `${cache_dir}/feedback-report-${document_key}.md` Write export_content to export_path ✅ Exported to: {{export_path}} Goto step 5 Opening synthesis workflow for {{doc_label}}... Load workflow: synthesize-feedback with document_key, document_type Goto step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ View Feedback closed. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Exit Goto step 5 ## Helper Functions ```javascript // Extract label value by prefix function extract_label(labels, prefix) { for (const label of labels) { if (label.startsWith(prefix)) { return label.replace(prefix, ''); } } return null; } // Get emoji for feedback type function get_type_emoji(type) { const emojis = { clarification: '📋', concern: '⚠️', suggestion: '💡', addition: '➕', priority: '🔢', scope: '📐', dependency: '🔗', 'technical-risk': '🔧', 'story-split': '✂️' }; return emojis[type] || '📝'; } // Format date for display function format_date(isoDate) { return new Date(isoDate).toISOString().split('T')[0]; } // Extract feedback content from issue body function extract_feedback(body) { if (!body) return 'No details provided.'; // Try to extract the Feedback section const match = body.match(/## Feedback\n\n([\s\S]*?)(?:\n##|$)/); if (match) { return match[1].trim().slice(0, 200) + (match[1].length > 200 ? '...' : ''); } // Fallback to first 200 chars return body.slice(0, 200) + (body.length > 200 ? '...' : ''); } ``` ## Natural Language Triggers This workflow responds to: - "View feedback for [document]" - "Show feedback on PRD" - "What feedback has been submitted?" - "See all feedback for [document]" - Menu trigger: `VF`