BMAD-METHOD/src/modules/bmm/workflows/1-requirements/crowdsource/submit-signoff/instructions.md

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:

  1. Prevent the document from being approved

  2. Notify the PO and stakeholders

  3. 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