Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    squirrelsoft-dev

    jira-integration

    squirrelsoft-dev/jira-integration
    Productivity
    1

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Master Jira integration using acli CLI, Jira REST API, issue management, sprint operations, JQL queries, and ADF comment formatting. Essential for Jira-based project management automation.

    SKILL.md

    Jira Integration Mastery

    This skill provides comprehensive guidance for integrating with Jira using the Atlassian CLI (acli), Jira REST API, and ADF (Atlassian Document Format). Essential for automating issue management, sprint planning, JQL queries, and building robust Jira-based workflows.

    When to Use This Skill

    • Automating Jira issue creation, updates, and transitions
    • Building sprint planning workflows
    • Writing and executing JQL queries
    • Formatting comments with ADF (Atlassian Document Format)
    • Parsing Jira URLs and extracting metadata
    • Integrating Jira into development automation
    • Creating bulk operations across multiple issues

    Atlassian CLI (acli) Mastery

    The Atlassian CLI (acli) is the primary tool for Jira automation via command line.

    Installation and Configuration

    # Download acli
    curl -O https://bobswift.atlassian.net/wiki/download/attachments/16285777/acli-9.8.0-distribution.zip
    
    # Extract and setup
    unzip acli-9.8.0-distribution.zip
    export PATH=$PATH:/path/to/acli
    
    # Configure connection
    acli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action getServerInfo
    
    # Store credentials (creates ~/.acli/acli.properties)
    acli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action login
    

    Configuration file (~/.acli/acli.properties):

    server=https://your-domain.atlassian.net
    user=user@example.com
    password=your-api-token
    

    Issue Operations

    List issues:

    # List issues in project
    acli jira --action getIssueList --project PROJ
    
    # List with JQL
    acli jira --action getIssueList --jql "project = PROJ AND status = 'To Do'"
    
    # List with specific fields
    acli jira --action getIssueList --jql "assignee = currentUser()" --outputFormat 2 --columns "key,summary,status"
    

    Get issue details:

    # Get full issue details
    acli jira --action getIssue --issue PROJ-123
    
    # Get specific fields
    acli jira --action getIssue --issue PROJ-123 --outputFormat 2 --columns "key,summary,description,status,assignee"
    

    Create issue:

    # Create issue
    acli jira --action createIssue \
      --project PROJ \
      --type "Story" \
      --summary "Implement authentication" \
      --description "Add OAuth2 authentication to the application" \
      --priority "High" \
      --labels "backend,security"
    
    # Create with custom fields
    acli jira --action createIssue \
      --project PROJ \
      --type "Bug" \
      --summary "Login fails on mobile" \
      --field "customfield_10001=High Priority"
    

    Update issue:

    # Update summary and description
    acli jira --action updateIssue \
      --issue PROJ-123 \
      --summary "Updated summary" \
      --description "Updated description"
    
    # Update custom fields
    acli jira --action updateIssue \
      --issue PROJ-123 \
      --field "customfield_10001=New Value"
    
    # Add labels
    acli jira --action updateIssue \
      --issue PROJ-123 \
      --labels "bug,urgent" \
      --labelsAdd
    

    Transition issue:

    # Move to different status
    acli jira --action transitionIssue \
      --issue PROJ-123 \
      --transition "In Progress"
    
    # Transition with comment
    acli jira --action transitionIssue \
      --issue PROJ-123 \
      --transition "Done" \
      --comment "Completed implementation and testing"
    

    Assign issue:

    # Assign to user
    acli jira --action assignIssue \
      --issue PROJ-123 \
      --assignee "john.doe"
    
    # Assign to me
    acli jira --action assignIssue \
      --issue PROJ-123 \
      --assignee "@me"
    

    Sprint Operations

    List sprints:

    # List sprints for board
    acli jira --action getSprintList \
      --board "PROJ Board"
    
    # List active sprints
    acli jira --action getSprintList \
      --board "PROJ Board" \
      --state "active"
    

    Add issues to sprint:

    # Add single issue
    acli jira --action addIssuesToSprint \
      --sprint "Sprint 24" \
      --issue "PROJ-123"
    
    # Add multiple issues
    acli jira --action addIssuesToSprint \
      --sprint "Sprint 24" \
      --issue "PROJ-123,PROJ-124,PROJ-125"
    

    Start sprint:

    # Start sprint with date range
    acli jira --action startSprint \
      --sprint "Sprint 24" \
      --startDate "2024-01-01" \
      --endDate "2024-01-14"
    

    Close sprint:

    # Complete sprint (moves incomplete issues to backlog)
    acli jira --action completeSprint \
      --sprint "Sprint 24"
    

    Board Management

    List boards:

    # List all boards
    acli jira --action getBoardList
    
    # List boards for project
    acli jira --action getBoardList \
      --project PROJ
    

    Get board configuration:

    # Get board details
    acli jira --action getBoard \
      --board "PROJ Board"
    

    Bulk Operations

    Bulk transition:

    # Transition multiple issues
    acli jira --action progressIssue \
      --issue "PROJ-123,PROJ-124,PROJ-125" \
      --transition "In Progress"
    

    Bulk update:

    # Update multiple issues
    acli jira --action updateIssue \
      --issue "PROJ-123,PROJ-124" \
      --labels "sprint-24" \
      --labelsAdd
    

    For complete acli command reference, see references/acli-reference.md.


    Jira REST API Patterns

    API Basics

    import axios from 'axios';
    
    // Configure API client
    const jiraClient = axios.create({
      baseURL: 'https://your-domain.atlassian.net/rest/api/3',
      auth: {
        username: 'user@example.com',
        password: 'your-api-token'
      },
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    });
    

    Issue CRUD Operations

    Get issue:

    const response = await jiraClient.get(`/issue/PROJ-123`);
    const issue = response.data;
    
    console.log(issue.key);
    console.log(issue.fields.summary);
    console.log(issue.fields.status.name);
    

    Create issue:

    const newIssue = await jiraClient.post('/issue', {
      fields: {
        project: {
          key: 'PROJ'
        },
        summary: 'Implement authentication',
        description: {
          type: 'doc',
          version: 1,
          content: [
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Add OAuth2 authentication'
                }
              ]
            }
          ]
        },
        issuetype: {
          name: 'Story'
        },
        priority: {
          name: 'High'
        },
        labels: ['backend', 'security']
      }
    });
    
    console.log(`Created issue: ${newIssue.data.key}`);
    

    Update issue:

    await jiraClient.put(`/issue/PROJ-123`, {
      fields: {
        summary: 'Updated summary',
        labels: ['bug', 'urgent']
      }
    });
    

    Transition issue:

    // Get available transitions
    const transitionsResp = await jiraClient.get(`/issue/PROJ-123/transitions`);
    const transitions = transitionsResp.data.transitions;
    
    // Find "In Progress" transition
    const inProgressTransition = transitions.find(t => t.name === 'In Progress');
    
    // Execute transition
    await jiraClient.post(`/issue/PROJ-123/transitions`, {
      transition: {
        id: inProgressTransition.id
      }
    });
    

    Advanced Operations

    Add comment:

    await jiraClient.post(`/issue/PROJ-123/comment`, {
      body: {
        type: 'doc',
        version: 1,
        content: [
          {
            type: 'paragraph',
            content: [
              {
                type: 'text',
                text: 'This issue has been reviewed and approved'
              }
            ]
          }
        ]
      }
    });
    

    Add attachment:

    import FormData from 'form-data';
    import fs from 'fs';
    
    const form = new FormData();
    form.append('file', fs.createReadStream('screenshot.png'));
    
    await jiraClient.post(`/issue/PROJ-123/attachments`, form, {
      headers: {
        ...form.getHeaders(),
        'X-Atlassian-Token': 'no-check'
      }
    });
    

    Link issues:

    await jiraClient.post('/issueLink', {
      type: {
        name: 'Blocks'
      },
      inwardIssue: {
        key: 'PROJ-123'
      },
      outwardIssue: {
        key: 'PROJ-456'
      }
    });
    

    For complete API patterns and examples, see references/jira-api-patterns.md.


    JQL (Jira Query Language)

    JQL is Jira's query language for searching and filtering issues.

    Basic JQL Syntax

    # Single condition
    project = PROJ
    
    # Multiple conditions (AND)
    project = PROJ AND status = "To Do"
    
    # Multiple conditions (OR)
    status = "To Do" OR status = "In Progress"
    
    # Negation
    status != Done
    
    # IN operator
    status IN ("To Do", "In Progress")
    
    # Comparison
    created >= -7d
    

    Common JQL Queries

    By status:

    # Open issues
    status IN ("To Do", "In Progress", "Review")
    
    # Closed issues
    status = Done
    
    # Not done
    status != Done
    

    By assignee:

    # Assigned to me
    assignee = currentUser()
    
    # Unassigned
    assignee IS EMPTY
    
    # Assigned to specific user
    assignee = "john.doe"
    

    By date:

    # Created in last 7 days
    created >= -7d
    
    # Updated today
    updated >= startOfDay()
    
    # Due this week
    due <= endOfWeek()
    

    By sprint:

    # Current sprint
    sprint in openSprints()
    
    # Specific sprint
    sprint = "Sprint 24"
    
    # Issues not in sprint
    sprint IS EMPTY
    

    By label:

    # Has specific label
    labels = backend
    
    # Has any of multiple labels
    labels IN (backend, frontend)
    
    # Missing labels
    labels IS EMPTY
    

    Advanced JQL Patterns

    Combination queries:

    # Sprint items assigned to me
    project = PROJ AND sprint in openSprints() AND assignee = currentUser()
    
    # High priority bugs
    project = PROJ AND issuetype = Bug AND priority IN (Highest, High)
    
    # Overdue items
    duedate < now() AND status != Done
    

    Using functions:

    # Issues updated by me
    updatedBy = currentUser()
    
    # Issues where I'm a watcher
    watcher = currentUser()
    
    # Issues in epics
    "Epic Link" IS NOT EMPTY
    

    Ordering results:

    # Order by priority, then created date
    project = PROJ ORDER BY priority DESC, created ASC
    
    # Multiple sort fields
    status = "To Do" ORDER BY priority DESC, updated DESC
    

    For 30+ JQL query examples, see examples/jql-query-examples.md.


    ADF (Atlassian Document Format)

    ADF is Jira's JSON-based format for rich text content in descriptions and comments.

    Basic ADF Structure

    // Simple text paragraph
    const adf = {
      type: 'doc',
      version: 1,
      content: [
        {
          type: 'paragraph',
          content: [
            {
              type: 'text',
              text: 'Hello, world!'
            }
          ]
        }
      ]
    };
    

    Text Formatting

    Bold, italic, code:

    {
      type: 'doc',
      version: 1,
      content: [
        {
          type: 'paragraph',
          content: [
            {
              type: 'text',
              text: 'This is ',
              marks: []
            },
            {
              type: 'text',
              text: 'bold',
              marks: [{ type: 'strong' }]
            },
            {
              type: 'text',
              text: ', '
            },
            {
              type: 'text',
              text: 'italic',
              marks: [{ type: 'em' }]
            },
            {
              type: 'text',
              text: ', and '
            },
            {
              type: 'text',
              text: 'code',
              marks: [{ type: 'code' }]
            }
          ]
        }
      ]
    }
    

    Links

    {
      type: 'text',
      text: 'Click here',
      marks: [
        {
          type: 'link',
          attrs: {
            href: 'https://example.com'
          }
        }
      ]
    }
    

    Code Blocks

    {
      type: 'codeBlock',
      attrs: {
        language: 'typescript'
      },
      content: [
        {
          type: 'text',
          text: 'function hello() {\n  console.log("Hello");\n}'
        }
      ]
    }
    

    Lists

    Bullet list:

    {
      type: 'bulletList',
      content: [
        {
          type: 'listItem',
          content: [
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'First item'
                }
              ]
            }
          ]
        },
        {
          type: 'listItem',
          content: [
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Second item'
                }
              ]
            }
          ]
        }
      ]
    }
    

    Ordered list:

    {
      type: 'orderedList',
      content: [
        // Same listItem structure as bulletList
      ]
    }
    

    Helper Functions

    // Create simple text paragraph
    function createParagraph(text: string) {
      return {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text
          }
        ]
      };
    }
    
    // Create ADF document
    function createADFDocument(...paragraphs: any[]) {
      return {
        type: 'doc',
        version: 1,
        content: paragraphs
      };
    }
    
    // Usage
    const doc = createADFDocument(
      createParagraph('First paragraph'),
      createParagraph('Second paragraph')
    );
    

    For complete ADF specification and templates, see references/adf-format-guide.md and examples/adf-comment-templates.md.


    Issue Detection and Parsing

    Jira URL Patterns

    // Jira issue URL pattern
    const JIRA_ISSUE_URL = /https?:\/\/([^\/]+)\.atlassian\.net\/browse\/([A-Z]+-\d+)/g;
    
    // Custom Jira domain
    const JIRA_CUSTOM_URL = /https?:\/\/jira\.([^\/]+)\.com\/browse\/([A-Z]+-\d+)/g;
    
    function detectJiraIssues(text: string) {
      const matches = Array.from(text.matchAll(JIRA_ISSUE_URL));
    
      return matches.map(match => ({
        url: match[0],
        domain: match[1],
        key: match[2]
      }));
    }
    
    // Example
    const text = "See https://mycompany.atlassian.net/browse/PROJ-123";
    const issues = detectJiraIssues(text);
    // => [{ url: "...", domain: "mycompany", key: "PROJ-123" }]
    

    Jira Issue Key Pattern

    // Issue key pattern (e.g., PROJ-123)
    const JIRA_KEY = /\b([A-Z]{2,10}-\d+)\b/g;
    
    function extractJiraKeys(text: string): string[] {
      const matches = Array.from(text.matchAll(JIRA_KEY));
      return matches.map(m => m[1]);
    }
    
    // Example
    const text = "Implements PROJ-123 and fixes PROJ-456";
    const keys = extractJiraKeys(text);
    // => ["PROJ-123", "PROJ-456"]
    

    Auto-fetch Issue Details

    async function fetchJiraIssue(key: string) {
      const response = await jiraClient.get(`/issue/${key}`);
      return {
        key: response.data.key,
        summary: response.data.fields.summary,
        status: response.data.fields.status.name,
        assignee: response.data.fields.assignee?.displayName,
        url: `https://your-domain.atlassian.net/browse/${key}`
      };
    }
    
    // Auto-enrich text with issue details
    async function enrichWithJiraData(text: string) {
      const keys = extractJiraKeys(text);
      const issues = await Promise.all(keys.map(fetchJiraIssue));
    
      let enriched = text;
      issues.forEach(issue => {
        const pattern = new RegExp(issue.key, 'g');
        enriched = enriched.replace(
          pattern,
          `[${issue.key}](${issue.url}) (${issue.summary})`
        );
      });
    
      return enriched;
    }
    

    Sprint Management

    Sprint Planning Workflow

    # 1. Create new sprint
    acli jira --action createSprint \
      --board "PROJ Board" \
      --name "Sprint 25" \
      --startDate "2024-01-15" \
      --endDate "2024-01-28"
    
    # 2. Add issues to sprint (from JQL query)
    acli jira --action getIssueList \
      --jql "project = PROJ AND labels = 'sprint-ready'" \
      --outputFormat 999 | \
      acli jira --action addIssuesToSprint \
        --sprint "Sprint 25" \
        --issue "@-"
    
    # 3. Start sprint
    acli jira --action startSprint \
      --sprint "Sprint 25"
    

    Sprint Reporting

    interface SprintMetrics {
      name: string;
      total: number;
      completed: number;
      inProgress: number;
      todo: number;
      velocity: number;
    }
    
    async function getSprintMetrics(sprintId: string): Promise<SprintMetrics> {
      const response = await jiraClient.get(`/sprint/${sprintId}/issues`);
      const issues = response.data.issues;
    
      const completed = issues.filter((i: any) => i.fields.status.name === 'Done').length;
      const inProgress = issues.filter((i: any) => i.fields.status.name === 'In Progress').length;
      const todo = issues.filter((i: any) => i.fields.status.name === 'To Do').length;
    
      return {
        name: response.data.sprint.name,
        total: issues.length,
        completed,
        inProgress,
        todo,
        velocity: (completed / issues.length) * 100
      };
    }
    

    Quick Reference

    Essential acli Commands

    • acli jira --action getIssueList --jql "query" - Query issues
    • acli jira --action createIssue --project PROJ --type Story --summary "..." - Create issue
    • acli jira --action transitionIssue --issue KEY --transition "Status" - Change status
    • acli jira --action addIssuesToSprint --sprint "Sprint" --issue "KEY" - Add to sprint
    • acli jira --action getSprintList --board "Board" - List sprints

    Key API Endpoints

    • GET /issue/{issueKey} - Get issue
    • POST /issue - Create issue
    • PUT /issue/{issueKey} - Update issue
    • POST /issue/{issueKey}/transitions - Transition issue
    • POST /issue/{issueKey}/comment - Add comment

    JQL Quick Patterns

    • project = PROJ AND assignee = currentUser() - My issues
    • sprint in openSprints() - Current sprint
    • status = "To Do" ORDER BY priority DESC - Prioritized backlog
    • created >= -7d - Recent issues

    ADF Basics

    • Paragraph: {type: 'paragraph', content: [{type: 'text', text: '...'}]}
    • Bold: marks: [{type: 'strong'}]
    • Code: marks: [{type: 'code'}]
    • Link: marks: [{type: 'link', attrs: {href: '...'}}]

    Best Practices

    • Always use API tokens (not passwords) for authentication
    • Cache Jira project metadata to reduce API calls
    • Use JQL for complex queries instead of filtering in code
    • Validate issue keys before API calls (format: PROJECT-123)
    • Use ADF for all rich text content (descriptions, comments)
    • Handle Jira API rate limits (10 requests/second)

    Multi-Specialist ADF Comment Support

    When working with multi-specialist implementations, Jira comments need to aggregate work from multiple specialists into a single, well-formatted comment.

    Detection Logic

    Check for multi-specialist mode:

    # Detect if this is a multi-specialist implementation
    FEATURE_NAME="authentication"  # Extract from issue or context
    
    if [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
      echo "Multi-specialist mode detected"
      MODE="multi-specialist"
    else
      echo "Single-specialist mode"
      MODE="single-specialist"
    fi
    

    Gather specialist information:

    # List all specialists who worked on this feature
    if [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
      specialists=$(ls -d .agency/handoff/${FEATURE_NAME}/*/ | xargs -n1 basename)
    
      for specialist in $specialists; do
        echo "Found specialist: $specialist"
    
        # Read specialist's summary
        if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md" ]; then
          cat ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md"
        fi
    
        # Read specialist's verification
        if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md" ]; then
          cat ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md"
        fi
      done
    fi
    

    Multi-Specialist ADF Comment Template

    Complete example with multiple specialists:

    interface SpecialistWork {
      name: string;
      displayName: string;
      summary: string;
      filesChanged: string[];
      testResults: string;
      status: 'success' | 'warning' | 'error';
    }
    
    function createMultiSpecialistComment(
      featureName: string,
      specialists: SpecialistWork[],
      overallStatus: 'success' | 'warning' | 'error',
      integrationPoints: string[]
    ): object {
      const statusEmoji = {
        success: '✅',
        warning: '⚠️',
        error: '❌'
      };
    
      const panelType = {
        success: 'success',
        warning: 'warning',
        error: 'error'
      };
    
      return {
        version: 1,
        type: 'doc',
        content: [
          // Header panel with overall status
          {
            type: 'panel',
            attrs: {
              panelType: panelType[overallStatus]
            },
            content: [
              {
                type: 'paragraph',
                content: [
                  {
                    type: 'text',
                    text: `${statusEmoji[overallStatus]} Multi-Specialist Implementation Complete`,
                    marks: [{ type: 'strong' }]
                  }
                ]
              },
              {
                type: 'paragraph',
                content: [
                  {
                    type: 'text',
                    text: `Feature: ${featureName} | Specialists: ${specialists.length}`
                  }
                ]
              }
            ]
          },
    
          // Specialists summary
          {
            type: 'heading',
            attrs: { level: 3 },
            content: [
              {
                type: 'text',
                text: 'Specialist Contributions'
              }
            ]
          },
    
          // List of specialists with status
          {
            type: 'bulletList',
            content: specialists.map(specialist => ({
              type: 'listItem',
              content: [
                {
                  type: 'paragraph',
                  content: [
                    {
                      type: 'text',
                      text: `${statusEmoji[specialist.status]} `,
                      marks: []
                    },
                    {
                      type: 'text',
                      text: specialist.displayName,
                      marks: [{ type: 'strong' }]
                    },
                    {
                      type: 'text',
                      text: ` - ${specialist.summary}`
                    }
                  ]
                }
              ]
            }))
          },
    
          // Detailed work by specialist (collapsible-like sections)
          {
            type: 'heading',
            attrs: { level: 3 },
            content: [
              {
                type: 'text',
                text: 'Detailed Work Breakdown'
              }
            ]
          },
    
          ...specialists.flatMap(specialist => [
            // Specialist heading
            {
              type: 'heading',
              attrs: { level: 4 },
              content: [
                {
                  type: 'text',
                  text: `${specialist.displayName} ${statusEmoji[specialist.status]}`
                }
              ]
            },
    
            // Summary
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Summary: ',
                  marks: [{ type: 'strong' }]
                },
                {
                  type: 'text',
                  text: specialist.summary
                }
              ]
            },
    
            // Files changed
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Files Changed: ',
                  marks: [{ type: 'strong' }]
                },
                {
                  type: 'text',
                  text: `${specialist.filesChanged.length} files`
                }
              ]
            },
            {
              type: 'bulletList',
              content: specialist.filesChanged.slice(0, 10).map(file => ({
                type: 'listItem',
                content: [
                  {
                    type: 'paragraph',
                    content: [
                      {
                        type: 'text',
                        text: file,
                        marks: [{ type: 'code' }]
                      }
                    ]
                  }
                ]
              }))
            },
    
            // Test results
            {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Tests: ',
                  marks: [{ type: 'strong' }]
                },
                {
                  type: 'text',
                  text: specialist.testResults
                }
              ]
            }
          ]),
    
          // Integration points
          {
            type: 'heading',
            attrs: { level: 3 },
            content: [
              {
                type: 'text',
                text: 'Integration Points'
              }
            ]
          },
          {
            type: 'bulletList',
            content: integrationPoints.map(point => ({
              type: 'listItem',
              content: [
                {
                  type: 'paragraph',
                  content: [
                    {
                      type: 'text',
                      text: point
                    }
                  ]
                }
              ]
            }))
          }
        ]
      };
    }
    

    Usage example:

    const specialists: SpecialistWork[] = [
      {
        name: 'backend-architect',
        displayName: 'Backend Architect',
        summary: 'Implemented authentication API with JWT and refresh tokens',
        filesChanged: [
          'src/api/auth/login.ts',
          'src/api/auth/refresh.ts',
          'src/middleware/authenticate.ts',
          'src/models/user.ts'
        ],
        testResults: 'All tests passing (24/24)',
        status: 'success'
      },
      {
        name: 'frontend-developer',
        displayName: 'Frontend Developer',
        summary: 'Created login/signup forms and integrated with auth API',
        filesChanged: [
          'src/components/LoginForm.tsx',
          'src/components/SignupForm.tsx',
          'src/hooks/useAuth.ts',
          'src/pages/profile.tsx'
        ],
        testResults: 'All tests passing (18/18)',
        status: 'success'
      }
    ];
    
    const comment = createMultiSpecialistComment(
      'Authentication System',
      specialists,
      'success',
      [
        'Backend exposes /api/auth/login and /api/auth/refresh endpoints',
        'Frontend uses useAuth hook to manage authentication state',
        'JWT tokens stored in httpOnly cookies',
        'Protected routes redirect to login when unauthenticated'
      ]
    );
    
    // Post to Jira
    await jiraClient.post(`/issue/PROJ-123/comment`, { body: comment });
    

    Single-Specialist ADF Comment Template

    Backward compatibility - Keep existing single-specialist format:

    function createSingleSpecialistComment(
      summary: string,
      filesChanged: string[],
      testResults: string,
      status: 'success' | 'warning' | 'error'
    ): object {
      const statusEmoji = {
        success: '✅',
        warning: '⚠️',
        error: '❌'
      };
    
      const panelType = {
        success: 'success',
        warning: 'warning',
        error: 'error'
      };
    
      return {
        version: 1,
        type: 'doc',
        content: [
          {
            type: 'panel',
            attrs: {
              panelType: panelType[status]
            },
            content: [
              {
                type: 'paragraph',
                content: [
                  {
                    type: 'text',
                    text: `${statusEmoji[status]} Implementation Complete`,
                    marks: [{ type: 'strong' }]
                  }
                ]
              }
            ]
          },
          {
            type: 'paragraph',
            content: [
              {
                type: 'text',
                text: 'Summary: ',
                marks: [{ type: 'strong' }]
              },
              {
                type: 'text',
                text: summary
              }
            ]
          },
          {
            type: 'paragraph',
            content: [
              {
                type: 'text',
                text: 'Files Changed: ',
                marks: [{ type: 'strong' }]
              },
              {
                type: 'text',
                text: `${filesChanged.length} files`
              }
            ]
          },
          {
            type: 'bulletList',
            content: filesChanged.slice(0, 10).map(file => ({
              type: 'listItem',
              content: [
                {
                  type: 'paragraph',
                  content: [
                    {
                      type: 'text',
                      text: file,
                      marks: [{ type: 'code' }]
                    }
                  ]
                }
              ]
            }))
          },
          {
            type: 'paragraph',
            content: [
              {
                type: 'text',
                text: 'Tests: ',
                marks: [{ type: 'strong' }]
              },
              {
                type: 'text',
                text: testResults
              }
            ]
          }
        ]
      };
    }
    

    Auto-Detection Wrapper

    Automatically choose the right format:

    async function postImplementationComment(
      issueKey: string,
      featureName: string
    ): Promise<void> {
      const handoffDir = `.agency/handoff/${featureName}`;
    
      // Check if multi-specialist mode
      if (fs.existsSync(handoffDir)) {
        // Multi-specialist mode
        const specialists: SpecialistWork[] = [];
        const specialistDirs = fs.readdirSync(handoffDir, { withFileTypes: true })
          .filter(d => d.isDirectory())
          .map(d => d.name);
    
        for (const specialistName of specialistDirs) {
          const summaryPath = `${handoffDir}/${specialistName}/summary.md`;
          const verificationPath = `${handoffDir}/${specialistName}/verification.md`;
    
          if (fs.existsSync(summaryPath)) {
            const summary = fs.readFileSync(summaryPath, 'utf-8');
            const verification = fs.existsSync(verificationPath)
              ? fs.readFileSync(verificationPath, 'utf-8')
              : '';
    
            // Parse summary and verification to extract data
            const specialist = parseSpecialistData(specialistName, summary, verification);
            specialists.push(specialist);
          }
        }
    
        // Determine overall status
        const overallStatus = specialists.every(s => s.status === 'success')
          ? 'success'
          : specialists.some(s => s.status === 'error')
          ? 'error'
          : 'warning';
    
        // Extract integration points from summaries
        const integrationPoints = extractIntegrationPoints(specialists);
    
        const comment = createMultiSpecialistComment(
          featureName,
          specialists,
          overallStatus,
          integrationPoints
        );
    
        await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
      } else {
        // Single-specialist mode (backward compatible)
        const summary = 'Implementation completed';
        const filesChanged = await getChangedFiles();
        const testResults = 'All tests passing';
        const status = 'success';
    
        const comment = createSingleSpecialistComment(
          summary,
          filesChanged,
          testResults,
          status
        );
    
        await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
      }
    }
    

    Helper Functions

    function parseSpecialistData(
      name: string,
      summary: string,
      verification: string
    ): SpecialistWork {
      // Extract display name
      const displayNames: Record<string, string> = {
        'backend-architect': 'Backend Architect',
        'frontend-developer': 'Frontend Developer',
        'database-specialist': 'Database Specialist',
        'devops-engineer': 'DevOps Engineer'
      };
    
      // Extract summary (first paragraph or heading)
      const summaryMatch = summary.match(/^##?\s+(.+)$/m) ||
                           summary.match(/^(.+)$/m);
      const summaryText = summaryMatch ? summaryMatch[1] : 'Work completed';
    
      // Extract files from summary (look for code blocks or lists)
      const filesMatch = summary.match(/```[^`]*```/s) ||
                         summary.match(/^[-*]\s+`([^`]+)`/gm);
      const filesChanged = filesMatch
        ? Array.from(summary.matchAll(/`([^`]+\.[a-z]+)`/g)).map(m => m[1])
        : [];
    
      // Extract test results
      const testMatch = verification.match(/Tests?:\s*(.+)/i) ||
                       verification.match(/(\d+\/\d+\s+passing)/i);
      const testResults = testMatch ? testMatch[1] : 'Tests completed';
    
      // Determine status from verification
      let status: 'success' | 'warning' | 'error' = 'success';
      if (verification.includes('❌') || verification.includes('FAIL')) {
        status = 'error';
      } else if (verification.includes('⚠️') || verification.includes('WARNING')) {
        status = 'warning';
      }
    
      return {
        name,
        displayName: displayNames[name] || name,
        summary: summaryText,
        filesChanged,
        testResults,
        status
      };
    }
    
    function extractIntegrationPoints(specialists: SpecialistWork[]): string[] {
      const points: string[] = [];
    
      // Look for API endpoints from backend
      const backend = specialists.find(s => s.name === 'backend-architect');
      if (backend) {
        const apiMatches = backend.summary.match(/\/api\/[^\s]+/g);
        if (apiMatches) {
          points.push(...apiMatches.map(api => `Backend exposes ${api} endpoint`));
        }
      }
    
      // Look for components from frontend
      const frontend = specialists.find(s => s.name === 'frontend-developer');
      if (frontend) {
        const componentMatches = frontend.filesChanged
          .filter(f => f.endsWith('.tsx') || f.endsWith('.jsx'));
        if (componentMatches.length > 0) {
          points.push(`Frontend components: ${componentMatches.join(', ')}`);
        }
      }
    
      return points.length > 0 ? points : ['See individual specialist sections for details'];
    }
    
    async function getChangedFiles(): Promise<string[]> {
      // Get changed files from git using execFile for security
      const { execFile } = require('child_process').promises;
      try {
        const { stdout } = await execFile('git', ['diff', '--name-only', 'HEAD']);
        return stdout.trim().split('\n').filter(Boolean);
      } catch (error) {
        console.error('Failed to get changed files:', error);
        return [];
      }
    }
    

    Best Practices

    Multi-specialist comments:

    1. Always include overall status panel at top
    2. List all specialists with emoji status indicators
    3. Provide detailed breakdown per specialist
    4. Highlight integration points between specialists
    5. Keep file lists reasonable (max 10 per specialist)
    6. Include test results for each specialist
    7. Use consistent formatting across all specialists

    ADF structure:

    1. Use panels for status (success/warning/error)
    2. Use headings (level 3-4) for major sections
    3. Use bullet lists for file listings
    4. Use code marks for file paths and code references
    5. Keep paragraphs focused and concise
    6. Always validate ADF structure before posting

    Detection logic:

    1. Always check for .agency/handoff/{feature} directory first
    2. Fall back to single-specialist format if not found
    3. Handle missing files gracefully (summary.md, verification.md)
    4. Parse specialist data defensively with defaults
    5. Validate all parsed data before creating ADF

    Related Skills

    • jira-adf-generator: Generate properly formatted ADF for Jira comments
    • github-integration: Alternative provider integration for comparison
    • agency-workflow-patterns: General workflow automation applicable to any provider
    • github-workflow-best-practices: Sprint concepts and planning patterns
    Recommended Servers
    GitHub
    GitHub
    Repository
    squirrelsoft-dev/agency
    Files