12 KiB
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<output>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ⚠️ 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} |
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
Goto step 5
Helper Functions
// 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