12 KiB
Submit Sign-off - Record Your Approval Decision
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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✍️ SUBMIT SIGN-OFF ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Call: mcp__github__get_me() current_user = response.login
❌ GitHub MCP not accessible HALT Which document are you signing off on? Enter key: document_key = response if (document_type === 'prd') { doc_path = `${docs_dir}/prd/${document_key}.md` doc_label = `prd:${document_key}` review_label = 'type:prd-review' } else { doc_path = `${docs_dir}/epics/epic-${document_key}.md` doc_label = `epic:${document_key}` review_label = 'type:epic-review' } Call: mcp__github__search_issues({ query: "repo:{{github_owner}}/{{github_repo}} label:{{review_label}} label:{{doc_label}} label:review-status:signoff is:open" })<check if="response.items.length == 0">
<output>
❌ No active sign-off request found for {{doc_label}}
The document may be:
- Still in feedback stage
- Already approved
- Not yet created
Use [MT] My Tasks to see what's pending for you. HALT
<action>review_issue = response.items[0]</action>
<output>
📋 Found sign-off request: #{{review_issue.number}} {{review_issue.title}}
Read doc_path ❌ Document not found: {{doc_path}} HALT doc_content = file_content title = extract_title(doc_content) version = extract_version(doc_content) // Check if user already signed off signoff_label_prefix = `signoff-${current_user}-` existing_signoff = review_issue.labels.some(l => l.name.startsWith(signoff_label_prefix) ) ⚠️ You have already submitted a sign-off decision for this document.Would you like to: [1] Change your decision [2] View current status [3] Cancel Choice: HALT Proceeding to update your sign-off decision...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📄 DOCUMENT SUMMARY ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Title: {{title}} Version: v{{version}} Key: {{doc_label}}
Would you like to view the full document before deciding?
View document? (y/n): ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📄 DOCUMENT CONTENT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{{doc_content}}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🗳️ YOUR DECISION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Please select your sign-off decision:
[1] ✅ APPROVE - I approve this document [2] ✅📝 APPROVE WITH NOTE - I approve with a minor note/observation [3] 🚫 BLOCK - I cannot approve, there is a blocking issue
Decision (1-3): decision_map = { '1': { key: 'approved', emoji: '✅', text: 'Approved' }, '2': { key: 'approved_with_note', emoji: '✅📝', text: 'Approved with Note' }, '3': { key: 'blocked', emoji: '🚫', text: 'Blocked' } } decision = decision_map[response] || decision_map['1']
Enter your note (this will be visible to all stakeholders): note = response ⚠️ Blocking a document requires a clear reason.This will:
-
Prevent the document from being approved
-
Notify the PO and stakeholders
-
May trigger a new feedback round
Enter your blocking reason: note = response
Would you like to create a formal feedback issue for this blocking concern? Create feedback issue? (y/n): create_feedback_issue = true
note = null // Build sign-off comment signoff_comment = `### ${decision.emoji} Sign-off from @${current_user}Decision: ${decision.text} Date: ${new Date().toISOString().split('T')[0]}`
if (note) {
signoff_comment += `
Note: ${note}` }
Call: mcp__github__add_issue_comment({ owner: "{{github_owner}}", repo: "{{github_repo}}", issue_number: review_issue.number, body: signoff_comment })
// Get current labels current_labels = review_issue.labels.map(l => l.name) // Remove any existing signoff label for this user
new_labels = current_labels.filter(l =>
!l.startsWith(`signoff-${current_user}-`)
)
// Add new signoff label
decision_label = decision.key.replace(/_/g, '-')
new_labels.push(`signoff-${current_user}-${decision_label}`)
</action>
<action>Call: mcp__github__issue_write({
method: 'update',
owner: "{{github_owner}}",
repo: "{{github_repo}}",
issue_number: review_issue.number,
labels: new_labels
})</action>
feedback_body = `# 🚫 Blocking Concern
Document: `${doc_label}` Review: #${review_issue.number} Type: Blocking concern requiring resolution
Concern
${note}
Submitted by @{current_user} as part of sign-off for v{version}`
<action>Call: mcp__github__issue_write({
method: 'create',
owner: "{{github_owner}}",
repo: "{{github_repo}}",
title: "🚫 Blocking: {{title}}",
body: feedback_body,
labels: ['type:{{document_type}}-feedback', doc_label, 'feedback-type:concern', 'priority:high', 'feedback-status:new', `linked-review:${review_issue.number}`]
})</action>
<output>
✅ Created feedback issue: #{{response.number}}
✅ Sign-off submitted: {{decision.emoji}} {{decision.text}} Checking if all required sign-offs are complete... // Refresh issue to get updated labels Call: mcp__github__issue_read({ method: 'get', owner: github_owner, repo: github_repo, issue_number: review_issue.number }) // Count sign-offs labels = response.labels.map(l => l.name) approved_count = labels.filter(l => l.includes('-approved') || l.includes('-approved-with-note') ).length blocked_count = labels.filter(l => l.includes('-blocked')).length// Get stakeholder count from document
stakeholder_count = extract_stakeholders(doc_content).length
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 SIGN-OFF STATUS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Approved: {{approved_count}} / {{stakeholder_count}} Blocked: {{blocked_count}} Pending: {{stakeholder_count - approved_count - blocked_count}}
⚠️ Document has {{blocked_count}} blocking concern(s). Cannot be approved until resolved. 🎉 ALL SIGN-OFFS RECEIVED!The document is ready to be marked as APPROVED.
<ask>Mark document as approved? (y/n):</ask>
<check if="response == 'y'">
<action>
// Update document status
updated_content = doc_content.replace(/\*\*Status:\*\* .+/, '**Status:** Approved')
</action>
<action>Write updated_content to doc_path</action>
<action>
// Update review issue
final_labels = labels
.filter(l => !l.startsWith('review-status:'))
.concat(['review-status:approved'])
</action>
<action>Call: mcp__github__issue_write({
method: 'update',
owner: "{{github_owner}}",
repo: "{{github_repo}}",
issue_number: review_issue.number,
labels: final_labels,
state: 'closed',
state_reason: 'completed'
})</action>
<action>Call: mcp__github__add_issue_comment({
owner: "{{github_owner}}",
repo: "{{github_repo}}",
issue_number: review_issue.number,
body: `## ✅ DOCUMENT APPROVED
All stakeholders have signed off. This document is now approved and ready for implementation.
Final Version: v${version}
Approved: ${new Date().toISOString().split('T')[0]}
Approvals: {approved_count} / {stakeholder_count}`
})
<output>
✅ Document marked as APPROVED! Review issue #{{review_issue.number}} closed.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ SIGN-OFF COMPLETE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Your Decision: {{decision.emoji}} {{decision.text}} Document: {{title}} v{{version}} Review Issue: #{{review_issue.number}}
Thank you for your review! 🙏
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Helper Functions
function extract_title(content) {
const match = content.match(/^#\s+(PRD|Epic):\s*(.+)$/m);
return match ? match[2].trim() : 'Untitled';
}
function extract_version(content) {
const match = content.match(/\*\*Version:\*\*\s*(\d+)/);
return match ? match[1] : '1';
}
function extract_stakeholders(content) {
const field = content.match(/\|\s*Stakeholders\s*\|\s*(.+?)\s*\|/);
if (!field) return [];
return field[1]
.split(/[,\s]+/)
.filter(s => s.startsWith('@'))
.map(s => s.replace('@', ''));
}
Natural Language Triggers
This workflow responds to:
- "Sign off on [document]"
- "Submit my sign-off"
- "Approve the PRD"
- "I approve [document]"
- Menu trigger:
SO